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 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