symcalc 0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/symcalc.rb +381 -343
  3. metadata +17 -12
  4. data/lib/LICENSE.txt +0 -95
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab527844085471beb0057a27b9538997e055c41331a4d72791a3017ab3b0babb
4
- data.tar.gz: 81d11403ccf1a3ebb81cc4af3ac952161f11d7213dbf9be1f0368db48f1380ba
3
+ metadata.gz: e57698a5da7f300ef21e41ecc27fe2e0be86186213daa9b3736c8603b005b9fa
4
+ data.tar.gz: b108fddfb4a9ad62327f2b1161e2557b9c4c0fc132403e5bea08658470bc903c
5
5
  SHA512:
6
- metadata.gz: eea3871f2af7102b5c7718153d681efaf6ddc8761afdcacb1e65946f236f23e57e86994937c6937d9a773bc3bd6c701dcd7ecf36b0835a5446b39d7f0ec8dbab
7
- data.tar.gz: a75450e788eebba783db6057055f8b2142df9e6a6663c2d97f6ce5b79267efde188d081b8f09d0c3b538758811aff6a4a638f66fd99e8f2d7487a94e62f1a799
6
+ metadata.gz: d811d18c96abea729176dd8b918c9c75de4e183706c7b7912d049efd2cfd028321d923cda9149ba443772ef21073aba693476a824b777c758b65f89c9128adf5
7
+ data.tar.gz: d56da69a5f839a0dd2c7efba40f85f4ca78980219109fad8228182b3cab2db48f7515a1be49910c19d9c4785ab187ad95618c1aaa15e8fa64560dade60b234a3
data/lib/symcalc.rb CHANGED
@@ -1,12 +1,25 @@
1
- #
2
- #
3
- # Copyright (c) 2023 Kyryl Shyshko
1
+ # Copyright (c) 2024 Kyryl Shyshko
4
2
  #
5
- # This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License
6
- # To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/legalcode.en, or read LICENSE.txt
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
7
6
  #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
8
  #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ # SymCalc auto simplify option
17
+ if !$SYMCALC_AUTO_SIMPLIFY
18
+ $SYMCALC_AUTO_SIMPLIFY = true
19
+ end
9
20
 
21
+ # The SymCalc module
22
+ module SymCalc
10
23
 
11
24
  class Equation
12
25
 
@@ -14,8 +27,12 @@ class Equation
14
27
  self.display()
15
28
  end
16
29
 
17
- def inspect
18
- self.display()
30
+ # def inspect
31
+ # self.display()
32
+ # end
33
+
34
+ def display
35
+ ""
19
36
  end
20
37
 
21
38
  def coerce(other)
@@ -23,23 +40,43 @@ class Equation
23
40
  end
24
41
 
25
42
  def *(eq)
26
- return Multiplication.new(self, to_equation(eq))
43
+ eq = Multiplication.new([self, to_equation(eq)])
44
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
45
+ eq
27
46
  end
28
47
 
29
48
  def /(eq)
30
- return Division.new(self, to_equation(eq))
49
+ eq = Division.new(self, to_equation(eq))
50
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
51
+ eq
31
52
  end
32
53
 
33
54
  def +(eq)
34
- return Sum.new(self, to_equation(eq))
55
+ eq = Sum.new([self, to_equation(eq)])
56
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
57
+ eq
58
+ end
59
+
60
+ def -@()
61
+ eq = Negate.new(self)
62
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
63
+ eq
35
64
  end
36
65
 
37
66
  def -(eq)
38
- return Subtraction.new(self, to_equation(eq))
67
+ eq = Sum.new([self, Negate.new(to_equation(eq))])
68
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
69
+ eq
39
70
  end
40
71
 
41
72
  def **(eq)
42
- return Power.new(self, to_equation(eq))
73
+ eq = Power.new(self, to_equation(eq))
74
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
75
+ eq
76
+ end
77
+
78
+ def __derivative__(variable: nil)
79
+ raise "No derivative function implemented for this object"
43
80
  end
44
81
 
45
82
  # Calculate the derivative of the given function
@@ -57,17 +94,19 @@ class Equation
57
94
  #
58
95
  def derivative(order: 1, variable: nil)
59
96
  if variable == nil && self.all_variables.size < 2
60
- fx = self
97
+ fx = self.simplify
61
98
  order.times do
62
- fx = fx.simplify.__derivative__.simplify
99
+ fx = fx.__derivative__
100
+ fx = fx.simplify if $SYMCALC_AUTO_SIMPLIFY
63
101
  end
64
102
  return fx
65
103
  elsif variable == nil && self.all_variables.size > 1
66
104
  raise "Expected a variable as input for a #{self.all_variables.size}-dimensional function"
67
105
  else
68
- fx = self
106
+ fx = self.simplify
69
107
  order.times do
70
- fx = fx.simplify.__derivative__(variable: variable).simplify
108
+ fx = fx.__derivative__(variable: variable)
109
+ fx = fx.simplify if $SYMCALC_AUTO_SIMPLIFY
71
110
  end
