Skip to content

Commit e3189c6

Browse files
authored
Merge pull request #302 from JuliaSymbolics/ale/terminterface
TermInterface.jl integration
2 parents 8ef0533 + 098d131 commit e3189c6

File tree

10 files changed

+77
-202
lines changed

10 files changed

+77
-202
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SymbolicUtils"
22
uuid = "d1185830-fcd6-423d-90d6-eec64667417b"
33
authors = ["Shashi Gowda"]
4-
version = "0.14.0"
4+
version = "0.15.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -21,6 +21,7 @@ Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
2121
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2222
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
2323
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
24+
TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"
2425
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
2526

2627
[compat]
@@ -39,6 +40,7 @@ NaNMath = "0.3"
3940
Setfield = "0.7"
4041
SpecialFunctions = "0.10, 1.0"
4142
StaticArrays = "0.12, 1.0"
43+
TermInterface = "0.1.7"
4244
TimerOutputs = "0.5"
4345
julia = "1.3"
4446

page/interface.md

Lines changed: 6 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -7,107 +7,23 @@ where appropriate -->
77

88
# Interfacing with SymbolicUtils.jl
99

10+
<!-- TODO: document TermInterface.jl -->
11+
1012
\tableofcontents <!-- you can use \toc as well -->
1113

1214
This section is for Julia package developers who may want to use the `simplify` and rule rewriting system on their own expression types.
1315

1416
## Defining the interface
1517

