symath 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +616 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/symath/definition/abs.rb +48 -0
- data/lib/symath/definition/arccos.rb +25 -0
- data/lib/symath/definition/arccot.rb +23 -0
- data/lib/symath/definition/arccsc.rb +24 -0
- data/lib/symath/definition/arcsec.rb +24 -0
- data/lib/symath/definition/arcsin.rb +25 -0
- data/lib/symath/definition/arctan.rb +23 -0
- data/lib/symath/definition/bounds.rb +39 -0
- data/lib/symath/definition/codiff.rb +31 -0
- data/lib/symath/definition/constant.rb +111 -0
- data/lib/symath/definition/cos.rb +17 -0
- data/lib/symath/definition/cot.rb +17 -0
- data/lib/symath/definition/csc.rb +17 -0
- data/lib/symath/definition/curl.rb +27 -0
- data/lib/symath/definition/d.rb +62 -0
- data/lib/symath/definition/div.rb +27 -0
- data/lib/symath/definition/exp.rb +112 -0
- data/lib/symath/definition/fact.rb +55 -0
- data/lib/symath/definition/flat.rb +31 -0
- data/lib/symath/definition/function.rb +197 -0
- data/lib/symath/definition/grad.rb +23 -0
- data/lib/symath/definition/hodge.rb +23 -0
- data/lib/symath/definition/int.rb +75 -0
- data/lib/symath/definition/laplacian.rb +23 -0
- data/lib/symath/definition/lmd.rb +97 -0
- data/lib/symath/definition/ln.rb +45 -0
- data/lib/symath/definition/number.rb +51 -0
- data/lib/symath/definition/operator.rb +228 -0
- data/lib/symath/definition/sec.rb +17 -0
- data/lib/symath/definition/sharp.rb +31 -0
- data/lib/symath/definition/sin.rb +17 -0
- data/lib/symath/definition/sqrt.rb +62 -0
- data/lib/symath/definition/tan.rb +17 -0
- data/lib/symath/definition/trig.rb +95 -0
- data/lib/symath/definition/variable.rb +284 -0
- data/lib/symath/definition/xd.rb +28 -0
- data/lib/symath/definition.rb +205 -0
- data/lib/symath/equation.rb +67 -0
- data/lib/symath/fraction.rb +177 -0
- data/lib/symath/matrix.rb +252 -0
- data/lib/symath/minus.rb +125 -0
- data/lib/symath/operation/differential.rb +167 -0
- data/lib/symath/operation/distributivelaw.rb +367 -0
- data/lib/symath/operation/exterior.rb +64 -0
- data/lib/symath/operation/integration.rb +329 -0
- data/lib/symath/operation/match.rb +166 -0
- data/lib/symath/operation/normalization.rb +458 -0
- data/lib/symath/operation.rb +36 -0
- data/lib/symath/operator.rb +163 -0
- data/lib/symath/parser.rb +473 -0
- data/lib/symath/parser.y +129 -0
- data/lib/symath/poly/dup.rb +835 -0
- data/lib/symath/poly/galois.rb +621 -0
- data/lib/symath/poly.rb +142 -0
- data/lib/symath/power.rb +224 -0
- data/lib/symath/product.rb +183 -0
- data/lib/symath/sum.rb +174 -0
- data/lib/symath/type.rb +282 -0
- data/lib/symath/value.rb +372 -0
- data/lib/symath/version.rb +3 -0
- data/lib/symath/wedge.rb +48 -0
- data/lib/symath.rb +157 -0
- data/symath.gemspec +39 -0
- metadata +160 -0
@@ -0,0 +1,329 @@
|
|
1
|
+
require 'symath/operation'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module SyMath::Operation::Integration
|
5
|
+
class IntegrationError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# This operation provides methods for calculating some simple indefinite
|
9
|
+
# integrals (anti derivatives), and definite integrals from the boundaries
|
10
|
+
# of the anti-derivatives.
|
11
|
+
# NB: The algorithm is home made and extermely limited. It should be
|
12
|
+
# replaced with some of the known integration algorithm
|
13
|
+
|
14
|
+
@@functions = {}
|
15
|
+
|
16
|
+
def self.initialize()
|
17
|
+
# Anti-derivatives of simple functions with one variable
|
18
|
+
# FIXME: Clean up formulas
|
19
|
+
@@functions = {
|
20
|
+
# Logarithm
|
21
|
+
:ln => :a.to_m*fn(:ln, :a.to_m) - :a.to_m,
|
22
|
+
# Trigonometric functions
|
23
|
+
:sin => - fn(:cos, :a.to_m),
|
24
|
+
:cos => fn(:sin, :a.to_m),
|
25
|
+
:tan => - fn(:ln, fn(:abs, fn(:cos, :a.to_m))),
|
26
|
+
:cot => fn(:ln, fn(:abs, fn(:sin, :a.to_m))),
|
27
|
+
:sec => fn(:ln, fn(:abs, fn(:sec, :a.to_m) + fn(:tan, :a.to_m))),
|
28
|
+
:csc => - fn(:ln, fn(:abs, fn(:csc, :a.to_m) + fn(:cot, :a.to_m))),
|
29
|
+
# Inverse trigonometric functions
|
30
|
+
:arcsin => :a.to_m*fn(:arcsin, :a.to_m) + fn(:sqrt, 1.to_m - :a.to_m**2),
|
31
|
+
:arccos => :a.to_m*fn(:arccos, :a.to_m) - fn(:sqrt, 1.to_m - :a.to_m**2),
|
32
|
+
:arctan => :a.to_m*fn(:arctan, :a.to_m) -
|
33
|
+
fn(:ln, fn(:abs, 1.to_m + :a.to_m**2))/2,
|
34
|
+
:arccot => :a.to_m*fn(:arccot, :a.to_m) +
|
35
|
+
fn(:ln, fn(:abs, 1.to_m + :a.to_m**2))/2,
|
36
|
+
:arcsec => :a.to_m*fn(:arcsec, :a.to_m) -
|
37
|
+
fn(:ln, fn(:abs, 1.to_m + fn(:sqrt, 1.to_m - :a.to_m**-2))),
|
38
|
+
:arccsc => :a.to_m*fn(:arccsc, :a.to_m) +
|
39
|
+
fn(:ln, fn(:abs, 1.to_m + fn(:sqrt, 1.to_m - :a.to_m**-2))),
|
40
|
+
# Hyperbolic functions
|
41
|
+
:sinh => fn(:cosh, :a.to_m),
|
42
|
+
:cosh => fn(:sinh, :a.to_m),
|
43
|
+
:tanh => fn(:ln, fn(:cosh, :a.to_m)),
|
44
|
+
:coth => fn(:ln, fn(:abs, fn(:sinh, :a.to_m))),
|
45
|
+
:sech => fn(:arctan, fn(:sinh, :a.to_m)),
|
46
|
+
:csch => fn(:ln, fn(:abs, fn(:tanh, :a.to_m/2))),
|
47
|
+
# Inverse hyperbolic functions
|
48
|
+
:arsinh => :a.to_m*fn(:arsinh, :a.to_m) - fn(:sqrt, :a.to_m**2 + 1),
|
49
|
+
:arcosh => :a.to_m*fn(:arcosh, :a.to_m) - fn(:sqrt, :a.to_m**2 - 1),
|
50
|
+
:artanh => :a.to_m*fn(:artanh, :a.to_m) + fn(:ln, 1.to_m - :a.to_m**2)/2,
|
51
|
+
:arcoth => :a.to_m*fn(:arcoth, :a.to_m) + fn(:ln, :a.to_m**2 - 1)/2,
|
52
|
+
:arsech => :a.to_m*fn(:arsech, :a.to_m) + fn(:arcsin, :a.to_m),
|
53
|
+
:arcsch => :a.to_m*fn(:arcsch, :a.to_m) + fn(:abs, fn(:arsinh, :a.to_m)),
|
54
|
+
}
|
55
|
+
|
56
|
+
@@patterns = {
|
57
|
+
# Polynomial functions
|
58
|
+
(1 - :a**2)**(-1.to_m/2) => fn(:arcsin, :a),
|
59
|
+
(1 + :a**2)**-1 => fn(:arctan, :a),
|
60
|
+
# Logarithmic functions
|
61
|
+
fn(:ln, :a)**2 => :a*fn(:ln, :a)**2 - 2*:a*fn(:ln, :a) + 2*:a,
|
62
|
+
1/(:a*fn(:ln, :a)) => fn(:ln, fn(:abs, fn(:ln, :a))),
|
63
|
+
# Trigonometric functions
|
64
|
+
fn(:sin, :a)**2 => (:a - fn(:sin, :a)*fn(:cos, :a))/2,
|
65
|
+
fn(:sin, :a)**3 => fn(:cos, 3*:a)/12 - 3*fn(:cos, :a)/4,
|
66
|
+
fn(:cos, :a)**2 => (:a + fn(:sin, :a)*fn(:cos, :a))/2,
|
67
|
+
fn(:cos, :a)**3 => fn(:sin, 3*:a)/12 + 3*fn(:sin, :a)/4,
|
68
|
+
fn(:sec, :a)**2 => fn(:tan, :a),
|
69
|
+
fn(:sec, :a)**3 => fn(:sec, :a)*fn(:tan, :a)/2 +
|
70
|
+
fn(:ln, fn(:abs, fn(:sec, :a) + fn(:tan, :a)))/2,
|
71
|
+
fn(:csc, :a)**2 => - fn(:cot, :a),
|
72
|
+
fn(:csc, :a)**3 => - fn(:csc, :a)*fn(:cot, :a)/2 -
|
73
|
+
fn(:ln, fn(:abs, fn(:csc, :a) + fn(:cot, :a)))/2,
|
74
|
+
# Hyperbolic functions
|
75
|
+
fn(:sinh, :x)**2 => fn(:sinh, 2*:a)/4 - :a/2,
|
76
|
+
fn(:cosh, :x)**2 => fn(:sinh, 2*:a)/4 + :a/2,
|
77
|
+
fn(:tanh, :x)**2 => :a - fn(:tanh, :a),
|
78
|
+
# Combined trigonometric functions
|
79
|
+
fn(:sin, :a)*fn(:cos, :a) => fn(:sin, :a)**2/2,
|
80
|
+
fn(:sec, :a)*fn(:tan, :a) => fn(:sec, :a),
|
81
|
+
fn(:csc, :a)*fn(:cot, :a) => - fn(:csc, :a),
|
82
|
+
1/(fn(:sin, :a)*fn(:cos, :a)) => fn(:ln, fn(:abs, fn(:tan, :a))),
|
83
|
+
fn(:sin, fn(:ln, :a)) => :a*(fn(:sin, fn(:ln, :a)) -
|
84
|
+
fn(:cos, fn(:ln, :a)))/2,
|
85
|
+
fn(:cos, fn(:ln, :a)) => :a*(fn(:sin, fn(:ln, :a)) +
|
86
|
+
fn(:cos, fn(:ln, :a)))/2,
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def anti_derivative(var)
|
91
|
+
raise 'Var is not a differential' if !var.is_d?
|
92
|
+
|
93
|
+
if is_constant?([var.undiff].to_set)
|
94
|
+
return int_constant(var)
|
95
|
+
end
|
96
|
+
|
97
|
+
if self.is_a?(SyMath::Minus)
|
98
|
+
return - self.argument.anti_derivative(var)
|
99
|
+
end
|
100
|
+
|
101
|
+
if is_sum_exp?
|
102
|
+
return int_sum(var)
|
103
|
+
end
|
104
|
+
|
105
|
+
if is_prod_exp?
|
106
|
+
return int_product(var)
|
107
|
+
end
|
108
|
+
|
109
|
+
if is_a?(SyMath::Operator) and @@functions.key?(name.to_sym)
|
110
|
+
return int_function(var)
|
111
|
+
end
|
112
|
+
|
113
|
+
return int_power(var)
|
114
|
+
end
|
115
|
+
|
116
|
+
def int_failure()
|
117
|
+
raise IntegrationError, 'Cannot find an antiderivative for expression ' + to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
def int_pattern(var)
|
121
|
+
# Try to match expression against various patterns
|
122
|
+
vu = var.undiff
|
123
|
+
a = :a.to_m
|
124
|
+
|
125
|
+
@@patterns.each do |f, f_int|
|
126
|
+
m = match(f, [a])
|
127
|
+
next if m.nil?
|
128
|
+
|
129
|
+
m.each do |mi|
|
130
|
+
# We must check that variable a maps to c1*x + c2
|
131
|
+
(c1, c2) = get_linear_constants(mi[a], var)
|
132
|
+
next if c1.nil?
|
133
|
+
|
134
|
+
# We have found a match, and the argument is a linear function.
|
135
|
+
# Substitute the argument into the free variable of the pattern
|
136
|
+
# function.
|
137
|
+
ret = f_int.deep_clone
|
138
|
+
ret.replace({ a => mi[a] })
|
139
|
+
return c1.inv*ret
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Give up!
|
144
|
+
int_failure
|
145
|
+
end
|
146
|
+
|
147
|
+
def int_constant(var)
|
148
|
+
# c => c*x
|
149
|
+
return mul(var.undiff)
|
150
|
+
end
|
151
|
+
|
152
|
+
def int_product(var)
|
153
|
+
vu = var.undiff
|
154
|
+
vset = [vu].to_set
|
155
|
+
|
156
|
+
prodc = 1.to_m
|
157
|
+
proda = []
|
158
|
+
diva = []
|
159
|
+
|
160
|
+
factors.each do |f|
|
161
|
+
if !f.type.is_subtype?(:scalar)
|
162
|
+
int_failure
|
163
|
+
end
|
164
|
+
|
165
|
+
if f.is_constant?(vset)
|
166
|
+
prodc *= f
|
167
|
+
next
|
168
|
+
end
|
169
|
+
|
170
|
+
if f.is_divisor_factor?
|
171
|
+
diva.push f.base**f.exponent.argument
|
172
|
+
next
|
173
|
+
end
|
174
|
+
|
175
|
+
proda.push f
|
176
|
+
end
|
177
|
+
|
178
|
+
# c/exp
|
179
|
+
if proda.length == 0 and diva.length == 1
|
180
|
+
return prodc*diva[0].int_inv(var)
|
181
|
+
end
|
182
|
+
|
183
|
+
# c*exp
|
184
|
+
if proda.length == 1 and diva.length == 0
|
185
|
+
return prodc*proda[0].anti_derivative(var)
|
186
|
+
end
|
187
|
+
|
188
|
+
exp = proda.inject(1.to_m, :*)/diva.inject(1.to_m, :*)
|
189
|
+
return prodc*exp.int_pattern(var)
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_linear_constants(arg, var)
|
193
|
+
# If arg is on the form c1*var + c2, return the two constants.
|
194
|
+
vu = var.undiff
|
195
|
+
vset = [vu].to_set
|
196
|
+
c1 = 0.to_m
|
197
|
+
c2 = 0.to_m
|
198
|
+
|
199
|
+
arg.terms.each do |t|
|
200
|
+
if t.is_constant?(vset)
|
201
|
+
c2 += t
|
202
|
+
else
|
203
|
+
# Split term into a constant part and (hopefully) a single factor
|
204
|
+
# which equals to var
|
205
|
+
prodc = 1.to_m
|
206
|
+
has_var = false
|
207
|
+
|
208
|
+
t.factors.each do |f|
|
209
|
+
if !f.type.is_subtype?(:scalar)
|
210
|
+
# Non-scalar factor. Don't know what to do
|
211
|
+
return
|
212
|
+
end
|
213
|
+
|
214
|
+
if f.is_constant?(vset)
|
215
|
+
prodc *= f
|
216
|
+
next
|
217
|
+
end
|
218
|
+
|
219
|
+
# Found more than one var. Return negative
|
220
|
+
if has_var
|
221
|
+
return
|
222
|
+
end
|
223
|
+
|
224
|
+
# Factor is var. Remember it, but continue to examine the other
|
225
|
+
# factors.
|
226
|
+
if f == vu
|
227
|
+
has_var = true
|
228
|
+
next
|
229
|
+
end
|
230
|
+
|
231
|
+
# Factor is a function of var. Return negative
|
232
|
+
return
|
233
|
+
end
|
234
|
+
|
235
|
+
c1 += prodc
|
236
|
+
end
|
237
|
+
|
238
|
+
# Return negative if the whole expression is constant
|
239
|
+
return if c1 == 0
|
240
|
+
end
|
241
|
+
|
242
|
+
return [c1, c2]
|
243
|
+
end
|
244
|
+
|
245
|
+
def int_inv(var)
|
246
|
+
# Hack: integrate 1/exp (by convention of the sibling functions,
|
247
|
+
# it should have integrated exp)
|
248
|
+
xp = exponent
|
249
|
+
vu = var.undiff
|
250
|
+
vset = [vu].to_set
|
251
|
+
|
252
|
+
if base == vu and xp.is_constant?(vset)
|
253
|
+
if xp == 1.to_m
|
254
|
+
# 1/x => ln|x|
|
255
|
+
return fn(:ln, fn(:abs, vu))
|
256
|
+
else
|
257
|
+
# 1/x**n => x**(1 - n)/(1 - n)
|
258
|
+
return vu**(1.to_m - xp)/(1.to_m - xp)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
(self**-1).int_pattern(var)
|
263
|
+
end
|
264
|
+
|
265
|
+
def int_function(var)
|
266
|
+
# At this point exp is a single argument function which we know how
|
267
|
+
# to integrate. Check that the argument is a linear function
|
268
|
+
arg = args[0]
|
269
|
+
(c1, c2) = get_linear_constants(arg, var)
|
270
|
+
if c1.nil?
|
271
|
+
# Argument is not linear. Try pattern match as a last resort.
|
272
|
+
return int_pattern(var)
|
273
|
+
else
|
274
|
+
# The function argument is linear. Do the integration.
|
275
|
+
# int(func(c1*x + c2)) -> Func(c1*x+ c2)/c1
|
276
|
+
fexp = @@functions[name.to_sym].deep_clone
|
277
|
+
fexp.replace({ :a.to_m => arg })
|
278
|
+
return c1.inv*fexp
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def int_power(var)
|
283
|
+
# At this point, exp should not be a constant, a sum or a product.
|
284
|
+
vu = var.undiff
|
285
|
+
vset = [vu].to_set
|
286
|
+
|
287
|
+
b = base
|
288
|
+
xp = exponent
|
289
|
+
|
290
|
+
if b == vu
|
291
|
+
if !xp.is_constant?(vset)
|
292
|
+
# Cannot integrate x**f(x)
|
293
|
+
int_failure
|
294
|
+
end
|
295
|
+
|
296
|
+
# x**n => x**(n + 1)/(n + 1)
|
297
|
+
return vu**(xp + 1)/(xp + 1)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Check exponential functions
|
301
|
+
if b.is_constant?(vset)
|
302
|
+
(c1, c2) = get_linear_constants(xp, var)
|
303
|
+
# b**(c1*x + c2) => b**(c1*x + c2)/(b*ln(c1))
|
304
|
+
if c1.nil?
|
305
|
+
int_failure
|
306
|
+
end
|
307
|
+
|
308
|
+
return b**(xp)/(c1*fn(:ln, b))
|
309
|
+
end
|
310
|
+
|
311
|
+
# Try pattern match as last resort
|
312
|
+
int_pattern(var)
|
313
|
+
end
|
314
|
+
|
315
|
+
def int_sum(var)
|
316
|
+
ret = 0.to_m
|
317
|
+
terms.each { |s| ret += s.anti_derivative(var) }
|
318
|
+
return ret
|
319
|
+
end
|
320
|
+
|
321
|
+
# This method calculates the difference of two boundary values of an
|
322
|
+
# expression (typically used for calculating the definite integral from
|
323
|
+
# the anti-derivative, using the fundamental theorem of calculus)
|
324
|
+
def integral_bounds(var, a, b)
|
325
|
+
bexp = deep_clone.replace({ var => b })
|
326
|
+
aexp = deep_clone.replace({ var => a })
|
327
|
+
return bexp - aexp
|
328
|
+
end
|
329
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'symath/operation'
|
2
|
+
|
3
|
+
module SyMath::Operation::Match
|
4
|
+
include SyMath::Operation
|
5
|
+
|
6
|
+
def build_assoc_op(args, opclass)
|
7
|
+
e = args[-1]
|
8
|
+
if args.length > 1
|
9
|
+
args[0..-2].each do |a|
|
10
|
+
e = opclass.new(a, e)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
return e
|
15
|
+
end
|
16
|
+
|
17
|
+
# Match the lists of arguments of two associative operators, binding the
|
18
|
+
# free variables in args2 to expressions in args1. Because of the
|
19
|
+
# associativity property, multiple matches may be possible. E.g.
|
20
|
+
# [x, y, z] == [a, b] gives two possible matches: a = (x op y), b = z or
|
21
|
+
# a = x, b = (y op z)
|
22
|
+
# A list of hashes is returned, each hash representing a match.
|
23
|
+
def match_assoc(args1, args2, freevars, boundvars, opclass)
|
24
|
+
# If args1 is shorter than args2 we do not have enough arguments for a
|
25
|
+
# match
|
26
|
+
return if args1.length < args2.length
|
27
|
+
|
28
|
+
# If args2 has only one argument, it must match the whole list of args1
|
29
|
+
if args2.length == 1
|
30
|
+
return build_assoc_op(args1, opclass).match(
|
31
|
+
args2[0], freevars, boundvars)
|
32
|
+
end
|
33
|
+
|
34
|
+
ret = []
|
35
|
+
all = (0..args1.length - 1).to_a
|
36
|
+
fv = freevars
|
37
|
+
bv = boundvars
|
38
|
+
|
39
|
+
(1..args1.length - args2.length + 1).each do |n|
|
40
|
+
# Match args1[0..m] with args2[0]
|
41
|
+
if is_commutative?
|
42
|
+
# Select all combinations of n arguments
|
43
|
+
sel = all.combination(n)
|
44
|
+
else
|
45
|
+
# Non-commutative operation. Make one selection of the
|
46
|
+
# first n arguments
|
47
|
+
sel = [(0..n - 1).to_a]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Iterate over all selections and find all possible matches
|
51
|
+
sel.each do |s|
|
52
|
+
select1 = s.map { |i| args1[i] }
|
53
|
+
remain1 = (all - s).map { |i| args1[i] }
|
54
|
+
|
55
|
+
if n == 1
|
56
|
+
m0 = select1[0].match(args2[0], freevars, boundvars)
|
57
|
+
else
|
58
|
+
if args2[0].is_a?(SyMath::Definition::Variable) and
|
59
|
+
freevars.include?(args2[0])
|
60
|
+
# Register match.
|
61
|
+
m0 = [{ args2[0] => build_assoc_op(select1, opclass) }]
|
62
|
+
else
|
63
|
+
# No match. Skip to the next argument combination
|
64
|
+
next
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set of matches is empty. Return negative
|
69
|
+
next if m0.nil?
|
70
|
+
|
71
|
+
# For each possible first argument match, we build new lists of free
|
72
|
+
# and bound variables and try to match the rest of the remaining list
|
73
|
+
# of the argument recursively.
|
74
|
+
m0.each do |m|
|
75
|
+
fv = freevars - m.keys
|
76
|
+
bv = boundvars.merge(m)
|
77
|
+
mn = match_assoc(remain1, args2[1..-1], fv, bv, opclass)
|
78
|
+
if mn.nil?
|
79
|
+
# No match. Skip to the next argument combination
|
80
|
+
next
|
81
|
+
else
|
82
|
+
# We have a complete match. Store it in res, and continue
|
83
|
+
m0.each do |m0i|
|
84
|
+
mn.each do |mni|
|
85
|
+
ret << m0i.merge(mni)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
return if ret.empty?
|
94
|
+
|
95
|
+
return ret
|
96
|
+
end
|
97
|
+
|
98
|
+
# Match self with an expression and a set of free variables. A match is
|
99
|
+
# found if each of the free variables can be replaced with subexpressions
|
100
|
+
# making the expression equal to self. In that case, a hash is returned
|
101
|
+
# mapping each of the variables to the corresponding subexpression. If no
|
102
|
+
# match is found, nil is returned. An optional boundvars hash contains a
|
103
|
+
# map of variables to expressions which are required to match exactly.
|
104
|
+
def match(exp, freevars, boundvars = {})
|
105
|
+
# Traverse self and exp in parallel. Match subexpressions recursively,
|
106
|
+
# and match end nodes one by one. The two value nodes are compared for
|
107
|
+
# equality and each argument are matched recursively.
|
108
|
+
# Constant or operator: Just match nodes by exact comparison
|
109
|
+
if exp.is_a?(SyMath::Definition) and
|
110
|
+
!exp.is_a?(SyMath::Definition::Variable)
|
111
|
+
# Node is a definition. Exact match is required
|
112
|
+
if (self == exp)
|
113
|
+
# Return match with no variable bindings
|
114
|
+
return [{}]
|
115
|
+
else
|
116
|
+
return
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Variable: If it is a free variable, it is a match. We remove it from
|
121
|
+
# the freevars set and add it to the boundvars set together with the
|
122
|
+
# expression it matches. If it is a bound variable, we require that
|
123
|
+
# the expression matches the binding.
|
124
|
+
if exp.is_a?(SyMath::Definition::Variable)
|
125
|
+
# Node is a variable
|
126
|
+
if freevars.include?(exp)
|
127
|
+
# Node is a free variable. Return binding.
|
128
|
+
return [{ exp => self }]
|
129
|
+
elsif boundvars.key?(exp)
|
130
|
+
# Node is a bound variable. Check that self matches the binding.
|
131
|
+
if boundvars[exp] == self
|
132
|
+
return [{}]
|
133
|
+
else
|
134
|
+
return
|
135
|
+
end
|
136
|
+
else
|
137
|
+
# Node is an unknown variable. Exact match is required
|
138
|
+
return (exp == self)? [{}] : nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Operator. Compare class and name. Then compare each argument
|
143
|
+
if exp.is_a?(SyMath::Operator)
|
144
|
+
# Node is an operator. Check class and name.
|
145
|
+
if self.class != exp.class or self.name != exp.name
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
# The args_assoc method takes care of associativity by returning the
|
150
|
+
# argument list of all directly connected arguments.
|
151
|
+
self_args = self.args_assoc
|
152
|
+
exp_args = exp.args_assoc
|
153
|
+
ret = {}
|
154
|
+
m = match_assoc(self_args, exp_args, freevars, boundvars, self.class)
|
155
|
+
|
156
|
+
return if m.nil?
|
157
|
+
|
158
|
+
return m.map { |r| boundvars.merge(r) }
|
159
|
+
end
|
160
|
+
|
161
|
+
# :nocov:
|
162
|
+
# All value types should be covered at this point, but one never knows.
|
163
|
+
raise 'Don\'t know how to compare value type ' + exp.class.to_s
|
164
|
+
# :nocov:
|
165
|
+
end
|
166
|
+
end
|