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/rewrite.md
+77-21Lines changed: 77 additions & 21 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,8 @@
4
4
5
5
Rewrite rules match and transform an expression. A rule is written using either the `@rule` macro or the `@acrule` macro. It creates callable `Rule` object.
6
6
7
+
### Basics of rule-based term rewriting in SymbolicUtils
8
+
7
9
Here is a simple rewrite rule, that uses formula for the double angle of the sine function:
8
10
9
11
```julia:rewrite1
@@ -44,7 +46,7 @@ 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:
@@ -68,8 +70,6 @@ Notice that the expression was autosimplified before application of the rule.
68
70
2 * (w+w+α+β)
69
71
```
70
72
71
-
72
-
73
73
### Predicates for matching
74
74
75
75
Matcher pattern may contain slot variables with attached predicates, written as `~x::f` where `f` is a function that takes a matched expression and returns a boolean value. Such a slot will be considered a match only if `f` returns true.
although in case of `Number` it also works with regular `@rule` since autosimplification orders and applies associativity and commutativity to the expression.
105
+
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
+
106
138
107
139
## Composing rewriters
108
140
@@ -122,39 +154,63 @@ rewriters.
122
154
-`IfElse(cond, rw1, rw2)` runs the `cond` function on the input, applies `rw1` if cond
123
155
returns true, `rw2` if it retuns false
124
156
-`If(cond, rw)` is the same as `IfElse(cond, rw, Empty())`
125
-
-`Prewalk(rw; threaded=false, thread_cutoff=100)` returns a rewriter which does a pre-order
126
-
traversal of a given expression and applies the rewriter `rw`. `threaded=true` will
127
-
use multi threading for traversal. `thread_cutoff` is the minimum number of nodes
128
-
in a subtree which should be walked in a threaded spawn.
129
-
-`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.
130
163
-`Fixpoint(rw)` returns a rewriter which applies `rw` repeatedly until there are no changes to be made.
131
164
-`PassThrough(rw)` returns a rewriter which if `rw(x)` returns `nothing` will instead
132
165
return `x` otherwise will return `rw(x)`.
133
166
167
+
### Chaining rewriters
134
168
135
-
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.
136
170
137
-
```julia:rewrite9
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)
155
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 the rule, so after `sqexpand` is hit it (re)starts again and successfully applies `acpyid` to resulting expression.
156
211
157
212
You can also use `Fixpoint` to apply the rules until there are no changes.
0 commit comments