16-
SymbolicUtils matchers can match any Julia object that implements an interface to traverse it as a tree.
18+
SymbolicUtils matchers can match any Julia object that implements an interface to traverse it as a tree. The interface in question, is defined in the [TermInterface.jl](https://github.com/JuliaSymbolics/TermInterface.jl) package. Its purpose is to provide a shared interface between various symbolic programming Julia packages.
1719

18-
In particular, the following methods should be defined for an expression tree type `T` with symbol types `S` to work
20+
In particular, you should define methods from TermInterface.jl for an expression tree type `T` with symbol types `S` to work
1921
with SymbolicUtils.jl
2022

21-
#### `istree(x::T)`
22-
23-
Check if `x` represents an expression tree. If returns true,
24-
it will be assumed that `operation(::T)` and `arguments(::T)`
25-
methods are defined. Definining these three should allow use
26-
of `simplify` on custom types. Optionally `symtype(x)` can be
27-
defined to return the expected type of the symbolic expression.
28-
29-
#### `operation(x::T)`
30-
31-
Returns the operation (a function object) performed by an expression
32-
tree. Called only if `istree(::T)` is true. Part of the API required
33-
for `simplify` to work. Other required methods are `arguments` and `istree`
34-
35-
#### `arguments(x::T)`
36-
37-
Returns the arguments (a `Vector`) for an expression tree.
38-
Called only if `istree(x)` is `true`. Part of the API required
39-
for `simplify` to work. Other required methods are `operation` and `istree`
40-
41-
In addition, the methods for `Base.hash` and `Base.isequal` should also be implemented by the types for the purposes of substitution and equality matching respectively.
42-
43-
#### `similarterm(t::MyType, f, args[, T])`
44-
45-
Construct a new term with the operation `f` and arguments `args`, the term should be similar to `t` in type. if `t` is a `Term` object a new Term is created with the same symtype as `t`. If not, the result is computed as `f(args...)`. Defining this method for your term type will reduce any performance loss in performing `f(args...)` (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use `promote_symtype` to infer this type.
46-
47-
The below two functions are internal to SymbolicUtils
48-
49-
### Optional
50-
51-
#### `symtype(x)`
52-
53-
The supposed type of values in the domain of x. Tracing tools can use this type to
54-
pick the right method to run or analyse code.
55-
56-
This defaults to `typeof(x)` if `x` is numeric, or `Any` otherwise.
57-
For the types defined in this package, namely `T<:Symbolic{S}` it is `S`.
23+
You can read the documentation of [TermInterface.jl](https://github.com/JuliaSymbolics/TermInterface.jl) on the [Github repository](https://github.com/JuliaSymbolics/TermInterface.jl).
5824

59-
Define this for your symbolic types if you want `simplify` to apply rules
60-
specific to numbers (such as commutativity of multiplication). Or such
61-
rules that may be implemented in the future.
25+
## SymbolicUtils.jl only methods
6226

6327
#### `promote_symtype(f, arg_symtypes...)`
6428

6529
Returns the appropriate output type of applying `f` on arguments of type `arg_symtypes`.
66-
67-
## Example
68-
69-
Suppose you were feeling the temptations of type piracy and wanted to make a quick and dirty
70-
symbolic library built on top of Julia's `Expr` type, e.g.
71-
72-
```julia:piracy1
73-
for f ∈ [:+, :-, :*, :/, :^] #Note, this is type piracy!
74-
@eval begin
75-
Base.$f(x::Union{Expr, Symbol}, y::Number) = Expr(:call, $f, x, y)
76-
Base.$f(x::Number, y::Union{Expr, Symbol}) = Expr(:call, $f, x, y)
77-
Base.$f(x::Union{Expr, Symbol}, y::Union{Expr, Symbol}) = (Expr(:call, $f, x, y))
78-
end
79-
end
80-
81-
82-
ex = 1 + (:x - 2)
83-
```
84-
85-
86-
How can we use SymbolicUtils.jl to convert `ex` to `(-)(:x, 1)`? We simply implement `istree`,
87-
`operation`, `arguments` and we'll be able to do rule-based rewriting on `Expr`s:
88-
```julia:piracy2
89-
using SymbolicUtils
90-
91-
SymbolicUtils.istree(ex::Expr) = ex.head == :call
92-
SymbolicUtils.operation(ex::Expr) = ex.args[1]
93-
SymbolicUtils.arguments(ex::Expr) = ex.args[2:end]
94-
95-
@rule(~x => ~x - 1)(ex)
96-
```
97-
98-
However, this is not enough to get SymbolicUtils to use its own algebraic simplification system on `Expr`s:
99-
```julia:piracy3
100-
simplify(ex)
101-
```
102-
103-
The reason that the expression was not simplified is that the expression tree is untyped, so SymbolicUtils
104-
doesn't know what rules to apply to the expression. To mimic the behaviour of most computer algebra
105-
systems, the simplest thing to do would be to assume that all `Expr`s are of type `Number`:
106-
107-
```julia:piracy4
108-
SymbolicUtils.symtype(s::Expr) = Number
109-
110-
simplify(ex)
111-
```
112-
113-
Now SymbolicUtils is able to apply the `Number` simplification rule to `Expr`.

src/SymbolicUtils.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module SymbolicUtils
66
using DocStringExtensions
77
export @syms, term, showraw, hasmetadata, getmetadata, setmetadata
88

9+
using TermInterface
10+
using TermInterface: node_count
11+
912
# Sym, Term,
1013
# Add, Mul and Pow
1114
using DataStructures
@@ -14,6 +17,7 @@ import Setfield: PropertyLens
1417
import Base: +, -, *, /, //, \, ^, ImmutableDict
1518
using ConstructionBase
1619
include("types.jl")
20+
export istree, operation, arguments, similarterm
1721

1822
# Methods on symbolic objects
1923
using SpecialFunctions, NaNMath

src/api.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function substitute(expr, dict; fold=true)
7474
similarterm(expr,
7575
op,
7676
args,
77-
symtype(expr),
77+
symtype(expr);
7878
metadata=metadata(expr))
7979
else
8080
expr

src/code.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ end
118118

119119
function_to_expr(::Sym, O, st) = get(st.symbolify, O, nothing)
120120

121+
toexpr(O::Expr, st) = O
122+
121123
function toexpr(O, st)
122124
!istree(O) && return O
123125
op = operation(O)

src/polyform.jl

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ PolyForm(sin((x+y)^2), recurse=true) #=> sin((x^2 + (2x)y + y^2))
2828
"""
2929
struct PolyForm{T, M} <: Symbolic{T}
3030
p::MP.AbstractPolynomialLike
31-
pvar2sym::Bijection{Any,Sym} # @polyvar x --> @sym x etc.
31+
pvar2sym::Bijection{Any,Any} # @polyvar x --> @sym x etc.
3232
sym2term::Dict{Sym,Any} # Symbol("sin-$hash(sin(x+y))") --> sin(x+y) => sin(PolyForm(...))
3333
metadata::M
3434
end
3535

3636
function (::Type{PolyForm{T}})(p, d1, d2, m=nothing) where {T}
3737
p isa Number && return p
38-
MP.isconstant(p) && return convert(Number, p)
38+
p isa MP.AbstractPolynomialLike && MP.isconstant(p) && return convert(Number, p)
3939
PolyForm{T, typeof(m)}(p, d1, d2, m)
4040
end
4141

@@ -51,7 +51,7 @@ clear_dicts() = (PVAR2SYM[] = WeakRef(nothing); SYM2TERM[] = WeakRef(nothing); n
5151
function get_pvar2sym()
5252
v = PVAR2SYM[].value
5353
if v === nothing
54-
d = Bijection{Any,Sym}()
54+
d = Bijection{Any,Any}()
5555
PVAR2SYM[] = WeakRef(d)
5656
return d
5757
else
@@ -93,7 +93,9 @@ end
9393
_isone(p::PolyForm) = isone(p.p)
9494

9595
function polyize(x, pvar2sym, sym2term, vtype, pow, Fs, recurse)
96-
if istree(x)
96+
if x isa Number
97+
return x
98+
elseif istree(x)
9799
if !(symtype(x) <: Number)
98100
error("Cannot convert $x of symtype $(symtype(x)) into a PolyForm")
99101
end
@@ -139,9 +141,7 @@ function polyize(x, pvar2sym, sym2term, vtype, pow, Fs, recurse)
139141

140142
return local_polyize(sym)
141143
end
142-
elseif x isa Number
143-
return x
144-
elseif x isa Sym
144+
elseif issym(x)
145145
if haskey(active_inv(pvar2sym), x)
146146
return pvar2sym(x)
147147
end
@@ -151,7 +151,7 @@ function polyize(x, pvar2sym, sym2term, vtype, pow, Fs, recurse)
151151
end
152152
end
153153

154-
function PolyForm(x::Symbolic{<:Number},
154+
function PolyForm(x,
155155
pvar2sym=get_pvar2sym(),
156156
sym2term=get_sym2term(),
157157
vtype=DynamicPolynomials.PolyVar{true};
@@ -164,13 +164,11 @@ function PolyForm(x::Symbolic{<:Number},
164164
PolyForm{symtype(x)}(p, pvar2sym, sym2term, metadata)
165165
end
166166

167-
PolyForm(x, args...;kw...) = x
168-
169-
istree(x::PolyForm) = true
167+
TermInterface.istree(x::Type{PolyForm}) = true
170168

171-
operation(x::PolyForm) = MP.nterms(x.p) == 1 ? (*) : (+)
169+
TermInterface.operation(x::PolyForm) = MP.nterms(x.p) == 1 ? (*) : (+)
172170

173-
function arguments(x::PolyForm{T}) where {T}
171+
function TermInterface.arguments(x::PolyForm{T}) where {T}
174172

175173
function is_var(v)
176174
MP.nterms(v) == 1 &&

src/rewriters.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ rewriters.
2929
3030
"""
3131
module Rewriters
32-
using SymbolicUtils: @timer, is_operation, istree, operation, similarterm, arguments, node_count
32+
using SymbolicUtils: @timer
33+
using TermInterface: is_operation, istree, operation, similarterm, arguments, node_count
3334

3435
export Empty, IfElse, If, Chain, RestartedChain, Fixpoint, Postwalk, Prewalk, PassThrough
3536

0 commit comments

Comments
 (0)