rical 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1ce7f2b2a92c5a7b58a2328a98fcabfdc447efa
4
- data.tar.gz: a26b4642e560f673200ed22f2e5d20de2dceb517
3
+ metadata.gz: 773c7c61e53f2619431d5434ee3454d966485e7a
4
+ data.tar.gz: 0864d8f3304f2b33347a67ac4aaaa93cee40439d
5
5
  SHA512:
6
- metadata.gz: 4957d9249119585429d3c9ea2e331a73613898d50b2691c2d7dcb7b47ed55863526c8b295424296ccc5c901dbeb06d5d612fb956968239df7276b2a7016affec
7
- data.tar.gz: 10a861d8159996d6dd080f69d549c03a8ed42bc2e8206fa511986c64df9fd24a3d34cf347b6042c32e52fe7b854a134989321e82931d55a188b5e6dceb0380e2
6
+ metadata.gz: 43242221a009ccb3d056639ac56e5edafdffd93ed9904f6775c11164429c5360073d2c9d27a9e49f8a582d87df0d4b12962f4da74f8d2ae68ea6965da563aa24
7
+ data.tar.gz: 9a003ea7281176124258f083c1a6ea0000bf228b80dc01b191c777caf61572f0be8604176a836c9251494a4300b305aff600999b205132a6a10f9da75db2a9c7
data/README.md CHANGED
@@ -3,21 +3,26 @@
3
3
 
4
4
  Using Newton-Raphson or Secant methods, and within an arbitrary error:
5
5
 
6
- * Computes the values of a root of an arbitrary function _f(x)_
7
- * Computes the value of inverse function _f<sup>-1</sup>(y) = x_ at point _y_
6
+ * Computes the values of a root of an arbitrary function _f(x, a, b, ...)_
7
+ * Computes the value of inverse function _f<sup>-1</sup>(y, a, b, ...) = x_ at point _y_
8
+ * Handles functions _f(x, a, b, ...)_, when fixed _f<sub>args</sub>_ = _a, b, ..._ are set
9
+ * Handles additional function arguments either as Array or Hash (matching positional or named parameters)
8
10
 
9
11
  The Newton-Raphson requires:
10
12
 
11
13
  * one "close enough" estimate - _x<sub>0</sub>_ - of the root or inverse value
12
- * the derivative function _f'(x)_
14
+ * the derivative function _f'(x, a, b, ...)_
15
+ * _f<sub>args</sub>_ containing _a, b, ..._ when existing
16
+ * _f'<sub>args</sub>_ containing _a, b, ..._ when existing (Note _f<sub>args</sub>_ are independent of _f'<sub>args</sub>_)
13
17
 
14
18
  The Secant method requires:
15
19
 
16
20
  * two "close enough" estimates - _x<sub>0</sub>_, _x<sub>1</sub>_ - of the root or inverse value
21
+ * _f<sub>args</sub>_ containing _a, b, ..._ when existing
17
22
 
18
- If the estimates are not "close enough" (and what that means depends on _f(x)_), the methods may not converge to a solution.
23
+ If the estimates are not "close enough" (and what that means depends on _f(x, a, b, ...)_), the methods may not converge to a solution.
19
24
  This may also happen for a "low" number of iterations and/or a "low" error limit.
20
- When no convergence is possible on the set conditions, a **`NoConvergenceError`** is raised.
25
+ When no convergence is possible on the set conditions, a `NoConvergenceError` is raised.
21
26
 
22
27
 
23
28
  ## Installation
@@ -42,26 +47,30 @@ Or install it yourself as:
42
47
  1. Set function(s) as lambdas
43
48
 
44
49
  ```ruby
45
- fx = -> (x) { x**2 - 1 }
46
- dfx = -> (x) { 2*x }
50
+ fx1 = -> (x) { x**2 - 2 }
51
+ dfx1 = -> (x) { 2*x }
47
52
  ```
48
53
 
49
54
  or set function(s) as Proc
50
55
 
51
56
  ```ruby
52
- fx = Proc.new do |x|
53
- x**2 - 1
57
+ fx2 = Proc.new do |x, a, b|
58
+ a*x**2 + b
59
+ end
60
+
61
+ dfx2 = Proc.new do |x, a|
62
+ 2*a*x
54
63
  end
55
64
  ```
