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 +4 -4
- data/README.md +30 -14
- data/lib/rical.rb +22 -11
- data/lib/rical/version.rb +1 -1
- data/spec/rical_spec.rb +40 -17
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 773c7c61e53f2619431d5434ee3454d966485e7a
|
|
4
|
+
data.tar.gz: 0864d8f3304f2b33347a67ac4aaaa93cee40439d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
x**2
|
|
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
|
|
61
|
-
x**2
|
|
69
|
+
def f3(x, a:, b:)
|
|
70
|
+
a*x**2 + b
|
|
62
71
|
end
|
|
63
72
|
|
|
64
|
-
|
|
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:
|
|
73
|
-
Rical.inverse_for f:
|
|
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:
|
data/lib/rical.rb
CHANGED
|
@@ -4,16 +4,18 @@ require 'rical/errors'
|
|
|
4
4
|
module Rical
|
|
5
5
|
extend self
|
|
6
6
|
|
|
7
|
-
def root_for f:,
|
|
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
|
-
|
|
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 - (
|
|
34
|
-
return x if f
|
|
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
|
-
|
|
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 - (
|
|
44
|
-
return x if f
|
|
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
|
data/lib/rical/version.rb
CHANGED
data/spec/rical_spec.rb
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe Rical do
|
|
4
|
-
|
|
5
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
42
|
-
expect(Rical.root_for(f:
|
|
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:
|
|
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:
|
|
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:
|
|
61
|
-
expect{ Rical.inverse_for f:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|