symbolic 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|