solver 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +5 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/lib/solver.rb +12 -0
- data/lib/solver/base.rb +108 -0
- data/lib/solver/function.rb +99 -0
- data/lib/solver/psolver.rb +70 -0
- data/lib/solver/rfsecant.rb +84 -0
- data/lib/solver/secant.rb +84 -0
- data/solver.gemspec +69 -0
- data/test/helper.rb +10 -0
- data/test/test_function.rb +26 -0
- data/test/test_psolver.rb +55 -0
- data/test/test_rfsecant.rb +66 -0
- data/test/test_secant.rb +66 -0
- metadata +115 -0
data/.document
ADDED
data/.gitignore
ADDED
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.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -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
|
data/lib/solver.rb
ADDED
data/lib/solver/base.rb
ADDED
@@ -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
|
data/solver.gemspec
ADDED
@@ -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
|
+
|
data/test/helper.rb
ADDED
@@ -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
|
data/test/test_secant.rb
ADDED
@@ -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
|