symath 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +616 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/symath/definition/abs.rb +48 -0
  13. data/lib/symath/definition/arccos.rb +25 -0
  14. data/lib/symath/definition/arccot.rb +23 -0
  15. data/lib/symath/definition/arccsc.rb +24 -0
  16. data/lib/symath/definition/arcsec.rb +24 -0
  17. data/lib/symath/definition/arcsin.rb +25 -0
  18. data/lib/symath/definition/arctan.rb +23 -0
  19. data/lib/symath/definition/bounds.rb +39 -0
  20. data/lib/symath/definition/codiff.rb +31 -0
  21. data/lib/symath/definition/constant.rb +111 -0
  22. data/lib/symath/definition/cos.rb +17 -0
  23. data/lib/symath/definition/cot.rb +17 -0
  24. data/lib/symath/definition/csc.rb +17 -0
  25. data/lib/symath/definition/curl.rb +27 -0
  26. data/lib/symath/definition/d.rb +62 -0
  27. data/lib/symath/definition/div.rb +27 -0
  28. data/lib/symath/definition/exp.rb +112 -0
  29. data/lib/symath/definition/fact.rb +55 -0
  30. data/lib/symath/definition/flat.rb +31 -0
  31. data/lib/symath/definition/function.rb +197 -0
  32. data/lib/symath/definition/grad.rb +23 -0
  33. data/lib/symath/definition/hodge.rb +23 -0
  34. data/lib/symath/definition/int.rb +75 -0
  35. data/lib/symath/definition/laplacian.rb +23 -0
  36. data/lib/symath/definition/lmd.rb +97 -0
  37. data/lib/symath/definition/ln.rb +45 -0
  38. data/lib/symath/definition/number.rb +51 -0
  39. data/lib/symath/definition/operator.rb +228 -0
  40. data/lib/symath/definition/sec.rb +17 -0
  41. data/lib/symath/definition/sharp.rb +31 -0
  42. data/lib/symath/definition/sin.rb +17 -0
  43. data/lib/symath/definition/sqrt.rb +62 -0
  44. data/lib/symath/definition/tan.rb +17 -0
  45. data/lib/symath/definition/trig.rb +95 -0
  46. data/lib/symath/definition/variable.rb +284 -0
  47. data/lib/symath/definition/xd.rb +28 -0
  48. data/lib/symath/definition.rb +205 -0
  49. data/lib/symath/equation.rb +67 -0
  50. data/lib/symath/fraction.rb +177 -0
  51. data/lib/symath/matrix.rb +252 -0
  52. data/lib/symath/minus.rb +125 -0
  53. data/lib/symath/operation/differential.rb +167 -0
  54. data/lib/symath/operation/distributivelaw.rb +367 -0
  55. data/lib/symath/operation/exterior.rb +64 -0
  56. data/lib/symath/operation/integration.rb +329 -0
  57. data/lib/symath/operation/match.rb +166 -0
  58. data/lib/symath/operation/normalization.rb +458 -0
  59. data/lib/symath/operation.rb +36 -0
  60. data/lib/symath/operator.rb +163 -0
  61. data/lib/symath/parser.rb +473 -0
  62. data/lib/symath/parser.y +129 -0
  63. data/lib/symath/poly/dup.rb +835 -0
  64. data/lib/symath/poly/galois.rb +621 -0
  65. data/lib/symath/poly.rb +142 -0
  66. data/lib/symath/power.rb +224 -0
  67. data/lib/symath/product.rb +183 -0
  68. data/lib/symath/sum.rb +174 -0
  69. data/lib/symath/type.rb +282 -0
  70. data/lib/symath/value.rb +372 -0
  71. data/lib/symath/version.rb +3 -0
  72. data/lib/symath/wedge.rb +48 -0
  73. data/lib/symath.rb +157 -0
  74. data/symath.gemspec +39 -0
  75. 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