56
65
 
57
66
  or set function(s) as existing method(s)
58
67
 
59
68
  ```ruby
60
- def f(x)
61
- x**2 - 1
69
+ def f3(x, a:, b:)
70
+ a*x**2 + b
62
71
  end
63
72
 
64
- fx = method(:f)
73
+ fx3 = method(:f3)
65
74
  ```
66
75
 
67
76
  2. Call `root_for` of `inverse_for` passing function(s) and estimate(s):
@@ -69,8 +78,12 @@ fx = method(:f)
69
78
  **Newton-Raphson's Method**
70
79
 
71
80
  ```ruby
72
- Rical.root_for f: fx, df: dfx, x0: 1.0, method: :newtonr_raphson # :newton_raphson aliased to :n, :newton
73
- Rical.inverse_for f: fx, df: dfx, x0: 1.0, y: 2.0, method: :newton
81
+ Rical.root_for f: fx1, df: dfx1, x0: 1.0, method: :newton_raphson # :newton_raphson aliased to :n, :newton
82
+ Rical.inverse_for f: fx1, df: dfx1, x0: 1.0, y: 2.0, method: :newton
83
+
84
+ # with fargs and dfargs
85
+ Rical.root_for f: fx2, fargs: [1, -2], df: dfx2, dfargs: 2, x0: 1.0, method: :newton
86
+ Rical.root_for f: fx3, fargs: { a: 1, b: -2 }, df: dfx3, dfargs: { a: 2 }, x0: 1.0, method: :newton
74
87
  ```
75
88
 
76
89
  **Secant's Method**
@@ -78,6 +91,9 @@ Rical.inverse_for f: fx, df: dfx, x0: 1.0, y: 2.0, method: :newton
78
91
  ```ruby
79
92
  Rical.root_for f: fx, x0: 0.0, x1: 1.0, method: :secant # :secant aliased to :sec, :s
80
93
  Rical.inverse_for f: fx, x0: 0.0, x1: 1.0, y: 2.0, method: :secant
94
+
95
+ # with fargs and dfargs
96
+ Rical.inverse_for f: fx, fargs: [1, -2], x0: 0.0, x1: 1.0, y: 2.0, method: :secant
81
97
  ```
82
98
 
83
99
  3. Change number of iterations - _num_ - or maximum allowable error - _err_ - if needed:
@@ -4,16 +4,18 @@ require 'rical/errors'
4
4
  module Rical
5
5
  extend self
6
6
 
7
- def root_for f:, df: nil, x0: 0.0, x1: 1.0, method:, num: 100, err: 1e-6
7
+ def root_for f:, fargs: nil,
8
+ df: nil, dfargs: nil,
9
+ x0: 0.0, x1: 1.0, method:, num: 100, err: 1e-6
8
10
  raise ArgumentError, 'f must respond_to call' unless f.respond_to? :call
9
11
 
10
12
  case method
11
13
  when :n, :newton, :newton_raphson
12
14
  raise ArgumentError, 'df is required' unless df
13
15
  raise ArgumentError, 'df must respond_to call' unless df.respond_to? :call
14
- newton_raphson_root_for f, df, Float(x0), num, err
16
+ newton_raphson_root_for f, fargs, df, dfargs, Float(x0), num, err
15
17
  when :s, :sec, :secant
16
- secant_root_for f, Float(x0), Float(x1), num, err
18
+ secant_root_for f, fargs, Float(x0), Float(x1), num, err
17
19
  else
18
20
  raise ArgumentError, 'unexpected method (allowed :newton, :secant)'
19
21
  end
@@ -26,24 +28,33 @@ module Rical
26
28
  alias_method :inv_for, :inverse_for
27
29
 
28
30
  private
29
- def newton_raphson_root_for f, df, x, n, err
31
+ def newton_raphson_root_for f, fargs, df, dfargs, x, n, err
30
32
  n.times do
31
- dfx = df.call x
33
+ fx = compute f, x, fargs
34
+ dfx = compute df, x, dfargs
32
35
  dfx = [1e-3, err * 1e3].max if dfx == 0.0
33
- x = x - ( f.call(x) / dfx )
34
- return x if f.call(x).abs < err
36
+ x = x - ( fx / dfx )
37
+ return x if compute(f, x, fargs).abs < err
35
38
  end
