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 CHANGED
@@ -6,25 +6,83 @@ gem install symbolic
6
6
 
7
7
  == Introduction:
8
8
 
9
- 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.
9
+ Symbolic doesn't have any external dependencies. It uses only pure ruby (less than 400 LOC (lines of code)).
10
10
 
11
- == Examples:
12
- x = var :name => 'x'
13
- y = var :name => 'y'
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
- f = (-2*(-x) + y*(-1.0) + 6*x/(3*x))*x - 2*y + 2*y - (2**x)**y
16
- puts f # => (2*x-y+2)*x-2**(x*y)
16
+ == Tutorial
17
17
 
18
- p f.operations # => {"+"=>1, "-"=>2, "*"=>3, "/"=>0, "**"=>1, "-@"=>0}
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
- puts f.value # => -252.0
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
- g = Symbolic::Math.cos(y)
28
- puts g # => cos(y)
29
- y.value = 0
30
- puts g.value # => 1.0
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.0'
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
- return Coerced.new(self), numeric
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
@@ -1,129 +1,162 @@
1
- class Symbolic::Factor
2
- include Symbolic
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
- class << self
5
- def exponent(base, exponent)
6
- simplify_exponents! factors = unite_exponents(base, exponent)
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
- def multiply(var1, var2)
11
- simplify_exponents! factors = unite(factors(var1), factors(var2))
12
- simplify(*factors) || new(factors)
13
- end
15
+ simplify_exponents! factors = unite(factors(var1), factors(var2))
16
+ simplify(*factors) || new(factors)
17
+ end
14
18
 
15
- def divide(var1, var2)
16
- simplify_exponents! factors = unite(factors(var1), reverse(var2))
17
- simplify(*factors) || new(factors)
18
- end
19
+ def divide(var1, var2)
20
+ simplify_exponents! factors = unite(factors(var1), reverse(var2))
21
+ simplify(*factors) || new(factors)
22
+ end
19
23
 
20
- def simplify_exponents!(factors)
21
- factors[1].delete_if {|base, exp| (base == 1) || (exp == 0) }
22
- factors[0] = 0 if factors[1].any? {|base, exp| base == 0 }
23
- end
24
+ def distributable?(var1, var2)
25
+ simple?(var1) && var2.is_a?(Summand)
26
+ end
24
27
 
25
- def simplify(numeric, symbolic)
26
- if numeric == 0 || symbolic.empty?
27
- (numeric.round == numeric) ? numeric.to_i : numeric.to_f
28
- elsif numeric == 1 && symbolic.size == 1 && symbolic.values.first == 1
29
- symbolic.keys.first
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
- def reverse(var)
34
- factors(var).dup.tap do |it|
35
- it[0] = it[0]**-1
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
- def factors(var)
41
- var.is_a?(Symbolic) ? var.send(:factors) : [var, {}]
42
- end
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
- def unite(factors1, factors2)
45
- numeric1, symbolic1 = factors1
46
- numeric2, symbolic2 = factors2
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
- numeric = numeric1 * numeric2
49
- symbolic = symbolic1.merge(symbolic2) {|base, exp1, exp2| exp1 + exp2 }
50
- return numeric, symbolic
51
- end
55
+ def factors(var)
56
+ var.is_a?(Symbolic) ? var.send(:factors) : [var, {}]
57
+ end
52
58
 
53
- def unite_exponents(base, exponent)
54
- if base.is_a? Symbolic::Factor
55
- numeric, symbolic = factors(base)
56
- return numeric**exponent, Hash[*symbolic.map {|base,exp| [base,exp*exponent] }.flatten]
57
- else
58
- [1, { base => exponent }]
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
- def initialize(factors)
64
- @factors = factors
65
- end
95
+ def initialize(factors)
96
+ @factors = factors
97
+ end
66
98
 
67
- def value
68
- @factors[1].inject(@factors[0]) {|value, (base, exp)| value * base.value ** exp.value }
69
- end
99
+ def value
100
+ @factors[1].inject(@factors[0]) {|value, (base, exp)| value * base.value ** exp.value }
101
+ end
70
102
 
