symbolic 0.3.7 → 0.3.8
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 +20 -4
- data/Rakefile +5 -16
- data/lib/symbolic.rb +30 -19
- data/lib/symbolic/constant.rb +27 -0
- data/lib/symbolic/constants.rb +15 -0
- data/lib/symbolic/expression.rb +18 -4
- data/lib/symbolic/extensions/kernel.rb +1 -1
- data/lib/symbolic/extensions/matrix.rb +8 -6
- data/lib/symbolic/extensions/module.rb +5 -0
- data/lib/symbolic/extensions/numeric.rb +8 -0
- data/lib/symbolic/extensions/rational.rb +18 -12
- data/lib/symbolic/factors.rb +26 -48
- data/lib/symbolic/function.rb +100 -19
- data/lib/symbolic/math.rb +31 -7
- data/lib/symbolic/misc.rb +5 -0
- data/lib/symbolic/plugins/var_name.rb +42 -0
- data/lib/symbolic/printer.rb +79 -0
- data/lib/symbolic/statistics.rb +13 -8
- data/lib/symbolic/sum.rb +43 -0
- data/lib/symbolic/summands.rb +36 -31
- data/lib/symbolic/variable.rb +23 -11
- data/spec/integration_spec.rb +193 -0
- data/spec/spec_helper.rb +7 -4
- data/spec/symbolic/equal_spec.rb +18 -0
- data/spec/symbolic_spec.rb +31 -123
- data/symbolic.gemspec +64 -0
- metadata +53 -45
- data/.gitignore +0 -22
- data/spec/spec.opts +0 -1
data/lib/symbolic/math.rb
CHANGED
@@ -2,14 +2,38 @@ module Symbolic::Math
|
|
2
2
|
=begin
|
3
3
|
This module is a reflection for Math module which allows to use symbolic expressions as parameters for its methods.
|
4
4
|
=end
|
5
|
-
|
5
|
+
require "#{File.dirname(__FILE__)}/function.rb"
|
6
|
+
require 'rational'
|
7
|
+
|
8
|
+
#for use in defining derivatives
|
9
|
+
Arg = Symbolic::Variable.new(:name=>'Arg')
|
10
|
+
|
11
|
+
#first, make the functions with derivatives
|
12
|
+
Abs = Symbolic::Function.new('abs', proc{|arg| arg/Abs[arg]}){|arg| arg.abs}
|
13
|
+
Sqrt = Symbolic::Function.new('sqrt', Rational(1,2) / Arg ** Rational(1,2))
|
14
|
+
Exp = Symbolic::Function.new('exp'); Exp.set_derivative(Exp)
|
15
|
+
Log = Symbolic::Function.new('log', 1 / Arg)
|
16
|
+
Log10 = Symbolic::Function.new('log10', 1 / Arg / ::Math.log(10)) #since log10(x) = log(x) / log(10)
|
17
|
+
Cos = Symbolic::Function.new('cos')
|
18
|
+
Sin = Symbolic::Function.new('sin',Cos); Cos.set_derivative(-Sin[Arg])
|
19
|
+
Tan = Symbolic::Function.new('tan', 1 / Cos[Arg] ** 2)
|
20
|
+
Cosh = Symbolic::Function.new('cosh')
|
21
|
+
Sinh = Symbolic::Function.new('sinh',Cosh); Cosh.set_derivative(Sinh)
|
22
|
+
Tanh = Symbolic::Function.new('tanh',1 / Cosh[Arg] ** 2)
|
23
|
+
Acos = Symbolic::Function.new('acos',- 1 / (1 - Arg) ** Rational(1,2))
|
24
|
+
Asin = Symbolic::Function.new('asin',1 / (1 - Arg) ** Rational(1,2))
|
25
|
+
Atan = Symbolic::Function.new('atan',1 / (Arg**2 + 1))
|
26
|
+
Acosh = Symbolic::Function.new('acosh',1 / (1 - Arg) ** Rational(1,2))
|
27
|
+
Asinh = Symbolic::Function.new('asinh',1 / (1 + Arg) ** Rational(1,2))
|
28
|
+
Atanh = Symbolic::Function.new('atanh',1/ (1 - Arg**2))
|
29
|
+
|
30
|
+
#make functions of the form fctn(arg) and add operation to each function
|
31
|
+
#for ruby 1.9, we have to convert them to strings (they were strings in 1.8)
|
32
|
+
Symbolic::Math.constants.collect{|c| c.to_s}.reject{|c| ['Arg','Abs'].include?(c)}.each do |fctn|
|
6
33
|
instance_eval <<-CODE, __FILE__, __LINE__ + 1
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
else
|
11
|
-
::Math.#{method} argument
|
12
|
-
end
|
34
|
+
#{fctn}.set_operation(proc{|arg| ::Math.#{fctn.downcase}(arg)})
|
35
|
+
def #{fctn.downcase}(argument)
|
36
|
+
#{fctn}[argument]
|
13
37
|
end
|
14
38
|
CODE
|
15
39
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# This is a simple module to give the name of the Ruby variable to the Symbolic::Variable
|
2
|
+
# x = var #=> x = var :name => 'x')
|
3
|
+
# It can also name multiple variables (but with no options then):
|
4
|
+
# x, y = vars
|
5
|
+
# This works with caller, and then need to be called directly
|
6
|
+
|
7
|
+
module Kernel
|
8
|
+
alias :_var :var
|
9
|
+
def var(options={}, &proc)
|
10
|
+
unless options.has_key? :name
|
11
|
+
file, ln = caller[0].split(':')
|
12
|
+
|
13
|
+
options.merge!(
|
14
|
+
:name => File.open(file) { |f|
|
15
|
+
f.each_line.take(ln.to_i)[-1]
|
16
|
+
}.match(/
|
17
|
+
\s*([[:word:]]+)
|
18
|
+
\s*=
|
19
|
+
\s*var/x
|
20
|
+
) {
|
21
|
+
$1
|
22
|
+
})
|
23
|
+
end
|
24
|
+
_var(options, &proc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def vars
|
28
|
+
file, ln = caller[0].split(':')
|
29
|
+
|
30
|
+
File.open(file) { |f|
|
31
|
+
f.each_line.take(ln.to_i)[-1]
|
32
|
+
}.match(/
|
33
|
+
((?:\s*[[:word:]]+?,?)+)
|
34
|
+
\s*=
|
35
|
+
\s*vars/x
|
36
|
+
) {
|
37
|
+
$1
|
38
|
+
}.scan(/([[:word:]]+)/).map { |capture|
|
39
|
+
_var(:name => capture[0])
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
=begin
|
2
|
+
This class intend to handle the String representation of a Symbolic expression
|
3
|
+
Two formats will be soon supported: standard and LaTeX
|
4
|
+
=end
|
5
|
+
module Symbolic
|
6
|
+
class Printer
|
7
|
+
class << self
|
8
|
+
def print(o)
|
9
|
+
send(o.class.simple_name.downcase, o)
|
10
|
+
end
|
11
|
+
|
12
|
+
def brackets(var)
|
13
|
+
[Numeric, Variable, Function].any? { |c| var.is_a? c } ? var.to_s : "(#{var})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def rational(r)
|
17
|
+
"#{r.round == r ? r.to_i : r.to_f}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def coef(c)
|
21
|
+
"#{'-' if c < 0}#{"#{rational c.abs}*" if c.abs != 1}"
|
22
|
+
end
|
23
|
+
def coef_with_sign(c)
|
24
|
+
"#{ c < 0 ? '-' : '+'}#{"#{rational c.abs}*" if c.abs != 1}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Factors
|
28
|
+
def factors(f)
|
29
|
+
rfactors, factors = f.symbolic.partition { |b,e| e.is_a?(Numeric) && e < 0 }
|
30
|
+
rfactors = rfactors.empty? ? nil : [ 1, Hash[*rfactors.flatten] ]
|
31
|
+
factors = factors.empty? ? nil : [ f.numeric, Hash[*factors.flatten] ]
|
32
|
+
|
33
|
+
s = (factors ? output(factors) : rational(f.numeric))
|
34
|
+
s << "/#{reversed_output rfactors}" if rfactors
|
35
|
+
s
|
36
|
+
end
|
37
|
+
def output(f) # This has to change ! can be inline in ::factors, but needed also in reversed_output
|
38
|
+
coef(f[0]) << f[1].map {|b,e| exponent b,e }.join('*')
|
39
|
+
end
|
40
|
+
def reversed_output(f) # This has to change ! Please make this non dependent of output ;)
|
41
|
+
result = output [f[0], Hash[*f[1].map {|b,e| [b,-e] }.flatten]]
|
42
|
+
(f[1].length > 1) ? "(#{result})" : result
|
43
|
+
end
|
44
|
+
def exponent(base, exponent)
|
45
|
+
"#{brackets base}#{"**#{brackets exponent}" if exponent != 1}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Summands
|
49
|
+
def summands(s)
|
50
|
+
out = s.symbolic.map { |base, coef| coef_with_sign(coef) + brackets(base) }
|
51
|
+
out << remainder(s.numeric)
|
52
|
+
out[0].sub!(/^\+/, '')
|
53
|
+
out.join
|
54
|
+
end
|
55
|
+
|
56
|
+
def remainder(n)
|
57
|
+
"#{'+' if n > 0}#{n unless n.zero?}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Variable
|
61
|
+
def variable(v)
|
62
|
+
"#{v.name || :unnamed_variable}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Function
|
66
|
+
def functionwrapper(f)
|
67
|
+
"#{f.name}(#{f.argument})"
|
68
|
+
end
|
69
|
+
|
70
|
+
def constant(c)
|
71
|
+
"#{c.name || :unnamed_variable}"
|
72
|
+
end
|
73
|
+
# Sums
|
74
|
+
def sum(s)
|
75
|
+
"Sum(#{s.term}, #{s.index} = #{s.lb}..#{s.ub})"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/symbolic/statistics.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module Symbolic
|
2
|
+
OPERATIONS = [:+, :-, :*, :/, :**, :-@]
|
2
3
|
def operations
|
3
4
|
formula = to_s
|
4
|
-
stats
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
OPERATIONS.inject({}) { |stats, op|
|
6
|
+
stats.merge({
|
7
|
+
op => formula.scan(
|
8
|
+
case op
|
9
|
+
when :- then /[^(]-/
|
10
|
+
when :* then /[^*]\*[^*]/
|
11
|
+
when :-@ then /\(-|^-/
|
12
|
+
else /#{Regexp.escape(op.to_s)}/
|
13
|
+
end
|
14
|
+
).size
|
15
|
+
})
|
16
|
+
}
|
12
17
|
end
|
13
18
|
end
|
data/lib/symbolic/sum.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Symbolic::Misc
|
2
|
+
=begin
|
3
|
+
blah
|
4
|
+
=end
|
5
|
+
class Sum
|
6
|
+
attr_reader :term, :index, :lb, :ub
|
7
|
+
include Symbolic
|
8
|
+
def initialize(term,index,lb,ub)
|
9
|
+
@term, @index, @lb, @ub = term, index, lb, ub
|
10
|
+
end
|
11
|
+
def Sum.[](term,index,lb,ub)
|
12
|
+
Symbolic::Sum.new(term,index,lb,ub)
|
13
|
+
end
|
14
|
+
def expand
|
15
|
+
(lb..ub).collect{|ind| @term.subs(@index,ind)}.inject{|m,t| m + t}
|
16
|
+
end
|
17
|
+
def variables
|
18
|
+
@term.variables.reject{|var| var == @index}
|
19
|
+
end
|
20
|
+
def diff(wrt)
|
21
|
+
#TODO: error if wrt is the index
|
22
|
+
if @term.diff(wrt) != 0
|
23
|
+
Sum.new(@term.diff(wrt),@index,@lb,@ub)
|
24
|
+
else
|
25
|
+
0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def variables
|
29
|
+
@term.variables.reject{|var| var == @index} #@index doesn't really count
|
30
|
+
end
|
31
|
+
def value
|
32
|
+
self.expand.value
|
33
|
+
end
|
34
|
+
def subs(to_replace, replacement=nil)
|
35
|
+
#TODO: error if to_replace is @index
|
36
|
+
if replacement == nil and to_replace.is_a?(Hash)
|
37
|
+
super(to_replace)
|
38
|
+
else
|
39
|
+
Sum.new(@term.subs(to_replace, replacement),@index,@lb,@ub)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/symbolic/summands.rb
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
# TODO: 2*symbolic is a 2 power of symbolic Summand
|
2
|
+
require "#{File.dirname(__FILE__)}/expression.rb"
|
2
3
|
module Symbolic
|
4
|
+
require "#{File.dirname(__FILE__)}/expression.rb"
|
3
5
|
class Summands < Expression
|
6
|
+
OPERATION = :+
|
7
|
+
IDENTITY = 0
|
4
8
|
class << self
|
5
|
-
def operation
|
6
|
-
'+'
|
7
|
-
end
|
8
|
-
|
9
|
-
def identity_element
|
10
|
-
0
|
11
|
-
end
|
12
|
-
|
13
9
|
def summands(summands)
|
14
10
|
summands
|
15
11
|
end
|
16
12
|
|
17
13
|
def factors(factors)
|
18
|
-
if factors.symbolic.length == 1 && factors.symbolic.
|
19
|
-
new
|
14
|
+
if factors.symbolic.length == 1 && factors.symbolic.first[1] == Factors::IDENTITY
|
15
|
+
new IDENTITY, factors.symbolic.first[0] => factors.numeric
|
20
16
|
else
|
21
|
-
new
|
17
|
+
new IDENTITY, Factors.new(1, factors.symbolic) => factors.numeric
|
22
18
|
end
|
23
19
|
end
|
24
20
|
|
@@ -27,41 +23,50 @@ module Symbolic
|
|
27
23
|
end
|
28
24
|
|
29
25
|
def simplify(numeric, symbolic)
|
30
|
-
if symbolic.empty?
|
26
|
+
if symbolic.empty? #only the numeric portion
|
31
27
|
numeric
|
32
|
-
elsif numeric ==
|
33
|
-
symbolic.
|
28
|
+
elsif numeric == IDENTITY && symbolic.size == 1 #no numeric to add and only one symbolic, so can just return the one base*coefficient
|
29
|
+
symbolic.first[1] * symbolic.first[0]
|
30
|
+
elsif symbolic.size > 1 #let's look to see if any base gets repeated, so that they can be combined
|
31
|
+
temp = []
|
32
|
+
symbolic.each_key do |base1|
|
33
|
+
temp = symbolic.find_all{|base2,v2| base1 == base2} #temp is an array of form [[b,c1],[b,c2]...]
|
34
|
+
break if temp.size > 1 #found a duplicate base
|
35
|
+
end
|
36
|
+
if temp.size > 1
|
37
|
+
repeated_base = temp[0][0]
|
38
|
+
new_coef = temp.inject(0){|sum, (b,coeff)| sum + coeff} #sum up the old coefficients
|
39
|
+
#it could be that there is more than one repeated base, but the next line effectively is recursion, and it'll take care of that
|
40
|
+
Summands.new(numeric, symbolic.reject{|k,v| k == repeated_base}) + new_coef * repeated_base
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
34
44
|
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
|
38
48
|
def value
|
39
|
-
if variables.all?
|
49
|
+
if variables.all?(&:value)
|
40
50
|
symbolic.inject(numeric) {|value, (base, coef)| value + base.value * coef.value }
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
44
|
-
def to_s
|
45
|
-
output = symbolic.map {|base, coef| coef_to_string(coef) + base.to_s }
|
46
|
-
output << remainder_to_string(numeric) if numeric != 0
|
47
|
-
output[0].sub!(/^\+/, '')
|
48
|
-
output.join
|
49
|
-
end
|
50
|
-
|
51
54
|
def reverse
|
52
|
-
self.class.new -numeric, Hash[*symbolic.map {|k,v| [k,-v]}.flatten]
|
55
|
+
self.class.new( -numeric, Hash[*symbolic.map {|k,v| [k,-v]}.flatten] )
|
53
56
|
end
|
54
|
-
|
55
|
-
|
56
|
-
"#{(coef > 0) ? '+' : '-' }#{ "#{rational_to_string(coef.abs)}*" if coef.abs != 1}"
|
57
|
+
def value
|
58
|
+
@symbolic.inject(@numeric){|m,(base,coefficient)| m + coefficient * base.value}
|
57
59
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
def subs(to_replace, replacement=nil)
|
61
|
+
if replacement == nil and to_replace.is_a?(Hash)
|
62
|
+
super(to_replace)
|
63
|
+
else
|
64
|
+
@symbolic.inject(@numeric){|m,(base,coefficient)| m + coefficient * base.subs(to_replace, replacement)}
|
65
|
+
end
|
61
66
|
end
|
62
67
|
|
63
|
-
def
|
64
|
-
((
|
68
|
+
def diff(wrt)
|
69
|
+
@symbolic.inject(0){|m,(base,coefficient)| m + coefficient * base.diff(wrt)}
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/symbolic/variable.rb
CHANGED
@@ -1,32 +1,44 @@
|
|
1
1
|
module Symbolic
|
2
2
|
=begin
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
This class is used to create symbolic variables.
|
4
|
+
Symbolic variables presented by name and value.
|
5
|
+
Name is neccessary for printing meaningful symbolic expressions.
|
6
|
+
Value is neccesary for calculation of symbolic expressions.
|
7
|
+
If value isn't set for variable, but there is an associated proc, then value is taken from evaluating the proc.
|
8
8
|
=end
|
9
9
|
class Variable
|
10
10
|
include Symbolic
|
11
11
|
attr_accessor :name, :proc
|
12
12
|
attr_writer :value
|
13
13
|
|
14
|
+
# Create a new Symbolic::Variable, with optional name, value and proc
|
14
15
|
def initialize(options={}, &proc)
|
15
|
-
@name, @value = options.values_at(:name, :value)
|
16
|
+
(@name, @value), @proc = options.values_at(:name, :value), proc
|
16
17
|
@name = @name.to_s if @name
|
17
|
-
@proc = proc
|
18
18
|
end
|
19
19
|
|
20
20
|
def value
|
21
21
|
@value || @proc && @proc.call.value
|
22
22
|
end
|
23
23
|
|
24
|
-
def to_s
|
25
|
-
@name || 'unnamed_variable'
|
26
|
-
end
|
27
|
-
|
28
24
|
def variables
|
29
25
|
[self]
|
30
26
|
end
|
27
|
+
|
28
|
+
def subs(to_replace, replacement=nil, expect_numeric = false)
|
29
|
+
if replacement == nil and to_replace.is_a?(Hash)
|
30
|
+
super(to_replace)
|
31
|
+
else
|
32
|
+
return replacement if self == to_replace
|
33
|
+
#Consider the possibility that @value is not numeric?
|
34
|
+
return self.value if expect_numeric
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def diff(wrt)
|
40
|
+
return 1 if self == wrt
|
41
|
+
0
|
42
|
+
end
|
31
43
|
end
|
32
44
|
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
require File.expand_path('../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe Symbolic do
|
5
|
+
describe "evaluation (x=1, y=2):" do
|
6
|
+
x = var :name => :x, :value => 1
|
7
|
+
y = var :name => :y, :value => 2
|
8
|
+
{
|
9
|
+
x => 1,
|
10
|
+
y => 2,
|
11
|
+
+x => 1,
|
12
|
+
-x => -1,
|
13
|
+
x + 4 => 5,
|
14
|
+
3 + x => 4,
|
15
|
+
x + y => 3,
|
16
|
+
x - 1 => 0,
|
17
|
+
1 - x => 0,
|
18
|
+
x - y => -1,
|
19
|
+
-x + 3 => 2,
|
20
|
+
-y - x => -3,
|
21
|
+
x*3 => 3,
|
22
|
+
4*y => 8,
|
23
|
+
(+x)*(-y) => -2,
|
24
|
+
x/2 => 0.5,
|
25
|
+
y/2 => 1,
|
26
|
+
-2/x => -2,
|
27
|
+
4/(-y) => -2,
|
28
|
+
x**2 => 1,
|
29
|
+
4**y => 16,
|
30
|
+
y**x => 2,
|
31
|
+
x-(y+x)/5 => Rational(2,5),
|
32
|
+
}.each_pair { |expr, value|
|
33
|
+
it expr do
|
34
|
+
expr.value.should == value
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "optimization:" do
|
40
|
+
x = var :name => :x
|
41
|
+
y = var :name => :y
|
42
|
+
{
|
43
|
+
-(-x) => x,
|
44
|
+
|
45
|
+
0 + x => x,
|
46
|
+
x + 0 => x,
|
47
|
+
x + (-2) => x - 2,
|
48
|
+
-2 + x => x - 2,
|
49
|
+
-x + 2 => 2 - x,
|
50
|
+
x + (-y) => x - y,
|
51
|
+
-y + x => x - y,
|
52
|
+
|
53
|
+
0 - x => -x,
|
54
|
+
x - 0 => x,
|
55
|
+
x - (-2) => x + 2,
|
56
|
+
-2 - (-x) => x - 2,
|
57
|
+
x - (-y) => x + y,
|
58
|
+
|
59
|
+
0 * x => 0,
|
60
|
+
x * 0 => 0,
|
61
|
+
1 * x => x,
|
62
|
+
x * 1 => x,
|
63
|
+
-1 * x => -x,
|
64
|
+
x * (-1) => -x,
|
65
|
+
x * (-3) => -(x*3),
|
66
|
+
-3 * x => -(x*3),
|
67
|
+
-3 * (-x) => x*3,
|
68
|
+
x*(-y) => -(x*y),
|
69
|
+
-x*y => -(x*y),
|
70
|
+
(-x)*(-y) => x*y,
|
71
|
+
|
72
|
+
0 / x => 0,
|
73
|
+
x / 1 => x,
|
74
|
+
|
75
|
+
0**x => 0,
|
76
|
+
1**x => 1,
|
77
|
+
x**0 => 1,
|
78
|
+
x**1 => x,
|
79
|
+
(-x)**1 => -x,
|
80
|
+
(-x)**2 => x**2,
|
81
|
+
(x**2)**y => x**(2*y),
|
82
|
+
|
83
|
+
x*4*x => 4*x**2,
|
84
|
+
x*(-1)*x**(-1) => -1,
|
85
|
+
x**2*(-1)*x**(-1) => -x,
|
86
|
+
x + y - x => y,
|
87
|
+
2*x + x**1 - y**2/y - y => 3*x - 2*y,
|
88
|
+
-(x+4) => -x-4,
|
89
|
+
|
90
|
+
(x/y)/(x/y) => 1,
|
91
|
+
(y/x)/(x/y) => y**2/x**2,
|
92
|
+
|
93
|
+
x - (y+x)/5 => 0.8*x-0.2*y,
|
94
|
+
}.each_pair { |non_optimized, optimized|
|
95
|
+
it non_optimized do
|
96
|
+
non_optimized.should == optimized
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'Variable methods:' do
|
102
|
+
let(:v) { var :name => :v }
|
103
|
+
let(:x) { var :name => :x, :value => 2 }
|
104
|
+
let(:y) { var :name => :y }
|
105
|
+
|
106
|
+
#initialize
|
107
|
+
it 'var(:name => :v)' do
|
108
|
+
v.name.should == 'v'
|
109
|
+
v.value.should be nil
|
110
|
+
end
|
111
|
+
it 'var.to_s == "unnamed_variable"' do
|
112
|
+
v.name = nil
|
113
|
+
v.to_s.should == 'unnamed_variable'
|
114
|
+
v.value.should be nil
|
115
|
+
end
|
116
|
+
it 'var init' do
|
117
|
+
x.value.should == 2
|
118
|
+
end
|
119
|
+
it 'proc value' do
|
120
|
+
y = var { x**2 }
|
121
|
+
x.value = 3
|
122
|
+
(x*y).value.should == 27
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'expression variables' do
|
126
|
+
x.variables.should == [x]
|
127
|
+
(-(x+y)).variables.should == [x,y]
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'operations' do
|
131
|
+
(-x**y-4*y+5-y/x).operations.should == {:+ => 1, :- => 2, :* => 1, :/ => 1, :-@ => 1, :** => 1}
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'math method' do
|
135
|
+
cos = Symbolic::Math.cos(x)
|
136
|
+
x.value = 0
|
137
|
+
cos.value.should == 1.0
|
138
|
+
cos.to_s.should == 'cos(x)'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "to_s:" do
|
143
|
+
x = var :name => :x
|
144
|
+
y = var :name => :y
|
145
|
+
{
|
146
|
+
x => 'x',
|
147
|
+
-x => '-x',
|
148
|
+
x+1 => 'x+1',
|
149
|
+
x-4 => 'x-4',
|
150
|
+
-x-4 => '-x-4',
|
151
|
+
-(x+y) => '-x-y',
|
152
|
+
-(x-y) => '-x+y',
|
153
|
+
x*y => 'x*y',
|
154
|
+
(-x)*y => '-x*y',
|
155
|
+
(y+3)*(x+2)*4 => '4*(y+3)*(x+2)',
|
156
|
+
4/x => '4/x',
|
157
|
+
2*x**(-1)*y**(-1) => '2/(x*y)',
|
158
|
+
(-(2+x))/(-(-y)) => '(-x-2)/y',
|
159
|
+
x**y => 'x**y',
|
160
|
+
x**(y-4) => 'x**(y-4)',
|
161
|
+
(x+1)**(y*2) => '(x+1)**(2*y)',
|
162
|
+
-(x**y-2)+5 => '-x**y+7',
|
163
|
+
x-(y+x)/5 => 'x-0.2*(y+x)',
|
164
|
+
}.each_pair { |expr, str|
|
165
|
+
it str do
|
166
|
+
expr.to_s.should == str
|
167
|
+
end
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "Symbolic plugins" do
|
173
|
+
if RUBY_VERSION > '1.9' # Not yet compatible with 1.8
|
174
|
+
describe "var_name" do
|
175
|
+
require "symbolic/plugins/var_name"
|
176
|
+
it 'single variable' do
|
177
|
+
x = var
|
178
|
+
x.name.should == 'x'
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'single variable with value' do
|
182
|
+
x = var :value => 7
|
183
|
+
x.name.should == 'x'
|
184
|
+
x.value.should == 7
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'multiple variables' do
|
188
|
+
x, yy, zzz = vars
|
189
|
+
[x, yy, zzz].map(&:name).should == ['x', 'yy', 'zzz']
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|