solver 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7e3863747205d299960de14f4f6fb574d435661
4
- data.tar.gz: 5d6eacf431b01313d025970c61b0d1dac0a16b13
3
+ metadata.gz: 494437a765172c4f950dc9f9b0d802b865bca747
4
+ data.tar.gz: 9f8454f7e3891a57cadd13eaba761cf4e13aeaf9
5
5
  SHA512:
6
- metadata.gz: a0d8c29f097fee10546696c1ae8d992286dd96e1c892fec7acdb56a069660bd5c0b539ef04cbdb42e0a6d5c5fe43ca24c0792866604162ae170a6a4aed0c9ed8
7
- data.tar.gz: 3778ce82d42956f7df0c02959e5e20264e6095881e480d617917ad53ff9e24d1f7491e4c54597936b99cb9adfc8f9cbffcba749a1834efda11abd64addb74d7f
6
+ metadata.gz: 9f152b61a7089fb97991f7624d97814f27a149b964f717e54ff397a942441beb0302d8dde8320015ee7445407ffaf6afbd3b45810a1f042bf0272372b3dc1f9b
7
+ data.tar.gz: 94b62848a1e693e6b2a51d55f8a29bf3c6835ccdc60ef131cc50cb537cbf07f3ae285e5c9ed875be7d9632456c77b2ecce38d6c7124be29438d27393231bdd0a
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # solver
2
+
3
+ This numeric solver is an example of the use of Flt.
4
+
5
+ ## Examples
6
+
7
+ Solve equation `2*exp(x)-10=0` using Float, with a tolerance of 10 decimal places,
8
+ using the `SecantSolver` algorithm and with initial guesses of 0, 10:
9
+
10
+ ```ruby
11
+ require 'solver'
12
+ equation = ->(x){ 2*exp(x)-10 }
13
+ algorithm = Flt::Solver::SecantSolver
14
+ tolerance = Flt::Tolerance(10, :decimals)
15
+ solver = algorithm.new(Float.context, tolerance, &equation)
16
+ puts solver.root(0, 10) # => 1.6094379124341003
17
+ ```
18
+
19
+ Now, solve the same equation, with the same algorithm, but requiring greater
20
+ accuracy and using a higher precision numeric type:
21
+
22
+ ```ruby
23
+ tolerance = Flt::Tolerance(25, :decimals)
24
+ solver = algorithm.new(Flt::DecNum.context(precision: 30), tolerance, &equation)
25
+ puts solver.root(0, 10) # => DecNum('1.609437912434100374600759333')
26
+ ```
27
+
28
+ Let's compute the same using a different algorithm:
29
+
30
+ ```ruby
31
+ algorithm = Flt::Solver::RFSecantSolver
32
+ solver = algorithm.new(Flt::DecNum.context(precision: 30), tolerance, &equation)
33
+ puts solver.root(0, 10) # => DecNum('1.609437912434100374600759333')
34
+ ```
35
+
36
+ The `PSolver` class can be used to define an equation with multiple parameters, then
37
+ solve for any of them.
38
+
39
+ Let's define an equation to compute the *Time Value of Money* (i.e. compound interest):
40
+
41
+ ```ruby
42
+ tvm_equation = ->(future_value, time, present_value, payment, interest, payments_per_year) {
43
+ i = interest/100/payments_per_year
44
+ n = -time
45
+ k = (i + 1)**n # not the right way to compute it!
46
+ present_value + payment*(1-k)/i + future_value*k
47
+ }
48
+ ```
49
+
50
+ Note that computing `(i + 1)**n` as in this example is not a good idea, see the `TVM`
51
+ class for a better approach.
52
+
53
+ Now let's use `PSolver` to solve this equation for any of its paramters:
54
+
55
+ ```ruby
56
+ tvm_solver = Flt::Solver::PSolver.new(
57
+ Flt::DecNum.context,
58
+ Flt.Tolerance(2,:decimals),
59
+ &tvm_equation
60
+ )
61
+ ```
62
+
63
+ For example, let's compute the monthly payment of a loan of $150,000, to be paid in twenty years
64
+ with a 7% interest rate:
65
+
66
+ ```ruby
67
+ pmt = tvm_solver.root(
68
+ :payment,
69
+ present_value: 150000,
70
+ future_value: 0,
71
+ time: 20*12,
72
+ interest: 7,
73
+ payments_per_year: 12
74
+ )
75
+ puts s.round(2) # => -1162.95
76
+ ```
77
+
78
+ A better (more accurate) financial solver is implemented by the `TVM` class:
79
+
80
+ ```ruby
81
+ tvm = Flt::Solver::TVM.new(Flt.Tolerance(2,:decimals), Flt::DecNum.context)
82
+ solution = tvm.solve(t: 20*12, m0: 150000, m: 0, i: 7, p: 12)
83
+ puts solution[:pmt].round(2) # => -1162.95
84
+ ```
85
+
86
+ # Licensing
87
+
88
+ Copyright (c) 2010 Javier Goizueta. See LICENSE for details.
data/lib/solver.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'flt/math'
2
+ require 'flt/tolerance'
2
3
 