72
111
  return fx
73
112
  end
@@ -81,100 +120,12 @@ class Equation
81
120
  # Accepts no arguments
82
121
  #
83
122
  def simplify
84
-
85
- simplified = self.__simplify__
86
-
87
- if [Multiplication, Division].include? simplified.class
88
- m_els = simplified.__get_m_elements__(Hash.new)
89
-
90
- if m_els.keys.size == 0
91
- return EquationValue.new(0)
92
- elsif m_els.keys.size == 1
93
- part = m_els.keys[0]
94
- power = to_equation(m_els.values[0])
95
- if part == "exp"
96
- part = (power == to_equation(1)) ? BasicVars::E : Exp.new(power)
97
- else
98
- part = part ** power if power != to_equation(1)
99
- end
100
- return part
101
- else
102
- els_index = 0
103
- eq = nil
104
-
105
- coeff = to_equation(1)
106
-
107
- m_els.size.times do |els_index|
108
- base = m_els.keys[els_index]
109
- power = m_els[base]
110
-
111
- base = base.simplify if base.is_a? Equation
112
- power = power.simplify if power.is_a? Equation
113
-
114
- base = to_equation base if base != "exp"
115
- power = to_equation power
116
-
117
- if power == to_equation(0)
118
- next
119
- end
120
-
121
- if base.is_a?(EquationValue) && power.is_a?(EquationValue)
122
- case power
123
- when EquationValue.new(1)
124
- coeff *= base
125
- else
126
- coeff *= base ** power
127
- end
128
- next
129
- end
130
-
131
- if base == "exp"
132
- case power
133
- when to_equation(1)
134
- part = BasicVars::E
135
- else
136
- part = Exp.new(power)
137
- end
138
- else
139
- case power
140
- when to_equation(1)
141
- part = base
142
- else
143
- part = base ** power
144
- end
145
- end
146
-
147
- if eq == nil
148
- eq = part
149
- else
150
- eq *= part
151
- end
152
-
153
- end
154
-
155
- # coeff = to_equation(coeff.eval({}))
156
- coeff = coeff.__simplify__
157
-
158
-
159
- if coeff == to_equation(1)
160
- eq = eq
161
- elsif coeff == to_equation(0)
162
- eq = to_equation(0)
163
- elsif eq == nil
164
- eq = coeff
165
- else
166
- eq = coeff * eq
167
- end
168
-
169
- return eq
170
- end
171
-
172
- else
173
- return simplified
174
- end
175
-
123
+ self.__simplify__
176
124
  end
177
125
 
126
+ def __eval__(var_hash)
127
+ raise "__eval__ method not implemented for this class"
128
+ end
178
129
 
179
130
  # Evaluates the function at given variable values
180
131
  # Accepts the hash of variables and their values to evalualte the function
@@ -184,28 +135,42 @@ class Equation
184
135
  # fx = x ** 2
185
136
  # puts fx.eval(x: 3)
186
137
  #
187
- def eval(var_hash)
188
- # begin
189
- if var_hash.values.size == 0
190
- return self.__eval__(Hash.new)
191
- elsif !var_hash.values[0].is_a?(Array)
192
- return self.__eval__ var_hash
193
- elsif var_hash.values[0].is_a? Array
194
- computed = []
195
- var_hash.values[0].size.times do |i|
196
- hash = var_hash.map {|k, v| [k, v[i]]}.to_h
197
- computed << self.__eval__(hash)
198
- end
199
- return computed
138
+ def eval(var_hash = nil)
139
+ var_hash = Hash.new if !var_hash
140
+ if var_hash.values.size == 0
141
+ result = self.__eval__(var_hash)
142
+ elsif !var_hash.values[0].is_a?(Array)
143
+ result = self.__eval__ var_hash
144
+ elsif var_hash.values[0].is_a? Array
145
+ result = []
146
+ var_hash.values[0].size.times do |i|
147
+ hash = var_hash.map {|k, v| [k, v[i]]}.to_h
148
+ result << self.__eval__(hash)
200
149
  end
201
- # rescue
202
- # puts "Could not compute the value. Skipping"
203
- # return nil
204
- # end
150
+ end
151
+ result
205
152
  end
206
153
 
207
154
 
208
155
 
156
+ def __sub__ original, replacement
157
+ return to_equation(replacement) if self == to_equation(original)
158
+ return self
159
+ end
160
+
161
+ # Returns an equation with an expression substituted for the replacement
162
+ #
163
+ # Example:
164
+ # a = SymCalc.var("a")
165
+ # f = a ** 2
166
+ # x = SymCalc.var("x")
167
+ # puts f.sub(a, 3 * x) # => (3 * x) ** 2
168
+ #
169
+ def sub original, replacement
170
+ eq = self.__sub__(original, replacement)
171
+ eq = eq.simplify if $SYMCALC_AUTO_SIMPLIFY
172
+ eq
173
+ end
209
174
 
