File Coverage

blib/lib/Math/Brent.pm
Criterion Covered Total %
statement 106 129 82.1
branch 28 40 70.0
condition 17 22 77.2
subroutine 10 10 100.0
pod 3 3 100.0
total 164 204 80.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Math::Brent - Single Dimensional Function Minimisation
4              
5             =head1 SYNOPSIS
6              
7             use Math::Brent qw(Minimise1D);
8              
9             my ($x, $y) = Minimise1D($guess, $scale, \&func, $tol, $itmax);
10              
11             or
12              
13             use Math::Brent qw(BracketMinimum Brent);
14              
15             my ($ax, $bx, $cx, $fa, $fb, $fc) = BracketMinimum($ax, $bx, \&func);
16             my ($x, $y) = Brent($ax, $bx, $cx, \&func, $tol, $itmax);
17              
18             =head1 DESCRIPTION
19              
20             This is an implementation of Brent's method for One-Dimensional
21             minimisation of a function without using derivatives. This algorithm
22             cleverly uses both the Golden Section Search and parabolic
23             interpolation.
24              
25             =head2 FUNCTIONS
26              
27             The functions may be imported by name, or by using the export
28             tag "all".
29              
30             =head3 Minimise1D()
31              
32             Provides a simple interface to the L and L
33             routines.
34              
35             Given a function, an initial guess for the function's
36             minimum, and its scaling, this routine converges
37             to the function's minimum using Brent's method.
38              
39             ($x, $y) = Minimise1D($guess, $scale, \&func);
40              
41             The minimum is reached within a certain tolerance (defaulting 1e-7), and
42             attempts to do so within a maximum number of iterations (defaulting to 100).
43             You may override them by providing alternate values:
44              
45             ($x, $y) = Minimise1D($guess, $scale, \&func, 1.5e-8, 120);
46              
47             =head3 Brent()
48              
49             Given a function and a triplet of abcissas B<$ax>, B<$bx>, B<$cx>, such that
50              
51             =over 4
52              
53             =item 1. B<$bx> is between B<$ax> and B<$cx>, and
54              
55             =item 2. B is less than both B and B),
56              
57             =back
58              
59             Brent() isolates the minimum to a fractional precision of about B<$tol>
60             using Brent's method.
61              
62             A maximum number of iterations B<$itmax> may be specified for this search - it
63             defaults to 100. Returned is a list consisting of the abcissa of the minum
64             and the function value there.
65              
66             =head3 BracketMinimum()
67              
68             Given a function reference B<\&func> and
69             distinct initial points B<$ax> and B<$bx> searches in the downhill
70             direction (defined by the function as evaluated at the initial points)
71             and returns a list of the three points B<$ax>, B<$bx>, B<$cx> which
72             bracket the minimum of the function and the function values at those
73             points.
74              
75             =head1 EXAMPLE
76              
77             use Math::Brent qw(Minimise1D);
78              
79             sub sinc {
80             my $x = shift ;
81             return $x ? sin($x)/$x: 1;
82             }
83              
84             my($x, $y) = Minimise1D(1, 1, \&sinc, 1e-7);
85             print "Minimum is at sinc($x) = $y\n";
86              
87             produces the output
88              
89             Minimum is at sinc(4.4934094397196) = -.217233628211222
90              
91             Anonymous subroutines may also be used as the function reference:
92              
93             my $cubic_ref = sub {my($x) = @_; return 6.25 + $x*$x*(-24 + $x*8));};
94              
95             my($x, $y) = Minimise1D(3, 1, $cubic_ref);
96             print "Minimum of the cubic at $x = $y\n";
97              
98              
99             =head1 BUGS
100              
101             Please report any bugs or feature requests via Github's L
102              
103             =head1 AUTHOR
104              
105             John A.R. Williams B
106              
107             John M. Gamble B (current maintainer)
108              
109             =head1 SEE ALSO
110              
111             "Numerical Recipies: The Art of Scientific Computing"
112             W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling.
113             Cambridge University Press. ISBN 0 521 30811 9.
114              
115             Richard P. Brent, L
116              
117             Professor (Emeritus) Richard Brent has a web page at
118             L
119              
120             =cut
121              
122             package Math::Brent;
123              
124 2     2   38227 use strict;
  2         6  
  2         47  
125 2     2   9 use warnings;
  2         3  
  2         45  
126 2     2   24 use 5.10.1;
  2         14  
127              
128 2     2   9 use Exporter;
  2         2  
  2         241  
