symbolic 0.3.0 → 0.3.1
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.
- data/README.rdoc +74 -16
- data/Rakefile +1 -1
- data/lib/symbolic.rb +5 -11
- data/lib/symbolic/factor.rb +130 -97
- data/lib/symbolic/function.rb +0 -4
- data/lib/symbolic/summand.rb +2 -2
- data/lib/symbolic/variable.rb +0 -4
- data/spec/symbolic_spec.rb +12 -11
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -6,25 +6,83 @@ gem install symbolic
|
|
6
6
|
|
7
7
|
== Introduction:
|
8
8
|
|
9
|
-
Symbolic
|
9
|
+
Symbolic doesn't have any external dependencies. It uses only pure ruby (less than 400 LOC (lines of code)).
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
This gem can help you
|
12
|
+
- if you want to get a simplified form of a big equation
|
13
|
+
- if you want to speed up similar calculations
|
14
|
+
- if you need an abstraction layer for math
|
14
15
|
|
15
|
-
|
16
|
-
puts f # => (2*x-y+2)*x-2**(x*y)
|
16
|
+
== Tutorial
|
17
17
|
|
18
|
-
|
19
|
-
p f.variables.map &:name # => ["x", "y"]
|
20
|
-
x.value = 2
|
21
|
-
p f.undefined_variables.map &:name # => ["y"]
|
22
|
-
y.value = 4
|
18
|
+
First, you need to create a symbolic variable.
|
23
19
|
|
24
|
-
|
20
|
+
x = var
|
25
21
|
|
22
|
+
# you can set a name of variable (useful if you print equations)
|
23
|
+
angle = var :name => 'θ'
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
# or starting value
|
26
|
+
pi = var :value => 3.14
|
27
|
+
|
28
|
+
# or bind its value to a proc
|
29
|
+
y = var { x ** 2 }
|
30
|
+
|
31
|
+
# you can set a value for already created variable
|
32
|
+
x.value = 3
|
33
|
+
|
34
|
+
Now, you can do any math operations with it.
|
35
|
+
|
36
|
+
f = 2*x + 1
|
37
|
+
puts f # => 2*x+1
|
38
|
+
|
39
|
+
To get value of symbolic expression you just call value:
|
40
|
+
|
41
|
+
f.value # => 7
|
42
|
+
|
43
|
+
If symbolic expression contains variables without value then it returns nil.
|
44
|
+
|
45
|
+
z = var
|
46
|
+
(z+1).value # => nil
|
47
|
+
|
48
|
+
All symbolic expression are automatically simplified when created:
|
49
|
+
|
50
|
+
0 * x # => 0
|
51
|
+
2 + x + 1 # => x + 3
|
52
|
+
-(x-y) + 2*x # => x + y
|
53
|
+
(x**2)**3 / x # => x**5
|
54
|
+
# etc. (more examples can be found in symbolic_spec.rb)
|
55
|
+
|
56
|
+
If you need to use a function from Math module with symbolic variable, use Symbolic::Math module.
|
57
|
+
|
58
|
+
cos = Symbolic::Math.cos(x)
|
59
|
+
x.value = 0
|
60
|
+
cos.value # => 1.0
|
61
|
+
|
62
|
+
You can get a list of variables from symbolic expression:
|
63
|
+
|
64
|
+
(x+y+1).variables # => [x, y]
|
65
|
+
|
66
|
+
So you can get a list of variables without value:
|
67
|
+
|
68
|
+
(x+y+1).variables.select {|var| var.value.nil? }
|
69
|
+
|
70
|
+
You can get an information about number of different operations used in a symbolic expression:
|
71
|
+
|
72
|
+
f = (2*x-y+2)*x-2**(x*y)
|
73
|
+
f.operations # => {"+"=>1, "-"=>2, "*"=>3, "/"=>0, "**"=>1, "-@"=>0}
|
74
|
+
|
75
|
+
|
76
|
+
== TODO:
|
77
|
+
- a lot of refactoring (code is pretty messy at this stage)
|
78
|
+
- plotting capabilities
|
79
|
+
- derivatives
|
80
|
+
- integrals
|
81
|
+
- thorough documentation
|
82
|
+
|
83
|
+
== Author
|
84
|
+
|
85
|
+
brainopia (ravwar at gmail.com).
|
86
|
+
|
87
|
+
I am ready to help with any questions related to Symbolic.
|
88
|
+
I welcome any contribution.
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "symbolic"
|
8
|
-
gem.version = '0.3.
|
8
|
+
gem.version = '0.3.1'
|
9
9
|
gem.summary = 'Symbolic math for ruby'
|
10
10
|
gem.description = %Q{Symbolic math can be really helpful if you want to simplify some giant equation or if you don't want to get a performance hit re-evaluating big math expressions every time when few variables change.}
|
11
11
|
gem.email = "ravwar@gmail.com"
|
data/lib/symbolic.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
Symbolic = Module.new
|
2
|
-
|
3
1
|
require 'symbolic/coerced'
|
4
2
|
require 'symbolic/variable'
|
5
3
|
require 'symbolic/summand'
|
@@ -14,14 +12,14 @@ require 'symbolic/extensions/matrix' if Object.const_defined? 'Matrix'
|
|
14
12
|
require 'symbolic/extensions/rational' if RUBY_VERSION == '1.8.7'
|
15
13
|
|
16
14
|
module Symbolic
|
17
|
-
def -@
|
18
|
-
Factor.multiply self, -1
|
19
|
-
end
|
20
|
-
|
21
15
|
def +@
|
22
16
|
self
|
23
17
|
end
|
24
18
|
|
19
|
+
def -@
|
20
|
+
Factor.multiply self, -1
|
21
|
+
end
|
22
|
+
|
25
23
|
def +(var)
|
26
24
|
Summand.add self, var
|
27
25
|
end
|
@@ -43,11 +41,7 @@ module Symbolic
|
|
43
41
|
end
|
44
42
|
|
45
43
|
def coerce(numeric)
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def undefined_variables
|
50
|
-
variables.select {|it| it.value.nil? }
|
44
|
+
[Coerced.new(self), numeric]
|
51
45
|
end
|
52
46
|
|
53
47
|
private
|
data/lib/symbolic/factor.rb
CHANGED
@@ -1,129 +1,162 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Symbolic
|
2
|
+
class Factor
|
3
|
+
include Symbolic
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def exponent(base, exponent)
|
7
|
+
simplify_exponents! factors = unite_exponents(base, exponent)
|
8
|
+
simplify(*factors) || new(factors)
|
9
|
+
end
|
3
10
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
simplify(*factors) || new(factors)
|
8
|
-
end
|
11
|
+
def multiply(var1, var2)
|
12
|
+
return distribute(var1, var2) if distributable? var1, var2
|
13
|
+
return distribute(var2, var1) if distributable? var2, var1
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
15
|
+
simplify_exponents! factors = unite(factors(var1), factors(var2))
|
16
|
+
simplify(*factors) || new(factors)
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
def divide(var1, var2)
|
20
|
+
simplify_exponents! factors = unite(factors(var1), reverse(var2))
|
21
|
+
simplify(*factors) || new(factors)
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
+
def distributable?(var1, var2)
|
25
|
+
simple?(var1) && var2.is_a?(Summand)
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def distribute(var1, var2)
|
29
|
+
numeric, symbolic = var2.send(:summands)
|
30
|
+
symbolic.map {|k,v| k*v }.inject(numeric*var1) do |sum, it|
|
31
|
+
sum + it*var1
|
32
|
+
end
|
30
33
|
end
|
31
|
-
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
it[1] = Hash[*it[1].map {|b,e| [b,-e] }.flatten]
|
35
|
+
def simplify_exponents!(factors)
|
36
|
+
factors[1].delete_if {|base, exp| (base == 1) || (exp == 0) }
|
37
|
+
factors[0] = 0 if factors[1].any? {|base, exp| base == 0 }
|
37
38
|
end
|
38
|
-
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
def simplify(numeric, symbolic)
|
41
|
+
if numeric == 0 || symbolic.empty?
|
42
|
+
(numeric.round == numeric) ? numeric.to_i : numeric.to_f
|
43
|
+
elsif numeric == 1 && symbolic.size == 1 && symbolic.values.first == 1
|
44
|
+
symbolic.keys.first
|
45
|
+
end
|
46
|
+
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
def reverse(var)
|
49
|
+
factors(var).dup.tap do |it|
|
50
|
+
it[0] = it[0]**-1
|
51
|
+
it[1] = Hash[*it[1].map {|b,e| [b,-e] }.flatten]
|
52
|
+
end
|
53
|
+
end
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
55
|
+
def factors(var)
|
56
|
+
var.is_a?(Symbolic) ? var.send(:factors) : [var, {}]
|
57
|
+
end
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
def unite(factors1, factors2)
|
60
|
+
numeric1, symbolic1 = factors1
|
61
|
+
numeric2, symbolic2 = factors2
|
62
|
+
|
63
|
+
numeric = numeric1 * numeric2
|
64
|
+
symbolic = symbolic1.merge(symbolic2) {|base, exp1, exp2| exp1 + exp2 }
|
65
|
+
[numeric, symbolic]
|
66
|
+
end
|
67
|
+
|
68
|
+
def unite_exponents(base, exponent)
|
69
|
+
if base.is_a? Factor
|
70
|
+
numeric, symbolic = factors(base)
|
71
|
+
return numeric**exponent, Hash[*symbolic.map {|base,exp| [base,exp*exponent] }.flatten]
|
72
|
+
else
|
73
|
+
[1, { base => exponent }]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def simple?(var)
|
78
|
+
case var
|
79
|
+
when Numeric
|
80
|
+
true
|
81
|
+
when Variable
|
82
|
+
true
|
83
|
+
when Function
|
84
|
+
false
|
85
|
+
when Summand
|
86
|
+
false
|
87
|
+
when Factor
|
88
|
+
var.send(:factors)[1].all? {|k,v| simple? k }
|
89
|
+
else
|
90
|
+
false
|
91
|
+
end
|
59
92
|
end
|
60
93
|
end
|
61
|
-
end
|
62
94
|
|
63
|
-
|
64
|
-
|
65
|
-
|
95
|
+
def initialize(factors)
|
96
|
+
@factors = factors
|
97
|
+
end
|
66
98
|
|
67
|
-
|
68
|
-
|
69
|
-
|
99
|
+
def value
|
100
|
+
@factors[1].inject(@factors[0]) {|value, (base, exp)| value * base.value ** exp.value }
|
101
|
+
end
|
70
102
|
|
71
|
-
|
72
|
-
|
73
|
-
|
103
|
+
def variables
|
104
|
+
@factors[1].map {|k,v| [variables_of(k), variables_of(v)] }.flatten.uniq
|
105
|
+
end
|
74
106
|
|
75
|
-
|
76
|
-
|
77
|
-
|
107
|
+
def to_s
|
108
|
+
simplify_output
|
109
|
+
end
|
78
110
|
|
79
|
-
|
80
|
-
|
81
|
-
|
111
|
+
def ==(object)
|
112
|
+
object.send(:factors) == @factors rescue false
|
113
|
+
end
|
82
114
|
|
83
|
-
|
115
|
+
private
|
84
116
|
|
85
|
-
|
117
|
+
attr_reader :factors
|
86
118
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
119
|
+
def summands
|
120
|
+
if @factors[0] == 1
|
121
|
+
super
|
122
|
+
else
|
123
|
+
[0, {(self/@factors[0]) => @factors[0]}]
|
124
|
+
end
|
92
125
|
end
|
93
|
-
end
|
94
126
|
|
95
|
-
|
96
|
-
|
97
|
-
|
127
|
+
def coefficient_to_string(numeric)
|
128
|
+
"#{'-' if numeric < 0}#{"#{rational_to_string numeric.abs}*" if numeric.abs != 1}"
|
129
|
+
end
|
98
130
|
|
99
|
-
|
100
|
-
|
101
|
-
|
131
|
+
def exponent_to_string(base, exponent)
|
132
|
+
"#{brackets base}#{"**#{brackets exponent}" if exponent != 1}"
|
133
|
+
end
|
102
134
|
|
103
|
-
|
104
|
-
|
105
|
-
|
135
|
+
def brackets(var)
|
136
|
+
[Numeric, Symbolic::Variable].any? {|klass| var.is_a? klass } ? var : "(#{var})"
|
137
|
+
end
|
106
138
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
139
|
+
def simplify_output
|
140
|
+
groups = @factors[1].group_by {|b,e| e.is_a?(Numeric) && e < 0 }
|
141
|
+
reversed_factors = groups[true] ? [1, Hash[*groups[true].flatten] ] : nil
|
142
|
+
factors = groups[false] ? [@factors[0], Hash[*groups[false].flatten] ] : nil
|
143
|
+
output = '' << (factors ? output(factors) : rational_to_string(@factors[0]))
|
144
|
+
output << "/#{reversed_output reversed_factors}" if reversed_factors
|
145
|
+
output
|
146
|
+
end
|
115
147
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
148
|
+
def output(factors)
|
149
|
+
coefficient_to_string(factors[0]) <<
|
150
|
+
factors[1].map {|base,exp| exponent_to_string base,exp }.join('*')
|
151
|
+
end
|
120
152
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
153
|
+
def reversed_output(factors)
|
154
|
+
result = output [factors[0], Hash[*factors[1].map {|b,e| [b,-e] }.flatten]]
|
155
|
+
(factors[1].length > 1) ? "(#{result})" : result
|
156
|
+
end
|
125
157
|
|
126
|
-
|
127
|
-
|
158
|
+
def rational_to_string(numeric)
|
159
|
+
((numeric.round == numeric) ? numeric.to_i : numeric.to_f).to_s
|
160
|
+
end
|
128
161
|
end
|
129
162
|
end
|
data/lib/symbolic/function.rb
CHANGED
data/lib/symbolic/summand.rb
CHANGED
@@ -29,7 +29,7 @@ class Symbolic::Summand
|
|
29
29
|
|
30
30
|
numeric = numeric1 + numeric2
|
31
31
|
symbolic = symbolic1.merge(symbolic2) {|base, coef1, coef2| coef1 + coef2 }
|
32
|
-
|
32
|
+
[numeric, symbolic]
|
33
33
|
end
|
34
34
|
|
35
35
|
def simplify_coefficients!(summands)
|
@@ -64,7 +64,7 @@ class Symbolic::Summand
|
|
64
64
|
def to_s
|
65
65
|
output = @summands[1].map {|base, coef| coef_to_string(coef) + base.to_s }
|
66
66
|
output << remainder_to_string(@summands[0]) if @summands[0] != 0
|
67
|
-
output[0] = output.first[1..-1]
|
67
|
+
output[0] = output.first[1..-1] if output.first[0] == '+'
|
68
68
|
output.join
|
69
69
|
end
|
70
70
|
|
data/lib/symbolic/variable.rb
CHANGED
data/spec/symbolic_spec.rb
CHANGED
@@ -96,18 +96,18 @@ describe "Symbolic" do
|
|
96
96
|
|
97
97
|
'x*4*x' => '4*x**2',
|
98
98
|
'x*(-1)*x**(-1)' => '-1',
|
99
|
-
'x**2*(-1)*x**(-1)' => '-x'
|
99
|
+
'x**2*(-1)*x**(-1)' => '-x',
|
100
|
+
'x + y - x' => 'y',
|
101
|
+
'2*x + x**1 - y**2/y - y' => '3*x - 2*y',
|
102
|
+
'-(x+4)' => '-x-4'
|
100
103
|
end
|
101
104
|
|
102
105
|
describe 'general methods:' do
|
103
106
|
should_equal \
|
104
107
|
'x.variables' => '[x]',
|
105
108
|
'(-(x+y)).variables' => '[x,y]',
|
106
|
-
'(x+y).
|
107
|
-
'
|
108
|
-
# '(-x**y-4*y+5-y/x).detailed_operations' =>
|
109
|
-
# '{"+" => 1, "-" => 2, "*" => 1, "/" => 1, "-@" => 1, "**" => 1}',
|
110
|
-
# '(-x**y-4*y+5-y/x).operations' => '7'
|
109
|
+
'(-x**y-4*y+5-y/x).operations' =>
|
110
|
+
'{"+" => 1, "-" => 2, "*" => 1, "/" => 1, "-@" => 1, "**" => 1}'
|
111
111
|
end
|
112
112
|
|
113
113
|
describe "to_s:" do
|
@@ -124,17 +124,18 @@ describe "Symbolic" do
|
|
124
124
|
'-x' => '-x',
|
125
125
|
'x+1' => 'x+1',
|
126
126
|
'x-4' => 'x-4',
|
127
|
-
|
128
|
-
|
127
|
+
'-x-4' => '-x-4',
|
128
|
+
'-(x+y)' => '-x-y',
|
129
|
+
'-(x-y)' => '-x+y',
|
129
130
|
'x*y' => 'x*y',
|
130
131
|
'(-x)*y' => '-x*y',
|
131
132
|
'(x+2)*(y+3)*4' => '4*(x+2)*(y+3)',
|
132
133
|
'4/x' => '4/x',
|
133
134
|
'2*x**(-1)*y**(-1)' => '2/(x*y)',
|
134
|
-
|
135
|
+
'(-(2+x))/(-(-y))' => '(-x-2)/y',
|
135
136
|
'x**y' => 'x**y',
|
136
137
|
'x**(y-4)' => 'x**(y-4)',
|
137
|
-
'(x+1)**(y*2)' => '(x+1)**(2*y)'
|
138
|
-
|
138
|
+
'(x+1)**(y*2)' => '(x+1)**(2*y)',
|
139
|
+
'-(x**y -2)+5' => '-x**y+7'
|
139
140
|
end
|
140
141
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: symbolic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- brainopia
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-12-
|
12
|
+
date: 2009-12-16 00:00:00 +03:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|