210
175
  end
211
176
 
@@ -232,7 +197,11 @@ class EquationValue < Equation
232
197
  end
233
198
 
234
199
  def __eval__ var_hash
235
- return @value.to_f
200
+ if @value.is_a? Integer
201
+ return @value.to_f
202
+ else
203
+ return @value
204
+ end
236
205
  end
237
206
 
238
207
  def __derivative__ variable: nil
@@ -240,38 +209,52 @@ class EquationValue < Equation
240
209
  end
241
210
 
242
211
  def ==(value)
243
- if value.is_a? EquationValue
244
- return @value == value.value
245
- else
246
- return false
247
- end
212
+ @value == value
248
213
  end
249
214
 
250
- def hash
251
- 1111000 + @value
215
+ def all_variables
216
+ []
252
217
  end
253
218
 
254
- def eql? obj
255
- return false if !obj.is_a? EquationValue
256
- return obj.value == @value
219
+ end
220
+
221
+ # Implements the Constant class. Behaves like EquationValue when evaluating and like Variable when printing
222
+ class Constant < Equation
223
+
224
+ attr_accessor :value, :name
225
+
226
+ def initialize(name, value)
227
+ @value = value
228
+ @name = name
229
+ end
230
+
231
+ def display
232
+ return @name
257
233
  end
258
234
 
259
- def __get_m_elements__ var_hash
260
- if var_hash.keys.include? self
261
- var_hash[self] = var_hash[self] + 1
235
+ def __eval__ var_hash
236
+ if @value.is_a? Integer
237
+ return @value.to_f
262
238
  else
263
- var_hash[self] = 1
239
+ return @value
264
240
  end
265
- return var_hash
266
241
  end
267
242
 
243
+ def __derivative__ variable: nil
244
+ return EquationValue.new 0
245
+ end
246
+
247
+ def ==(value)
248
+ @value == value
249
+ end
268
250
 
269
251
  def all_variables
270
252
  []
271
253
  end
272
-
254
+
273
255
  end
274
256
 
257
+
275
258
  # Implements the Variable class
276
259
  class Variable < Equation
277
260
 
@@ -284,20 +267,16 @@ class Variable < Equation
284
267
  # fx = x ** 2
285
268
  # fx.eval # => 25
286
269
  #
287
- def initialize name, fixed_value = nil
270
+ def initialize name
288
271
  @name = name
289
- @fixed_value = fixed_value
290
272
  end
291
273
 
292
274
  def display
293
275
  return @name
294
276
  end
295
277
 
296
-
297
278
  def __eval__ var_hash
298
- if @fixed_value
299
- return @fixed_value
300
- elsif var_hash.keys.include?(@name.to_sym) or var_hash.keys.include?(@name.to_s)
279
+ if var_hash.keys.include?(@name.to_sym) or var_hash.keys.include?(@name.to_s)
301
280
  return (var_hash[@name.to_sym] or var_hash[@name.to_s])
302
281
  else
303
282
  raise "No value provided for #{@name.to_s} in eval"
@@ -313,13 +292,8 @@ class Variable < Equation
313
292
  end
314
293
  end
315
294
 
316
- def __get_m_elements__ var_hash
317
- if var_hash.keys.include? self
318
- var_hash[self] += 1
319
- else
320
- var_hash[self] = 1
321
- end
322
- return var_hash
295
+ def __simplify__
296
+ self
323
297
  end
324
298
 
325
299
  def all_variables
@@ -327,160 +301,183 @@ class Variable < Equation
327
301
  end
328
302
  end
329
303
 
330
- # Basic variables that are already implemented in SymCalc and have fixed values
331
- module BasicVars
332
-
333
- E = Variable.new "e", Math::E
334
-
335
- end
336
304
 
337
305
  # Implements sum operations in SymCalc
338
306
  class Sum < Equation
339
- def initialize lside, rside
340
- @lside = lside
341
- @rside = rside
307
+ def initialize elements
308
+ @elements = []
309
+ elements.each do |eq|
310
+ if eq.is_a? Sum
311
+ @elements += eq.instance_variable_get(:@elements)
312
+ else
313
+ @elements << eq
314
+ end
315
+ end
342
316
  end
343
317
 
344
318
  def display
345
- return "(#{@lside.display}) + (#{@rside.display})"
319
+ return @elements.map{|eq| "(#{eq.display})"}.join(" + ")
346
320
  end
347
321
 
348
322
  def __eval__ var_hash
349
- return @lside.eval(var_hash) + @rside.eval(var_hash)
323
+ return @elements.map{|eq| eq.eval(var_hash)}.sum
350
324
  end
351
325
 
352
326
  def __derivative__ variable: nil
353
- return @lside.derivative(variable: variable) + @rside.derivative(variable: variable)
327
+ return Sum.new(@elements.map{|eq| eq.__derivative__(variable: variable)})
354
328
  end
355
329
 
