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