solver 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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