36
39
  raise NoConvergenceError
37
40
  end
38
41
 
39
- def secant_root_for f, x0, x1, n, err
42
+ def secant_root_for f, fargs, x0, x1, n, err
40
43
  n.times do
41
- delta = f.call(x1) - f.call(x0)
44
+ fx0 = compute f, x0, fargs
45
+ fx1 = compute f, x1, fargs
46
+ delta = fx1 - fx0
42
47
  delta = [1e-3, err * 1e3].max if delta == 0.0
43
- x = x1 - (f.call(x1) * (x1 - x0)) / delta
44
- return x if f.call(x).abs < err
48
+ x = x1 - (fx1 * (x1 - x0)) / delta
49
+ return x if compute(f, x, fargs).abs < err
45
50
  x0, x1 = x1, x
46
51
  end
47
52
  raise NoConvergenceError
48
53
  end
54
+
55
+ def compute f, x, fargs
56
+ return f.call x unless fargs
57
+ return f.call x, **fargs if fargs.is_a? Hash
58
+ return f.call x, *fargs
59
+ end
49
60
  end
@@ -1,3 +1,3 @@
1
1
  module Rical
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -1,27 +1,45 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rical do
4
- f = -> (x) { x**2 - 2 }
5
- df = -> (x) { 2*x }
4
+ f1 = -> (x) { x**2 - 2 }
5
+ df1 = -> (x) { 2*x }
6
6
 
7
7
  context 'Newton-Raphson method' do
8
8
  context 'root computation' do
9
9
  it 'yields +√2 for f(x) = x²-2' do
10
- expect(Rical.root_for f: f, df: df, x0: 10, method: :newton).to be_within(1e-6).of(2**0.5)
10
+ expect(Rical.root_for f: f1, df: df1, x0: 10, method: :newton).to be_within(1e-6).of(2**0.5)
11
11
  end
12
12
 
13
13
  it 'yields -√2 for f(x) = x²-2' do
14
- expect(Rical.root_for f: f, df: df, x0: -10, method: :newton).to be_within(1e-6).of(-2**0.5)
14
+ expect(Rical.root_for f: f1, df: df1, x0: -10, method: :newton).to be_within(1e-6).of(-2**0.5)
15
15
  end
16
16
 
17
17
  it 'rescues when df(x0) = 0' do
18
- expect(Rical.root_for f: f, df: df, x0: 0.0, method: :newton).to be_within(1e-6).of(2**0.5)
18
+ expect(Rical.root_for f: f1, df: df1, x0: 0.0, method: :newton).to be_within(1e-6).of(2**0.5)
19
+ end
20
+
21
+ it 'passes extra arguments (array) to f' do
22
+ f2 = -> (x, a, b) { a*x**2 + b } # f2 = f1 for a=1, b=-2
23
+ df2 = -> (x, a) { a*x } # df2 = df1 for a=2
24
+ expect(Rical.root_for f: f2, fargs: [1, -2],
25
+ df: df2, dfargs: 2,
26
+ x0: 10,
27
+ method: :newton).to be_within(1e-6).of(2**0.5)
28
+ end
29
+
30
+ it 'passes extra arguments (hash) to f' do
31
+ f3 = -> (x, a:, b:) { a*x**2 + b } # f3 = f1 for { a: 1, b: -2 }
32
+ df3 = -> (x, a:) { a*x } # df3 = df1 for { a: 2 }
33
+ expect(Rical.root_for f: f3, fargs: { a: 1, b: -2 },
34
+ df: df3, dfargs: { a: 2 },
35
+ x0: 10,
36
+ method: :newton).to be_within(1e-6).of(2**0.5)
19
37
  end
20
38
  end
21
39
 
22
40
  context 'inverse computation' do
23
41
  it 'yields +2.0 for y=2, f(x)=y=x²-2' do
24
- expect(Rical.inverse_for f: f, df: df, y: 2, x0: 0, method: :newton).to be_within(1e-6).of(2.0)
42
+ expect(Rical.inverse_for f: f1, df: df1, y: 2, x0: 0, method: :newton).to be_within(1e-6).of(2.0)
25
43
  end
26
44
  end
27
45
 
@@ -30,48 +48,53 @@ describe Rical do
30
48
  context 'Secant method' do
31
49
  context 'root computation' do
