You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: page/index.md
+41-9Lines changed: 41 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,8 +18,8 @@ where appropriate -->
18
18
**Features:**
19
19
20
20
- Fast expressions
21
-
- A [combinator library](/rewrite/#composing_rewriters) for making rewriters.
22
21
- A [rule-based rewriting language](/rewrite/#rule-based_rewriting).
22
+
- A [combinator library](/rewrite/#composing_rewriters) for making rewriters.
23
23
- Type promotion:
24
24
- Symbols (`Sym`s) carry type information. ([read more](#creating_symbolic_expressions))
25
25
- Compound expressions composed of `Sym`s propagate type information. ([read more](#expression_interface))
@@ -65,17 +65,44 @@ Note however that they are not subtypes of these types!
65
65
@show α isa Real
66
66
```
67
67
68
+
As their types are different:
69
+
70
+
```julia:symtype3
71
+
@show typeof(w)
72
+
@show typeof(α)
73
+
```
74
+
68
75
(see [this post](https://discourse.julialang.org/t/ann-symbolicutils-jl-groundwork-for-a-symbolic-ecosystem-in-julia/38455/13?u=shashi) for why they are all not just subtypes of `Number`)
69
76
70
77
You can do basic arithmetic on symbols to get symbolic expressions:
71
78
72
79
```julia:expr
73
-
expr1 = α*sin(w)^2 + β*cos(z)^2
74
-
expr2 = α*cos(z)^2 + β*sin(w)^2
80
+
expr1 = α*sin(w)^2 + β*cos(z)^2
81
+
expr2 = α*cos(z)^2 + β*sin(w)^2
75
82
76
83
expr1 + expr2
77
84
```
78
85
86
+
SymbolicUtils automatically simplifies
87
+
88
+
```julia:creating1
89
+
2w + 3w - 3z + α
90
+
```
91
+
92
+
and reorders
93
+
94
+
```julia:creating2
95
+
(z + w)*(α + β)
96
+
```
97
+
98
+
expressions of type `Symbolic{<:Number}` (which includes `Sym{Real}`) when they are created. It also does constant elimination (including rational numbers)
99
+
100
+
```julia:creating3
101
+
5 + 2w - 3z + α - (β + 5//3) + 3w - 2 + 3//2 * β
102
+
```
103
+
104
+
It's worth remembering that the expression may be transformed with respect to the input when it's created.
105
+
79
106
80
107
**Function-like symbols**
81
108
@@ -88,19 +115,18 @@ using SymbolicUtils
88
115
f(z) + g(1, α) + sin(w)
89
116
```
90
117
118
+
This does not work since `z` is a `Number`, not a `Real`.
91
119
92
120
```julia:sym3
93
121
g(1, z)
94
122
```
95
123
96
-
97
-
This does not work since `z` is a `Number`, not a `Real`.
By default `*` and `+` operations apply the most basic simplification upon construction.
152
+
By default `*` and `+` operations apply the most basic simplification upon construction of the expression.
127
153
128
-
The `simplify` function applies a built-in set of rules to rewrite expressions in order to simplify it.
154
+
Commutativity and associativity are assumed over `+` and `*` operations on `Symbolic{<:Number}`.
129
155
130
156
```julia:simplify1
157
+
2 * (w+w+α+β + sin(z)^2 + cos(z)^2 - 1)
158
+
```
159
+
160
+
The `simplify` function applies a built-in set of rules to rewrite expressions in order to simplify it.
161
+
162
+
```julia:simplify2
131
163
simplify(2 * (w+w+α+β + sin(z)^2 + cos(z)^2 - 1))
132
164
```
133
165
134
-
The rules in the default simplify applies simple constant elemination, trigonometric identities.
166
+
The rules in the default simplify applies simple constant elimination and trigonometric identities.
135
167
136
168
If you read the previous section on the rules DSL, you should be able to read and understand the [rules](https://github.com/JuliaSymbolics/SymbolicUtils.jl/blob/master/src/simplify_rules.jl) that are used by `simplify`.
Rewrite rules match and transform an expression. A rule is written using either the `@rule` macro or the `@acrule` macro.
5
+
Rewrite rules match and transform an expression. A rule is written using either the `@rule` macro or the `@acrule` macro. It creates a callable `Rule` object.
6
6
7
-
Here is a simple rewrite rule:
7
+
### Basics of rule-based term rewriting in SymbolicUtils
8
+
9
+
Here is a simple rewrite rule, that uses formula for the double angle of the sine function:
8
10
9
11
```julia:rewrite1
10
12
using SymbolicUtils
@@ -13,37 +15,60 @@ using SymbolicUtils
13
15
14
16
(w, z, α, β) # hide
15
17
16
-
r1 = @rule ~x + ~x => 2 * (~x)
18
+
r1 = @rule sin(2(~x)) => 2sin(~x)*cos(~x)
17
19
18
-
r1(sin(1+z) + sin(1+z))
20
+
r1(sin(2z))
19
21
```
20
22
21
23
The `@rule` macro takes a pair of patterns -- the _matcher_ and the _consequent_ (`@rule matcher => consequent`). If an expression matches the matcher pattern, it is rewritten to the consequent pattern. `@rule` returns a callable object that applies the rule to an expression.
22
24
23
25
`~x` in the example is what is a **slot variable** named `x`. In a matcher pattern, slot variables are placeholders that match exactly one expression. When used on the consequent side, they stand in for the matched expression. If a slot variable appears twice in a matcher pattern, all corresponding matches must be equal (as tested by `Base.isequal` function). Hence this rule says: if you see something added to itself, make it twice of that thing, and works as such.
24
26
25
-
If you try to apply this rule to an expression where the two summands are not equal, it will return `nothing` -- this is the way a rule signifies failure to match.
27
+
If you try to apply this rule to an expression with triple angle, it will return `nothing` -- this is the way a rule signifies failure to match.
26
28
```julia:rewrite2
27
-
r1(sin(1+z) + sin(1+w)) === nothing
29
+
r1(sin(3z)) === nothing
28
30
```
29
31
30
-
If you want to match a variable number of subexpressions at once, you will need a **segment variable**. `~~xs` in the following example is a segment variable:
32
+
Slot variable (matcher) is not necessary a single variable
31
33
32
34
```julia:rewrite3
35
+
r1(sin(2*(w-z)))
36
+
```
37
+
38
+
but it must be a single expression
39
+
40
+
```julia:rewrite4
41
+
r1(sin(2*(w+z)*(α+β))) === nothing
42
+
```
43
+
44
+
Rules are of course not limited to single slot variable
If you want to match a variable number of subexpressions at once, you will need a **segment variable**. `~~xs` in the following example is a segment variable:
53
+
54
+
```julia:rewrite6
33
55
@syms x y z
34
56
@rule(+(~~xs) => ~~xs)(x + y + z)
35
57
```
36
58
37
59
`~~xs` is a vector of subexpressions matched. You can use it to construct something more useful:
Given an expression `f(x, f(y, z, u), v, w)`, a `f` is said to be associative if the expression is equivalent to `f(x, y, z, u, v, w)` and commutative if the order of arguments does not matter. SymbolicUtils has a special `@acrule` macro meant for rules on functions which are associate and commutative such as addition and multiplication of real and complex numbers.
although in case of `Number` it also works the same way with regular `@rule` since autosimplification orders and applies associativity and commutativity to the expression.
106
+
107
+
### Example of applying the rules to simplify expression
108
+
109
+
Consider expression `(cos(x) + sin(x))^2` that we would like simplify by applying some trigonometric rules. First, we need rule to expand square of `cos(x) + sin(x)`. First we try the simplest rule to expand square of the sum and try it on simple expression
It works. This can be further simplified using Pythagorean identity and check it
121
+
122
+
```julia:rewrite10
123
+
pyid = @rule sin(~x)^2 + cos(~x)^2 => 1
124
+
125
+
pyid(cos(x)^2 + sin(x)^2) === nothing
126
+
```
127
+
128
+
Why does it return `nothing`? If we look at the rule, we see that the order of `sin(x)` and `cos(x)` is different. Therefore, in order to work, the rule needs to be associative-commutative.
129
+
130
+
```julia:rewrite11
131
+
acpyid = @acrule sin(~x)^2 + cos(~x)^2 => 1
132
+
133
+
acpyid(cos(x)^2 + sin(x)^2 + 2cos(x)*sin(x))
134
+
```
135
+
136
+
It has been some work. Fortunately rules may be [chained together](#chaining rewriters) into more sophisticated rewirters to avoid manual application of the rules.
137
+
78
138
79
139
## Composing rewriters
80
140
@@ -94,39 +154,63 @@ rewriters.
94
154
- `IfElse(cond, rw1, rw2)` runs the `cond` function on the input, applies `rw1` if cond
95
155
returns true, `rw2` if it retuns false
96
156
- `If(cond, rw)` is the same as `IfElse(cond, rw, Empty())`
97
-
- `Prewalk(rw; threaded=false, thread_cutoff=100)` returns a rewriter which does a pre-order
98
-
traversal of a given expression and applies the rewriter `rw`. `threaded=true` will
99
-
use multi threading for traversal. `thread_cutoff` is the minimum number of nodes
100
-
in a subtree which should be walked in a threaded spawn.
101
-
- `Postwalk(rw; threaded=false, thread_cutoff=100)` similarly does post-order traversal.
157
+
- `Prewalk(rw; threaded=false, thread_cutoff=100)` returns a rewriter which does a pre-order
158
+
(*from top to bottom and from left to right*) traversal of a given expression and applies
159
+
the rewriter `rw`. `threaded=true` will use multi threading for traversal. `thread_cutoff`
160
+
is the minimum number of nodes in a subtree which should be walked in a threaded spawn.
161
+
- `Postwalk(rw; threaded=false, thread_cutoff=100)` similarly does post-order
162
+
(*from left to right and from bottom to top*) traversal.
102
163
- `Fixpoint(rw)` returns a rewriter which applies `rw` repeatedly until there are no changes to be made.
103
164
- `PassThrough(rw)` returns a rewriter which if `rw(x)` returns `nothing` will instead
104
165
return `x` otherwise will return `rw(x)`.
105
166
167
+
### Chaining rewriters
106
168
107
-
Example using Postwalk, and Chain
169
+
Several rules may be chained to give chain of rules. Chain is an array of rules which are subsequently applied to the expression.
108
170
109
-
```julia:rewrite6
171
+
To check that, we will combine rules from [previous example](#example of applying the rules to simplify expression) into a chain
its important to notice, that chain is ordered, so if rules are in different order it wouldn't work the same as in earlier example
192
+
193
+
```julia:composing3
194
+
cas = Chain([acpyid, sqexpand])
195
+
196
+
cas((cos(x) + sin(x))^2)
197
+
```
198
+
since Pythagorean identity is applied before square expansion, so it is unable to match squares of sine and cosine.
199
+
200
+
One way to circumvent the problem of order of applying rules in chain is to use `RestartedChain`
201
+
202
+
```julia:composing4
203
+
using SymbolicUtils.Rewriters: RestartedChain
204
+
205
+
rcas = RestartedChain([acpyid, sqexpand])
206
+
207
+
rcas((cos(x) + sin(x))^2)
208
+
```
209
+
210
+
It restarts the chain after each successful application of a rule, so after `sqexpand` is hit it (re)starts again and successfully applies `acpyid` to resulting expression.
211
+
129
212
You can also use `Fixpoint` to apply the rules until there are no changes.
0 commit comments