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