line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*- perl -*- |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package Math::Integral::Romberg; |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
require Exporter; |
6
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
8729
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
600
|
|
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
7
|
use vars qw( @ISA @EXPORT @EXPORT_OK ); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
110
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
12
|
|
|
|
|
|
|
@EXPORT = qw(); |
13
|
|
|
|
|
|
|
@EXPORT_OK = qw(integral return_point_count); |
14
|
|
|
|
|
|
|
|
15
|
1
|
|
|
|
|
901
|
use vars qw( $VERSION $abort $return_point_count |
16
|
1
|
|
|
1
|
|
8
|
$rel_err $abs_err $max_split $min_split ); |
|
1
|
|
|
|
|
7
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$VERSION = "0.04"; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
$abort = 0; |
21
|
|
|
|
|
|
|
$return_point_count = 0; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
$rel_err = 1e-10; |
24
|
|
|
|
|
|
|
$abs_err = 1e-20; |
25
|
|
|
|
|
|
|
$max_split = 16; |
26
|
|
|
|
|
|
|
$min_split = 5; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 NAME |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
Math::Integral::Romberg - Scalar numerical integration |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 SYNOPSIS |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
use Math::Integral::Romberg 'integral'; |
35
|
|
|
|
|
|
|
sub f { my ($x) = @_; 1/($x ** 2 + 1) } # ... or whatever |
36
|
|
|
|
|
|
|
$area = integral(\&f, $x1, $x2); # Short form |
37
|
|
|
|
|
|
|
$area = integral # Long form |
38
|
|
|
|
|
|
|
(\&f, $x1, $x2, $rel_err, $abs_err, $max_split, $min_split); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# an alternative way of doing the long form |
41
|
|
|
|
|
|
|
$Math::Integral::Romberg::rel_err = $rel_err; |
42
|
|
|
|
|
|
|
$Math::Integral::Romberg::abs_err = $abs_err; |
43
|
|
|
|
|
|
|
$Math::Integral::Romberg::max_split = $max_split; |
44
|
|
|
|
|
|
|
$Math::Integral::Romberg::min_split = $min_split; |
45
|
|
|
|
|
|
|
$area = integral(\&f, $x1, $x2); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head1 DESCRIPTION |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
integral() numerically estimates the integral of f() using Romberg |
50
|
|
|
|
|
|
|
integration, a faster relative of Simpson's method. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 Parameters |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=over |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=item $f |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
A reference to the function to be integrated. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=item $x1, $x2 |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
The limits of the integration domain. C<&$f(x1)> and C<&$f(x2)> must |
63
|
|
|
|
|
|
|
be finite. |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=item $rel_err |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
Maximum acceptable relative error. Estimates of relative and absolute |
68
|
|
|
|
|
|
|
error are based on a comparison of the estimate computed using C<2**n |
69
|
|
|
|
|
|
|
+ 1> points with the estimate computed using C<2**(n-1) + 1> |
70
|
|
|
|
|
|
|
points. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Once $min_split has been reached (see below), computation stops as |
73
|
|
|
|
|
|
|
soon as relative error drops below $rel_err, absolute error drops |
74
|
|
|
|
|
|
|
below $abs_err, or $max_split is reached. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
If not supplied, uses the value B<$Math::Integral::Romberg::rel_err> |
77
|
|
|
|
|
|
|
whose default is 10**-10. The accuracy limit of double-precision |
78
|
|
|
|
|
|
|
floating point is about 10**-15. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item $abs_err |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Maximum acceptable absolute error. If not supplied, uses |
83
|
|
|
|
|
|
|
B<$Math::Integral::Romberg::abs_err>, which defaults to |
84
|
|
|
|
|
|
|
10**-20. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item $max_split |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
At most C<2 ** $max_split + 1> different sample x values are used to |
89
|
|
|
|
|
|
|
estimate the integral of C. If not supplied, uses the value |
90
|
|
|
|
|
|
|
B<$Math::Integral::Romberg::max_split>, which defaults to 16, |
91
|
|
|
|
|
|
|
corresponding to 65537 sample points. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=item $min_split |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
At least C<2 ** $min_split + 1> different sample x values are used to |
96
|
|
|
|
|
|
|
estimate the integral of C. If not supplied, uses the value of |
97
|
|
|
|
|
|
|
B<$Math::Integral::Romberg::max_split>, which defaults to 5, |
98
|
|
|
|
|
|
|
corresponding to 33 sample points. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=item $Math::Integral::Romberg::return_point_count |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
This value defaults to 0. If you set it to 1, then when invoked in a |
103
|
|
|
|
|
|
|
list context, integral() will return a two-element list, containing |
104
|
|
|
|
|
|
|
the estimate followed by the number of sample points used to compute |
105
|
|
|
|
|
|
|
the estimate. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=item $Math::Integral::Romberg::abort |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
This value is set to 1 if neither the $rel_err nor the $abs_err |
110
|
|
|
|
|
|
|
thresholds are reached before computation stops. Once set, this |
111
|
|
|
|
|
|
|
variable remains set until you reset it to 0. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=back |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head2 Default values |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Using the long form of integral() sets the convergence parameters |
118
|
|
|
|
|
|
|
for that call only - you must use the package-qualified variable |
119
|
|
|
|
|
|
|
names (e.g. $Math::Integral::Romberg::abs_tol) to change the values |
120
|
|
|
|
|
|
|
for all calls. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head2 About the Algorithm |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
Romberg integration uses progressively higher-degree polynomial |
125
|
|
|
|
|
|
|
approximations each time you double the number of sample points. For |
126
|
|
|
|
|
|
|
example, it uses a 2nd-degree polynomial approximation (as Simpson's |
127
|
|
|
|
|
|
|
method does) after one split (2**1 + 1 sample points), and it uses a |
128
|
|
|
|
|
|
|
10th-degree polynomial approximation after five splits (2**5 + 1 |
129
|
|
|
|
|
|
|
sample points). Typically, this will greatly improve accuracy |
130
|
|
|
|
|
|
|
(compared to simpler methods) for smooth functions, while not making |
131
|
|
|
|
|
|
|
much difference for badly behaved ones. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head1 AUTHOR |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Eric Boesch (ericboesch@gmail.com) |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=cut |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub integral { |
140
|
2
|
|
33
|
2
|
0
|
127
|
my $return_pts = wantarray && $Math::Integral::Romberg::return_point_count; |
141
|
2
|
|
|
|
|
3
|
my $abort = \$Math::Integral::Romberg::abort; |
142
|
2
|
|
|
|
|
13
|
my ($f,$lo,$hi,$rel_err,$abs_err,$max_split,$min_split)=@_; |
143
|
2
|
50
|
|
|
|
7
|
($lo, $hi) = ($hi, $lo) if $lo > $hi; |
144
|
|
|
|
|
|
|
|
145
|
2
|
|
66
|
|
|
70
|
$rel_err ||= $Math::Integral::Romberg::rel_err; |
146
|
2
|
|
66
|
|
|
7
|
$abs_err ||= $Math::Integral::Romberg::abs_err; |
147
|
2
|
|
66
|
|
|
7
|
$max_split ||= $Math::Integral::Romberg::max_split; |
148
|
2
|
|
66
|
|
|
7
|
$min_split ||= $Math::Integral::Romberg::min_split; |
149
|
|
|
|
|
|
|
|
150
|
2
|
|
|
|
|
3
|
my ($estimate, $split, $steps); |
151
|
2
|
|
|
|
|
3
|
my $step_len = $hi - $lo; |
152
|
2
|
|
|
|
|
11
|
my $tot = (&$f($lo) + &$f($hi))/2; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# tot is used to compute the trapezoid approximations. It is more or |
155
|
|
|
|
|
|
|
# less a total of all f() values computed so far. The trapezoid |
156
|
|
|
|
|
|
|
# method assigns half as much weight to f(hi) and f(lo) as it does to |
157
|
|
|
|
|
|
|
# all other f() values, so f(hi) and f(lo) are divided by two here. |
158
|
|
|
|
|
|
|
|
159
|
2
|
|
|
|
|
106
|
my @row = $estimate = $tot * $step_len; # 0th trapezoid approximation. |
160
|
|
|
|
|
|
|
|
161
|
2
|
|
|
|
|
4
|
for ($split = 1, $steps=2; ; $split++, $step_len /=2, $steps *= 2) { |
162
|
13
|
|
|
|
|
17
|
my ($x, $new_estimate); |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# Don't let $step_len drop below the limits of numeric precision. |
165
|
|
|
|
|
|
|
# (This should prevent infinite loops, but not loss of accuracy.) |
166
|
13
|
50
|
33
|
|
|
143
|
if ($lo + $step_len/$steps == $lo || $hi - $step_len/$steps == $hi) { |
167
|
0
|
|
|
|
|
0
|
$$abort = 1; |
168
|
0
|
0
|
|
|
|
0
|
return $return_pts ? ($estimate, $steps/2 + 1) : $estimate; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# Compute the (split)th trapezoid approximation. |
172
|
13
|
|
|
|
|
155
|
for ($x = $lo + $step_len/2; $x < $hi; $x += $step_len) { |
173
|
190
|
|
|
|
|
1293
|
$tot += &$f($x); |
174
|
|
|
|
|
|
|
} |
175
|
13
|
|
|
|
|
109
|
unshift @row, $tot * $step_len / 2; |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# Compute the more refined approximations, based on the (split)th |
178
|
|
|
|
|
|
|
# trapezoid approximation and the various (split-1)th refined |
179
|
|
|
|
|
|
|
# approximations stored in @row. |
180
|
|
|
|
|
|
|
|
181
|
13
|
|
|
|
|
91
|
my $pow4 = 4; |
182
|
|
|
|
|
|
|
|
183
|
13
|
|
|
|
|
27
|
foreach my $td ( 1 .. $split ) { |
184
|
49
|
|
|
|
|
197
|
$row[$td] = $row[$td-1] + |
185
|
|
|
|
|
|
|
($row[$td-1]-$row[$td])/($pow4 - 1); |
186
|
49
|
|
|
|
|
343
|
$pow4 *= 4; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# row[0] now contains the (split)th trapezoid approximation, |
190
|
|
|
|
|
|
|
# row[1] now contains the (split)th Simpson approximation, and |
191
|
|
|
|
|
|
|
# so on up to row[split] which contains the (split)th Romberg |
192
|
|
|
|
|
|
|
# approximation. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# Is this estimate accurate enough? |
195
|
13
|
|
|
|
|
150
|
$new_estimate = $row[-1]; |
196
|
13
|
100
|
66
|
|
|
92
|
if (($split >= $min_split && |
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
197
|
|
|
|
|
|
|
(abs($new_estimate - $estimate) < $abs_err || |
198
|
|
|
|
|
|
|
abs($new_estimate - $estimate) < $rel_err * abs($estimate))) || |
199
|
|
|
|
|
|
|
($split == $max_split && ($$abort = 1))) { |
200
|
2
|
50
|
|
|
|
14
|
return $return_pts ? ($new_estimate, $steps + 1) : $new_estimate; |
201
|
|
|
|
|
|
|
} |
202
|
11
|
|
|
|
|
21
|
$estimate = $new_estimate; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
1; |