356
330
  def __simplify__
357
- @lside = @lside.__simplify__
358
- @rside = @rside.__simplify__
359
- if (@lside.is_a? EquationValue) && (@rside.is_a? EquationValue)
360
- return EquationValue.new((@lside + @rside).eval({}))
361
- elsif @lside == EquationValue.new(0)
362
- return @rside
363
- elsif @rside == EquationValue.new(0)
364
- return @lside
365
- else
366
- return self
331
+ simplified = @elements.map{|eq| eq.__simplify__}
332
+
333
+ simplified.filter! do |el|
334
+ if el.is_a? EquationValue
335
+ next if el.value == 0
336
+ end
337
+ true
367
338
  end
368
- end
369
-
370
- def __get_m_elements__ el_hash
371
- if el_hash.keys.include? self
372
- el_hash[self] += 1
373
- else
374
- el_hash[self] = 1
339
+
340
+ if simplified.size == 1
341
+ return simplified[0]
375
342
  end
376
- return el_hash
343
+
344
+ return Sum.new(simplified)
377
345
  end
378
346
 
379
347
  def all_variables
380
- return (@lside.all_variables + @rside.all_variables).uniq
348
+ return @elements.map{|eq| eq.all_variables }.flatten.uniq
381
349
  end
382
350
 
351
+ def __sub__ original, replacement
352
+ return to_equation(replacement) if self == to_equation(original)
353
+
354
+ return Sum.new(@elements.map{|eq| eq.__sub__(original, replacement)})
355
+ end
383
356
  end
384
357
 
385
358
  # Implements the subtraction operation in SymCalc
386
- class Subtraction < Equation
387
- def initialize lside, rside
388
- @lside = lside
389
- @rside = rside
359
+ class Negate < Equation
360
+ def initialize eq
361
+ @eq = eq
390
362
  end
391
363
 
392
364
  def display
393
- return "(#{@lside.display}) - (#{@rside.display})"
365
+ return "-(#{@eq.display})"
394
366
  end
395
367
 
396
368
  def __eval__ var_hash
397
- return @lside.eval(var_hash) - @rside.eval(var_hash)
369
+ return -@eq.eval(var_hash)
398
370
  end
399
371
 
400
372
  def __derivative__ variable: nil
401
- return @lside.derivative(variable: variable) - @rside.derivative(variable: variable)
402
- end
403
-
404
- def __get_m_elements__ el_hash
405
- if el_hash.keys.include? self
406
- el_hash[self] += 1
407
- else
408
- el_hash[self] = 1
409
- end
410
- return el_hash
373
+ return -@eq.__derivative__(variable: variable)
411
374
  end
412
375
 
413
376
  def __simplify__
414
- @lside = @lside.__simplify__
415
- @rside = @rside.__simplify__
416
-
417
- if (@lside.is_a? EquationValue) && (@rside.is_a? EquationValue)
418
- return EquationValue.new((@lside - @rside).eval(Hash.new))
419
- elsif @rside == EquationValue.new(0)
420
- return @lside
421
- elsif @lside == EquationValue.new(0)
422
- return -1 * @rside
423
- elsif @lside == @rside
424
- return EquationValue.new(0)
425
- else
426
- return self
427
- end
377
+ return Negate.new(@eq.__simplify__)
428
378
  end
429
379
 
430
380
  def all_variables
431
- return (@lside.all_variables + @rside.all_variables).uniq
381
+ return @eq.all_variables
382
+ end
383
+
384
+ def __sub__ original, replacement
385
+ return to_equation(replacement) if self == to_equation(original)
386
+ return Negate.new(@eq.__sub__(original, replacement))
432
387
  end
433
388
  end
434
389
 
435
390
  # Implements the multiplication operation in SymCalc
436
391
  class Multiplication < Equation
437
- def initialize lside, rside
438
- @lside = lside
439
- @rside = rside
392
+ def initialize elements
393
+ @elements = []
394
+ elements.each do |el|
395
+ if el.is_a? Multiplication
396
+ @elements += el.instance_variable_get(:@elements)
397
+ else
398
+ @elements << el
399
+ end
400
+ end
440
401
  end
441
402
 
442
403
  def display
443
- return "(#{@lside.display}) * (#{@rside.display})"
404
+ return @elements.map{|el| "(#{el.display})"}.join(" * ")
444
405
  end
445
406
 
446
407
  def __eval__ var_hash
447
- return @lside.eval(var_hash) * @rside.eval(var_hash)
408
+ result = 1
409
+ @elements.each do |el|
410
+ result *= el.__eval__(var_hash)
411
+ end
412
+ result
448
413
  end
449
414
 
450
415
  def __derivative__ variable: nil
