Skip to content

Commit 0aa94dd

Browse files
committed
Merge remote-tracking branch 'origin/master' into b/611-migrate-to-exproniconjl-v08-from-unityperjl
2 parents b0c5de1 + 5bca5b9 commit 0aa94dd

File tree

7 files changed

+89
-29
lines changed

7 files changed

+89
-29
lines changed

.github/workflows/Documentation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
build:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v4
1515
- uses: julia-actions/setup-julia@latest
1616
with:
1717
version: '1'

.github/workflows/Downstream.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- {user: SciML, repo: ModelOrderReduction.jl, group: All}
2727

2828
steps:
29-
- uses: actions/checkout@v2
29+
- uses: actions/checkout@v4
3030
- uses: julia-actions/setup-julia@v1
3131
with:
3232
version: ${{ matrix.julia-version }}

.github/workflows/benchmark_pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414

1515
steps:
16-
- uses: actions/checkout@v2
16+
- uses: actions/checkout@v4
1717
- uses: julia-actions/setup-julia@v1
1818
with:
1919
version: "1"

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
test:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
13+
- uses: actions/checkout@v4
1414
with:
1515
fetch-depth: 0
1616
- run: |

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using Documenter, SymbolicUtils
22

33
include("pages.jl")
4+
DocMeta.setdocmeta!(SymbolicUtils, :DocTestSetup, :(using SymbolicUtils); recursive=true)
45

