solver 0.0.0

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