3
4
  require 'solver/version'
4
5
  require 'solver/function'
data/lib/solver/base.rb CHANGED
@@ -6,13 +6,27 @@ module Flt::Solver
6
6
 
7
7
  class Base
8
8
 
9
+ # Admitted options:
10
+ #
11
+ # * :context : Numerical context (e.g. Flt::Decimal::Context) Float by default
12
+ # * :default_guesses : default initial guesses
13
+ # * :tolerance : numerical tolerance
14
+ # * :equation : equation to be solved; can also be passed as a block
15
+ #
9
16
  # 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)
17
+ #
18
+ # The values of any of the parameters can also be passed as arguments
19
+ # (not in the options Hash, if present) in any order, e.g.:
20
+ #
21
+ # Solver::Base.new Flt::DecNum.context, tolerance: Flt::Tolerance(3, :decimals)
22
+ #
23
+ def initialize(*args, &blk)
24
+ options = Base.extract_options(*args, &blk)
25
+ @context = options[:context] || Float.context
26
+ @default_guesses = Array(options[:default_guesses])
13
27
  @x = @default_guesses.first
14
- @f = eqn || blk
15
- @tol = tol # user-requested tolerance
28
+ @f = options[:equation]
29
+ @tol = options[:tolerance] # user-requested tolerance
16
30
  @max_it = 8192
17
31
  reset
18
32
  end
@@ -25,8 +39,6 @@ class Base
25
39
  @conv = false
26
40
  end
27
41
 
28
- # value of parameters[var] is used as a guess in precedence to the pre-guesses if not nil
29
- # use Array for two guesses
30
42
  def root(*guesses)
31
43
  @guess = (guesses + @default_guesses).map{|g| num(g)}
32
44
  reset
@@ -36,7 +48,7 @@ class Base
36
48
  @conv = false
37
49
 
38
50
  # Minimum tolerance of the numeric type used
39
- @numeric_tol = Tolerance(1,:ulps) # Tolerance(@context.epsilon, :floating)
51
+ @numeric_tol = Flt::Tolerance(1,:ulps) # Tolerance(@context.epsilon, :floating)
40
52
 
41
53
  raise "Invalid parameters" unless validate
42
54
 
@@ -107,6 +119,30 @@ class Base
107
119
  true
108
120
  end
109
121
 
122
+ def self.extract_options(*args, &blk)
123
+ options = {}
124
+ args.each do |arg|
125
+ case arg
126
+ when Hash
127
+ options.merge! arg
128
+ when Flt::Num::ContextBase, Flt::FloatContext
129
+ options.merge! context: arg
130
+ when Array, Numeric
131
+ options.merge! default_guesses: Array(arg)
132
+ when Proc
133
+ options.merge! equation: arg
134
+ when Flt::Tolerance
135
+ options.merge! tolerance: arg
136
+ when Base
137
+ options.merge! solver_class: arg
138
+ else
139
+ raise "Invalid Argument #{arg.inspect}"
140
+ end
141
+ end
142
+ options[:equation] ||= blk
143
+ options
144
+ end
145
+
110
146
  end # Base
111
147
 
112
148
  end # Flt::Solve
@@ -1,7 +1,7 @@
1
1
  module Flt::Solver
