File Coverage

blib/lib/Math/Integral/Romberg.pm
Criterion Covered Total %
statement 35 37 94.5
branch 5 10 50.0
condition 18 29 62.0
subroutine 4 4 100.0
pod 0 1 0.0
total 62 81 76.5


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;