451
- return @lside.derivative(variable: variable) * @rside + @lside * @rside.derivative(variable: variable)
416
+
417
+ sum_of_mults_arr = []
418
+
419
+ @elements.size.times do |element_index|
420
+ mult_arr = []
421
+
422
+
423
+ mult_arr << @elements[element_index].__derivative__(variable: variable)
424
+
425
+ @elements.size.times do |mult_el_index|
426
+ if element_index == mult_el_index
427
+ next
428
+ end
429
+ mult_arr << @elements[mult_el_index]
430
+ end
431
+
432
+ sum_of_mults_arr << Multiplication.new(mult_arr)
433
+ end
434
+
435
+ return Sum.new(sum_of_mults_arr)
452
436
  end
453
437
 
454
438
  def __simplify__
455
- @lside = @lside.__simplify__
456
- @rside = @rside.__simplify__
457
439
 
458
- if (@lside == EquationValue.new(0)) || (@rside == EquationValue.new(0))
459
- return EquationValue.new 0
460
- elsif @lside == EquationValue.new(1)
461
- return @rside
462
- elsif @rside == EquationValue.new(1)
463
- return @lside
464
- elsif @rside.is_a? EquationValue and @lside.is_a? EquationValue
465
- calculated = @rside.value * @lside.value
466
- if calculated.to_s.size <= 6
467
- return to_equation(calculated)
440
+ numbers = []
441
+ simplified = []
442
+
443
+ @elements.each do |eq|
444
+ s_eq = eq.__simplify__()
445
+
446
+ if s_eq.is_a? EquationValue
447
+ if s_eq.value == 0
448
+ return EquationValue.new(0)
449
+ elsif s_eq.value == 1
450
+ next
451
+ else
452
+ numbers << s_eq.value
453
+ end
468
454
  else
469
- return self
470
- end
471
- else
472
- return self
455
+ simplified << s_eq
456
+ end
457
+ end
458
+
459
+
460
+ coeff = 1
461
+ numbers.each {|n| coeff *= n}
462
+
463
+ if coeff != 1
464
+ simplified.insert 0, EquationValue.new(coeff)
465
+ end
466
+
467
+ if simplified.size == 1
468
+ return simplified[0]
473
469
  end
470
+
471
+ return Multiplication.new(simplified)
474
472
  end
475
473
 
476
- def __get_m_elements__ el_hash
477
- el_hash = @lside.__get_m_elements__(el_hash)
478
- el_hash = @rside.__get_m_elements__(el_hash)
479
- return el_hash
474
+ def all_variables
475
+ return @elements.map{|el| el.all_variables}.flatten.uniq
480
476
  end
481
477
 
482
- def all_variables
483
- return (@lside.all_variables + @rside.all_variables).uniq
478
+ def __sub__ original, replacement
479
+ return to_equation(replacement) if self == to_equation(original)
480
+ return Multiplication.new(@elements.map{|el| el.__sub__(original, replacement)})
484
481
  end
485
482
  end
486
483
 
@@ -503,8 +500,8 @@ class Division < Equation
503
500
  end
504
501
 
505
502
  def __derivative__ variable: nil
506
- return (@lside * @rside ** (-1)).derivative(variable: variable)
507
- # return (@lside.derivative * @rside - @lside * @rside.derivative) / (@rside ** 2)
503
+ # return (@lside * @rside ** (-1)).derivative(variable: variable)
504
+ return (@lside.derivative * @rside - @lside * @rside.derivative) / (@rside ** 2)
508
505
  end
509
506
 
510
507
  def __simplify__
@@ -525,23 +522,15 @@ class Division < Equation
525
522
  end
526
523
  end
527
524
 
528
- def __get_m_elements__ el_hash
529
- mult = @lside.__get_m_elements__(el_hash)
530
- div = @rside.__get_m_elements__({})
531
- div.each do |k, v|
532
- if mult.keys.include? k
533
- mult[k] -= v
534
- else
535
- mult[k] = -v
536
- end
537
- end
538
- return mult
539
- end
540
-
541
525
 
542
526
  def all_variables
543
527
  return (@lside.all_variables + @rside.all_variables).uniq
544
528
  end
529
+
530
+ def __sub__ original, replacement
531
+ return to_equation(replacement) if self == to_equation(original)
532
+ return @lside.__sub__(original, replacement) / @rside.__sub__(original, replacement)
533
+ end
545
534
  end
546
535
 
547
536
 
@@ -584,18 +573,14 @@ class Exp < Equation
584
573
  end
585
574
  end
586
575
 
587
- def __get_m_elements__ var_hash
588
- if var_hash.keys.include? "exp"
589
- var_hash["exp"] += @power
590
- else
591
- var_hash["exp"] = @power
592
- end
593
- return var_hash
594
- end
595
-
596
576
  def all_variables
597
577
  return @power.all_variables
598
578
  end
579
+
580
+ def __sub__ original, replacement
581
+ return to_equation(replacement) if self == to_equation(original)
582
+ return Exp.new(@power.__sub__(original, replacement))
583
+ end
599
584
  end
600
585
 
601
586
 
@@ -631,37 +616,29 @@ class Power < Equation
631
616
  return false
632
617
  end
633
618
  end