32
50
  it 'yields +√2 for f(x) = x²-2' do
33
- expect(Rical.root_for f: f, x0: 10, x1: 9, method: :secant).to be_within(1e-6).of(2**0.5)
51
+ expect(Rical.root_for f: f1, x0: 10, x1: 9, method: :secant).to be_within(1e-6).of(2**0.5)
34
52
  end
35
53
 
36
54
  it 'yields -√2 for f(x) = x²-2' do
37
- expect(Rical.root_for f: f, x0: -10, x1: -9, method: :secant).to be_within(1e-6).of(-2**0.5)
55
+ expect(Rical.root_for f: f1, x0: -10, x1: -9, method: :secant).to be_within(1e-6).of(-2**0.5)
38
56
  end
39
57
 
40
58
  it 'rescues when f(x1) - f(x0) = 0' do
41
- # converges but to either one of the roots
42
- expect(Rical.root_for(f: f, x0: 9, x1: -9, method: :secant).abs).to be_within(1e-6).of(2**0.5)
59
+ # converges to either one of the roots
60
+ expect(Rical.root_for(f: f1, x0: 9, x1: -9, method: :secant).abs).to be_within(1e-6).of(2**0.5)
61
+ end
62
+
63
+ it 'passes extra arguments (array) to f' do
64
+ f2 = -> (x, a, b) { a*x**2 + b } # f2 = f1 for a=1, b=-2
65
+ expect(Rical.root_for f: f2, fargs: [1, -2], x0: 10, x1: 9, method: :secant).to be_within(1e-6).of(2**0.5)
43
66
  end
44
67
  end
45
68
 
46
69
  context 'inverse computation' do
47
70
  it 'yields +2.0 for y=2, f(x)=y=x²-2' do
48
- expect(Rical.inverse_for f: f, y: 2, x0: 0, x1: 1, method: :secant).to be_within(1e-6).of(2.0)
71
+ expect(Rical.inverse_for f: f1, y: 2, x0: 0, x1: 1, method: :secant).to be_within(1e-6).of(2.0)
49
72
  end
50
73
  end
51
74
  end
52
75
 
53
76
  context 'error handling' do
54
77
  it 'raises ArgumentError when f is not callable' do
55
- expect{ Rical.root_for f: :dummy, df: df, method: :newton }.to raise_error ArgumentError
78
+ expect{ Rical.root_for f: :dummy, df: df1, method: :newton }.to raise_error ArgumentError
56
79
  expect{ Rical.root_for f: :dummy, x0: 0, x1: 1, method: :secant }.to raise_error ArgumentError
57
80
  end
58
81
 
59
82
  it 'raises ArgumentError when no df given and method is Newton-Raphson' do
60
- expect{ Rical.root_for f: f, method: :newton }.to raise_error ArgumentError
61
- expect{ Rical.inverse_for f: f, method: :newton }.to raise_error ArgumentError
83
+ expect{ Rical.root_for f: f1, method: :newton }.to raise_error ArgumentError
84
+ expect{ Rical.inverse_for f: f1, method: :newton }.to raise_error ArgumentError
62
85
  end
63
86
 
64
87
  it 'raises ArgumentError when df is not callable and method is Newton-Raphson' do
65
- expect{ Rical.root_for f: f, df: :dummy, method: :newton }.to raise_error ArgumentError
88
+ expect{ Rical.root_for f: f1, df: :dummy, method: :newton }.to raise_error ArgumentError
66
89
  end
67
90
 
68
91
  it 'raises NoConvergenceError when Newton-Raphson does not converge' do
69
- expect{ Rical.root_for f: f, df: df, x0: 1e32, method: :newton, num: 10
92
+ expect{ Rical.root_for f: f1, df: df1, x0: 1e32, method: :newton, num: 10
70
93
  }.to raise_error Rical::NoConvergenceError
71
94
  end
72
95
 
73
96
  it 'raises NoConvergenceError when Secant does not converge' do
74
- expect{ Rical.root_for f: f, x0: 1e32, x1: 1.1e32, method: :secant, num: 10
97
+ expect{ Rical.root_for f: f1, x0: 1e32, x1: 1.1e32, method: :secant, num: 10
75
98
  }.to raise_error Rical::NoConvergenceError
76
99
  end
77
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rical
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis Bacelar