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