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: docs/src/manual/rewrite.md
+82-18Lines changed: 82 additions & 18 deletions
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Rewrite rules match and transform an expression. A rule is written using either
8
8
9
9
Here is a simple rewrite rule, that uses formula for the double angle of the sine function:
10
10
11
-
```julia:rewrite1
11
+
```jldoctest rewrite
12
12
using SymbolicUtils
13
13
14
14
@syms w z α::Real β::Real
@@ -18,56 +18,83 @@ using SymbolicUtils
18
18
r1 = @rule sin(2(~x)) => 2sin(~x)*cos(~x)
19
19
20
20
r1(sin(2z))
21
+
22
+
# output
23
+
2sin(z)*cos(z)
21
24
```
22
25
23
26
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.
24
27
25
28
`~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.
26
29
27
30
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.
28
-
```julia:rewrite2
31
+
```jldoctest rewrite
29
32
r1(sin(3z)) === nothing
33
+
34
+
# output
35
+
true
30
36
```
31
37
32
38
Slot variable (matcher) is not necessary a single variable
33
39
34
-
```julia:rewrite3
40
+
```jldoctest rewrite
35
41
r1(sin(2*(w-z)))
42
+
43
+
# output
44
+
2cos(w - z)*sin(w - z)
36
45
```
37
46
38
47
but it must be a single expression
39
48
40
-
```julia:rewrite4
49
+
```jldoctest rewrite
41
50
r1(sin(2*(w+z)*(α+β))) === nothing
51
+
52
+
# output
53
+
true
42
54
```
43
55
44
56
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
68
54
-
```julia:rewrite6
69
+
```jldoctest rewrite
55
70
@syms x y z
56
71
@rule(+(~~xs) => ~~xs)(x + y + z)
72
+
73
+
# output
74
+
3-element view(::Vector{Any}, 1:3) with eltype Any:
75
+
z
76
+
y
77
+
x
57
78
```
58
79
59
80
`~~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
144
107
145
### Example of applying the rules to simplify expression
108
146
109
147
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
162
122
-
```julia:rewrite10
163
+
```jldoctest rewriteex
123
164
pyid = @rule sin(~x)^2 + cos(~x)^2 => 1
124
165
125
166
pyid(cos(x)^2 + sin(x)^2) === nothing
167
+
168
+
# output
169
+
true
126
170
```
127
171
128
172
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
173
130
-
```julia:rewrite11
174
+
```jldoctest rewriteex
131
175
acpyid = @acrule sin(~x)^2 + cos(~x)^2 => 1
132
176
133
177
acpyid(cos(x)^2 + sin(x)^2 + 2cos(x)*sin(x))
178
+
179
+
# output
180
+
1 + 2sin(x)*cos(x)
134
181
```
135
182
136
183
It has been some work. Fortunately rules may be [chained together](#chaining rewriters) into more sophisticated rewriters to avoid manual application of the rules.
@@ -175,47 +222,64 @@ Several rules may be chained to give chain of rules. Chain is an array of rules
175
222
176
223
To check that, we will combine rules from [previous example](#example of applying the rules to simplify expression) into a chain
it's important to notice, that chain is ordered, so if rules are in different order it wouldn't work the same as in earlier example
197
252
198
-
```julia:composing3
253
+
```jldoctest composing
199
254
cas = Chain([acpyid, sqexpand])
200
255
201
256
cas((cos(x) + sin(x))^2)
257
+
258
+
# output
259
+
sin(x)^2 + 2sin(x)*cos(x) + cos(x)^2
202
260
```
203
261
since Pythagorean identity is applied before square expansion, so it is unable to match squares of sine and cosine.
204
262
205
263
One way to circumvent the problem of order of applying rules in chain is to use `RestartedChain`
206
264
207
-
```julia:composing4
265
+
```jldoctest composing
208
266
using SymbolicUtils.Rewriters: RestartedChain
209
267
210
268
rcas = RestartedChain([acpyid, sqexpand])
211
269
212
270
rcas((cos(x) + sin(x))^2)
271
+
272
+
# output
273
+
1 + 2sin(x)*cos(x)
213
274
```
214
275
215
276
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.
216
277
217
278
You can also use `Fixpoint` to apply the rules until there are no changes.
0 commit comments