634
-
635
- def __get_m_elements__ var_hash
636
- elms = @base.__get_m_elements__({})
637
- elms.each do |k, v|
638
- if var_hash.keys.include? k
639
- var_hash[k] += v * @power
640
- else
641
- var_hash[k] = @power
642
- end
643
- end
644
- return var_hash
645
- end
646
-
619
+
647
620
  def __simplify__
648
621
 
649
- base = @base.simplify
650
- power = @power.simplify
622
+ s_base = @base.__simplify__
623
+ s_power = @power.__simplify__
651
624
 
652
- if power == EquationValue.new(0)
625
+ if s_power == EquationValue.new(0)
653
626
  return EquationValue.new(1)
654
- elsif power == EquationValue.new(1)
655
- return base
656
- elsif base.is_a?(EquationValue) && power.is_a?(EquationValue)
657
- computed = base.value ** power.value
627
+ elsif s_power == EquationValue.new(1)
628
+ return s_base
629
+ elsif s_base.is_a?(EquationValue) && s_power.is_a?(EquationValue)
630
+ computed = s_base.value ** s_power.value
658
631
  if computed.to_s.size <= 6
659
632
  return to_equation(computed)
660
633
  else
661
- return base ** power
634
+ return s_base ** power
662
635
  end
636
+ elsif s_base.is_a?(Power)
637
+ new_base = s_base.base
638
+ new_power = s_base.power * s_power
639
+ return (new_base ** new_power).__simplify__
663
640
  else
664
- return base ** power
641
+ return Power.new(s_base, s_power)
665
642
  end
666
643
 
667
644
  end
@@ -669,6 +646,11 @@ class Power < Equation
669
646
  def all_variables
670
647
  return (@base.all_variables + @power.all_variables).uniq
671
648
  end
649
+
650
+ def __sub__ original, replacement
651
+ return to_equation(replacement) if self == to_equation(original)
652
+ return @base.__sub__(original, replacement) ** @power.__sub__(original, replacement)
653
+ end
672
654
  end
673
655
 
674
656
  # Implements the sin operation in SymCalc
@@ -689,18 +671,18 @@ class Sin < Equation
689
671
  return Cos.new(@eq) * @eq.derivative(variable: variable)
690
672
  end
691
673
 
692
- def __get_m_elements__ var_hash
693
- if var_hash.keys.include? self
694
- var_hash[self] += 1
695
- else
696
- var_hash[self] = 1
697
- end
698
- return var_hash
674
+ def __simplify__
675
+ return Sin.new(@eq.__simplify__)
699
676
  end
700
677
 
701
678
  def all_variables
702
679
  return @eq.all_variables
703
680
  end
681
+
682
+ def __sub__ original, replacement
683
+ return to_equation(replacement) if self == to_equation(original)
684
+ return Sin.new(@eq.__sub__(original, replacement))
685
+ end
704
686
  end
705
687
 
706
688
  # Implements the cos operation in SymCalc
@@ -721,18 +703,18 @@ class Cos < Equation
721
703
  return -1 * Sin.new(@eq) * @eq.derivative(variable: variable)
722
704
  end
723
705
 
724
- def __get_m_elements__ var_hash
725
- if var_hash.keys.include? self
726
- var_hash[self] += 1
727
- else
728
- var_hash[self] = 1
729
- end
730
- return var_hash
706
+ def __simplify__
707
+ return Cos.new(@eq.__simplify__)
731
708
  end
732
709
 
733
710
  def all_variables
734
711
  return @eq.all_variables
735
712
  end
713
+
714
+ def __sub__ original, replacement
715
+ return to_equation(replacement) if self == to_equation(original)
716
+ return Cos.new(@eq.__sub__(original, replacement))
717
+ end
736
718
  end
737
719
 
738
720
 
@@ -757,18 +739,18 @@ class Ln < Equation
757
739
  return 1 / @eq * @eq.derivative(variable: variable)
758
740
  end
759
741
 
760
- def __get_m_elements__ var_hash
761
- if var_hash.keys.include? self
762
- var_hash[self] += 1
763
- else
764
- var_hash[self] = 1
765
- end
766
- return var_hash
742
+ def __simplify__
743
+ return Ln.new(@eq.__simplify__)
767
744
  end
768
-
745
+
769
746
  def all_variables
770
747
  return @eq.all_variables
771
748
  end
749
+
750
+ def __sub__ original, replacement
751
+ return to_equation(replacement) if self == to_equation(original)
752
+ return Ln.new(@eq.__sub__(original, replacement))
753
+ end
772
754
  end
773
755
 
774
756
  # Implements the logarithm operation in SymCalc
@@ -776,7 +758,7 @@ class Log < Equation
776
758
 
777
759
  attr_accessor :eq, :base
778
760
 
779
- # Implements the subtraction operation in SymCalc
761
+ # Implements the logarithm operation in SymCalc
780
762
  # Accepts the base and equation arguments
781
763
  #
