Skip to content

Commit b575def

Browse files
authored
Merge pull request #27 from JuliaSymbolics/interface-tests
interface tests
2 parents bcb3ad9 + 22908fe commit b575def

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

README.md

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,22 @@ julia> R(sin(sin(sin(x -1))), depth=2)
195195
cos(cos(sin((x + -1))))
196196
```
197197

198-
## Type conversion interface
198+
## Interfacing with SymbolicUtils.jl
199199

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

202-
The following functions should be defined for `T` to work.
202+
Our intention is for SymbolicUtils to be useful even for packages with their own custom symbolic types which
203+
differ from those offered by SymbolicUtils. To this end, SymbolicUtils provides an interface to convert expression
204+
tree types which have
205+
* an `operation`, (i.e. function to apply)
206+
* `arguments` which the `operation` is applied to
207+
* `variable` types which are the atoms from which the expression tree is built
208+
* optionally, a type which should `typeof(operation(arguments...))` should return if it were to be run.
209+
210+
SymbolicUtils uses a function `to_symbolic` to convert aribtrarty types to it's own internal types.
211+
212+
The following methods should be defined for an expression tree type `T` with symbol types `S` to work
213+
with SymbolicUtils.jl
203214

204215
#### `istree(x::T)`
205216

@@ -221,6 +232,17 @@ Returns the arguments (a `Vector`) for an expression tree.
221232
Called only if `istree(x)` is `true`. Part of the API required
222233
for `simplify` to work. Other required methods are `operation` and `istree`
223234

235+
#### `to_symbolic(x::S)`
236+
Convert your variable type to a `SymbolicUtils.Variable`. Suppose you have
237+
```julia
238+
struct MySymbol
239+
s::Symbol
240+
end
241+
```
242+
which could represent any type symbolically, then you would define
243+
```julia
244+
SymbolicUtils.to_symbolic(s::MySymbol) = SymbolicUtils.Variable(s.s)
245+
```
224246

225247
### Optional
226248

@@ -239,3 +261,73 @@ rules that may be implemented in the future.
239261
#### `promote_symtype(f, arg_symtypes...)`
240262

241263
Returns the appropriate output type of applying `f` on arguments of type `arg_symtypes`.
264+
265+
### Example
266+
267+
Suppose you were feeling the temptations of type piracy and wanted to make a quick and dirty
268+
symbolic library built on top of Julia's `Expr` type, e.g.
269+
270+
```julia
271+
for f [:+, :-, :*, :/, :^] #Note, this is type piracy!
272+
@eval begin
273+
Base.$f(x::Union{Expr, Symbol}, y::Number) = Expr(:call, $f, x, y)
274+
Base.$f(x::Number, y::Union{Expr, Symbol}) = Expr(:call, $f, x, y)
275+
Base.$f(x::Union{Expr, Symbol}, y::Union{Expr, Symbol}) = (Expr(:call, $f, x, y))
276+
end
277+
end
278+
279+
280+
julia> ex = 1 + (:x - 2)
281+
:((+)(1, (-)(x, 2)))
282+
```
283+
How can we use SymbolicUtils.jl to convert `ex` to `(-)(:x, 1)`? We simply implement `istree`,
284+
`operation`, `arguments` and `to_symbolic` and we'll be off to the races:
285+
```julia
286+
using SymbolicUtils: Variable, istree, operation, arguments, to_symbolic
287+
288+
SymbolicUtils.istree(ex::Expr) = ex.head == :call
289+
SymbolicUtils.operation(ex::Expr) = ex.args[1]
290+
SymbolicUtils.arguments(ex::Expr) = ex.args[2:end]
291+
SymbolicUtils.to_symbolic(s::Symbol) = Variable(s)
292+
293+
julia> simplify(ex)
294+
(-1 + x)
295+
296+
julia> dump(simplify(ex))
297+
Term{Any}
298+
f: + (function of type typeof(+))
299+
arguments: Array{Any}((2,))
300+
1: Int64 -1
301+
2: Variable{Any}
302+
name: Symbol x
303+
```
304+
this thing returns a `Term{Any}`, but it's not hard to convert back to `Expr`:
305+
```julia
306+
to_expr(t::Term) = Expr(:call, operation(t), to_expr.(arguments(t))...)
307+
to_expr(x) = x
308+
309+
julia> to_expr(simplify(ex))
310+
:((+)(-1, x))
311+
312+
julia> dump(ans)
313+
Expr
314+
head: Symbol call
315+
args: Array{Any}((3,))
316+
1: + (function of type typeof(+))
317+
2: Int64 -1
318+
3: Symbol x
319+
```
320+
321+
Now suppose we actaully wanted all `Symbol`s to be treated as `Real` numbers. We can simply define
322+
```julia
323+
SymbolicUtils.symtype(s::Symbol) = Real
324+
325+
julia> dump(simplify(ex))
326+
Term{Real}
327+
f: + (function of type typeof(+))
328+
arguments: Array{Any}((2,))
329+
1: Int64 -1
330+
2: Variable{Real}
331+
name: Symbol x
332+
```
333+
and now all our analysis is able to figure out that the `Term`s are `Number`s.

src/types.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ Base.one( s::Symbolic) = one( symtype(s))
8484

8585
Base.zero(s::Symbolic) = zero(symtype(s))
8686

87-
@noinline function promote_symtype(f, xs...)
88-
error("promote_symtype($f, $(join(xs, ", "))) not defined")
89-
end
87+
promote_symtype(f, xs...) = Any
9088

9189

9290

@@ -107,7 +105,7 @@ struct Variable{T} <: Symbolic{T}
107105
name::Symbol
108106
end
109107

110-
Variable(x) = Variable{Number}(x)
108+
Variable(x) = Variable{symtype(x)}(x)
111109

112110
Base.nameof(v::Variable) = v.name
113111

test/interface.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using SymbolicUtils, Test
2+
using SymbolicUtils: Term, Variable, to_symbolic, istree, operation, arguments, symtype
3+
4+
SymbolicUtils.istree(ex::Expr) = ex.head == :call
5+
SymbolicUtils.operation(ex::Expr) = ex.args[1]
6+
SymbolicUtils.arguments(ex::Expr) = ex.args[2:end]
7+
SymbolicUtils.to_symbolic(s::Symbol) = Variable(s)
8+
9+
for f [:+, :-, :*, :/, :^]
10+
@eval begin
11+
Base.$f(x::Union{Expr, Symbol}, y::Number) = Expr(:call, $f, x, y)
12+
Base.$f(x::Number, y::Union{Expr, Symbol}) = Expr(:call, $f, x, y)
13+
Base.$f(x::Union{Expr, Symbol}, y::Union{Expr, Symbol}) = (Expr(:call, $f, x, y))
14+
end
15+
end
16+
17+
ex = 1 + (:x - 2)
18+
19+
@test to_symbolic(ex) == Term{Any}(+, [1, Term{Any}(-, [Variable{Any}(:x), 2])])
20+
@test simplify(ex) == to_symbolic(-1 + :x)
21+
22+
to_expr(t::Term) = Expr(:call, operation(t), to_expr.(arguments(t))...)
23+
to_expr(s::Variable) = s.name
24+
to_expr(x) = x
25+
26+
@test to_expr(simplify(ex)) == Expr(:call, +, -1, :x)
27+
28+
SymbolicUtils.symtype(::Symbol) = Real
29+
30+
@test symtype(simplify(ex)) == Real

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ SymbolicUtils.show_simplified[] = false
88
include("basics.jl")
99
include("order.jl")
1010
include("rewrite.jl")
11+
include("interface.jl")
1112
include("rulesets.jl")
1213
include("fuzz.jl")

0 commit comments

Comments
 (0)