129             our (@ISA, @EXPORT_OK, %EXPORT_TAGS);
130             @ISA = qw(Exporter);
131             %EXPORT_TAGS = (
132             all => [qw(
133             FindMinima
134             BracketMinimum
135             Brent Minimise1D
136             ) ],
137             );
138              
139             @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
140              
141             our $VERSION = 0.05;
142              
143 2     2   1526 use Math::VecStat qw(max min);
  2         2391  
  2         136  
144 2     2   1403 use Math::Utils qw(:fortran);
  2         2928  
  2         264  
145 2     2   10 use Carp;
  2         3  
  2         2193  
146              
147             sub Minimise1D
148             {
149 5     5 1 2074 my ($guess, $scale, $func, $tol, $itmax) = @_;
150 5         17 my ($a, $b, $c) = BracketMinimum($guess - $scale, $guess + $scale, $func);
151              
152 5         17 return Brent($a, $b, $c, $func, $tol, $itmax);
153             }
154              
155             #
156             # BracketMinimum
157             #
158             # BracketMinimum is MNBRAK minimum bracketing routine from section 10.1
159             # of Numerical Recipies
160             #
161             # Given a function func, and distinct initial points ax & bx this
162             # routine searches in the downhill direction and returns new points ax,
163             # bx, cx which bracket the minimum. The function values at the 3 points
164             # are returned in fa, fb, fc respectively.
165             #
166              
167             my $GOLD = 0.5 + sqrt(1.25); # Default magnification ratio for intervals is phi.
168             my $GLIMIT = 100.0; # Max magnification for parabolic fit step
169             my $TINY = 1E-20;
170              
171             sub BracketMinimum
172             {
173 5     5 1 8 my ($ax, $bx, $func) = @_;
174 5         17 my ($fa, $fb) = (&$func($ax), &$func($bx));
175              
176             #
177             # Swap the a and b values if we weren't going in
178             # a downhill direction.
179             #
180 5 100       12738 if ($fb > $fa)
181             {
182 2         4 my $t = $ax; $ax = $bx; $bx = $t;
  2         2  
  2         3  
183 2         3 $t = $fa; $fa = $fb; $fb = $t;
  2         3  
  2         3  
184             }
185              
186 5         17 my $cx = $bx + $GOLD * ($bx - $ax);
187 5         19 my $fc = &$func($cx);
188              
189             # Loop here until we bracket
190 5         47 while ($fb >= $fc)
191             {
192             #
193             # Compute U by parabolic extrapolation from
194             # a, b, c. TINY used to prevent div by zero
195             #
196 2         16 my $r = ($bx - $ax) * ($fb - $fc);
197 2         5 my $q = ($bx - $cx) * ($fb - $fa);
198 2         19 my $u = $bx - (($bx - $cx) * $q - ($bx - $ax) * $r)/
199             (2.0 * copysign(max(abs($q - $r), $TINY), $q - $r));
200              
201 2         57 my $ulim = $bx + $GLIMIT * ($cx - $bx); # We won't go further than this
202 2         4 my $fu;
203              
204             #
205             # Parabolic U between B & C - try it
206             #
207 2 50       13 if (($bx - $u) * ($u - $cx) > 0.0)
    50          
    0          
208             {
209 0         0 $fu = &$func($u);
210              
211 0 0       0 if ($fu < $fc)
    0          
212             {
213             # Minimum between B & C
214 0         0 $ax = $bx; $fa = $fb; $bx = $u; $fb = $fu;
  0         0  
  0         0  
  0         0  
215 0         0 next;
216             }
217             elsif ($fu > $fb)
218             {
219             # Minimum between A & U
220 0         0 $cx = $u; $fc = $fu;
  0         0  
221 0         0 next;
222             }
223              
224 0         0 $u = $cx + $GOLD * ($cx - $bx);
225 0         0 $fu = &$func($u);
226             }
227             elsif (($cx - $u) * ($u - $ulim) > 0)
228             {
229             # parabolic fit between C and limit
230 2         5 $fu = &$func($u);
231              
232 2 50       22 if ($fu < $fc)
233             {
234 0         0 $bx = $cx; $cx = $u;
  0         0  
235 0         0 $u = $cx + $GOLD * ($cx - $bx);
236 0         0 $fb = $fc; $fc = $fu;
  0         0  
237 0         0 $fu = &$func($u);
238             }
239             }
240             elsif (($u - $ulim) * ($ulim - $cx) >= 0)
241             {
242             # Limit parabolic U to maximum
243 0         0 $u = $ulim;
244 0         0 $fu = &$func($u);
245             }
246             else
247             {
248             # eject parabolic U, use default magnification
249 0         0 $u = $cx + $GOLD * ($cx - $bx);
250 0         0 $fu = &$func($u);
251             }
252              
253             # Eliminate oldest point & continue
254 2         4 $ax = $bx; $bx = $cx; $cx = $u;
  2         5  
  2         3  
255 2         3 $fa = $fb; $fb = $fc; $fc = $fu;
  2         3  
  2         6  
256             }
257              
258 5         13 return ($ax, $bx, $cx, $fa, $fb, $fc);
259             }
260              
261             #
262             # The complementary step is (3 - sqrt(5))/2, which resolves to 2 - phi.
263             #
264             my $CGOLD = 2 - $GOLD;
265             my $ZEPS = 1e-10;
266              
267             sub Brent
268             {
269 5     5 1 8 my ($ax, $bx, $cx, $func, $tol, $ITMAX) = @_;
270 5         49 my ($d, $u, $x, $w, $v); # ordinates
271 0         0 my ($fu, $fx, $fw, $fv); # function evaluations
272              
273 5   50     53 $ITMAX //= 100;
274 5   100     14 $tol //= 1e-8;
275              
276 5         14 my $a = min($ax, $cx);
277 5         66 my $b = max($ax, $cx);
278              
279 5         55 $x = $w = $v = $bx;
280 5         10 $fx = $fw = $fv = &$func($x);
281 5         22 my $e = 0.0; # will be distance moved on the step before last
282 5         7 my $iter = 0;
283              
284 5         11 while ($iter < $ITMAX)
285             {
286 49         67 my $xm = 0.5 * ($a + $b);
287 49         60 my $tol1 = $tol * abs($x) + $ZEPS;
288 49         58 my $tol2 = 2.0 * $tol1;
289              
290 49 100       192 last if (abs($x - $xm) <= ($tol2 - 0.5 * ($b - $a)));
291              
292 44 100       80 if (abs($e) > $tol1)
293             {
294 39         65 my $r = ($x-$w) * ($fx-$fv);
295 39         48 my $q = ($x-$v) * ($fx-$fw);
296 39         59 my $p = ($x-$v) * $q-($x-$w)*$r;
297              
298 39 100       85 $p = -$p if (($q = 2 * ($q - $r)) > 0.0);
299              
300 39         42 $q = abs($q);
301 39         41 my $etemp = $e;
302 39         40 $e = $d;
303              
304 39 50 66     219 unless ( (abs($p) >= abs(0.5 * $q * $etemp)) ||
      66        
305             ($p <= $q * ($a - $x)) || ($p >= $q * ($b - $x)) )
306             {
307             #
308             # Parabolic step OK here - take it.
309             #
310 34         34 $d = $p/$q;
311 34         35 $u = $x + $d;
312              
313 34 100 100     115 if ( (($u - $a) < $tol2) || (($b - $u) < $tol2) )
314             {
315 5         13 $d = copysign($tol1, $xm - $x);
316             }
317 34         231 goto dcomp; # Skip the golden section step.
318             }
319             }
320              
321             #
322             # Golden section step.
323             #
324 10 100       18 $e = (($x >= $xm) ? $a : $b) - $x;
325 10         14 $d = $CGOLD * $e;
326              
327             #
328             # We arrive here with d from Golden section or parabolic step.
329             #
330 44 100       101 dcomp:
331             $u = $x + ((abs($d) >= $tol1) ? $d : copysign($tol1, $d));
332 44         102 $fu = &$func($u); # 1 &$function evaluation per iteration
333              
334             #
335             # Decide what to do with &$function evaluation
336             #
337 44 100       200 if ($fu <= $fx)
338             {
339 31 100       56 if ($u >= $x)
340             {
341 14         17 $a = $x;
342             }
343             else
344             {
345 17         20 $b = $x;
346             }
347 31         29 $v = $w; $fv = $fw;
  31         47  
348 31         28 $w = $x; $fw = $fx;
  31         31  
349 31         32 $x = $u; $fx = $fu;
  31         32  
350             }
351             else
352             {
353 13 100       27 if ($u < $x)
354             {
355 7         10 $a = $u;
356             }
357             else
358             {
359 6         13 $b = $u;
360             }
361              
362 13 100 100     69 if ($fu <= $fw || $w == $x)
    50 66        
      66        
363             {
364 7         7 $v = $w; $fv = $fw;
  7         8  
365 7         8 $w = $u; $fw = $fu;
  7         8  
366             }
367             elsif ( $fu <= $fv || $v == $x || $v == $w )
368             {
369 6         6 $v = $u; $fv = $fu;
  6         10  
370             }
371             }
372              
373 44         83 $iter++;
374             }
375              
376 5 50       14 carp "Brent Exceed Maximum Iterations.\n" if ($iter >= $ITMAX);
377 5         27 return ($x, $fx);
378             }
379              
380             1;