782
764
  # Example:
@@ -801,53 +783,109 @@ class Log < Equation
801
783
  return 1 / (Ln.new(@base) * @eq) * @eq.derivative(variable: variable)
802
784
  end
803
785
 
804
- def __get_m_elements__ var_hash
805
- if var_hash.keys.include? self
806
- var_hash[self] += 1
807
- else
808
- var_hash[self] = 1
809
- end
810
- return var_hash
786
+ def __simplify__
787
+ return Log.new(@base.__simplify__, @eq.__simplify__)
811
788
  end
812
789
 
813
790
  def all_variables
814
791
  return (@base.all_variables + @eq.all_variables).uniq
815
792
  end
793
+
794
+ def __sub__ original, replacement
795
+ return to_equation(replacement) if self == to_equation(original)
796
+ return Log.new(@base.__sub__(original, replacement), @eq.__sub__(original, replacement))
797
+ end
816
798
  end
817
799
 
818
- # The SymCalc module implemenets the standard function class creations shorter
819
- module SymCalc
820
-
821
- # sin(equation) is the same as Sin.new(equation), just shorter
822
- def sin(eq)
823
- return Sin.new(to_equation(eq))
800
+ # Implements the absolute value operation in SymCalc
801
+ class Abs < Equation
802
+
803
+ attr_accessor :eq
804
+
805
+ def initialize eq
806
+ @eq = to_equation(eq)
824
807
  end
825
808
 
826
- # cos(equation) is the same as Cos.new(equation), just shorter
827
- def cos(eq)
828
- return Cos.new(to_equation(eq))
809
+ def display
810
+ return "|#{@eq.display}|"
829
811
  end
830
812
 
831
- # ln(equation) is the same as Ln.new(equation), just shorter
832
- def ln(eq)
833
- return Ln.new(to_equation(eq))
813
+ def __eval__ var_hash
814
+ return @eq.eval(var_hash).abs
815
+ end
816
+
817
+ def __derivative__ variable: nil
818
+ return @eq / Abs.new(@eq) * @eq.derivative(variable: variable)
834
819
  end
835
820
 
836
- # log(base, equation) is the same as Log.new(base, equation), just shorter
837
- def log(base, eq)
838
- return Log.new(to_equation(base), to_equation(eq))
821
+ def __simplify__
822
+ if @eq.is_a?(Power) && @eq.power.is_a?(EquationValue) && @eq.power.value.is_a?(Numeric) && (@eq.power.value % 2 == 0)
823
+ return @eq
824
+ end
825
+ self
839
826
  end
840
827
 
841
- # exp(equation) is the same as Exp.new(equation), just shorter
842
- def exp(power)
843
- return Exp.new(to_equation(power))
828
+ def all_variables
829
+ return @eq.all_variables
844
830
  end
845
831
 
846
- # var(name) is the same as Variable.new(name), just shorter
847
- def var(name, fixed_value=nil)
848
- Variable.new name, fixed_value
832
+ def __sub__ original, replacement
833
+ return to_equation(replacement) if self == to_equation(original)
834
+ return Abs.new(@eq.__sub__(original, replacement))
849
835
  end
836
+ end
837
+
838
+
839
+ # Basic variables that are already implemented in SymCalc and have fixed values
840
+ module Constants
850
841
 
851
- module_function :sin, :cos, :ln, :log, :exp, :var
842
+ Pi = Constant.new "pi", Math::PI
843
+ E = Constant.new "e", Math::E
844
+
845
+ end
846
+
847
+
848
+
849
+
850
+ # sin(equation) is the same as Sin.new(equation), just shorter
851
+ def sin(eq)
852
+ return Sin.new(to_equation(eq))
853
+ end
854
+
855
+ # cos(equation) is the same as Cos.new(equation), just shorter
856
+ def cos(eq)
857
+ return Cos.new(to_equation(eq))
858
+ end
859
+
860
+ # ln(equation) is the same as Ln.new(equation), just shorter
861
+ def ln(eq)
862
+ return Ln.new(to_equation(eq))
863
+ end
864
+
865
+ # log(base, equation) is the same as Log.new(base, equation), just shorter
866
+ def log(base, eq)
867
+ return Log.new(to_equation(base), to_equation(eq))
868
+ end
869
+
870
+ # exp(equation) is the same as Exp.new(equation), just shorter
871
+ def exp(power)
872
+ return Exp.new(to_equation(power))
873
+ end
874
+
875
+ # var(name) is the same as Variable.new(name), just shorter
876
+ def var(name)
877
+ Variable.new name
878
+ end
879
+
880
+ def const(name, value)
881
+ Constant.new name, value
882
+ end
883
+
884
+ # abs(equation) is the same as Abs.new(equation), just shorter
885
+ def abs eq
886
+ return Abs.new(to_equation(eq))
887
+ end
888
+
889
+ module_function :sin, :cos, :ln, :log, :exp, :var, :abs, :const
852
890
 
853
891
  end
