solver 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Javier Goizueta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ = solver
2
+
3
+ This numeric solver is an example of the use of Flt.
4
+
5
+ Copyright (c) 2010 Javier Goizueta. See LICENSE for details.
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "solver"
8
+ gem.summary = %Q{Numeric solver to exercise the Flt library}
9
+ gem.description = %Q{This numeric solver is an example of the use of Flt}
10
+ gem.email = "jgoizueta@gmail.com"
11
+ gem.homepage = "http://github.com/jgoizueta/solver"
12
+ gem.authors = ["Javier Goizueta"]
13
+ gem.add_development_dependency "shoulda"
14
+ gem.add_dependency "flt", ">= 1.3.0"
15
+ gem.required_ruby_version = '> 1.9.1'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.main = 'README.rdoc'
52
+ rdoc.title = "solver #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,12 @@
1
+ require 'flt/math'
2
+
3
+ module Flt::Solver
4
+ end
5
+
6
+ require 'solver/function'
7
+ require 'solver/base'
8
+ require 'solver/secant'
9
+ require 'solver/rfsecant'
10
+ # TODO: Crenshaw/Secant method
11
+ require 'solver/psolver'
12
+ # require 'solver/tvmsolver'
@@ -0,0 +1,108 @@
1
+ # TODO: add method to check for result: zero or sign reversal / extrema / inflection / asymptote
2
+ # TODO: automatic guesses if single guess or no guesses
3
+
4
+ # Base class for Fixed-Point Numeric Solvers
5
+ module Flt::Solver
6
+
7
+ class Base
8
+
9
+ # default_guesses: nil for no pre-guess, or one or two guesses (use array for two)
10
+ def initialize(context, default_guesses, tol, eqn=nil, &blk)
11
+ @context = context
12
+ @default_guesses = Array(default_guesses)
13
+ @x = @default_guesses.first
14
+ @f = eqn || blk
15
+ @tol = tol # user-requested tolerance
16
+ @l_x = nil
17
+ @fx = nil
18
+ @l_fx = nil
19
+ @ok = true
20
+ @conv = false
21
+ @max_it = 8192
22
+ end
23
+
24
+ # value of parameters[var] is used as a guess in precedence to the pre-guesses if not nil
25
+ # use Array for two guesses
26
+ def root(*guesses)
27
+ @guess = (guesses + @default_guesses).map{|g| num(g)}
28
+ @l_x = @x = @guess.first
29
+ @l_fx = @fx = eval_f(@x)
30
+ @ok = true
31
+ @conv = false
32
+
33
+ # Minimum tolerance of the numeric type used
34
+ @numeric_tol = Tolerance(1,:ulps) # Tolerance(@context.epsilon, :floating)
35
+
36
+ raise "Invalid parameters" unless validate
37
+
38
+ @reason = nil
39
+ @iteration = 0
40
+ # TODO: handle NaNs (stop or try to find other guess)
41
+ while @ok && @iteration < @max_it
42
+ next_x = step()
43
+ @l_x = @x
44
+ @l_fx = @fx
45
+ @x = next_x
46
+ @fx = eval_f(@x)
47
+ # puts "X=#{@x.inspect}[#{@fx.inspect}]"
48
+ @conv = test_conv() if @ok
49
+ break if @conv
50
+ @iteration += 1
51
+ end
52
+ @ok = false if @iteration >= @max_it # TODO: set reason
53
+ @x
54
+
55
+ end
56
+
57
+ def value
58
+ @fx
59
+ end
60
+
61
+ attr_reader :reason, :iteration
62
+
63
+ protected
64
+
65
+ def eval_f(x)
66
+ @context.math x, &@f
67
+ end
68
+
69
+ def num(v)
70
+ @context.Num(v)
71
+ end
72
+
73
+ def zero?
74
+ @tol.zero?(@fx)
75
+ end
76
+
77
+ def test_conv
78
+ #@tol.eq?(@x, @l_x) || @tol.eq?(@fx, @l_fx) || zero?
79
+ # puts "test #{@x} #{@fx}"
80
+ # if @tol.eq?(@fx, @l_fx) && !(@tol.eq?(@x, @l_x) || zero?)
81
+ # puts "---> v=#{@tol.relative_to_many(:max, @fx, @l_fx)} #{@fx} #{@l_fx} x=#{@x}"
82
+ # end
83
+
84
+ # zero? || @x==@l_x || @fx == @l_fx
85
+ #@numeric_tol.eq?(@x, @l_x) || zero? || @numeric_tol.eq?(@fx, @l_fx)
86
+
87
+ # TODO : use symbols for reason
88
+ if zero?
89
+ @reason = "Zero found #{@fx.inspect} @ #{@x.inspect}"
90
+ elsif @numeric_tol.eq?(@x, @l_x)
91
+ @reason = "Critical point" # Sign Reversal (@fx != @l_fx) or vertical tangent / asymptote
92
+ elsif @numeric_tol.eq?(@fx, @l_fx)
93
+ @reason = "Flat" # flat function
94
+ end
95
+ !@reason.nil?
96
+
97
+ # TODO: try to get out of flat; if @x==@l_x try to find other point?;
98
+
99
+ #zero?
100
+ end
101
+
102
+ def validate
103
+ true
104
+ end
105
+
106
+ end # Base
107
+
108
+ end # Flt::Solve
@@ -0,0 +1,99 @@
1
+ # Some utilities to work with functions defined by blocks or lambdas, and defining parameter values with
2
+ # hashes without the need to redundantly define the names of the parameters.
3
+ # Currently works only with Ruby >= 1.9.2 (it uses Proc#parameters).
4
+ #
5
+ # Examples:
6
+ # f = Function.with_named_parameters(){|x,y,z| puts "x=#{x} y=#{y} z=#{z}"}
7
+ # f[x:100, y:200, z:300] # => "x=100 y=200 z=300"
8
+ # f = Function.bind(:x=>1000,:z=>2000){|x,y,z| puts "x=#{x} y=#{y} z=#{z}"}
9
+ # f[5000] # => "x=1000 y=5000 z=2000"
10
+ # f[6000] # => "x=1000 y=6000 z=2000"
11
+ # Or, with Function[] syntax:
12
+ # f = lambda{|x,y,z| puts "x=#{x} y=#{y} z=#{z}"}
13
+ # Function[f][x:100, y:200, z:300] # => "x=100 y=200 z=300"
14
+ # f = Function[f, :x=>1000, :z=>2000]
15
+ # f[5000] # => "x=1000 y=5000 z=2000"
16
+ # f[6000] # => "x=1000 y=6000 z=2000"
17
+ #
18
+ module Flt::Solver::Function
19
+
20
+ # Names of the parameters of a functor (block, Proc, etc.) (including optional and 'rest' parameters)
21
+ # Function.parameters(lambda{|a,b,c|...}) => [:a, :b, :c]
22
+ # Function.parameters{|a,b,c|...} => [:a, :b, :c]
23
+ # Note that a block access through a &block variable is converted with Proc.new and this makes
24
+ # all arguments optional (or :rest). On the other hand if a lambda is passed as a block it retains the
25
+ # :req/:opt/attributes of its arguments.
26
+ def self.parameters(fun=nil, &blk)
27
+ fun = get(:parameters, fun, blk)
28
+ fun.parameters.select{|p,cls| cls!=:rest}.map{|p| p.last} # requires Ruby >= 1.9.2
29
+ end
30
+
31
+ # Returns a function by binding some of the arguments of a functor to values passed in a hash
32
+ # Function.bind(f=lambda{|a,b,c|...}, :a=>1,:c=>2) => lambda{|x| f[1,x,2]}
33
+ # Function.bind(:a=>1, :c=>2){|a,b,c|...} => lambda{|x| f[1,x,2]}
34
+ def self.bind(*args, &blk)
35
+ fun = args.shift unless args.first.kind_of?(Hash)
36
+ params = args.shift || {}
37
+ fun = get(:bind, fun, blk)
38
+ fun_parameters = parameters(fun)
39
+ fun_args = fun_parameters - params.keys
40
+ lambda do |*args|
41
+ fun[*params.merge(Hash[fun_args.zip(args)]).values_at(*fun_parameters)]
42
+ end
43
+ end
44
+
45
+ # Returns a function that takes parameters from a hash
46
+ # Function.with_named_parameters(f=lambda{|a,b,c|...}) => lambda{|params| f[params[:a], params[:b], params[:c]]}
47
+ # Function.with_named_parameters{|a,b,c|...} => lambda{|params| f[params[:a], params[:b], params[:c]]}
48
+ def self.with_named_parameters(*args, &blk)
49
+ fun = args.shift unless args.first.kind_of?(Hash)
50
+ fun = get(:with_named_parameters, fun, blk)
51
+ lambda do |parameters|
52
+ fun[*parameters.values_at(*parameters(fun))]
53
+ end
54
+ end
55
+
56
+ # Shortcut for Function.bind or Function.with_named_parameters
57
+ # Function[f=lambda{|a,b,c|...}, :a=>1,:c=>2] => lambda{|x| f[1,x,2]}
58
+ # Function[:a=>1, :c=>2, &lambda{|a,b,c|...}] => lambda{|x| f[1,x,2]}
59
+ # Function[f=lambda{|a,b,c|...}] => lambda{|params| f[params[:a], params[:b], params[:c]]}
60
+ # Function[&lambda{|a,b,c|...}] => lambda{|params| f[params[:a], params[:b], params[:c]]}
61
+ # Note that this syntax is not admitted by Ruby:
62
+ # Function[:a=>1, :c=>2]{|a,b,c|...}
63
+ # Function[]{|a,b,c|...}
64
+ def self.[](*args, &blk)
65
+ fun = args.shift unless args.first.kind_of?(Hash)
66
+ parameters = args.shift
67
+ if parameters.nil?
68
+ with_named_parameters(fun, &blk)
69
+ else
70
+ bind(fun, parameters, &blk)
71
+ end
72
+ end
73
+
74
+ # # alternative implementation
75
+ # def self.bind(fun, parameters, &blk)
76
+ # fun = get(:bind, fun, blk)
77
+ # fun_parameters = parameters(fun)
78
+ # fun_args = (fun_parameters - parameters.keys).map{|arg| fun_parameters.index(arg)}
79
+ # parameters = fun_parameters.map{|p| parameters[p]}
80
+ # lambda do |*args|
81
+ # fun_args.each_with_index do |arg_pos, i|
82
+ # parameters[arg_pos] = args[i]
83
+ # end
84
+ # fun[*parameters]
85
+ # end
86
+ # end
87
+
88
+
89
+ class <<self
90
+ private
91
+ def get(mth, fun, blk)
92
+ raise "Must pass either a proc/lambda or a block to #{mth}" unless fun || blk
93
+ raise "Must pass only one of a proc/lambda or a block to #{mth}" if fun && blk
94
+ fun || blk
95
+ end
96
+ end
97
+
98
+ end
99
+
@@ -0,0 +1,70 @@
1
+ module Flt::Solver
2
+
3
+ # Solver with equation parameters passed in a hash
4
+ #
5
+ # Example: a simple TVM (Time Value of Money) solver
6
+ #
7
+ # # ln(x+1)
8
+ # def lnp1(x)
9
+ # v = x + 1
10
+ # (v == 1) ? x : (x*ln(v) / (v - 1))
11
+ # end
12
+ #
13
+ # tvm = Flt::Solver::PSolver.new(Float.context, Flt.Tolerance(3,:decimals)) do |m, t, m0, pmt, i, p|
14
+ # i /= 100
15
+ # i /= p
16
+ # n = -t
17
+ # k = exp(lnp1(i)*n) # (i+1)**n
18
+ # # Equation: -m*k = m0 + pmt*(1-k)/i
19
+ # m0 + pmt*(Num(1)-k)/i + m*k
20
+ # end
21
+ # tvm.default_guesses = 1,2
22
+ # sol = tvm.root :pmt, :t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12 #, :pmt=>[1,2]
23
+ # puts sol.inspect # => -55.45975978539105
24
+ #
25
+ class PSolver
26
+
27
+ def initialize(context, tol, solver_class=nil, &blk)
28
+
29
+ @solver_class = solver_class || RFSecantSolver
30
+ @eqn = blk
31
+ @vars = Function.parameters(@eqn)
32
+ # alternatively, we could keep @eqn = Function[@eqn] and dispense with @vars
33
+
34
+ @default_guesses = nil
35
+
36
+ @context = context
37
+ @tol = tol
38
+ @solver = nil
39
+
40
+ end
41
+
42
+ def default_guesses=(*g)
43
+ @default_guesses = g
44
+ @solver = nil
45
+ end
46
+
47
+ def root(var, parameters)
48
+ init_solver
49
+ @var = var
50
+ @parameters = parameters
51
+ guesses = Array(parameters[var])
52
+ @solver.root *guesses
53
+ end
54
+
55
+ def equation_value(v)
56
+ values = @parameters.merge(@var=>v)
57
+ #@context.math(*values.values_at(*@vars).map{|v| @context.Num(v)}, &@eqn)
58
+ @context.math(*@vars.map{|v| @context.Num(values[v])}, &@eqn)
59
+ # equivalent to: @context.math(values, &Function[@eqn]) # which doesn't need @vars
60
+ end
61
+
62
+ private
63
+ def init_solver
64
+ this = self
65
+ @solver ||= @solver_class.new(@context, @default_guesses, @tol){|v| this.equation_value(v)}
66
+ end
67
+
68
+ end
69
+
70
+ end # Flt::PSolver
@@ -0,0 +1,84 @@
1
+ module Flt::Solver
2
+
3
+ # Regula-Falsi/Secant method solver
4
+ # Secant is used if no bracketing is available
5
+ #
6
+ # Example of use:
7
+ # require 'solver'
8
+ # include Flt
9
+ # solver = Solver::SecantSolver.new(Float.context, [0.0, 100.0], Tolerance(3, :decimals)) do |x|
10
+ # 2*x+11.0
11
+ # end
12
+ # puts solver.root
13
+ # # with a guess:
14
+ # puts solver.root(5.0)
15
+ #
16
+ # solver = SecantSolver.new(Float.context, [0.0, 10.0], Tolerance(3, :decimals)) do |x|
17
+ # y = 2
18
+ # y*exp(x)-10
19
+ # end
20
+ # puts solver.root
21
+ #
22
+ class RFSecantSolver < Base
23
+
24
+ def initialize(context, default_guesses, tol, eqn=nil, &blk)
25
+ super context, default_guesses, tol, eqn, &blk
26
+ @a = @b = @fa = @fb = nil
27
+ @bracketing = false
28
+ @half = num(Rational(1,2))
29
+ end
30
+
31
+ def step
32
+ return @guess[1] if @iteration == 0
33
+ regula_falsi = false
34
+ dy = @fx - @l_fx
35
+
36
+ if @tol.zero?(dy)
37
+ if @bracketing
38
+ regula_falsi = true
39
+ else
40
+ @ok = false
41
+ return @x
42
+ end
43
+ end
44
+
45
+ if !regula_falsi
46
+ next_x = @x - ((@x - @l_x)*@fx)/dy
47
+ regula_falsi = true if @bracketing && (next_x < @a || next_x > @b)
48
+ end
49
+ next_x = @b - (@b - @a)*@fb/(@fb - @fa) if regula_falsi
50
+ next_fx = eval_f(next_x)
51
+
52
+ if @bracketing
53
+ if @context.sign(@fa) == @context.sign(next_fx)
54
+ @a = next_x
55
+ @fa = next_fx
56
+ else
57
+ @b = next_x
58
+ @fb = next_fx
59
+ end
60
+ else
61
+ if @context.sign(next_fx) != @context.sign(@fx)
62
+ @a, @b = @x, next_x
63
+ @a, @b = @b, @a if @a > @b
64
+ @fa = eval_f(@a)
65
+ @fb = eval_f(@b)
66
+ @bracketing = true
67
+ end
68
+ end
69
+ # puts "br: #{@bracketing} r-f: #{regula_falsi} x:#{@x}[#{@fx}] l_x:#{@l_x}[#{@l_fx}] #{next_x}"
70
+ next_x
71
+ end
72
+
73
+ def validate
74
+ @guess = @guess.uniq
75
+ if @guess.size < 2
76
+ return false if @guess.empty?
77
+ @guess << (@guess.first + 1)
78
+ end
79
+ true
80
+ end
81
+
82
+ end # RFSecantSolver
83
+
84
+ end # Flt::Solver
@@ -0,0 +1,84 @@
1
+ module Flt::Solver
2
+
3
+ # Secant method solver
4
+ # Bisect method is used is bracketing found (sign(f(a)) != sign(f(b)))
5
+ #
6
+ # Example of use:
7
+ # require 'solver'
8
+ # require 'flt/tolerance'
9
+ # include Flt
10
+ # solver = Solver::SecantSolver.new(Float.context, [0.0, 100.0], Tolerance(3, :decimals)) do |x|
11
+ # 2*x+11.0
12
+ # end
13
+ # puts solver.root
14
+ # # with a guess:
15
+ # puts solver.root(5.0)
16
+ #
17
+ # solver = SecantSolver.new(Float.context, [0.0, 10.0], Tolerance(3, :decimals)) do |x|
18
+ # y = 2
19
+ # y*exp(x)-10
20
+ # end
21
+ # puts solver.root
22
+ #
23
+ class SecantSolver < Base
24
+
25
+ def initialize(context, default_guesses, tol, eqn=nil, &blk)
26
+ super context, default_guesses, tol, eqn, &blk
27
+ @a = @b = @fa = @fb = nil
28
+ @bracketing = false
29
+ @half = num(Rational(1,2))
30
+ end
31
+
32
+ def step
33
+ return @guess[1] if @iteration == 0
34
+ bisect = false
35
+ dy = @fx - @l_fx
36
+
37
+ if @tol.zero?(dy)
38
+ if @bracketing
39
+ bisect = true
40
+ else
41
+ @ok = false
42
+ return @x
43
+ end
44
+ end
45
+
46
+ if !bisect
47
+ next_x = @x - ((@x - @l_x)*@fx)/dy
48
+ bisect = true if @bracketing && (next_x < @a || next_x > @b)
49
+ end
50
+ next_x = (@a + @b)*@half if bisect
51
+ next_fx = eval_f(next_x)
52
+
53
+ if @bracketing
54
+ if @context.sign(@fa) == @context.sign(next_fx)
55
+ @a = next_x
56
+ @fa = next_fx
57
+ else
58
+ @b = next_x
59
+ @fb = next_fx
60
+ end
61
+ else
62
+ if @context.sign(next_fx) != @context.sign(@fx)
63
+ @a, @b = @x, next_x
64
+ @a, @b = @b, @a if @a > @b
65
+ @fa = eval_f(@a)
66
+ @fb = eval_f(@b)
67
+ @bracketing = true
68
+ end
69
+ end
70
+ next_x
71
+ end
72
+
73
+ def validate
74
+ @guess = @guess.uniq
75
+ if @guess.size < 2
76
+ return false if @guess.empty?
77
+ @guess << (@guess.first + 1)
78
+ end
79
+ true
80
+ end
81
+
82
+ end # SecantSolver
83
+
84
+ end # Flt::Solver
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{solver}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Javier Goizueta"]
12
+ s.date = %q{2010-06-29}
13
+ s.description = %q{This numeric solver is an example of the use of Flt}
14
+ s.email = %q{jgoizueta@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/solver.rb",
27
+ "lib/solver/base.rb",
28
+ "lib/solver/function.rb",
29
+ "lib/solver/psolver.rb",
30
+ "lib/solver/rfsecant.rb",
31
+ "lib/solver/secant.rb",
32
+ "solver.gemspec",
33
+ "test/helper.rb",
34
+ "test/test_function.rb",
35
+ "test/test_psolver.rb",
36
+ "test/test_rfsecant.rb",
37
+ "test/test_secant.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/jgoizueta/solver}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.required_ruby_version = Gem::Requirement.new("> 1.9.1")
43
+ s.rubygems_version = %q{1.3.7}
44
+ s.summary = %q{Numeric solver to exercise the Flt library}
45
+ s.test_files = [
46
+ "test/helper.rb",
47
+ "test/test_function.rb",
48
+ "test/test_psolver.rb",
49
+ "test/test_rfsecant.rb",
50
+ "test/test_secant.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
59
+ s.add_runtime_dependency(%q<flt>, [">= 1.3.0"])
60
+ else
61
+ s.add_dependency(%q<shoulda>, [">= 0"])
62
+ s.add_dependency(%q<flt>, [">= 1.3.0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<shoulda>, [">= 0"])
66
+ s.add_dependency(%q<flt>, [">= 1.3.0"])
67
+ end
68
+ end
69
+
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'solver'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,26 @@
1
+ require 'helper'
2
+
3
+ class TestFunction < Test::Unit::TestCase
4
+
5
+ should "convert function to use a hash for parameters" do
6
+ f = Flt::Solver::Function.with_named_parameters(){|x,y,z| "x=#{x} y=#{y} z=#{z}"}
7
+ assert_equal "x=100 y=200 z=300", f[:x=>100, :y=>200, :z=>300]
8
+ assert_equal "x=1 y=2 z=3", f[:x=>1, :y=>2, :z=>3]
9
+
10
+ f = lambda{|x,y,z| "x=#{x} y=#{y} z=#{z}"}
11
+ g = Flt::Solver::Function[f]
12
+ assert_equal "x=100 y=200 z=300", g[:x=>100, :y=>200, :z=>300]
13
+ end
14
+
15
+ should "bind some of a function's parameters" do
16
+ f = Flt::Solver::Function.bind(:x=>1000,:z=>2000){|x,y,z| "x=#{x} y=#{y} z=#{z}"}
17
+ assert_equal "x=1000 y=5000 z=2000", f[5000]
18
+ assert_equal "x=1000 y=6000 z=2000", f[6000]
19
+
20
+ f = lambda{|x,y,z| "x=#{x} y=#{y} z=#{z}"}
21
+ f = Flt::Solver::Function[f, :x=>1000, :z=>2000]
22
+ assert_equal "x=1000 y=5000 z=2000", f[5000]
23
+ assert_equal "x=1000 y=6000 z=2000", f[6000]
24
+ end
25
+
26
+ end
@@ -0,0 +1,55 @@
1
+ require 'helper'
2
+ require 'flt/float'
3
+ require 'flt/tolerance'
4
+
5
+ include Flt
6
+
7
+ class TestPSolver < Test::Unit::TestCase
8
+
9
+ context "The PSolver class" do
10
+
11
+ context "using Float arithmetic" do
12
+
13
+ setup do
14
+ @context = Float.context
15
+ @tolerance = Flt.Tolerance(3,:decimals)
16
+ @delta = 5E-4
17
+ end
18
+
19
+ context "with a TVM-equation definition" do
20
+
21
+ setup do
22
+ @context.class.class_eval do
23
+ define_method :lnp1 do |x|
24
+ v = x + 1
25
+ (v == 1) ? x : (x*ln(v) / (v - 1))
26
+ end
27
+ end
28
+ @tvm = Flt::Solver::PSolver.new(@context, @tolerance) do |m, t, m0, pmt, i, p|
29
+ i /= 100
30
+ i /= p
31
+ n = -t
32
+ k = exp(lnp1(i)*n) # (i+1)**n
33
+ # Equation: -m*k = m0 + pmt*(1-k)/i
34
+ m0 + pmt*(Num(1)-k)/i + m*k
35
+ end
36
+ @tvm.default_guesses = 1,2
37
+ end
38
+
39
+ should "solve for :pmt correctly" do
40
+ solution = @tvm.root :pmt, :t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12 #, :pmt=>[1,2]
41
+ assert_in_delta @context.Num('-55.45975978539105'), solution, @delta
42
+ end
43
+
44
+ should "solve for :t correctly" do
45
+ solution = @tvm.root :t, :pmt=>@context.Num('-55.45975978539105'), :m0=>10000, :m=>0, :i=>3, :p=>12 #, :pmt=>[1,2]
46
+ assert_in_delta 240, solution, @delta
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,66 @@
1
+ require 'helper'
2
+ require 'flt/float'
3
+ require 'flt/tolerance'
4
+
5
+ include Flt
6
+
7
+ class TestRFSecant < Test::Unit::TestCase
8
+
9
+ context "The Regula-Falsi/Secant Solver" do
10
+
11
+ context "using Float arithmetic" do
12
+
13
+ setup do
14
+ @context = Float.context
15
+ @tolerance = Flt.Tolerance(3,:decimals)
16
+ @delta = 5E-4
17
+ end
18
+
19
+ should "solve equations" do
20
+ solver = Flt::Solver::RFSecantSolver.new(@context, [0.0, 100.0], @tolerance) do |x|
21
+ 2*x+11.0
22
+ end
23
+ assert_in_delta -5.5, solver.root, @delta
24
+ assert_in_delta -5.5, solver.root(5.0), @delta
25
+ assert_in_delta -5.5, solver.root(6.0), @delta
26
+
27
+ solver = Flt::Solver::SecantSolver.new(@context, [0.0, 10.0], @tolerance) do |x|
28
+ y = 2
29
+ y*exp(x)-10
30
+ end
31
+ assert_in_delta 1.6094389956808506, solver.root, @delta
32
+ assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
33
+ assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
34
+ end
35
+ end
36
+
37
+ context "using DecNum arithmetic" do
38
+
39
+ setup do
40
+ @context = Flt::DecNum.context(:precision=>12)
41
+ @tolerance = Flt.Tolerance(5,:decimals)
42
+ @delta = @tolerance.value
43
+ end
44
+
45
+ should "solve equations" do
46
+ solver = Flt::Solver::RFSecantSolver.new(@context, [0, 100].map{|v| @context.Num(v)}, @tolerance) do |x|
47
+ 2*x+11
48
+ end
49
+ assert_in_delta @context.Num('-5.5'), solver.root, @delta
50
+ assert_in_delta @context.Num('-5.5'), solver.root(5), @delta
51
+ assert_in_delta @context.Num('-5.5'), solver.root(6), @delta
52
+
53
+ solver = Flt::Solver::SecantSolver.new(@context, [0, 10].map{|v| @context.Num(v)}, @tolerance) do |x|
54
+ y = 2
55
+ y*exp(x)-10
56
+ end
57
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root, @delta
58
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root(1), @delta
59
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root(2), @delta
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,66 @@
1
+ require 'helper'
2
+ require 'flt/float'
3
+ require 'flt/tolerance'
4
+
5
+ include Flt
6
+
7
+ class TestSecant < Test::Unit::TestCase
8
+
9
+ context "The Secant Solver" do
10
+
11
+ context "using Float arithmetic" do
12
+
13
+ setup do
14
+ @context = Float.context
15
+ @tolerance = Flt.Tolerance(3,:decimals)
16
+ @delta = 5E-4
17
+ end
18
+
19
+ should "solve equations" do
20
+ solver = Flt::Solver::SecantSolver.new(@context, [0.0, 100.0], @tolerance) do |x|
21
+ 2*x+11.0
22
+ end
23
+ assert_in_delta -5.5, solver.root, @delta
24
+ assert_in_delta -5.5, solver.root(5.0), @delta
25
+ assert_in_delta -5.5, solver.root(6.0), @delta
26
+
27
+ solver = Flt::Solver::SecantSolver.new(@context, [0.0, 10.0], @tolerance) do |x|
28
+ y = 2
29
+ y*exp(x)-10
30
+ end
31
+ assert_in_delta 1.6094389956808506, solver.root, @delta
32
+ assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
33
+ assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
34
+ end
35
+ end
36
+
37
+ context "using DecNum arithmetic" do
38
+
39
+ setup do
40
+ @context = Flt::DecNum.context(:precision=>12)
41
+ @tolerance = Flt.Tolerance(5,:decimals)
42
+ @delta = @tolerance.value
43
+ end
44
+
45
+ should "solve equations" do
46
+ solver = Flt::Solver::SecantSolver.new(@context, [0, 100].map{|v| @context.Num(v)}, @tolerance) do |x|
47
+ 2*x+11
48
+ end
49
+ assert_in_delta @context.Num('-5.5'), solver.root, @delta
50
+ assert_in_delta @context.Num('-5.5'), solver.root(5), @delta
51
+ assert_in_delta @context.Num('-5.5'), solver.root(6), @delta
52
+
53
+ solver = Flt::Solver::SecantSolver.new(@context, [0, 10].map{|v| @context.Num(v)}, @tolerance) do |x|
54
+ y = 2
55
+ y*exp(x)-10
56
+ end
57
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root, @delta
58
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root(1), @delta
59
+ assert_in_delta @context.Num('1.6094389956808506'), solver.root(2), @delta
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solver
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Javier Goizueta
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-29 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: flt
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 3
44
+ - 0
45
+ version: 1.3.0
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: This numeric solver is an example of the use of Flt
49
+ email: jgoizueta@gmail.com
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - LICENSE
56
+ - README.rdoc
57
+ files:
58
+ - .document
59
+ - .gitignore
60
+ - LICENSE
61
+ - README.rdoc
62
+ - Rakefile
63
+ - VERSION
64
+ - lib/solver.rb
65
+ - lib/solver/base.rb
66
+ - lib/solver/function.rb
67
+ - lib/solver/psolver.rb
68
+ - lib/solver/rfsecant.rb
69
+ - lib/solver/secant.rb
70
+ - solver.gemspec
71
+ - test/helper.rb
72
+ - test/test_function.rb
73
+ - test/test_psolver.rb
74
+ - test/test_rfsecant.rb
75
+ - test/test_secant.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/jgoizueta/solver
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">"
89
+ - !ruby/object:Gem::Version
90
+ segments:
91
+ - 1
92
+ - 9
93
+ - 1
94
+ version: 1.9.1
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project:
106
+ rubygems_version: 1.3.7
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Numeric solver to exercise the Flt library
110
+ test_files:
111
+ - test/helper.rb
112
+ - test/test_function.rb
113
+ - test/test_psolver.rb
114
+ - test/test_rfsecant.rb
115
+ - test/test_secant.rb