56
makedocs(
67
sitename="SymbolicUtils.jl",
78
authors="Shashi Gowda",
89
modules=[SymbolicUtils],
9-
clean=true,doctest=false,
10+
clean=true, doctest=true,
1011
warnonly=Documenter.except(
1112
:doctest,
1213
:linkcheck,

docs/src/manual/rewrite.md

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Rewrite rules match and transform an expression. A rule is written using either
88

99
Here is a simple rewrite rule, that uses formula for the double angle of the sine function:
1010

11-
```julia:rewrite1
11+
```jldoctest rewrite
1212
using SymbolicUtils
1313
1414
@syms w z α::Real β::Real
@@ -18,56 +18,83 @@ using SymbolicUtils
1818
r1 = @rule sin(2(~x)) => 2sin(~x)*cos(~x)
1919
2020
r1(sin(2z))
21+
22+
# output
23+
2sin(z)*cos(z)
2124
```
2225

2326
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.
2427

2528
`~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.
2629

2730
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
2932
r1(sin(3z)) === nothing
33+
34+
# output
35+
true
3036
```
3137

3238
Slot variable (matcher) is not necessary a single variable
3339

34-
```julia:rewrite3
40+
```jldoctest rewrite
3541
r1(sin(2*(w-z)))
42+
43+
# output
44+
2cos(w - z)*sin(w - z)
3645
```
3746

3847
but it must be a single expression
3948

40-
```julia:rewrite4
49+
```jldoctest rewrite
4150
r1(sin(2*(w+z)*(α+β))) === nothing
51+
52+
# output
53+
true
4254
```
4355

4456
Rules are of course not limited to single slot variable
4557

46-
```julia:rewrite5
58+
```jldoctest rewrite
4759
r2 = @rule sin(~x + ~y) => sin(~x)*cos(~y) + cos(~x)*sin(~y);
4860
4961
r2(sin(α+β))
62+
63+
# output
64+
sin(β)*cos(α) + cos(β)*sin(α)
5065
```
5166

5267
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:
5368

54-
```julia:rewrite6
69+
```jldoctest rewrite
5570
@syms x y z
5671
@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
5778
```
5879

5980
`~~xs` is a vector of subexpressions matched. You can use it to construct something more useful:
6081

61-
```julia:rewrite7
82+
```jldoctest rewrite
6283
r3 = @rule ~x * +(~~ys) => sum(map(y-> ~x * y, ~~ys));
6384
6485
r3(2 * (w+w+α+β))
86+
87+
# output
88+
4w + 2α + 2β
6589
```
6690

6791
Notice that the expression was autosimplified before application of the rule.
6892

69-
```julia:rewrite8
93+
```jldoctest rewrite
7094
2 * (w+w+α+β)
95+
96+
# output
97+
2(2w + α + β)
7198
```
7299

73100
### Predicates for matching
@@ -78,7 +105,8 @@ Similarly `~~x::g` is a way of attaching a predicate `g` to a segment variable.
78105

79106
For example,
80107

81-
```julia:pred1
108+
```jldoctest pred
109+
using SymbolicUtils
82110
@syms a b c d
83111
84112
r = @rule ~x + ~~y::(ys->iseven(length(ys))) => "odd terms";
@@ -87,50 +115,69 @@ r = @rule ~x + ~~y::(ys->iseven(length(ys))) => "odd terms";
87115
@show r(b + c + d)
88116
@show r(b + c + b)
89117
@show r(a + b)
118+
119+
# output
120+
r(a + b + c + d) = nothing
121+
r(b + c + d) = "odd terms"
122+
r(b + c + b) = nothing
123+
r(a + b) = nothing
90124
```
91125

92126

93127
### Associative-Commutative Rules
94128

95129
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.
96130

97-
```julia:acr
131+
```jldoctest acr
132+
using SymbolicUtils
98133
@syms x y z
99134
100135
acr = @acrule((~a)^(~x) * (~a)^(~y) => (~a)^(~x + ~y))
101136
102137
acr(x^y * x^z)
138+
139+
# output
140+
x^(y + z)
103141
```
104142

105143
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.
106144

107145
### Example of applying the rules to simplify expression
108146

109147
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
110-
```julia:rewrite9
148+
```jldoctest rewriteex
111149
using SymbolicUtils
112150
113151
@syms x::Real y::Real
114152
115153
sqexpand = @rule (~x + ~y)^2 => (~x)^2 + (~y)^2 + 2 * ~x * ~y
116154
117155
sqexpand((cos(x) + sin(x))^2)
156+
157+
# output
158+
sin(x)^2 + 2sin(x)*cos(x) + cos(x)^2
118159
```
119160

120161
It works. This can be further simplified using Pythagorean identity and check it
121162

122-
```julia:rewrite10
163+
```jldoctest rewriteex
123164
pyid = @rule sin(~x)^2 + cos(~x)^2 => 1
124165
125166
pyid(cos(x)^2 + sin(x)^2) === nothing
167+
168+
# output
169+
true
126170
```
127171

128172
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.
129173

130-
```julia:rewrite11
174+
```jldoctest rewriteex
131175
acpyid = @acrule sin(~x)^2 + cos(~x)^2 => 1
132176
133177
acpyid(cos(x)^2 + sin(x)^2 + 2cos(x)*sin(x))
178+
179+
# output
180+
1 + 2sin(x)*cos(x)
134181
```
135182

136183
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
175222

176223
To check that, we will combine rules from [previous example](#example of applying the rules to simplify expression) into a chain
177224

178-
```julia:composing1
225+
```jldoctest composing
179226
using SymbolicUtils
180227
using SymbolicUtils.Rewriters
181228
229+
@syms x
230+
182231
sqexpand = @rule (~x + ~y)^2 => (~x)^2 + (~y)^2 + 2 * ~x * ~y
183232
acpyid = @acrule sin(~x)^2 + cos(~x)^2 => 1
184233
185234
csa = Chain([sqexpand, acpyid])
186235
187236
csa((cos(x) + sin(x))^2)
237+
238+
# output
239+
1 + 2sin(x)*cos(x)
188240
```
189241

190242
Important feature of `Chain` is that it returns the expression instead of `nothing` if it doesn't change the expression
191243

192-
```julia:composing2
244+
```jldoctest composing
193245
Chain([@acrule sin(~x)^2 + cos(~x)^2 => 1])((cos(x) + sin(x))^2)
246+
247+
# output
248+
(sin(x) + cos(x))^2
194249
```
195250

196251
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
197252

198-
```julia:composing3
253+
```jldoctest composing
199254
cas = Chain([acpyid, sqexpand])
200255
201256
cas((cos(x) + sin(x))^2)
257+
258+
# output
259+
sin(x)^2 + 2sin(x)*cos(x) + cos(x)^2
202260
```
203261
since Pythagorean identity is applied before square expansion, so it is unable to match squares of sine and cosine.
204262

205263
One way to circumvent the problem of order of applying rules in chain is to use `RestartedChain`
206264

207-
```julia:composing4
265+
```jldoctest composing
208266
using SymbolicUtils.Rewriters: RestartedChain
209267
210268
rcas = RestartedChain([acpyid, sqexpand])
211269
212270
rcas((cos(x) + sin(x))^2)
271+
272+
# output
273+
1 + 2sin(x)*cos(x)
213274
```
214275

215276
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.
216277

217278
You can also use `Fixpoint` to apply the rules until there are no changes.
218279

219-
```julia:composing5
280+
```jldoctest composing
220281
Fixpoint(cas)((cos(x) + sin(x))^2)
282+
283+
# output
284+
1 + 2sin(x)*cos(x)
221285
```

test/runtests.jl

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ DocMeta.setdocmeta!(
1212
recursive=true
1313
)
1414

15-
# Only test one Julia version to avoid differences due to changes in printing.
16-
if v"1.6" VERSION < v"1.7-beta3.0"
17-
doctest(SymbolicUtils)
18-
else
19-
@warn "Skipping doctests"
20-
end
15+
doctest(SymbolicUtils)
2116
SymbolicUtils.show_simplified[] = false
2217

2318
include("utils.jl")

0 commit comments

Comments
 (0)