metadata CHANGED
@@ -1,28 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: symcalc
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyryl Shyshko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-16 00:00:00.000000000 Z
11
+ date: 2024-07-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: SymCalc empowers developers with the ability of creating symbolic functions,
14
- deriving them and evaluating them, substituting variables for numbers.
13
+ description: 'SymCalc adds symbolic mathematics and calculus to your code. Create,
14
+ evaluate and differentiate mathematical functions with a single method call.
15
+
16
+ '
15
17
  email: kyryloshy@gmail.com
16
18
  executables: []
17
19
  extensions: []
18
20
  extra_rdoc_files: []
19
21
  files:
20
- - lib/LICENSE.txt
21
22
  - lib/symcalc.rb
22
- homepage: https://rubygems.org/gems/symcalc
23
+ homepage: https://symcalc.site/ruby
23
24
  licenses:
24
- - CC BY-SA 4.0
25
- metadata: {}
25
+ - Apache-2.0
26
+ metadata:
27
+ changelog_uri: https://symcalc.site/ruby/changelog
28
+ documentation_uri: https://symcalc.site/ruby/docs
29
+ homepage_uri: https://symcalc.site/ruby
30
+ source_code_uri: https://github.com/symcalc/symcalc-ruby
31
+ bug_tracker_uri: https://github.com/symcalc/symcalc-ruby/issues
26
32
  post_install_message:
27
33
  rdoc_options: []
28
34
  require_paths:
@@ -31,16 +37,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
37
  requirements:
32
38
  - - ">="
33
39
  - !ruby/object:Gem::Version
34
- version: '0'
40
+ version: 3.0.0
35
41
  required_rubygems_version: !ruby/object:Gem::Requirement
36
42
  requirements:
37
43
  - - ">="
38
44
  - !ruby/object:Gem::Version
39
45
  version: '0'
40
46
  requirements: []
41
- rubygems_version: 3.3.11
47
+ rubygems_version: 3.5.10
42
48
  signing_key:
43
49
  specification_version: 4
44
- summary: SymCalc is a Ruby gem for symbolic mathematics that implements basic calculus
45
- in code.
50
+ summary: Symbolic mathematics and calculus in Ruby
46
51
  test_files: []
data/lib/LICENSE.txt DELETED
@@ -1,95 +0,0 @@
1
- Attribution-ShareAlike 4.0 International
2
-
3
- By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
4
-
5
- Section 1 – Definitions.
6
-
7
- Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
8
- Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
9
- BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses , approved by Creative Commons as essentially the equivalent of this Public License.
10
- Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
11
- Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
12
- Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
13
- License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.
14
- Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
15
- Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
16
- Licensor means the individual(s) or entity(ies) granting rights under this Public License.
17
- Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
18
- Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
19
- You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
20
- Section 2 – Scope.
21
-
22
- License grant .
23
- Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
24
- reproduce and Share the Licensed Material, in whole or in part; and
25
- produce, reproduce, and Share Adapted Material.
26
- Exceptions and Limitations . For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
27
- Term . The term of this Public License is specified in Section 6(a) .
28
- Media and formats; technical modifications allowed . The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
29
- Downstream recipients .
30
- Offer from the Licensor – Licensed Material . Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
31
- Additional offer from the Licensor – Adapted Material . Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.
32
- No downstream restrictions . You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
33
- No endorsement . Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i) .
34
- Other rights .
35
- Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
36
- Patent and trademark rights are not licensed under this Public License.
37
- To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
38
- Section 3 – License Conditions.
39
-
40
- Your exercise of the Licensed Rights is expressly made subject to the following conditions.
41
-
42
- Attribution .
43
- If You Share the Licensed Material (including in modified form), You must:
44
-
45
- retain the following if it is supplied by the Licensor with the Licensed Material:
46
- identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
47
- a copyright notice;
48
- a notice that refers to this Public License;
49
- a notice that refers to the disclaimer of warranties;
50
- a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
51
- indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
52
- indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
53
- You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
54
- If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
55
- ShareAlike .
56
- In addition to the conditions in Section 3(a) , if You Share Adapted Material You produce, the following conditions also apply.
57
-
58
- The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.
59
- You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
60
- You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
61
- Section 4 – Sui Generis Database Rights.
62
-
63
- Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
64
-
65
- for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
66
- if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b) ; and
67
- You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
68
- For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
69
-
70
- Section 5 – Disclaimer of Warranties and Limitation of Liability.
71
-
72
- Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
73
- To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
74
- The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
75
- Section 6 – Term and Termination.
76
-
77
- This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
78
- Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
79
-
80
- automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
81
- upon express reinstatement by the Licensor.
82
- For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
83
-
84
- For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
85
- Sections 1 , 5 , 6 , 7 , and 8 survive termination of this Public License.
86
- Section 7 – Other Terms and Conditions.
87
-
88
- The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
89
- Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
90
- Section 8 – Interpretation.
91
-
92
- For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
93
- To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
94
- No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
95
- Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.