Skip to content

Commit

Permalink
Merge branch 'master' into s/equality-saturation
Browse files Browse the repository at this point in the history
  • Loading branch information
shashi committed Aug 2, 2023
2 parents a1f7094 + 9f5873e commit 2d8eeb2
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 71 deletions.
78 changes: 78 additions & 0 deletions .github/workflows/benchmark_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Benchmark a pull request

on:
pull_request_target:
branches:
- master

permissions:
pull-requests: write

jobs:
generate_plots:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: "1.8"
- uses: julia-actions/cache@v1
- name: Extract Package Name from Project.toml
id: extract-package-name
run: |
PACKAGE_NAME=$(grep "^name" Project.toml | sed 's/^name = "\(.*\)"$/\1/')
echo "::set-output name=package_name::$PACKAGE_NAME"
- name: Build AirspeedVelocity
env:
JULIA_NUM_THREADS: 2
run: |
# Lightweight build step, as sometimes the runner runs out of memory:
julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.add("AirspeedVelocity")'
julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.build("AirspeedVelocity")'
- name: Add ~/.julia/bin to PATH
run: |
echo "$HOME/.julia/bin" >> $GITHUB_PATH
- name: Run benchmarks
run: |
echo $PATH
ls -l ~/.julia/bin
mkdir results
benchpkg ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --url=${{ github.event.repository.clone_url }} --bench-on="${{github.event.repository.default_branch}}" --output-dir=results/ --tune --exeflags="-O3 --threads=auto"
- name: Create plots from benchmarks
run: |
mkdir -p plots
benchpkgplot ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --npart=10 --format=png --input-dir=results/ --output-dir=plots/
- name: Upload plot as artifact
uses: actions/upload-artifact@v2
with:
name: plots
path: plots
- name: Create markdown table from benchmarks
run: |
benchpkgtable ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --input-dir=results/ --ratio > table.md
echo '### Benchmark Results' > body.md
echo '' >> body.md
echo '' >> body.md
cat table.md >> body.md
echo '' >> body.md
echo '' >> body.md
echo '### Benchmark Plots' >> body.md
echo 'A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.' >> body.md
echo 'Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).' >> body.md
- name: Find Comment
uses: peter-evans/find-comment@v2
id: fcbenchmark
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Benchmark Results

- name: Comment on PR
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.fcbenchmark.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: body.md
edit-mode: replace
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SymbolicUtils"
uuid = "d1185830-fcd6-423d-90d6-eec64667417b"
authors = ["Shashi Gowda"]
version = "1.0.3"
version = "1.2.0"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
Expand Down Expand Up @@ -32,10 +32,10 @@ Combinatorics = "1.0"
ConstructionBase = "1.1"
DataStructures = "0.18"
DocStringExtensions = "0.8, 0.9"
DynamicPolynomials = "0.3, 0.4"
DynamicPolynomials = "0.5"
IfElse = "0.1"
LabelledArrays = "1.5"
MultivariatePolynomials = "0.3, 0.4"
MultivariatePolynomials = "0.5"
NaNMath = "0.3, 1"
Setfield = "0.7, 0.8, 1"
SpecialFunctions = "0.10, 1.0, 2"
Expand Down
42 changes: 19 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<h1 align="center"><a href="https://juliasymbolics.github.io/SymbolicUtils.jl/">SymbolicUtils.jl</a></h1>

<p align="center">
<a href="https://github.com/JuliaSymbolics/SymbolicUtils.jl/actions">
<img src="https://github.com/JuliaSymbolics/SymbolicUtils.jl/workflows/CI/badge.svg"
alt="CI">
</a>
</a>
<a href="https://codecov.io/gh/JuliaSymbolics/SymbolicUtils.jl">
<img src="https://codecov.io/gh/JuliaSymbolics/SymbolicUtils.jl/branch/master/graph/badge.svg?token=UL72EBCQRW"/>
</a>
</p>
# SymbolicUtils.jl

