integrator 0.0.1 → 0.0.2
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/README.txt +10 -0
- data/lib/integrator.rb +1 -1
- data/lib/integrator/adaptive.rb +8 -39
- data/lib/integrator/rkck.rb +6 -43
- data/lib/integrator/rkqs.rb +5 -6
- data/lib/integrator/version.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/test_integrator.rb +57 -3
- metadata +2 -2
data/README.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
This gem includes a framework for numerical integrators, both fixed-step and
|
2
2
|
adaptive, and includes an excellent sample integrator of each type to get
|
3
3
|
you started.
|
4
|
+
|
5
|
+
It's faster to write such things in C or C++, but better for
|
6
|
+
portability to keep it in Ruby. I'm using Ruby vector operations
|
7
|
+
rather than iterating manually so that at least the scalability isn't
|
8
|
+
too bad...
|
9
|
+
|
10
|
+
The Cash-Karp embedded Runge-Kutta 4/5 integrator's code is adapted
|
11
|
+
from the code in chapter 16 of "Numerical Recipes in C", second
|
12
|
+
edition. Adapting this stuff to Ruby and to OO operation is all done
|
13
|
+
ad-hoc and by me.
|
data/lib/integrator.rb
CHANGED
data/lib/integrator/adaptive.rb
CHANGED
@@ -11,20 +11,18 @@ require "matrix"
|
|
11
11
|
require "rubygems"
|
12
12
|
require "integrator"
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
class Adaptive_Cash_Karp_RK45 < OneStep_Cash_Karp_RK45
|
14
|
+
# This is the top-level driver, with logging, used by the more complex
|
15
|
+
# integrators in "Numerical Methods in C", second edition. Code is
|
16
|
+
# adapted from C, of course.
|
17
|
+
#
|
18
|
+
module AdaptiveIntegrator
|
21
19
|
include Integrator
|
22
20
|
|
23
21
|
MAXSTP = 10000
|
24
22
|
TINY = 1.0e-30
|
25
23
|
|
26
|
-
def
|
27
|
-
|
24
|
+
def integrate_ad_step(y, dydx, x, h, eps, yscal, derivs)
|
25
|
+
raise "Adaptive integrators must implement integrate_ad_step!"
|
28
26
|
end
|
29
27
|
|
30
28
|
def adaptive_integrate(ystart, x1, x2, eps, h1, hmin, derivs)
|
@@ -77,33 +75,4 @@ class Adaptive_Cash_Karp_RK45 < OneStep_Cash_Karp_RK45
|
|
77
75
|
raise "Too many steps in integration!"
|
78
76
|
end
|
79
77
|
|
80
|
-
end
|
81
|
-
|
82
|
-
if __FILE__ == $0
|
83
|
-
|
84
|
-
rk45 = Adaptive_Cash_Karp_RK45.new()
|
85
|
-
|
86
|
-
def assert_within_epsilon(actual, expected)
|
87
|
-
if expected.kind_of?(Vector)
|
88
|
-
actual.collect2(expected) { |x, y|
|
89
|
-
assert_within_epsilon(x, y)
|
90
|
-
}
|
91
|
-
else
|
92
|
-
(actual - expected).abs / expected < 0.001 or
|
93
|
-
raise "Values don't match! Actual was #{actual}, not #{expected}!"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
assert_within_epsilon(10000.0, 10001)
|
97
|
-
|
98
|
-
# This takes the derivative of e^x for each vector element.
|
99
|
-
# The derivative of e^x is just e^x again.
|
100
|
-
exp_deriv = proc { |x, y| y.collect { |yi| yi } }
|
101
|
-
|
102
|
-
exp_start = Vector.elements([1.0] * 100)
|
103
|
-
|
104
|
-
y = rk45.adaptive_integrate(exp_start, 0.0, 1.0, 0.001, 0.01, 0.0001,
|
105
|
-
exp_deriv)
|
106
|
-
assert_within_epsilon(y, Vector.elements( [ Math::E ] * 100 ))
|
107
|
-
|
108
|
-
print "All tests completed...\n"
|
109
|
-
end
|
78
|
+
end # module AdaptiveIntegrator
|
data/lib/integrator/rkck.rb
CHANGED
@@ -15,6 +15,8 @@
|
|
15
15
|
require "matrix"
|
16
16
|
|
17
17
|
class Cash_Karp_RK4
|
18
|
+
include Integrator
|
19
|
+
|
18
20
|
A2 = 0.2
|
19
21
|
A3 = 0.3
|
20
22
|
A4 = 0.6
|
@@ -45,6 +47,10 @@ class Cash_Karp_RK4
|
|
45
47
|
DC5 = -277.00/14336.0
|
46
48
|
DC6 = C6-0.25
|
47
49
|
|
50
|
+
def initialize
|
51
|
+
Integrator_initialize()
|
52
|
+
end
|
53
|
+
|
48
54
|
def integrate_fixed_step(y, dydx, x, h, derivs)
|
49
55
|
raise "Bad type!" unless [y, dydx].all? { |var| var.kind_of?(Vector) }
|
50
56
|
raise "Bad type!" unless [x, h].all? { |var| var.kind_of?(Float) }
|
@@ -68,46 +74,3 @@ class Cash_Karp_RK4
|
|
68
74
|
[yout, yerr]
|
69
75
|
end
|
70
76
|
end
|
71
|
-
|
72
|
-
if __FILE__ == $0
|
73
|
-
|
74
|
-
def assert_within_epsilon(actual, expected)
|
75
|
-
if expected.kind_of?(Vector)
|
76
|
-
expected.collect2(actual) { |x, y|
|
77
|
-
assert_within_epsilon(x, y)
|
78
|
-
}
|
79
|
-
else
|
80
|
-
(actual - expected).abs / expected < 0.001 or
|
81
|
-
raise "Values don't match! Actual was #{actual}, not #{expected}!"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
assert_within_epsilon(10000.0, 10000.00001)
|
85
|
-
|
86
|
-
const_deriv = proc do |x, y|
|
87
|
-
y.collect { |yi| 0.1 }
|
88
|
-
end
|
89
|
-
|
90
|
-
exp_deriv = proc do |x, y|
|
91
|
-
y.collect { |yi| yi }
|
92
|
-
end
|
93
|
-
|
94
|
-
integ = Cash_Karp_RK4.new()
|
95
|
-
|
96
|
-
constvec = Vector.elements([1.0] * 100)
|
97
|
-
constderiv = const_deriv.call(0, constvec)
|
98
|
-
|
99
|
-
# Integrate a constant derivative over a distance of 1.0
|
100
|
-
yout, yerr = integ.integrate_fixed_step(constvec, constderiv, 0.0, 1.0,
|
101
|
-
const_deriv)
|
102
|
-
assert_within_epsilon(yout, Vector.elements([ 1.1 ] * 100))
|
103
|
-
|
104
|
-
onevec = Vector.elements([1.0] * 100)
|
105
|
-
expderiv = exp_deriv.call(0, onevec)
|
106
|
-
dist = 0.01 # Keep this small because we only make one integration step
|
107
|
-
# Integrate e^x over a distance of 1.0, starting at e^0 == 1.0
|
108
|
-
yout, yerr = integ.integrate_fixed_step(onevec, expderiv, 0.0, dist,
|
109
|
-
exp_deriv)
|
110
|
-
assert_within_epsilon(yout, Vector.elements([ Math::E ** dist ] * 100))
|
111
|
-
|
112
|
-
print "Tests completed...\n"
|
113
|
-
end
|
data/lib/integrator/rkqs.rb
CHANGED
@@ -9,9 +9,12 @@
|
|
9
9
|
#
|
10
10
|
|
11
11
|
require "matrix"
|
12
|
-
require "integrator/rkck
|
12
|
+
require "integrator/rkck"
|
13
|
+
require "integrator/adaptive"
|
14
|
+
|
15
|
+
class Adaptive_Cash_Karp_RK45 < Cash_Karp_RK4
|
16
|
+
include AdaptiveIntegrator
|
13
17
|
|
14
|
-
class OneStep_Cash_Karp_RK45 < Cash_Karp_RK4
|
15
18
|
SAFETY = 0.9
|
16
19
|
PGROW = -0.2
|
17
20
|
PSHRNK = -0.25
|
@@ -45,7 +48,3 @@ class OneStep_Cash_Karp_RK45 < Cash_Karp_RK4
|
|
45
48
|
end
|
46
49
|
|
47
50
|
end
|
48
|
-
|
49
|
-
if __FILE__ == $0
|
50
|
-
|
51
|
-
end
|
data/lib/integrator/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
require File.dirname(__FILE__) + '/../lib/integrator'
|
2
|
+
#require File.dirname(__FILE__) + '/../lib/integrator'
|
data/test/test_integrator.rb
CHANGED
@@ -1,11 +1,65 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'integrator/adaptive.rb'
|
3
|
+
require 'integrator/rkck.rb'
|
4
|
+
require 'integrator/rkqs.rb'
|
2
5
|
|
3
6
|
class TestIntegrator < Test::Unit::TestCase
|
4
7
|
|
5
8
|
def setup
|
9
|
+
# Test both fixed-step and adaptive integrators
|
10
|
+
@fixed_integrator = Cash_Karp_RK4.new()
|
11
|
+
@ad_integrator = Adaptive_Cash_Karp_RK45.new()
|
6
12
|
end
|
7
|
-
|
8
|
-
def
|
9
|
-
|
13
|
+
|
14
|
+
def assert_within_epsilon(actual, expected)
|
15
|
+
if expected.kind_of?(Vector)
|
16
|
+
expected.collect2(actual) { |x, y|
|
17
|
+
assert_within_epsilon(x, y)
|
18
|
+
}
|
19
|
+
else
|
20
|
+
(actual - expected).abs / expected < 0.001 or
|
21
|
+
raise "Values don't match! Actual was #{actual}, not #{expected}!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_a_w_e
|
26
|
+
assert_within_epsilon(10000.0, 10000.00001)
|
27
|
+
assert_within_epsilon(10000.0, 10001)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_const_func
|
31
|
+
const_deriv_proc = proc do |x, y|
|
32
|
+
y.collect { |yi| 0.1 }
|
33
|
+
end
|
34
|
+
constvec = Vector.elements([1.0] * 100)
|
35
|
+
constderiv = const_deriv_proc.call(0, constvec)
|
36
|
+
|
37
|
+
# Integrate a constant derivative over a distance of 1.0
|
38
|
+
yout, yerr = @fixed_integrator.integrate_fixed_step(constvec, constderiv,
|
39
|
+
0.0, 1.0,
|
40
|
+
const_deriv_proc)
|
41
|
+
assert_within_epsilon(yout, Vector.elements([ 1.1 ] * 100))
|
10
42
|
end
|
43
|
+
|
44
|
+
def test_exp_func
|
45
|
+
# This takes the derivative of e^x for each vector element.
|
46
|
+
# The derivative of e^x is just e^x again.
|
47
|
+
exp_deriv_proc = proc do |x, y|
|
48
|
+
y.collect { |yi| yi }
|
49
|
+
end
|
50
|
+
|
51
|
+
onevec = Vector.elements([1.0] * 100)
|
52
|
+
expderiv = exp_deriv_proc.call(0, onevec)
|
53
|
+
dist = 0.01 # Keep this small because we only make one integration step
|
54
|
+
# Integrate e^x over a distance of 1.0, starting at e^0 == 1.0
|
55
|
+
yout, yerr = @fixed_integrator.integrate_fixed_step(onevec, expderiv,
|
56
|
+
0.0, dist,
|
57
|
+
exp_deriv_proc)
|
58
|
+
assert_within_epsilon(yout, Vector.elements([ Math::E ** dist ] * 100))
|
59
|
+
|
60
|
+
y = @ad_integrator.adaptive_integrate(onevec, 0.0, 1.0, 0.001, 0.01,
|
61
|
+
0.0001, exp_deriv_proc)
|
62
|
+
assert_within_epsilon(y, Vector.elements( [ Math::E ] * 100 ))
|
63
|
+
end
|
64
|
+
|
11
65
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: integrator
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-10-
|
6
|
+
version: 0.0.2
|
7
|
+
date: 2007-10-15 00:00:00 -07:00
|
8
8
|
summary: Numerical integration framework
|
9
9
|
require_paths:
|
10
10
|
- lib
|