2
-
2
+
3
3
  # Solver with equation parameters passed in a hash
4
- #
4
+ #
5
5
  # Example: a simple TVM (Time Value of Money) solver
6
6
  #
7
7
  # # ln(x+1)
@@ -9,7 +9,7 @@ module Flt::Solver
9
9
  # v = x + 1
10
10
  # (v == 1) ? x : (x*ln(v) / (v - 1))
11
11
  # end
12
- #
12
+ #
13
13
  # tvm = Flt::Solver::PSolver.new(Float.context, Flt.Tolerance(3,:decimals)) do |m, t, m0, pmt, i, p|
14
14
  # i /= 100
15
15
  # i /= p
@@ -23,20 +23,20 @@ module Flt::Solver
23
23
  # puts sol.inspect # => -55.45975978539105
24
24
  #
25
25
  class PSolver
26
-
27
- def initialize(context, tol, solver_class=nil, &blk)
28
26
 
29
- @solver_class = solver_class || RFSecantSolver
30
- @eqn = blk
27
+ def initialize(*args, &blk)
28
+ options = Base.extract_options(*args, &blk)
29
+
30
+ @solver_class = options[:solver_class] || RFSecantSolver
31
+ @eqn = options[:equation]
31
32
  @vars = Function.parameters(@eqn)
32
33
  # alternatively, we could keep @eqn = Function[@eqn] and dispense with @vars
33
34
 
34
- @default_guesses = nil
35
+ @default_guesses = options[:default_guesses]
35
36
 
36
- @context = context
37
- @tol = tol
37
+ @context = options[:context]
38
+ @tol = options[:tolerance]
38
39
  @solver = nil
39
-
40
40
  end
41
41
 
42
42
  def default_guesses=(*g)
@@ -55,16 +55,20 @@ module Flt::Solver
55
55
  def equation_value(v)
56
56
  values = @parameters.merge(@var=>v)
57
57
  #@context.math(*values.values_at(*@vars).map{|v| @context.Num(v)}, &@eqn)
58
- @context.math(*@vars.map{|v| @context.Num(values[v])}, &@eqn)
58
+ @context.math(*@vars.map{|v| values[v] && @context.Num(values[v])}.compact, &@eqn)
59
59
  # equivalent to: @context.math(values, &Function[@eqn]) # which doesn't need @vars
60
60
  end
61
61
 
62
62
  private
63
63
  def init_solver
64
64
  this = self
65
- @solver ||= @solver_class.new(@context, @default_guesses, @tol){|v| this.equation_value(v)}
65
+ @solver ||= @solver_class.new(
66
+ context: @context,
67
+ default_guesses: @default_guesses,
68
+ tolerance: @tol
69
+ ) { |v| this.equation_value(v) }
66
70
  end
67
71
 
68
72
  end
69
73
 
70
- end # Flt::PSolver
74
+ end # Flt::PSolver
@@ -1,8 +1,8 @@
1
1
  module Flt::Solver
2
-
2
+
3
3
  # Regula-Falsi/Secant method solver
4
4
  # Secant is used if no bracketing is available
5
- #
5
+ #
6
6
  # Example of use:
7
7
  # require 'solver'
8
8
  # include Flt
@@ -21,12 +21,12 @@ module Flt::Solver
21
21
  #
22
22
  class RFSecantSolver < Base
23
23
 
24
- def initialize(context, default_guesses, tol, eqn=nil, &blk)
25
- super context, default_guesses, tol, eqn, &blk
24
+ def initialize(*args, &blk)
25
+ super
26
26
  @half = num(Rational(1,2))
27
27
  reset
28
28
  end
29
-
29
+
30
30
  def reset
31
31
  super
32
32
  @a = @b = @fa = @fb = nil
@@ -85,5 +85,5 @@ module Flt::Solver
85
85
  end
86
86
 
87
87
  end # RFSecantSolver
88
-
89
- end # Flt::Solver
88
+
89
+ end # Flt::Solver
data/lib/solver/secant.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  module Flt::Solver
2
-
2
+
3
3
  # Secant method solver
4
4
  # Bisect method is used is bracketing found (sign(f(a)) != sign(f(b)))
5
- #
5
+ #
6
6
  # Example of use:
7
7
  # require 'solver'
8
- # require 'flt/tolerance'
9
8
  # include Flt
10
9
  # solver = Solver::SecantSolver.new(Float.context, [0.0, 100.0], Tolerance(3, :decimals)) do |x|
11
10
  # 2*x+11.0
@@ -22,12 +21,12 @@ module Flt::Solver
22
21
  #
23
22
  class SecantSolver < Base
24
23
 
25
- def initialize(context, default_guesses, tol, eqn=nil, &blk)
26
- super context, default_guesses, tol, eqn, &blk
24
+ def initialize(*args, &blk)
25
+ super
27
26
  @half = num(Rational(1,2))
28
27
  reset
29
28
  end
30
-
29
+
31
30
  def reset
32
31
  super
33
32
  @a = @b = @fa = @fb = nil
@@ -85,5 +84,5 @@ module Flt::Solver
85
84
  end
86
85
 
87
86
  end # SecantSolver
88
-
89
- end # Flt::Solver
87
+
88
+ end # Flt::Solver
data/lib/solver/tvm.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  module Flt::Solver
2
2
 
3
3
  # A Time-Value-of-Money solver
4
- #
4
+ #
5
5
  # Example:
6
6
  # tvm = TVM.new(Tolerance(3, :decimals), Float.context)
7
7
  # puts tvm.solve(:t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12).inspect # => {:pmt=>-55.45975978539105}
8
- #
8
+ #
9
9
  class TVM
10
10
 
11
11
  def initialize(tol, context=Float.context)
@@ -69,4 +69,4 @@ module Flt::Solver
69
69
 
70
70
  end # TVM
71
71
 
72
- end # Flt::Solver
72
+ end # Flt::Solver
@@ -1,5 +1,5 @@
1
1
  module Flt
2
2
  module Solver
3
- VERSION = "0.1.2"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
data/solver.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency "flt", '~> 1.3.1'
21
+ spec.add_runtime_dependency "flt", '~> 1.5'
22
22
  spec.add_development_dependency "bundler", "~> 1.6"
23
23
  spec.add_development_dependency "rake"
24
24
  spec.add_development_dependency "minitest"
data/test/test_secant.rb CHANGED
@@ -32,6 +32,32 @@ class TestSecant < MiniTest::Unit::TestCase
32
32
  assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
33
33
  assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
34
34
  end
35
+
36
+ should "be flexible with the solver parameters" do
37
+ solver = Flt::Solver::SecantSolver.new(@context, default_guesses: [0.0, 10.0], tolerance: @tolerance) do |x|
38
+ y = 2
39
+ y*exp(x)-10
40
+ end
41
+ assert_in_delta 1.6094389956808506, solver.root, @delta
42
+ assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
43
+ assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
44
+
45
+ solver = Flt::Solver::SecantSolver.new(context: @context, default_guesses: [0.0, 10.0], tolerance: @tolerance) do |x|
46
+ y = 2
47
+ y*exp(x)-10
48
+ end
49
+ assert_in_delta 1.6094389956808506, solver.root, @delta
50
+ assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
51
+ assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
52
+
53
+ solver = Flt::Solver::SecantSolver.new(@tolerance, @context, [0.0, 10.0]) do |x|
54
+ y = 2
55
+ y*exp(x)-10
56
+ end
57
+ assert_in_delta 1.6094389956808506, solver.root, @delta
58
+ assert_in_delta 1.6094389956808506, solver.root(1.0), @delta
59
+ assert_in_delta 1.6094389956808506, solver.root(2.0), @delta
60
+ end
35
61
  end
36
62
 
37
63
  context "using DecNum arithmetic" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Goizueta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-21 00:00:00.000000000 Z
11
+ date: 2015-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flt
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3.1
19
+ version: '1.5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.3.1
26
+ version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -91,7 +91,7 @@ files:
91
91
  - ".gitignore"
92
92
  - Gemfile
93
93
  - LICENSE
94
- - README.rdoc
94
+ - README.md
95
95
  - Rakefile
96
96
  - lib/solver.rb
97
97
  - lib/solver/base.rb
data/README.rdoc DELETED
@@ -1,5 +0,0 @@
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.