[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged)
[![Global Docs](https://img.shields.io/badge/docs-SciML-blue.svg)](https://docs.sciml.ai/SymbolicUtils/stable/)

[![codecov](https://codecov.io/gh/JuliaSymbolics/SymbolicUtils.jl/branch/master/graph/badge.svg)](https://app.codecov.io/gh/JuliaSymbolics/SymbolicUtils.jl)
[![Build Status](https://github.com/JuliaSymbolics/SymbolicUtils.jl/workflows/CI/badge.svg)](https://github.com/JuliaSymbolics/SymbolicUtils.jl/actions?query=workflow%3ACI)
[![Build status](https://badge.buildkite.com/3db222e469784b365e4b45f2b0155d252cf0ae70fef708bfa1.svg?branch=master)](https://buildkite.com/julialang/symbolicutils-dot-jl)

[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac)
[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle)


## Tutorials and Documentation

For information on using the package,
[see the stable documentation](https://symbolicutils.juliasymbolics.org/stable/). Use the
[in-development documentation](https://symbolicutils.juliasymbolics.org/dev/) for the version of
the documentation, which contains the unreleased features.

SymbolicUtils.jl provides various utilities for symbolic computing. SymbolicUtils.jl is what one would use to build
a Computer Algebra System (CAS). If you're looking for a complete CAS, similar to SymPy or Mathematica, see
Expand All @@ -18,19 +25,8 @@ Octonian algebras, you've come to the right place.

[Symbols in SymbolicUtils](https://symbolicutils.juliasymbolics.org/#creating_symbolic_expressions) carry type information. Operations on them propagate this information. [A rule-based rewriting language](https://symbolicutils.juliasymbolics.org/rewrite/#rule-based_rewriting) can be used to find subexpressions that satisfy arbitrary conditions and apply arbitrary transformations on the matches. The library also contains a set of useful [simplification](https://juliasymbolics.github.io/SymbolicUtils.jl/#simplification) rules for expressions of numeric symbols and numbers. These can be remixed and extended for special purposes.


If you are a Julia package develper in need of a rule rewriting system for your own types, have a look at the [interfacing guide](https://symbolicutils.juliasymbolics.org/interface/).

[**Go to the manual**](https://juliasymbolics.github.io/SymbolicUtils.jl/)

SymbolicUtils.jl is on the general registry and can be added the usual way:
```julia
pkg> add SymbolicUtils
```
or
```julia
julia> using Pkg; Pkg.add("SymbolicUtils")
```

### "I don't want to read your manual, just show me some cool code"
```julia
Expand Down
2 changes: 2 additions & 0 deletions benchmark/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[deps]
Metatheory = "e9d8d322-4543-424a-9be4-0cc815abe26c"
2 changes: 1 addition & 1 deletion docs/src/manual/representation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $p / q$ is represented by `Div(p, q)`. The result of `*` on `Div` is maintainted

Packages like DynamicPolynomials.jl provide representations that are even more efficient than the `Add` and `Mul` types mentioned above. They are designed specifically for multi-variate polynomials. They provide common algorithms such as multi-variate polynomial GCD. The restrictions that make it fast also mean some things are not possible: Firstly, DynamicPolynomials can only represent flat polynomials. For example, `(x-3)*(x+5)` can only be represented as `(x^2) + 15 - 8x`. Secondly, DynamicPolynomials does not have ways to represent generic Terms such as `sin(x-y)` in the tree.

To reconcile these differences while being able to use the efficient algorithms of DynamicPolynomials we have the `PolyForm` type. This type holds a polynomial and the mappings necessary to present the polynomial as a SymbolicUtils expression (i.e. by defining `operation` and `arguments`). The mappings constructed for the conversion are 1) a bijection from DynamicPolynomials PolyVar type to a Symbolics `Sym`, and 2) a mapping from `Sym`s to non-polynomial terms that the `Sym`s stand-in for. These terms may themselves contain PolyForm if there are polynomials inside them. The mappings are transiently global, that is, when all references to the mappings go out of scope, they are released and re-created.
To reconcile these differences while being able to use the efficient algorithms of DynamicPolynomials we have the `PolyForm` type. This type holds a polynomial and the mappings necessary to present the polynomial as a SymbolicUtils expression (i.e. by defining `operation` and `arguments`). The mappings constructed for the conversion are 1) a bijection from DynamicPolynomials Variable type to a Symbolics `Sym`, and 2) a mapping from `Sym`s to non-polynomial terms that the `Sym`s stand-in for. These terms may themselves contain PolyForm if there are polynomials inside them. The mappings are transiently global, that is, when all references to the mappings go out of scope, they are released and re-created.

```julia
julia> @syms x y
Expand Down
2 changes: 1 addition & 1 deletion page/representation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $p / q$ is represented by `Div(p, q)`. The result of `*` on `Div` is maintainted

Packages like DynamicPolynomials.jl provide representations that are even more efficient than the `Add` and `Mul` types mentioned above. They are designed specifically for multi-variate polynomials. They provide common algorithms such as multi-variate polynomial GCD. The restrictions that make it fast also mean some things are not possible: Firstly, DynamicPolynomials can only represent flat polynomials. For example, `(x-3)*(x+5)` can only be represented as `(x^2) + 15 - 8x`. Secondly, DynamicPolynomials does not have ways to represent generic Terms such as `sin(x-y)` in the tree.

To reconcile these differences while being able to use the efficient algorithms of DynamicPolynomials we have the `PolyForm` type. This type holds a polynomial and the mappings necessary to present the polynomial as a SymbolicUtils expression (i.e. by defining `operation` and `arguments`). The mappings constructed for the conversion are 1) a bijection from DynamicPolynomials PolyVar type to a Symbolics `Sym`, and 2) a mapping from `Sym`s to non-polynomial terms that the `Sym`s stand-in for. These terms may themselves contain PolyForm if there are polynomials inside them. The mappings are transiently global, that is, when all references to the mappings go out of scope, they are released and re-created.
To reconcile these differences while being able to use the efficient algorithms of DynamicPolynomials we have the `PolyForm` type. This type holds a polynomial and the mappings necessary to present the polynomial as a SymbolicUtils expression (i.e. by defining `operation` and `arguments`). The mappings constructed for the conversion are 1) a bijection from DynamicPolynomials Variable type to a Symbolics `Sym`, and 2) a mapping from `Sym`s to non-polynomial terms that the `Sym`s stand-in for. These terms may themselves contain PolyForm if there are polynomials inside them. The mappings are transiently global, that is, when all references to the mappings go out of scope, they are released and re-created.

```julia
julia> @syms x y
Expand Down
3 changes: 1 addition & 2 deletions src/SymbolicUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ include("equality-saturation.jl")
include("rewriters.jl")

# Convert to an efficient multi-variate polynomial representation
import MultivariatePolynomials
const MP = MultivariatePolynomials
import MultivariatePolynomials as MP
import DynamicPolynomials
export expand
include("polyform.jl")
Expand Down
27 changes: 25 additions & 2 deletions src/code.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Code

using StaticArrays, LabelledArrays, SparseArrays, LinearAlgebra
using StaticArrays, LabelledArrays, SparseArrays, LinearAlgebra, NaNMath, SpecialFunctions

export toexpr, Assignment, (), Let, Func, DestructuredArgs, LiteralExpr,
SetArray, MakeArray, MakeSparseArray, MakeTuple, AtIndex,
Expand Down Expand Up @@ -96,7 +96,30 @@ Base.convert(::Type{Assignment}, p::Pair) = Assignment(pair[1], pair[2])

toexpr(a::Assignment, st) = :($(toexpr(a.lhs, st)) = $(toexpr(a.rhs, st)))

function_to_expr(op, args, st) = nothing
const NaNMathFuns = (
sin,
cos,
tan,
asin,
acos,
acosh,
atanh,
log,
log2,
log10,
lgamma,
log1p,
sqrt,
)
function function_to_expr(op, O, st)
(get(st.rewrites, :nanmath, false) && op in NaNMathFuns) || return nothing
name = nameof(op)
fun = GlobalRef(NaNMath, name)
args = map(Base.Fix2(toexpr, st), arguments(O))
expr = Expr(:call, fun)
append!(expr.args, args)
return expr
end

function function_to_expr(op::Union{typeof(*),typeof(+)}, O, st)
out = get(st.rewrites, O, nothing)
Expand Down
11 changes: 9 additions & 2 deletions src/methods.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import NaNMath
import SpecialFunctions: gamma, loggamma, erf, erfc, erfcinv, erfi, erfcx,
dawson, digamma, trigamma, invdigamma, polygamma,
airyai, airyaiprime, airybi, airybiprime, besselj0,
Expand All @@ -12,9 +13,12 @@ const monadic = [deg2rad, rad2deg, transpose, asind, log1p, acsch,
atand, sec, acscd, cot, exp2, expm1, atanh, gamma,
loggamma, erf, erfc, erfcinv, erfi, erfcx, dawson, digamma,
trigamma, invdigamma, polygamma, airyai, airyaiprime, airybi,
airybiprime, besselj0, besselj1, bessely0, bessely1, isfinite]
airybiprime, besselj0, besselj1, bessely0, bessely1, isfinite,
NaNMath.sin, NaNMath.cos, NaNMath.tan, NaNMath.asin, NaNMath.acos,
NaNMath.acosh, NaNMath.atanh, NaNMath.log, NaNMath.log2,
NaNMath.log10, NaNMath.lgamma, NaNMath.log1p, NaNMath.sqrt]

const diadic = [max, min, hypot, atan, mod, rem, copysign,
const diadic = [max, min, hypot, atan, NaNMath.atanh, mod, rem, copysign,
besselj, bessely, besseli, besselk, hankelh1, hankelh2,
polygamma, beta, logbeta]
const previously_declared_for = Set([])
Expand Down Expand Up @@ -137,6 +141,9 @@ function Base.literal_pow(::typeof(^), x::Symbolic, ::Val{p}) where {p}
T = symtype(x)
T <: Number ? Base.:^(x, p) : error_f_symbolic(rem2pi, T)
end
function promote_symtype(::typeof(Base.literal_pow), _, ::Type{T}, ::Type{Val{S}}) where{T<:Number,S}
return promote_symtype(^, T, typeof(S))
end

promote_symtype(::Any, T) = promote_type(T, Real)
for f in monadic
Expand Down
5 changes: 2 additions & 3 deletions src/polyform.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export PolyForm, simplify_fractions, quick_cancel, flatten_fractions
using Bijections
using DynamicPolynomials: PolyVar

"""
PolyForm{T} <: Symbolic
Expand Down Expand Up @@ -44,7 +43,7 @@ end
Base.hash(p::PolyForm, u::UInt64) = xor(hash(p.p, u), trunc(UInt, 0xbabacacababacaca))
Base.isequal(x::PolyForm, y::PolyForm) = isequal(x.p, y.p)

# We use the same PVAR2SYM bijection to maintain the PolyVar <-> Sym mapping,
# We use the same PVAR2SYM bijection to maintain the MP.AbstractVariable <-> Sym mapping,
# When all PolyForms go out of scope in a session, we allow it to free up memory and
# start over if necessary
const PVAR2SYM = Ref(WeakRef())
Expand Down Expand Up @@ -156,7 +155,7 @@ end
function PolyForm(x,
pvar2sym=get_pvar2sym(),
sym2term=get_sym2term(),
vtype=DynamicPolynomials.PolyVar{true};
vtype=DynamicPolynomials.Variable{ DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder},DynamicPolynomials.Graded{MP.LexOrder}};
Fs = Union{typeof(+), typeof(*), typeof(^)},
recurse=false,
metadata=metadata(x))
Expand Down
42 changes: 27 additions & 15 deletions src/simplify_rules.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ the argument to the predicate satisfies `istree` and `operation(x) == f`
"""
is_operation(f) = @nospecialize(x) -> istree(x) && (operation(x) == f)

const PLUS_RULES = [
let
CANONICALIZE_PLUS = [
@rule(~x::isnotflat(+) => flatten_term(+, ~x))
@rule(~x::needs_sorting(+) => sort_args(+, ~x))
@ordered_acrule(~a::is_literal_number + ~b::is_literal_number => ~a + ~b)

@acrule(*(~~x) + *(~β, ~~x) => *(1 + ~β, (~~x)...))
@acrule(*(~α, ~~x) + *(~β, ~~x) => *(~α + ~β, (~~x)...))
@acrule(*(~~x, ~α) + *(~~x, ~β) => *(~α + ~β, (~~x)...))

@acrule(~x + *(~β, ~x) => *(1 + ~β, ~x))
@acrule(*(~α::is_literal_number, ~x) + ~x => *(~α + 1, ~x))
Expand All @@ -23,28 +22,37 @@ const PLUS_RULES = [
@rule(+(~x) => ~x)
]

TIMES_RULES = [
PLUS_DISTRIBUTE = [
@acrule(*(~α, ~~x) + *(~β, ~~x) => *(~α + ~β, (~~x)...))
@acrule(*(~~x, ~α) + *(~~x, ~β) => *(~α + ~β, (~~x)...))
]

CANONICALIZE_TIMES = [
@rule(~x::isnotflat(*) => flatten_term(*, ~x))
@rule(~x::needs_sorting(*) => sort_args(*, ~x))

@ordered_acrule(~a::is_literal_number * ~b::is_literal_number => ~a * ~b)
@rule(*(~~x::hasrepeats) => *(merge_repeats(^, ~~x)...))

@acrule((~y)^(~n) * ~y => (~y)^(~n+1))
@ordered_acrule((~x)^(~n) * (~x)^(~m) => (~x)^(~n + ~m))

@ordered_acrule((~z::_isone * ~x) => ~x)
@ordered_acrule((~z::_iszero * ~x) => ~z)
@rule(*(~x) => ~x)
]

MUL_DISTRIBUTE = @ordered_acrule((~x)^(~n) * (~x)^(~m) => (~x)^(~n + ~m))


const POW_RULES = [
CANONICALIZE_POW = [
@rule(^(*(~~x), ~y::_isinteger) => *(map(a->pow(a, ~y), ~~x)...))
@rule((((~x)^(~p::_isinteger))^(~q::_isinteger)) => (~x)^((~p)*(~q)))
@rule(^(~x, ~z::_iszero) => 1)
@rule(^(~x, ~z::_isone) => ~x)
@rule(inv(~x) => 1/(~x))
]

POW_RULES = [
@rule(^(~x::_isone, ~z) => 1)
]

Expand All @@ -60,9 +68,10 @@ const POW_RULES = [
@rule(real(~x::_isreal) => ~x)
@rule(imag(~x::_isreal) => zero(symtype(~x)))
@rule(ifelse(~x::is_literal_number, ~y, ~z) => ~x ? ~y : ~z)
@rule(ifelse(~x, ~y, ~y) => ~y)
]

const TRIG_EXP_RULES = [
TRIG_EXP_RULES = [
@acrule(~r*~x::has_trig_exp + ~r*~y => ~r*(~x + ~y))
@acrule(~r*~x::has_trig_exp + -1*~r*~y => ~r*(~x - ~y))
@acrule(sin(~x)^2 + cos(~x)^2 => one(~x))
Expand All @@ -86,7 +95,7 @@ const TRIG_EXP_RULES = [
@rule(exp(~x)^(~y) => exp(~x * ~y))
]

const BOOLEAN_RULES = [
BOOLEAN_RULES = [
@rule((true | (~x)) => true)
@rule(((~x) | true) => true)
@rule((false | (~x)) => ~x)
Expand Down Expand Up @@ -116,15 +125,18 @@ const BOOLEAN_RULES = [
@rule((~f)(~x::is_literal_number, ~y::is_literal_number) => (~f)(~x, ~y))
]

let
function number_simplifier()
rule_tree = [If(istree, Chain(ASSORTED_RULES)),
If(is_operation(+),
Chain(PLUS_RULES)),
If(is_operation(*),
Chain(TIMES_RULES)),
If(is_operation(^),
Chain(POW_RULES))] |> RestartedChain
If(x -> !isadd(x) && is_operation(+)(x),
Chain(CANONICALIZE_PLUS)),
If(is_operation(+), Chain(PLUS_DISTRIBUTE)), # This would be useful even if isadd
If(x -> !ismul(x) && is_operation(*)(x),
Chain(CANONICALIZE_TIMES)),
If(is_operation(*), MUL_DISTRIBUTE),
If(x -> !ispow(x) && is_operation(^)(x),
Chain(CANONICALIZE_POW)),
If(is_operation(^), Chain(POW_RULES)),
] |> RestartedChain

rule_tree
end
Expand Down
Loading

0 comments on commit 2d8eeb2

Please sign in to comment.