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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +616 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/symath/definition/abs.rb +48 -0
- data/lib/symath/definition/arccos.rb +25 -0
- data/lib/symath/definition/arccot.rb +23 -0
- data/lib/symath/definition/arccsc.rb +24 -0
- data/lib/symath/definition/arcsec.rb +24 -0
- data/lib/symath/definition/arcsin.rb +25 -0
- data/lib/symath/definition/arctan.rb +23 -0
- data/lib/symath/definition/bounds.rb +39 -0
- data/lib/symath/definition/codiff.rb +31 -0
- data/lib/symath/definition/constant.rb +111 -0
- data/lib/symath/definition/cos.rb +17 -0
- data/lib/symath/definition/cot.rb +17 -0
- data/lib/symath/definition/csc.rb +17 -0
- data/lib/symath/definition/curl.rb +27 -0
- data/lib/symath/definition/d.rb +62 -0
- data/lib/symath/definition/div.rb +27 -0
- data/lib/symath/definition/exp.rb +112 -0
- data/lib/symath/definition/fact.rb +55 -0
- data/lib/symath/definition/flat.rb +31 -0
- data/lib/symath/definition/function.rb +197 -0
- data/lib/symath/definition/grad.rb +23 -0
- data/lib/symath/definition/hodge.rb +23 -0
- data/lib/symath/definition/int.rb +75 -0
- data/lib/symath/definition/laplacian.rb +23 -0
- data/lib/symath/definition/lmd.rb +97 -0
- data/lib/symath/definition/ln.rb +45 -0
- data/lib/symath/definition/number.rb +51 -0
- data/lib/symath/definition/operator.rb +228 -0
- data/lib/symath/definition/sec.rb +17 -0
- data/lib/symath/definition/sharp.rb +31 -0
- data/lib/symath/definition/sin.rb +17 -0
- data/lib/symath/definition/sqrt.rb +62 -0
- data/lib/symath/definition/tan.rb +17 -0
- data/lib/symath/definition/trig.rb +95 -0
- data/lib/symath/definition/variable.rb +284 -0
- data/lib/symath/definition/xd.rb +28 -0
- data/lib/symath/definition.rb +205 -0
- data/lib/symath/equation.rb +67 -0
- data/lib/symath/fraction.rb +177 -0
- data/lib/symath/matrix.rb +252 -0
- data/lib/symath/minus.rb +125 -0
- data/lib/symath/operation/differential.rb +167 -0
- data/lib/symath/operation/distributivelaw.rb +367 -0
- data/lib/symath/operation/exterior.rb +64 -0
- data/lib/symath/operation/integration.rb +329 -0
- data/lib/symath/operation/match.rb +166 -0
- data/lib/symath/operation/normalization.rb +458 -0
- data/lib/symath/operation.rb +36 -0
- data/lib/symath/operator.rb +163 -0
- data/lib/symath/parser.rb +473 -0
- data/lib/symath/parser.y +129 -0
- data/lib/symath/poly/dup.rb +835 -0
- data/lib/symath/poly/galois.rb +621 -0
- data/lib/symath/poly.rb +142 -0
- data/lib/symath/power.rb +224 -0
- data/lib/symath/product.rb +183 -0
- data/lib/symath/sum.rb +174 -0
- data/lib/symath/type.rb +282 -0
- data/lib/symath/value.rb +372 -0
- data/lib/symath/version.rb +3 -0
- data/lib/symath/wedge.rb +48 -0
- data/lib/symath.rb +157 -0
- data/symath.gemspec +39 -0
- 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
|
data/lib/symath/minus.rb
ADDED
@@ -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
|