71
- def variables
72
- @factors[1].map {|k,v| [variables_of(k), variables_of(v)] }.flatten.uniq
73
- end
103
+ def variables
104
+ @factors[1].map {|k,v| [variables_of(k), variables_of(v)] }.flatten.uniq
105
+ end
74
106
 
75
- def to_s
76
- simplify_output
77
- end
107
+ def to_s
108
+ simplify_output
109
+ end
78
110
 
79
- def ==(object)
80
- object.send(:factors) == @factors rescue false
81
- end
111
+ def ==(object)
112
+ object.send(:factors) == @factors rescue false
113
+ end
82
114
 
83
- private
115
+ private
84
116
 
85
- attr_reader :factors
117
+ attr_reader :factors
86
118
 
87
- def summands
88
- if @factors[0] == 1
89
- super
90
- else
91
- [0, {(self/@factors[0]) => @factors[0]}]
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
- def coefficient_to_string(numeric)
96
- "#{'-' if numeric < 0}#{"#{rational_to_string numeric.abs}*" if numeric.abs != 1}"
97
- end
127
+ def coefficient_to_string(numeric)
128
+ "#{'-' if numeric < 0}#{"#{rational_to_string numeric.abs}*" if numeric.abs != 1}"
129
+ end
98
130
 
99
- def exponent_to_string(base, exponent)
100
- "#{brackets base}#{"**#{brackets exponent}" if exponent != 1}"
101
- end
131
+ def exponent_to_string(base, exponent)
132
+ "#{brackets base}#{"**#{brackets exponent}" if exponent != 1}"
133
+ end
102
134
 
103
- def brackets(var)
104
- [Numeric, Symbolic::Variable].any? {|klass| var.is_a? klass } ? var : "(#{var})"
105
- end
135
+ def brackets(var)
136
+ [Numeric, Symbolic::Variable].any? {|klass| var.is_a? klass } ? var : "(#{var})"
137
+ end
106
138
 
107
- def simplify_output
108
- groups = @factors[1].group_by {|b,e| e.is_a?(Numeric) && e < 0 }
109
- reversed_factors = groups[true] ? [1, Hash[*groups[true].flatten] ] : nil
110
- factors = groups[false] ? [@factors[0], Hash[*groups[false].flatten] ] : nil
111
- output = '' << (factors ? output(factors) : rational_to_string(@factors[0]))
112
- output << "/#{reversed_output reversed_factors}" if reversed_factors
113
- output
114
- end
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
- def output(factors)
117
- coefficient_to_string(factors[0]) <<
118
- factors[1].map {|base,exp| exponent_to_string base,exp }.join('*')
119
- end
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
- def reversed_output(factors)
122
- result = output [factors[0], Hash[*factors[1].map {|b,e| [b,-e] }.flatten]]
123
- (factors[1].length > 1) ? "(#{result})" : result
124
- end
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
- def rational_to_string(numeric)
127
- ((numeric.round == numeric) ? numeric.to_i : numeric.to_f).to_s
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
@@ -17,10 +17,6 @@ class Symbolic::Function
17
17
  @variable.variables
18
18
  end
19
19
 
20
- def undefined_variables
21
- @variable.undefined_variables
22
- end
23
-
24
20
  def detailed_operations
25
21
  @variable.detailed_operations.tap {|it| it[@operation] += 1}
26
22
  end
@@ -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
- return numeric, symbolic
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
 
@@ -22,10 +22,6 @@ module Symbolic
22
22
  [self]
23
23
  end
24
24
 
25
- def undefined_variables
26
- value ? [] : variables
27
- end
28
-
29
25
  def detailed_operations
30
26
  Hash.new 0
31
27
  end
@@ -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).undefined_variables' => '[]',
107
- '(x*y).undefined_variables' => '[]'#,
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
- # '-(x+y)' => '-x-y',
128
- # '-(x-y)' => 'y-x',
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
- # '(-(2+x))/(-(-y))' => '(-2-x)/y',
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
- # '-(x**y -2)+5' => '2-x**y+5'
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.0
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-13 00:00:00 +03:00
12
+ date: 2009-12-16 00:00:00 +03:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency