solver 0.0.0 → 0.1.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
@@ -9,4 +9,4 @@ require 'solver/secant'
9
9
  require 'solver/rfsecant'
10
10
  # TODO: Crenshaw/Secant method
11
11
  require 'solver/psolver'
12
- # require 'solver/tvmsolver'
12
+ require 'solver/tvm'
@@ -0,0 +1,72 @@
1
+ module Flt::Solver
2
+
3
+ # A Time-Value-of-Money solver
4
+ #
5
+ # Example:
6
+ # tvm = TVM.new(Tolerance(3, :decimals), Float.context)
7
+ # puts tvm.solve(:t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12).inspect # => {:pmt=>-55.45975978539105}
8
+ #
9
+ class TVM
10
+
11
+ def initialize(tol, context=Float.context)
12
+ @context = context
13
+ @var_descriptions = {
14
+ :m=>'money value at time t',
15
+ :t=>'time',
16
+ :m0=>'initial money value',
17
+ :pmt=>'payment per time unit',
18
+ :i=>'percent interest per year',
19
+ :p=>'numer of time units per year'
20
+ }
21
+ @vars = @var_descriptions.keys
22
+ vars = @vars
23
+ tvm = self
24
+ @solver = PSolver.new(context, tol) do |m, t, m0, pmt, i, p|
25
+ tvm.equation(m, t, m0, pmt, i, p)
26
+ end
27
+ @solver.default_guesses = 1,2
28
+ @one = @context.Num(1)
29
+ end
30
+
31
+ def parameter_descriptions
32
+ @var_descriptions
33
+ end
34
+
35
+ # Parameters:
36
+ # :t time in periods
37
+ # :p number of periods per year
38
+ # :i percent yearly interest rate
39
+ # :pmt payment per period
40
+ # :m0 initial value
41
+ # :m value at time :t
42
+ def solve(parameters)
43
+ nil_vars = @vars.select{|var| parameters[var].nil?}
44
+ raise "Too many unknowns" if nil_vars.size>1
45
+ raise "Nothing to solve" if nil_vars.empty?
46
+ var = nil_vars.first
47
+ # determine sensible initial value? => parameters[var] = initial_value
48
+ {var=>@solver.root(var, parameters)}
49
+ end
50
+
51
+ def value(parameters)
52
+ @solver.equation_value(paramters)
53
+ end
54
+
55
+ def equation(m, t, m0, pmt, i, p)
56
+ i /= 100
57
+ i /= p
58
+ n = -t
59
+ k = @context.exp(lnp1(i)*n) # (i+1)**n
60
+ # Equation: -m*k = m0 + pmt*(1-k)/i
61
+ m0 + pmt*(@one-k)/i + m*k
62
+ end
63
+
64
+ # ln(x+1)
65
+ def lnp1(x)
66
+ v = x + 1
67
+ (v == 1) ? x : (x*@context.ln(v) / (v - 1))
68
+ end
69
+
70
+ end # TVM
71
+
72
+ end # Flt::Solver
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{solver}
8
- s.version = "0.0.0"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Javier Goizueta"]
@@ -29,12 +29,14 @@ Gem::Specification.new do |s|
29
29
  "lib/solver/psolver.rb",
30
30
  "lib/solver/rfsecant.rb",
31
31
  "lib/solver/secant.rb",
32
+ "lib/solver/tvm.rb",
32
33
  "solver.gemspec",
33
34
  "test/helper.rb",
34
35
  "test/test_function.rb",
35
36
  "test/test_psolver.rb",
36
37
  "test/test_rfsecant.rb",
37
- "test/test_secant.rb"
38
+ "test/test_secant.rb",
39
+ "test/test_tvm.rb"
38
40
  ]
39
41
  s.homepage = %q{http://github.com/jgoizueta/solver}
40
42
  s.rdoc_options = ["--charset=UTF-8"]
@@ -47,7 +49,8 @@ Gem::Specification.new do |s|
47
49
  "test/test_function.rb",
48
50
  "test/test_psolver.rb",
49
51
  "test/test_rfsecant.rb",
50
- "test/test_secant.rb"
52
+ "test/test_secant.rb",
53
+ "test/test_tvm.rb"
51
54
  ]
52
55
 
53
56
  if s.respond_to? :specification_version then
@@ -0,0 +1,58 @@
1
+ require 'helper'
2
+ require 'flt/float'
3
+ require 'flt/tolerance'
4
+
5
+ include Flt
6
+
7
+ class TestTVM < Test::Unit::TestCase
8
+
9
+ context "The TVM 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
+ @tvm = Flt::Solver::TVM.new(@tolerance, @context)
18
+ end
19
+
20
+ should "solve correclty for :pmt" do
21
+ sol = @tvm.solve(:t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12)
22
+ assert_equal [:pmt], sol.keys
23
+ assert_in_delta -55.45975978539105, sol[:pmt], @delta
24
+ end
25
+
26
+ should "solve correclty for :t" do
27
+ sol = @tvm.solve(:pmt=>-55.45975978539105, :m0=>10000, :m=>0, :i=>3, :p=>12)
28
+ assert_equal [:t], sol.keys
29
+ assert_in_delta 240, sol[:t], @delta
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ context "using DecNum arithmetic" do
37
+
38
+ setup do
39
+ @context = Float.context
40
+ @tolerance = Flt.Tolerance(3,:decimals)
41
+ @delta = 5E-4
42
+ @tvm = Flt::Solver::TVM.new(@tolerance, @context)
43
+ end
44
+
45
+ should "solve correclty for :pmt" do
46
+ sol = @tvm.solve(:t=>240, :m0=>10000, :m=>0, :i=>3, :p=>12)
47
+ assert_equal [:pmt], sol.keys
48
+ assert_in_delta @context.Num('-55.45975978539105'), sol[:pmt], @delta
49
+ end
50
+
51
+ should "solve correclty for :t" do
52
+ sol = @tvm.solve(:pmt=>-55.45975978539105, :m0=>10000, :m=>0, :i=>3, :p=>12)
53
+ assert_equal [:t], sol.keys
54
+ assert_in_delta 240, sol[:t], @delta
55
+ end
56
+
57
+ end
58
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 0
9
- version: 0.0.0
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Javier Goizueta
@@ -67,12 +67,14 @@ files:
67
67
  - lib/solver/psolver.rb
68
68
  - lib/solver/rfsecant.rb
69
69
  - lib/solver/secant.rb
70
+ - lib/solver/tvm.rb
70
71
  - solver.gemspec
71
72
  - test/helper.rb
72
73
  - test/test_function.rb
73
74
  - test/test_psolver.rb
74
75
  - test/test_rfsecant.rb
75
76
  - test/test_secant.rb
77
+ - test/test_tvm.rb
76
78
  has_rdoc: true
77
79
  homepage: http://github.com/jgoizueta/solver
78
80
  licenses: []
@@ -113,3 +115,4 @@ test_files:
113
115
  - test/test_psolver.rb
114
116
  - test/test_rfsecant.rb
115
117
  - test/test_secant.rb
118
+ - test/test_tvm.rb