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,177 @@
1
+ require 'symath/operator'
2
+
3
+ module SyMath
4
+ class Fraction < Operator
5
+ def self.compose_with_simplify(a, b)
6
+ a = a.to_m
7
+ b = b.to_m
8
+
9
+ return a if b == 1
10
+
11
+ if a.is_finite?() == false or b.is_finite?() == false
12
+ return self.simplify_inf(a, b)
13
+ end
14
+
15
+ # Divide by zero
16
+ if b.is_zero?
17
+ if SyMath.setting(:complex_arithmetic)
18
+ if a.is_zero?
19
+ return :nan.to_m
20
+ else
21
+ return :oo.to_m
22
+ end
23
+ else
24
+ return :nan.to_m
25
+ end
26
+ end
27
+
28
+ if a.is_a?(SyMath::Fraction)
29
+ if b.is_a?(SyMath::Fraction)
30
+ return self.new(a.dividend*b.divisor, a.divisor*b.dividend)
31
+ else
32
+ return self.new(a.dividend, a.divisor*b)
33
+ end
34
+ elsif b.is_a?(SyMath::Fraction)
35
+ return self.new(a*b.divisor, b.dividend)
36
+ end
37
+
38
+ return self.new(a, b)
39
+ end
40
+
41
+ # Divide infinite values
42
+ def self.simplify_inf(a, b)
43
+ # Indefinite factors
44
+ if a.is_finite?.nil? or b.is_finite?.nil?
45
+ return self.new(a, b)
46
+ end
47
+
48
+ # NaN/* = */NaN = NaN
49
+ if a.is_nan? or b.is_nan?
50
+ return :nan.to_m
51
+ end
52
+
53
+ # oo/oo = oo/-oo = -oo/oo = NaN
54
+ if a.is_finite? == false and b.is_finite? == false
55
+ return :nan.to_m
56
+ end
57
+
58
+ # */0 = NaN
59
+ if b.is_zero?
60
+ if SyMath.setting(:complex_arithmetic)
61
+ return :oo.to_m
62
+ else
63
+ return :nan.to_m
64
+ end
65
+ end
66
+
67
+ # n/oo = n/-oo = 0
68
+ if a.is_finite?
69
+ return 0.to_m
70
+ end
71
+
72
+ # oo/n = -oo/-n = oo, -oo/n = oo/-n = -oo
73
+ if b.is_finite?
74
+ if SyMath.setting(:complex_arithmetic)
75
+ return :oo.to_m
76
+ else
77
+ if a.sign == b.sign
78
+ return :oo.to_m
79
+ else
80
+ return -:oo.to_m
81
+ end
82
+ end
83
+ end
84
+
85
+ # :nocov:
86
+ raise 'Internal error'
87
+ # :nocov:
88
+ end
89
+
90
+ def initialize(dividend, divisor)
91
+ super('/', [dividend, divisor])
92
+ end
93
+
94
+ def dividend()
95
+ return @args[0]
96
+ end
97
+
98
+ def divisor()
99
+ return @args[1]
100
+ end
101
+
102
+ def is_prod_exp?()
103
+ return true
104
+ end
105
+
106
+ def factors()
107
+ return Enumerator.new do |f|
108
+ dividend.factors.each { |d1| f << d1 }
109
+ divisor.factors.each { |d2|
110
+ if d2 != 1
111
+ f << d2**-1
112
+ end
113
+ }
114
+ end
115
+ end
116
+
117
+ def evaluate()
118
+ # Evaluate matrix division by divding elements
119
+ if dividend.is_a?(SyMath::Matrix)
120
+ return dividend.matrix_div(divisor)
121
+ end
122
+
123
+ # Evaluate df/dx expression.
124
+ if dividend.is_a?(SyMath::Operator) and
125
+ dividend.definition.is_a?(SyMath::Definition::D)
126
+ # Evaluate if the divisor is a simple dform. The composed form
127
+ # d(x) is accepted as well as the simple dx variable.
128
+ if divisor.is_a?(SyMath::Definition::Variable) and divisor.is_d?
129
+ v = divisor.undiff
130
+ elsif divisor.is_a?(SyMath::Definition::D) and
131
+ divisor.args[0].is_a?(SyMath::Definition::Variable) and
132
+ divisor.args[0].type.is_scalar?
133
+ v = divisor.args[0]
134
+ else
135
+ return super
136
+ end
137
+
138
+ diff = dividend.args[0].evaluate.d([v]).normalize
139
+ # Hack: We must divide all terms by dv since the simplification does
140
+ # not recognize factors common to each term
141
+ ret = 0
142
+ dv = v.to_d
143
+ diff.terms.each do |t|
144
+ ret += (t/dv).normalize
145
+ end
146
+
147
+ return ret
148
+ end
149
+
150
+ return super
151
+ end
152
+
153
+ def type()
154
+ if dividend.type.is_subtype?('rational')
155
+ return 'rational'.to_t
156
+ else
157
+ return dividend.type
158
+ end
159
+ end
160
+
161
+ def to_s()
162
+ dividend_str = dividend.is_sum_exp? ? '(' + dividend.to_s + ')' : dividend.to_s
163
+ divisor_str = (divisor.is_sum_exp? or divisor.is_prod_exp?) ?
164
+ '(' + divisor.to_s + ')' :
165
+ divisor.to_s
166
+ if SyMath.setting(:expl_parentheses)
167
+ return '('.to_s + dividend_str + '/' + divisor_str + ')'.to_s
168
+ else
169
+ return dividend_str + '/' + divisor_str
170
+ end
171
+ end
172
+
173
+ def to_latex()
174
+ return '\frac{' + dividend.to_latex + '}{' + divisor.to_latex + '}'
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,252 @@
1
+ require 'symath/value'
2
+
3
+ module SyMath
4
+ class Matrix < Value
5
+ attr_reader :nrows, :ncols
6
+
7
+ def initialize(data)
8
+ raise 'Not an array: ' + data.to_s if !data.is_a?(Array)
9
+ raise 'Array is empty' if data.length == 0
10
+
11
+ if data[0].is_a?(Array) then
12
+ # Multidimensional array
13
+ @nrows = data.length
14
+ raise 'Number of columns is zero' if data[0].length == 0
15
+ @ncols = data[0].length
16
+ # Check that all rows contain arrays of the same length
17
+ data.each do |r|
18
+ raise 'Row is not array' if !r.is_a?(Array)
19
+ raise 'Row has invalid length' if r.length != @ncols
20
+ end
21
+ @elements = data.map { |r| r.map { |c| c.to_m } }
22
+ else
23
+ # Simple array. Creates a single row matrix
24
+ @nrows = 1
25
+ @ncols = data.length
26
+ @elements = [data.map { |c| c.to_m }]
27
+ end
28
+ end
29
+
30
+ # :nocov:
31
+ def is_commutative?()
32
+ return false
33
+ end
34
+
35
+ def is_associative?()
36
+ return true
37
+ end
38
+ # :nocov:
39
+
40
+ def hash()
41
+ return [0, 0].hash
42
+ end
43
+
44
+ def row(i)
45
+ return @elements[i]
46
+ end
47
+
48
+ def col(i)
49
+ return @elements.map { |r| r[i] }
50
+ end
51
+
52
+ def [](i, j)
53
+ return @elements[i][j]
54
+ end
55
+
56
+ def is_square?()
57
+ return @ncols == @nrows
58
+ end
59
+
60
+ def matrix_mul(other)
61
+ if !other.is_a?(SyMath::Matrix)
62
+ data = (0..@nrows - 1).map do |r|
63
+ (0..@ncols - 1).map { |c| self[r, c]*other }
64
+ end
65
+
66
+ return SyMath::Matrix.new(data)
67
+ end
68
+
69
+ raise 'Invalid dimensions' if @ncols != other.nrows
70
+
71
+ data = (0..@nrows - 1).map do |r|
72
+ (0..other.ncols - 1).map do |c|
73
+ (0..@ncols - 1).map do |c2|
74
+ self[r, c2]*other[c2, c]
75
+ end.inject(:+)
76
+ end
77
+ end
78
+
79
+ return SyMath::Matrix.new(data)
80
+ end
81
+
82
+ def matrix_div(other)
83
+ raise 'Cannot divide matrix by matrix' if other.is_a?(SyMath::Matrix)
84
+
85
+ data = (0..@nrows - 1).map do |r|
86
+ (0..@ncols - 1).map { |c| self[r, c]/other }
87
+ end
88
+
89
+ return SyMath::Matrix.new(data)
90
+ end
91
+
92
+ def /(other)
93
+ return div(other)
94
+ end
95
+
96
+ def matrix_add(other)
97
+ if other.is_a?(SyMath::Minus) and other.argument.is_a?(SyMath::Matrix)
98
+ return self.matrix_sub(other.argument)
99
+ end
100
+
101
+ raise 'Invalid dimensions' if @ncols != other.ncols or @nrows != other.nrows
102
+
103
+ data = (0..@nrows - 1).map do |r|
104
+ (0..@ncols - 1).map do |c|
105
+ self[r, c] + other[r, c]
106
+ end
107
+ end
108
+
109
+ return SyMath::Matrix.new(data)
110
+ end
111
+
112
+ def +(other)
113
+ return add(other)
114
+ end
115
+
116
+ def matrix_sub(other)
117
+ raise 'Invalid dimensions' if @ncols != other.ncols or @nrows != other.nrows
118
+
119
+ data = (0..@nrows - 1).map do |r|
120
+ (0..@ncols - 1).map do |c|
121
+ self[r, c] - other[r, c]
122
+ end
123
+ end
124
+
125
+ return SyMath::Matrix.new(data)
126
+ end
127
+
128
+ def -(other)
129
+ return sub(other)
130
+ end
131
+
132
+ def matrix_neg()
133
+ data = @elements.map do |r|
134
+ r.map do |e|
135
+ - e
136
+ end
137
+ end
138
+
139
+ return SyMath::Matrix.new(data)
140
+ end
141
+
142
+ def -@()
143
+ return neg
144
+ end
145
+
146
+ def transpose()
147
+ return SyMath::Matrix.new(@elements.transpose)
148
+ end
149
+
150
+ def inverse()
151
+ raise 'Matrix is not square' if !is_square?
152
+
153
+ return adjugate.matrix_div(determinant)
154
+ end
155
+
156
+ # The adjugate of a matrix is the transpose of the cofactor matrix
157
+ def adjugate()
158
+ data = (0..@ncols - 1).map do |c|
159
+ (0..@nrows - 1).map { |r| cofactor(r, c) }
160
+ end
161
+
162
+ return SyMath::Matrix.new(data)
163
+ end
164
+
165
+ def determinant()
166
+ raise 'Matrix is not square' if !is_square?
167
+
168
+ return minor((0..@nrows - 1).to_a, (0..@ncols - 1).to_a)
169
+ end
170
+
171
+ # The minor is the determinant of a submatrix. The submatrix is given by
172
+ # the rows and cols which are arrays of indexes to the rows and columns
173
+ # to be included
174
+ def minor(rows, cols)
175
+ raise 'Not square' if rows.length != cols.length
176
+
177
+ # Determinant of a single element is just the element
178
+ if rows.length == 1
179
+ return self[rows[0], cols[0]]
180
+ end
181
+
182
+ ret = 0.to_m
183
+ sign = 1
184
+ subrows = rows - [rows[0]]
185
+
186
+ # Loop over all elements e in first row. Calculate determinant as:
187
+ # sum(sign*e*det(rows + cols except the one including e))
188
+ # The sign variable alternates between 1 and -1 for each summand
189
+ cols.each do |c|
190
+ subcols = cols - [c]
191
+ if (sign > 0)
192
+ ret += self[rows[0], c]*minor(subrows, subcols)
193
+ else
194
+ ret -= self[rows[0], c]*minor(subrows, subcols)
195
+ end
196
+
197
+ sign *= -1
198
+ end
199
+
200
+ return ret
201
+ end
202
+
203
+ # The cofactor of an element is the minor given by the rows and columns
204
+ # not including the element, multiplied by a sign factor which alternates
205
+ # for each row and column
206
+ def cofactor(r, c)
207
+ sign = (-1)**(r + c)
208
+ rows = (0..@nrows - 1).to_a - [r]
209
+ cols = (0..@ncols - 1).to_a - [c]
210
+ return minor(rows, cols)*sign.to_m
211
+ end
212
+
213
+ def trace()
214
+ raise 'Matrix is not square' if !is_square?
215
+
216
+ return (0..@nrows - 1).map { |i| self[i, i] }.inject(:+)
217
+ end
218
+
219
+ def ==(other)
220
+ return false if !other.is_a?(SyMath::Matrix)
221
+
222
+ return false if nrows != other.nrows
223
+ return false if ncols != other.ncols
224
+
225
+ (0..@nrows - 1).each do |r|
226
+ (0..@ncols - 1).each do |c|
227
+ return false if self[r, c] != other[r, c]
228
+ end
229
+ end
230
+
231
+ return true
232
+ end
233
+
234
+ alias eql? ==
235
+
236
+ def to_s()
237
+ # This will in many cases look rather messy, but we don't have the option
238
+ # to format the matrix over multiple lines.
239
+ return '[' + @elements.map { |r| r.map { |c| c.to_s }.join(', ') }.join('; ') + ']'
240
+ end
241
+
242
+ def type()
243
+ return SyMath::Type.new('matrix', dimn: ncols, dimm: nrows)
244
+ end
245
+ end
246
+ end
247
+
248
+ class Array
249
+ def to_m()
250
+ return SyMath::Matrix.new(self)
251
+ end
252
+ end
@@ -0,0 +1,125 @@
1
+ require 'symath/operator'
2
+
3
+ module SyMath
4
+ class Minus < Operator
5
+ def self.compose_with_simplify(a)
6
+ a = a.to_m
7
+
8
+ if a == 0
9
+ return a
10
+ end
11
+
12
+ if a.is_a?(SyMath::Minus)
13
+ # - - a => a
14
+ return a.argument
15
+ else
16
+ return self.new(a)
17
+ end
18
+ end
19
+
20
+ def initialize(arg)
21
+ super('-', [arg])
22
+ end
23
+
24
+ def argument()
25
+ return @args[0]
26
+ end
27
+
28
+ def is_positive?()
29
+ if argument.is_nan?
30
+ return false
31
+ end
32
+
33
+ if SyMath.setting(:complex_arithmetic) and (argument.is_finite? == false)
34
+ # Define complex infinity to be positive
35
+ return true
36
+ end
37
+
38
+ if argument.is_positive?.nil?
39
+ return
40
+ end
41
+
42
+ return (!argument.is_positive? and !argument.is_zero?)
43
+ end
44
+
45
+ def is_negative_number?()
46
+ return argument.is_number?
47
+ end
48
+
49
+ def is_zero?()
50
+ return argument.is_zero?
51
+ end
52
+
53
+ def is_finite?()
54
+ return argument.is_finite?
55
+ end
56
+
57
+ def is_sum_exp?()
58
+ return true
59
+ end
60
+
61
+ def is_prod_exp?()
62
+ return true
63
+ end
64
+
65
+ def factors()
66
+ return Enumerator.new do |f|
67
+ f << -1.to_m
68
+ argument.factors.each { |f1| f << f1 }
69
+ end
70
+ end
71
+
72
+ def sign()
73
+ return -argument.sign
74
+ end
75
+
76
+ def terms()
77
+ return Enumerator.new do |s|
78
+ argument.terms.each { |s1| s << s1.neg }
79
+ end
80
+ end
81
+
82
+ def reduce_constant_factors()
83
+ return -argument.reduce_constant_factors
84
+ end
85
+
86
+ # Simple reduction rules, allows sign to change. Returns
87
+ # (reduced exp, sign, changed).
88
+ def reduce_modulo_sign
89
+ red, sign, changed = argument.reduce_modulo_sign
90
+ return red, -sign, true
91
+ end
92
+
93
+ def evaluate
94
+ if argument.is_a?(SyMath::Matrix)
95
+ return argument.matrix_neg
96
+ end
97
+
98
+ return super
99
+ end
100
+
101
+ def type()
102
+ if argument.type.is_subtype?('integer')
103
+ return 'integer'.to_t
104
+ else
105
+ return argument.type
106
+ end
107
+ end
108
+
109
+ def to_s()
110
+ if SyMath.setting(:expl_parentheses)
111
+ return '(- '.to_s + argument.to_s + ')'.to_s
112
+ else
113
+ if argument.is_a?(SyMath::Sum)
114
+ return '- ('.to_s + argument.to_s + ')'.to_s
115
+ else
116
+ return '- '.to_s + argument.to_s
117
+ end
118
+ end
119
+ end
120
+
121
+ def to_latex()
122
+ return '- '.to_s + argument.to_latex
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,167 @@
1
+ require 'symath/operation'
2
+ require 'set'
3
+
4
+ module SyMath::Operation::Differential
5
+ class DifferentialError < StandardError
6
+ end
7
+
8
+ include SyMath::Operation
9
+
10
+ # The d() method provided in this operation module calculates the
11
+ # differential with respect to a given set of variables. Note that the
12
+ # operation returns the differential and not the derivative, so the
13
+ # resulting expression is a differential form.
14
+
15
+ # FIXME. The differential method should work on a function and return
16
+ # a lambda with :x/:dx as free variable, for each variable of the
17
+ # input function.
18
+
19
+ # Module initialization
20
+ def self.initialize()
21
+ # Map of single argument functions to their derivative.
22
+ # FIXME: Check whether this still works if the symbol a is defined?
23
+ @@functions = {
24
+ # Exponential and trigonometric functions
25
+ :exp => definition(:exp),
26
+ :ln => lmd(1.to_m/:a.to_m, :a),
27
+ # Trigonometric functions
28
+ :sin => definition(:cos),
29
+ :cos => lmd(- fn(:sin, :a), :a),
30
+ :tan => lmd(1.to_m + fn(:tan, :a)**2, :a),
31
+ :cot => lmd(- (1.to_m + fn(:cot, :a)**2), :a),
32
+ :sec => lmd(fn(:sec, :a)*fn(:tan, :a), :a),
33
+ :csc => lmd(- fn(:cot, :a.to_m)*fn(:csc, :a.to_m), :a),
34
+ # Inverse trigonometric functions
35
+ :arcsin => lmd(1.to_m/fn(:sqrt, 1.to_m - :a.to_m**2), :a),
36
+ :arccos => lmd(- 1.to_m/fn(:sqrt, 1.to_m - :a.to_m**2), :a),
37
+ :arctan => lmd(1.to_m/fn(:sqrt, 1.to_m + :a.to_m**2), :a),
38
+ :arcsec => lmd(1.to_m/(fn(:abs, :a)*fn(:sqrt, :a.to_m**2 - 1)), :a),
39
+ :arccsc => lmd(- 1.to_m/(fn(:abs, :a)*fn(:sqrt, :a.to_m**2 - 1)), :a),
40
+ :arccot => lmd(- 1.to_m/(1.to_m + :a.to_m**2), :a),
41
+ # Hyperbolic functions
42
+ :sinh => definition(:cosh),
43
+ :cosh => definition(:sinh),
44
+ :tanh => lmd(fn(:sech, :a)**2, :a),
45
+ :sech => lmd(- fn(:tanh, :a)*fn(:sech, :a), :a),
46
+ :csch => lmd(- fn(:coth, :a)*fn(:csch, :a), :a),
47
+ :coth => lmd(- fn(:csch, :a)**2, :a),
48
+ # Inverse hyperbolic functions
49
+ :arsinh => lmd(1.to_m/fn(:sqrt, :a.to_m**2 + 1), :a),
50
+ :arcosh => lmd(1.to_m/fn(:sqrt, :a.to_m**2 - 1), :a),
51
+ :artanh => lmd(1.to_m/(1.to_m - :a.to_m**2), :a),
52
+ :arsech => lmd(- 1.to_m/(:a.to_m*fn(:sqrt, 1.to_m - :a.to_m**2)), :a),
53
+ :arcsch => lmd(- 1.to_m/(fn(:abs, :a.to_m)*fn(:sqrt, :a.to_m**2 + 1)), :a),
54
+ :arcoth => lmd(1.to_m/(1.to_m - :a.to_m**2), :a),
55
+ }
56
+ end
57
+
58
+ def d(vars)
59
+ if self.is_a?(SyMath::Definition::Function)
60
+ return d_function_def(vars)
61
+ end
62
+
63
+ # d(c) = 0 for constant c
64
+ if is_constant?(vars)
65
+ return 0.to_m
66
+ end
67
+
68
+ # d(v) = dv for variable v
69
+ if vars.member?(self)
70
+ return to_d
71
+ end
72
+
73
+ # d(a + b + ...) = d(a) + d(b) + ...
74
+ if is_a?(SyMath::Sum)
75
+ return term1.d(vars) + term2.d(vars)
76
+ end
77
+
78
+ # d(-a) = -d(a)
79
+ if is_a?(SyMath::Minus)
80
+ return -argument.d(vars)
81
+ end
82
+
83
+ # Product rule
84
+ if is_a?(SyMath::Product)
85
+ return d_product(vars)
86
+ end
87
+
88
+ # Fraction rule
89
+ if is_a?(SyMath::Fraction)
90
+ return d_fraction(vars)
91
+ end
92
+
93
+ # Power rule
94
+ if is_a?(SyMath::Power)
95
+ return d_power(vars)
96
+ end
97
+
98
+ # Derivative of function
99
+ return d_function(vars)
100
+ end
101
+
102
+ def d_failure()
103
+ raise DifferentialError, 'Cannot calculate differential of expression ' + to_s
104
+ end
105
+
106
+ # For simplicity, just use wedge products all the time. They will be
107
+ # normalized to scalar products afterwards.
108
+ def d_product(vars)
109
+ return (_d_wedge(factor1.d(vars), factor2) +
110
+ _d_wedge(factor1, factor2.d(vars)))
111
+ end
112
+
113
+ def d_fraction(vars)
114
+ return (_d_wedge(dividend.d(vars), divisor) -
115
+ _d_wedge(dividend, divisor.d(vars))) /
116
+ (divisor**2)
117
+ end
118
+
119
+ def d_power(vars)
120
+ if (exponent.is_constant?(vars))
121
+ return _d_wedge(_d_wedge(exponent, base**(exponent - 1)), base.d(vars))
122
+ else
123
+ return _d_wedge(_d_wedge(self, fn(:ln, base)), exponent.d(vars)) +
124
+ _d_wedge(_d_wedge(exponent, base**(exponent - 1)), base.d(vars))
125
+ end
126
+ end
127
+
128
+ def d_function_def(vars)
129
+ if name != '' and @@functions.key?(name.to_sym)
130
+ df = @@functions[name.to_sym]
131
+ dfcall = df.(args[0]).evaluate
132
+ return _d_wedge(dfcall, args[0].d(vars))
133
+ end
134
+
135
+ if !exp.nil?
136
+ return self.(*args).evaluate.d(vars)
137
+ end
138
+
139
+ d_failure
140
+ end
141
+
142
+ def d_function(vars)
143
+ if !self.is_a?SyMath::Operator
144
+ d_failure
145
+ end
146
+
147
+ if name != '' and @@functions.key?(name.to_sym)
148
+ df = @@functions[name.to_sym]
149
+ dfcall = df.(args[0]).evaluate
150
+ return _d_wedge(dfcall, args[0].d(vars))
151
+ end
152
+
153
+ if !definition.exp.nil?
154
+ return definition.(*args).evaluate.d(vars)
155
+ end
156
+
157
+ d_failure
158
+ end
159
+
160
+ # Apply wedge product or ordinary product between two expressions,
161
+ # depending on whether or not they have vector parts.
162
+ def _d_wedge(exp1, exp2)
163
+ # The product operator will determine whether this is a scalar
164
+ # or a wedge product.
165
+ return (exp1.factors.to_a + exp2.factors.to_a).inject(:*)
166
+ end
167
+ end