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,458 @@
1
+ require 'symath/operation'
2
+ require 'prime'
3
+
4
+ module SyMath::Operation::Normalization
5
+ include SyMath::Operation
6
+
7
+ # This operation provides an expression object with the normalize() method
8
+ # which normalizes an expression:
9
+ #
10
+ # equal arguments of a product are contracted to integer powers
11
+ # arguments of a product are sorted
12
+ #
13
+ # equal arguments of a sum (with subtractions) are contracted to integer
14
+ # products arguments in a sum are sorted
15
+ # subtractive elements are put after the additive elements
16
+ #
17
+ # integer sums are calculated
18
+ # integer products are calculated
19
+ #
20
+ # fractions of integers are simplified as far as possible
21
+ #
22
+ # The operation is repeated until the expression is no longer changed
23
+
24
+ def normalize()
25
+ if self.is_a?(SyMath::Equation)
26
+ return SyMath::Equation.new(args[0].normalize, args[1].normalize)
27
+ end
28
+
29
+ return iterate('normalize_single_pass')
30
+ end
31
+
32
+ def normalize_single_pass
33
+ if is_sum_exp?
34
+ return normalize_sum
35
+ end
36
+
37
+ if is_prod_exp?
38
+ return normalize_product
39
+ end
40
+
41
+ if is_a?(SyMath::Power)
42
+ return normalize_power
43
+ end
44
+
45
+ if is_a?(SyMath::Matrix)
46
+ return normalize_matrix
47
+ end
48
+
49
+ if is_a?(SyMath::Definition::Operator) and !@exp.nil?
50
+ @exp = @exp.normalize
51
+ end
52
+
53
+ return recurse('normalize', 'reduce')
54
+ end
55
+
56
+ def normalize_sum()
57
+ # Get normalized terms
58
+ terms = self.terms.map do |e|
59
+ if e.is_a?(SyMath::Minus)
60
+ e.argument.normalize.neg
61
+ else
62
+ e.normalize
63
+ end
64
+ end
65
+
66
+ # Collect equal elements into integer products
67
+
68
+ # Hash: product[vector part][scalar part]
69
+ products = {}
70
+
71
+ terms.each do |t|
72
+ c = 1
73
+ p = []
74
+
75
+ t.factors.each do |f|
76
+ if f == -1
77
+ c *= -1
78
+ next
79
+ elsif f.is_number?
80
+ c *= f.value
81
+ else
82
+ p.push f
83
+ end
84
+ end
85
+
86
+ if products.key?(p)
87
+ products[p] += c
88
+ else
89
+ products[p] = c
90
+ end
91
+ end
92
+
93
+ terms2 = []
94
+ products.keys.sort.each do |p|
95
+ p.unshift products[p]
96
+
97
+ p = p.inject(1.to_m, :*)
98
+
99
+ if !SyMath.setting(:fraction_exponent_form)
100
+ p = p.product_on_fraction_form
101
+ end
102
+
103
+ terms2.push p
104
+ end
105
+
106
+ ret = terms2.reverse.inject(:+)
107
+
108
+ return ret
109
+ end
110
+
111
+ def normalize_product()
112
+ # Flatten the expression and order it
113
+ e = factors.map do |f|
114
+ f = f.normalize
115
+ end
116
+
117
+ e = e.inject(:*)
118
+
119
+ if e.is_prod_exp?
120
+ e = e.order_product
121
+ end
122
+
123
+ if e.is_prod_exp?
124
+ e = e.reduce_constant_factors
125
+ end
126
+
127
+ if !SyMath.setting(:fraction_exponent_form)
128
+ e = e.product_on_fraction_form
129
+ end
130
+
131
+ return e
132
+ end
133
+
134
+ def normalize_power()
135
+ norm = base.normalize.power(exponent.normalize)
136
+ e, sign, changed = norm.reduce_modulo_sign
137
+ e *= -1 if sign == -1
138
+
139
+ return e
140
+ end
141
+
142
+ def normalize_matrix()
143
+ data = (0..nrows - 1).map do |r|
144
+ row(r).map { |e| e.normalize }
145
+ end
146
+
147
+ return SyMath::Matrix.new(data)
148
+ end
149
+
150
+ def product_on_fraction_form
151
+ ret = []
152
+ fact = 1.to_m
153
+ divf = 1.to_m
154
+
155
+ factors.each do |f|
156
+ if f.type.is_scalar?
157
+ if f.is_divisor_factor?
158
+ divf *= f.base**f.exponent.argument
159
+ else
160
+ fact *= f
161
+ end
162
+ else
163
+ if divf != 1
164
+ fact = fact/divf
165
+ end
166
+ if fact != 1
167
+ ret.push fact
168
+ end
169
+
170
+ fact = f
171
+ divf = 1.to_m
172
+ end
173
+ end
174
+
175
+ if divf != 1
176
+ fact = fact/divf
177
+ end
178
+ if fact != 1
179
+ ret.push fact
180
+ end
181
+
182
+ return ret.empty? ? 1.to_m : ret.inject(:*)
183
+ end
184
+
185
+ # Order the factors first by type, then, for commutative and anti-
186
+ # commutative factors, by content using bubble sort:
187
+ # sign * constant numbers * scalar factors * other factors
188
+ #
189
+ # - Commutative factors are swapped without changing sign.
190
+ # - Swapping anti-commutative factors changes the sign.
191
+ #
192
+ # Constant numers are multiplied to a single coefficient
193
+ # Other factors are reduced if possible:
194
+ # fundamental quaternions can always be reduced.
195
+ # exterior algebra basis vectors can be reduced whenever
196
+ # a double occurrence is found.
197
+ def order_product()
198
+ # Bubble sort factors. Reduce factors and combine thm whenever possible
199
+ done = false
200
+ sign = 1
201
+ head = self
202
+
203
+ while !done
204
+ done = true
205
+
206
+ ex = head
207
+ prev = nil
208
+
209
+ while ex.is_a?(SyMath::Product)
210
+ if factor1.is_a?(SyMath::Product)
211
+ f, sign2, changed = factor1.factor2.reduce_modulo_sign
212
+ if changed
213
+ self.factor1.factor2 = f
214
+ done = false
215
+ end
216
+ else
217
+ f, sign2, changed = factor1.reduce_modulo_sign
218
+ if changed
219
+ self.factor1 = f
220
+ done = false
221
+ end
222
+ end
223
+
224
+ sign *= sign2
225
+
226
+ ex, sign2, changed = ex.combine_factors
227
+ done = false if changed
228
+ sign *= sign2
229
+
230
+ # The product has been combined.
231
+ if prev.nil?
232
+ # No prev element. Replace head with ex
233
+ head = ex
234
+ else
235
+ # Attach the combined expression onto the previous product
236
+ # exp and continue
237
+ prev.factor1 = ex
238
+ end
239
+
240
+ if !ex.is_a?(SyMath::Product)
241
+ next
242
+ end
243
+
244
+ sign2, changed = ex.compare_factors_and_swap
245
+ done = false if changed
246
+ sign *= sign2
247
+
248
+ prev = ex
249
+ ex = ex.factor1
250
+ end
251
+ end
252
+
253
+ if sign == -1
254
+ return -head
255
+ else
256
+ return head
257
+ end
258
+ end
259
+
260
+ # FIXME: Do the reduction in the combine_factors part.
261
+ # Reduce c and c**-1 by gdc. The expression is expected to be flattened
262
+ # and ordered so that the first argument is the constand and the second
263
+ # argument is the divisor constant.
264
+ def reduce_constant_factors()
265
+ c = nil
266
+ dc = nil
267
+ ret = []
268
+
269
+ self.factors.each do |f|
270
+ if dc.nil?
271
+ if f.is_divisor_factor?
272
+ if f.base.is_number?
273
+ dc = f.base.value**f.exponent.argument.value
274
+ next
275
+ end
276
+ end
277
+ end
278
+
279
+ if c.nil?
280
+ if f.is_number?
281
+ c = f.value
282
+ next
283
+ end
284
+ end
285
+
286
+ c = 1 if c.nil?
287
+ dc = 1 if dc.nil?
288
+
289
+ ret.push f
290
+ end
291
+
292
+ c = 1 if c.nil?
293
+ dc = 1 if dc.nil?
294
+
295
+ # First examine the coefficients
296
+ if c == 0 and dc > 0
297
+ return 0.to_m
298
+ end
299
+
300
+ if c > 0
301
+ # Reduce coefficients by greatest common divisor
302
+ gcd = c.gcd(dc)
303
+ c /= gcd
304
+ dc /= gcd
305
+ end
306
+
307
+ if dc != 1
308
+ ret.unshift dc.to_m**-1
309
+ end
310
+
311
+ if c != 1
312
+ ret.unshift c.to_m
313
+ end
314
+
315
+ return ret.inject(:*)
316
+ end
317
+
318
+ # Return result of the two factors multiplied if it simplifies
319
+ # the expression.
320
+ # Returns (new_exp, sign, changed)
321
+ def combine_factors
322
+ if factor1.is_a?(SyMath::Product)
323
+ f1 = factor1.factor2
324
+ else
325
+ f1 = factor1
326
+ end
327
+ f2 = factor2
328
+
329
+ # Natural numbers are calculated
330
+ if f1.is_number? and f2.is_number?
331
+ return replace_combined_factors((f1.value*f2.value).to_m), 1, true
332
+ end
333
+
334
+ if f1.is_unit_quaternion? and f2.is_unit_quaternion?
335
+ ret = f1.calc_unit_quaternions(f2)
336
+ if ret.is_a?(SyMath::Minus)
337
+ return replace_combined_factors(ret.argument), -1, true
338
+ else
339
+ return replace_combined_factors(ret), 1, true
340
+ end
341
+ end
342
+
343
+ if f1.is_a?(SyMath::Power)
344
+ base1 = f1.base
345
+ exp1 = f1.exponent
346
+ else
347
+ base1 = f1
348
+ exp1 = 1.to_m
349
+ end
350
+
351
+ if f2.is_a?(SyMath::Power)
352
+ base2 = f2.base
353
+ exp2 = f2.exponent
354
+ else
355
+ base2 = f2
356
+ exp2 = 1.to_m
357
+ end
358
+
359
+ if base1 == base2
360
+ if base1.type.is_subtype?('tensor') and
361
+ base2.type.is_subtype?('tensor') and
362
+ (exp1 + exp2).is_number? and
363
+ (exp1 + exp2).value > 1
364
+ return replace_combined_factors(0.to_m), 1, true
365
+ end
366
+
367
+ return replace_combined_factors(base1**(exp1 + exp2)), 1, true
368
+ end
369
+
370
+ return self, 1, false
371
+ end
372
+
373
+ # Replace factor1 and factor2 with e. Return new combined expression
374
+ def replace_combined_factors(e)
375
+ if factor1.is_a?(SyMath::Product)
376
+ return factor1.factor1*e
377
+ else
378
+ return e
379
+ end
380
+ end
381
+
382
+ # Compare first and second element in product. Swap if they can and
383
+ # should be swapped. Return (sign, changed).
384
+ def compare_factors_and_swap()
385
+ f1 = factor1.is_a?(SyMath::Product) ? factor1.factor2 : factor1
386
+ f2 = factor2
387
+
388
+ if !f1.type.is_subtype?(:linop) or !f2.type.is_subtype?(:linop)
389
+ # Non-linear operator cannot be swapped
390
+ return 1, false
391
+ end
392
+
393
+ if !f1.type.is_scalar? and f2.type.is_scalar?
394
+ # Scalars always go before non-scalar linops
395
+ swap_factors
396
+ return 1, true
397
+ end
398
+
399
+ if (f1.type.is_vector? or f1.type.is_dform?) and
400
+ (f2.type.is_vector? or f2.type.is_dform?)
401
+ # Only order simple vectors. Don't order vector
402
+ # expressions
403
+ # FIXME: We could do that. If so, we must get the dimension
404
+ # of the variable and swap sign only if dim(f1)*dim(f2) is
405
+ # odd.
406
+ if f1.is_a?(SyMath::Definition::Variable) and f2.is_a?(SyMath::Definition::Variable)
407
+ # Order vector factors
408
+ if f2 < f1
409
+ swap_factors
410
+ return -1, true
411
+ else
412
+ return 1, false
413
+ end
414
+ end
415
+ end
416
+
417
+ if f1.type.is_scalar? and f2.type.is_scalar?
418
+ # Corner case. Order the imagninary unit above other scalars in order
419
+ # to make it bubble up to the other quaternions.
420
+ if f2 == :i
421
+ return 1, false
422
+ end
423
+
424
+ if f1 == :i
425
+ swap_factors
426
+ return 1, true
427
+ end
428
+
429
+ # Normalize as power factors so all factors with the same base
430
+ # end up at the same place and can be combined.
431
+ f1 = f1.power(1) if !f1.is_a?(SyMath::Power)
432
+ f2 = f2.power(1) if !f2.is_a?(SyMath::Power)
433
+
434
+ # Order scalar factors
435
+ if f2 < f1
436
+ swap_factors
437
+ return 1, true
438
+ else
439
+ return 1, false
440
+ end
441
+ end
442
+
443
+ # FIXME: Order other commutative and anti-commutative operators
444
+ return 1, false
445
+ end
446
+
447
+ # Swap first and second argument in product
448
+ def swap_factors()
449
+ f2 = self.factor2
450
+ if self.factor1.is_a?(SyMath::Product)
451
+ self.factor2 = self.factor1.factor2
452
+ self.factor1.factor2 = f2
453
+ else
454
+ self.factor2 = self.factor1
455
+ self.factor1 = f2
456
+ end
457
+ end
458
+ end
@@ -0,0 +1,36 @@
1
+ module SyMath::Operation
2
+ # Repeat method until there are no changes
3
+ def iterate(method)
4
+ ret = deep_clone.send(method)
5
+ if ret == self
6
+ return ret
7
+ else
8
+ return ret.iterate(method)
9
+ end
10
+ end
11
+
12
+ # Call method recursively down the arguments of the expression
13
+ # and call self_method on self.
14
+ def recurse(method, self_method = method)
15
+ if is_a?(SyMath::Definition) or is_a?(SyMath::Matrix)
16
+ if self_method.nil?
17
+ return self
18
+ else
19
+ ret = self.send(self_method)
20
+ return ret
21
+ end
22
+ end
23
+
24
+ # Call method on each argument
25
+ newargs = args.map { |a| a.send('recurse', method) }
26
+
27
+ ret = self.deep_clone
28
+ ret.args = newargs
29
+
30
+ if self_method.nil?
31
+ return ret
32
+ else
33
+ return ret.send(self_method)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,163 @@
1
+ require 'symath/value'
2
+ require 'set'
3
+
4
+ module SyMath
5
+ class Operator < Value
6
+ attr_reader :definition
7
+ attr_accessor :args
8
+
9
+ # Compose with simplify. Defaults to composition with no reductions
10
+ def self.compose_with_simplify(*args)
11
+ d = args[0]
12
+ if d.is_a?(SyMath::Definition::Operator)
13
+ ret = d.compose_with_simplify(*args)
14
+ if ret
15
+ return ret
16
+ end
17
+ end
18
+
19
+ return self.new(*args)
20
+ end
21
+
22
+ def name()
23
+ return definition.name
24
+ end
25
+
26
+ # Return arguments
27
+ def args_assoc()
28
+ if is_associative?
29
+ return args.map { |a| a.class == self.class ? a.args_assoc : [a] }.inject(:+)
30
+ else
31
+ return args
32
+ end
33
+ end
34
+
35
+ def is_commutative?()
36
+ return false
37
+ end
38
+
39
+ def is_associative?()
40
+ return false
41
+ end
42
+
43
+ def evaluate()
44
+ # Hack: Don't evaluate arguments if the operator is the integral.
45
+ # this is taken care of inside the definition.
46
+ if name != :int
47
+ @args = @args.map { |a| a.evaluate }
48
+ end
49
+ definition.evaluate_call(self)
50
+ end
51
+
52
+ def arity()
53
+ return @args.length
54
+ end
55
+
56
+ def initialize(definition, args)
57
+ if !definition.is_a?(SyMath::Value)
58
+ definition = SyMath::Definition.get(definition)
59
+ end
60
+
61
+ @definition = definition
62
+ @args = args
63
+
64
+ if definition.is_a?(SyMath::Definition::Operator)
65
+ definition.validate_args(self)
66
+ end
67
+ end
68
+
69
+ def to_s()
70
+ if definition.is_a?(SyMath::Definition::Operator)
71
+ return definition.to_s(@args)
72
+ else
73
+ # Expression call
74
+ arglist = @args.map { |a| a.to_s }.join(',')
75
+
76
+ return "(#{definition}).(#{arglist})"
77
+ end
78
+ end
79
+
80
+ def to_latex()
81
+ return definition.to_latex(@args)
82
+ end
83
+
84
+ def dump(indent = 0)
85
+ i = ' '*indent
86
+ ret = super(indent)
87
+ arglist = args.map do |a|
88
+ a.dump(indent + 2)
89
+ end
90
+
91
+ return ret + "\n" + arglist.join("\n")
92
+ end
93
+
94
+ def hash()
95
+ h = @name.hash
96
+ @args.each do |a|
97
+ h ^= a.hash
98
+ end
99
+
100
+ return h
101
+ end
102
+
103
+ def ==(other)
104
+ o = other.to_m
105
+ return false if self.class.name != o.class.name
106
+ return false if name.to_s != o.name.to_s
107
+ return false if arity != o.arity
108
+ return args == o.args
109
+ end
110
+
111
+ def <=>(other)
112
+ if self.class.name != other.class.name
113
+ return super(other)
114
+ end
115
+
116
+ if name != other.name
117
+ return name.to_s <=> other.name.to_s
118
+ end
119
+
120
+ if arity != other.arity
121
+ return arity <=> other.arity
122
+ end
123
+
124
+ (0...arity).to_a.each do |i|
125
+ diff = args[i] <=> other.args[i]
126
+ if diff != 0
127
+ return diff
128
+ end
129
+ end
130
+
131
+ return 0
132
+ end
133
+
134
+ alias eql? ==
135
+
136
+ def reduce()
137
+ return definition.reduce_call(self)
138
+ end
139
+
140
+ def is_constant?(vars = nil)
141
+ @args.each do |a|
142
+ return false if !a.is_constant?(vars)
143
+ end
144
+
145
+ return true
146
+ end
147
+
148
+ def variables()
149
+ vars = @args.map { |a| a.variables }
150
+ return vars.length == 0 ? vars : vars.inject(:|)
151
+ end
152
+
153
+ def replace(map)
154
+ @args = @args.map do |a|
155
+ a.replace(map)
156
+ end
157
+
158
+ @definition = definition.replace(map)
159
+
160
+ return self
161
+ end
162
+ end
163
+ end