File Coverage

blib/lib/Math/Brent.pm
Criterion Covered Total %
statement 155 184 84.2
branch 44 60 73.3
condition 37 50 74.0
subroutine 11 11 100.0
pod 3 4 75.0
total 250 309 80.9


line stmt bran cond sub pod time code
1             package Math::Brent;
2              
3 4     4   83239 use 5.010001;
  4         16  
4 4     4   20 use strict;
  4         7  
  4         93  
5 4     4   19 use warnings;
  4         11  
  4         105  
6              
7 4     4   19 use Exporter;
  4         7  
  4         517  
8             our (@ISA, @EXPORT_OK, %EXPORT_TAGS);
9             @ISA = qw(Exporter);
10             %EXPORT_TAGS = (
11             all => [qw(
12             BracketMinimum
13             Brent Minimise1D
14             Brentzero
15             ) ],
16             );
17              
18             @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
19              
20             our $VERSION = 1.00;
21              
22 4     4   3125 use Math::VecStat qw(max min);
  4         5107  
  4         288  
23 4     4   3155 use Math::Utils qw(:fortran);
  4         11635  
  4         590  
24 4     4   25 use Carp;
  4         7  
  4         6480  
25             #use Smart::Comments ('###', '####'); # 3 for variables, 4 for 'here we are'.
26              
27             =head1 NAME
28              
29             Math::Brent - Brent's single dimensional function minimisation, and Brent's zero finder.
30              
31             =head1 SYNOPSIS
32              
33             use Math::Brent qw(Minimise1D);
34              
35             my $tolerance = 1e-7;
36             my $itmax = 80;
37              
38             sub sinc {
39             my $x = shift ;
40             return $x ? sin($x)/$x: 1;
41             }
42              
43             my($x, $y) = Minimise1D(1, 1, \&sinc, $tolerance, $itmax);
44              
45             print "Minimum is at sinc($x) = $y\n";
46              
47             or
48              
49             use Math::Brent qw(BracketMinimum Brent);
50              
51             my $tolerance = 1e-7;
52             my $itmax = 80;
53              
54             #
55             # If you want to use the separate functions
56             # instead of a single call to Minimise1D().
57             #
58             my($ax, $bx, $cx, $fa, $fb, $fc) = BracketMinimum($ax, $bx, \&sinc);
59             my($x, $y) = Brent($ax, $bx, $cx, \&sinc, $tolerance, $itmax);
60              
61             print "Minimum is at sinc($x) = $y\n";
62              
63             In either case the output will be C
64              
65             This module has implementations of Brent's method for one-dimensional
66             minimisation of a function without using derivatives. This algorithm
67             cleverly uses both the Golden Section Search and parabolic
68             interpolation.
69              
70             Anonymous subroutines may also be used as the function reference:
71              
72             my $cubic_ref = sub {my($x) = @_; return 6.25 + $x*$x*(-24 + $x*8));};
73              
74             my($x, $y) = Minimise1D(3, 1, $cubic_ref);
75             print "Minimum of the cubic at $x = $y\n";
76              
77             In addition to finding the minimum, there is also an implementation of the
78             Van Wijngaarden-Dekker-Brent Method, used to find a function's root without
79             using derivatives.
80              
81             use Math::Brent qw(Brentzero);
82              
83             my $tolerance = 1e-7;
84             my $itmax = 80;
85              
86             sub wobble
87             {
88             my($t) = @_;
89             return $t - cos($t);
90             }
91              
92             #
93             # Find the zero somewhere between .5 and 1.
94             #
95             $r = Brentzero(0.5, 1.0, \&wobble, $tolerance, $itmax);
96              
97             =head1 EXPORT
98              
99             Each function can be exported by name, or all may be exported by using the tag 'all'.
100              
101             =head2 FUNCTIONS
102              
103             The functions may be imported by name, or by using the export
104             tag "all".
105              
106             =cut
107              
108             =head3 Minimise1D()
109              
110             Provides a simple interface to the L and L
111             routines.
112              
113             Given a function, an initial guess for the function's
114             minimum, and its scaling, this routine converges
115             to the function's minimum using Brent's method.
116              
117             ($x, $y) = Minimise1D($guess, $scale, \&func);
118              
119             The minimum is reached within a certain tolerance (defaulting 1e-7), and
120             attempts to do so within a maximum number of iterations (defaulting to 100).
121             You may override them by providing alternate values:
122              
123             ($x, $y) = Minimise1D($guess, $scale, \&func, 1.5e-8, 120);
124              
125             =cut
126              
127             sub Minimise1D
128             {
129 5     5 1 3519 my ($guess, $scale, $func, $tol, $itmax) = @_;
130 5         26 my ($a, $b, $c) = BracketMinimum($guess - $scale, $guess + $scale, $func);
131              
132 5         27 return Brent($a, $b, $c, $func, $tol, $itmax);
133             }
134              
135             #
136             # BracketMinimum
137             #
138             # BracketMinimum is MNBRAK minimum bracketing routine from section 10.1
139             # of Numerical Recipies
140             #
141              
142             my $GOLD = 0.5 + sqrt(1.25); # Default magnification ratio for intervals is phi.
143             my $GLIMIT = 100.0; # Max magnification for parabolic fit step
144             my $TINY = 1E-20;
145              
146             =head3 BracketMinimum()
147              
148             ($ax, $bx, $cx, $fa, $fb, $fc) = BracketMinimum($ax, $bx);
149              
150             Given a function reference B<\&func> and distinct initial points B<$ax>
151             and B<$bx>, this routine searches in the downhill direction and returns
152             a list of the three points B<$ax>, B<$bx>, B<$cx> which bracket the
153             minimum of the function, along with the function values at those three
154             points, $fa, $fb, $fc.
155              
156             The points B<$ax>, B<$bx>, B<$cx> may then be used in the function Brent().
157              
158             =cut
159              
160             sub BracketMinimum
161             {
162 5     5 1 14 my ($ax, $bx, $func) = @_;
163 5         23 my ($fa, $fb) = (&$func($ax), &$func($bx));
164              
165             #
166             # Swap the a and b values if we weren't going in
167             # a downhill direction.
168             #
169 5 100       341448 if ($fb > $fa)
170             {
171 2         7 my $t = $ax; $ax = $bx; $bx = $t;
  2         5  
  2         4  
172 2         4 $t = $fa; $fa = $fb; $fb = $t;
  2         5  
  2         5  
173             }
174              
175 5         23 my $cx = $bx + $GOLD * ($bx - $ax);
176 5         26 my $fc = &$func($cx);
177              
178             #
179             # Loop here until we bracket
180             #
181 5         72 while ($fb >= $fc)
182             {
183             #
184             # Compute U by parabolic extrapolation from
185             # a, b, c. TINY used to prevent div by zero
186             #
187 2         23 my $r = ($bx - $ax) * ($fb - $fc);
188 2         7 my $q = ($bx - $cx) * ($fb - $fa);
189 2         24 my $u = $bx - (($bx - $cx) * $q - ($bx - $ax) * $r)/
190             (2.0 * copysign(max(abs($q - $r), $TINY), $q - $r));
191              
192 2         91 my $ulim = $bx + $GLIMIT * ($cx - $bx); # We won't go further than this
193 2         5 my $fu;
194              
195             #
196             # Parabolic U between B and C - try it.
197             #
198 2 50       18 if (($bx - $u) * ($u - $cx) > 0.0)
    50          
    0          
199             {
200 0         0 $fu = &$func($u);
201              
202 0 0       0 if ($fu < $fc)
    0          
203             {
204             # Minimum between B and C
205 0         0 $ax = $bx; $fa = $fb; $bx = $u; $fb = $fu;
  0         0  
  0         0  
  0         0  
206 0         0 next;
207             }
208             elsif ($fu > $fb)
209             {
210             # Minimum between A and U
211 0         0 $cx = $u; $fc = $fu;
  0         0  
212 0         0 next;
213             }
214              
215 0         0 $u = $cx + $GOLD * ($cx - $bx);
216 0         0 $fu = &$func($u);
217             }
218             elsif (($cx - $u) * ($u - $ulim) > 0)
219             {
220             # parabolic fit between C and limit
221 2         10 $fu = &$func($u);
222              
223 2 50       52 if ($fu < $fc)
224             {
225 0         0 $bx = $cx; $cx = $u;
  0         0  
226 0         0 $u = $cx + $GOLD * ($cx - $bx);
227 0         0 $fb = $fc; $fc = $fu;
  0         0  
228 0         0 $fu = &$func($u);
229             }
230             }
231             elsif (($u - $ulim) * ($ulim - $cx) >= 0)
232             {
233             # Limit parabolic U to maximum
234 0         0 $u = $ulim;
235 0         0 $fu = &$func($u);
236             }
237             else
238             {
239             # eject parabolic U, use default magnification
240 0         0 $u = $cx + $GOLD * ($cx - $bx);
241 0         0 $fu = &$func($u);
242             }
243              
244             # Eliminate oldest point and continue
245 2         7 $ax = $bx; $bx = $cx; $cx = $u;
  2         5  
  2         5  
246 2         4 $fa = $fb; $fb = $fc; $fc = $fu;
  2         5  
  2         11  
247             }
248              
249 5         22 return ($ax, $bx, $cx, $fa, $fb, $fc);
250             }
251              
252             #
253             # The complementary step is (3 - sqrt(5))/2, which resolves to 2 - phi.
254             #
255             my $CGOLD = 2 - $GOLD;
256             my $ZEPS = 1e-10;
257              
258             =head3 Brent()
259              
260             Given a function and a triplet of abcissas B<$ax>, B<$bx>, B<$cx>, such that
261              
262             =over 4
263              
264             =item 1. B<$bx> is between B<$ax> and B<$cx>, and
265              
266             =item 2. B is less than both B and B),
267              
268             =back
269              
270             Brent() isolates the minimum to a fractional precision of about B<$tol>
271             using Brent's method.
272              
273             A maximum number of iterations B<$itmax> may be specified for this search - it
274             defaults to 100. Returned is a list consisting of the abcissa of the minum
275             and the function value there.
276              
277             =cut
278              
279             sub Brent
280             {
281 5     5 1 15 my ($ax, $bx, $cx, $func, $tol, $ITMAX) = @_;
282 5         44 my ($d, $u, $x, $w, $v); # ordinates
283 0         0 my ($fu, $fx, $fw, $fv); # function evaluations
284              
285 5   50     40 $ITMAX //= 100;
286 5   100     28 $tol //= 1e-8;
287              
288 5         24 my $a = min($ax, $cx);
289 5         113 my $b = max($ax, $cx);
290              
291 5         134 $x = $w = $v = $bx;
292 5         19 $fx = $fw = $fv = &$func($x);
293 5         38 my $e = 0.0; # will be distance moved on the step before last
294 5         10 my $iter = 0;
295              
296 5         23 while ($iter < $ITMAX)
297             {
298 49         120 my $xm = 0.5 * ($a + $b);
299 49         116 my $tol1 = $tol * abs($x) + $ZEPS;
300 49         106 my $tol2 = 2.0 * $tol1;
301              
302 49 100       205 last if (abs($x - $xm) <= ($tol2 - 0.5 * ($b - $a)));
303              
304 44 100       163 if (abs($e) > $tol1)
305             {
306 39         88 my $r = ($x-$w) * ($fx-$fv);
307 39         133 my $q = ($x-$v) * ($fx-$fw);
308 39         100 my $p = ($x-$v) * $q-($x-$w)*$r;
309              
310 39 100       158 $p = -$p if (($q = 2 * ($q - $r)) > 0.0);
311              
312 39         74 $q = abs($q);
313 39         70 my $etemp = $e;
314 39         66 $e = $d;
315              
316 39 50 66     439 unless ( (abs($p) >= abs(0.5 * $q * $etemp)) or
      66        
317             ($p <= $q * ($a - $x)) or ($p >= $q * ($b - $x)) )
318             {
319             #
320             # Parabolic step OK here - take it.
321             #
322 34         64 $d = $p/$q;
323 34         62 $u = $x + $d;
324              
325 34 100 100     211 if ( (($u - $a) < $tol2) or (($b - $u) < $tol2) )
326             {
327 5         36 $d = copysign($tol1, $xm - $x);
328             }
329 34         429 goto dcomp; # Skip the golden section step.
330             }
331             }
332              
333             #
334             # Golden section step.
335             #
336 10 100       34 $e = (($x >= $xm) ? $a : $b) - $x;
337 10         23 $d = $CGOLD * $e;
338              
339             #
340             # We arrive here with d from Golden section or parabolic step.
341             #
342 44 100       156 dcomp:
343             $u = $x + ((abs($d) >= $tol1) ? $d : copysign($tol1, $d));
344 44         193 $fu = &$func($u); # 1 &$function evaluation per iteration
345              
346             #
347             # Decide what to do with &$function evaluation
348             #
349 44 100       354 if ($fu <= $fx)
350             {
351 31 100       86 if ($u >= $x)
352             {
353 14         27 $a = $x;
354             }
355             else
356             {
357 17         33 $b = $x;
358             }
359 31         59 $v = $w; $fv = $fw;
  31         51  
360 31         57 $w = $x; $fw = $fx;
  31         46  
361 31         52 $x = $u; $fx = $fu;
  31         61  
362             }
363             else
364             {
365 13 100       54 if ($u < $x)
366             {
367 7         17 $a = $u;
368             }
369             else
370             {
371 6         13 $b = $u;
372             }
373              
374 13 100 100     120 if ($fu <= $fw or $w == $x)
    50 66        
      66        
375             {
376 7         14 $v = $w; $fv = $fw;
  7         13  
377 7         13 $w = $u; $fw = $fu;
  7         16  
378             }
379             elsif ( $fu <= $fv or $v == $x or $v == $w )
380             {
381 6         12 $v = $u; $fv = $fu;
  6         14  
382             }
383             }
384              
385 44         160 $iter++;
386             }
387              
388 5 50       27 carp "Brent Exceed Maximum Iterations.\n" if ($iter >= $ITMAX);
389 5         31 return ($x, $fx);
390             }
391              
392             sub Brentzero
393             {
394 9     9 0 3208 my($a, $b, $func, $tol, $ITMAX) = @_;
395 9         26 my $fa = &$func($a);
396 9         264 my $fb = &$func($b);
397              
398 9 50 66     267 if (($fa > 0.0 and $fb > 0.0) or ($fa < 0.0 and $fb < 0.0))
      66        
      33        
399             {
400 0         0 carp "Brentzero(): root was not bracketed by [$a, $b].";
401 0         0 return undef;
402             }
403              
404 9   50     36 $ITMAX //= 100;
405 9   100     39 $tol //= 1e-8;
406              
407 9         13 my($c, $fc) = ($b, $fb);
408 9         11 my($d, $e);
409 9         12 my $iter = 0;
410              
411 9         29 while ($iter < $ITMAX)
412             {
413             #
414             # Adjust bounding interval $d.
415             #
416             ### iteration: $iter
417             ### a: $a
418             ### b: $b
419             ### fa: $fa
420             ### fb: $fb
421             ### fc: $fc
422             #
423 75 100 100     491 if (($fb > 0.0 and $fc > 0.0) or ($fb < 0.0 and $fc < 0.0))
      100        
      66        
424             {
425 50         62 $fc = $fa;
426 50         68 $c = $a;
427 50         56 $d = $b - $a;
428 50         70 $e = $d;
429             }
430              
431 75 100       176 if (abs($fc) < abs($fb))
432             {
433 16         19 $a = $b;
434 16         19 $b = $c;
435 16         20 $c = $a;
436 16         19 $fa = $fb;
437 16         17 $fb = $fc;
438 16         23 $fc = $fa;
439             }
440              
441             #
442             # Convergence check.
443             #
444             ### a: $a
445             ### b: $b
446             ### c: $c
447             ### d: $d
448             ### fa: $fa
449             ### fb: $fb
450             ### fc: $fc
451             #
452 75         107 my $xm = ($c - $b) * 0.5;
453 75         124 my $tol1 = 2.0 * $ZEPS * abs($b) + ($tol * 0.5);
454              
455             #
456             ### tol1: $tol1
457             ### xm: $xm
458             #
459 75 100 66     323 return $b if (abs($xm) <= $tol1 or $fb == 0.0);
460              
461 66 100 66     305 if (abs($e) >= $tol1 and abs($fa) > abs($fb))
462             {
463             #
464             # Attempt inverse quadratic interpolation.
465             #
466             #### Branch (abs(e) >= tol1 and abs(fa) > abs(fb))
467             #
468 57         62 my($p, $q);
469 57         71 my $s = $fb/$fa;
470              
471 57 100       94 if ($a == $c)
472             {
473             #### Branch (a == c)
474 41         53 $p = 2.0 * $xm * $s;
475 41         48 $q = 1.0 - $s;
476             }
477             else
478             {
479             #### Branch (a != c)
480 16         21 my $r = $fb/$fc;
481 16         24 $q = $fa/$fc;
482 16         36 $p = $s * (2.0 * $xm * $q * ($q - $r) -
483             ($b - $a) * ($r - 1.0));
484 16         54 $q = ($q - 1.0) * ($r - 1.0) * ($s - 1.0);
485             }
486              
487             #
488             # Check if in bounds.
489             #
490             ### q: $q
491             ### p: $p
492             ### s: $s
493             ### e: $e
494             #
495 57 100       123 $q = - $q if ($p > 0.0);
496 57         61 $p = abs($p);
497 57         96 my $min1 = 3.0 * $xm * $q - abs($tol1 * $q);
498 57         72 my $min2 = abs($e * $q);
499              
500 57 50       144 if (2.0 * $p < min($min1, $min2))
501             {
502             #
503             # Interpolation worked, use it.
504             #
505             #### Branch (2.0 * p < min(min1, min2))
506             #
507 57         667 $e = $d;
508 57         96 $d = $p/$q;
509             }
510             else
511             {
512             #
513             # Interpolation failed, use bisection.
514             #
515             #### Branch (2.0 * p >= min(min1, min2))
516             #
517 0         0 $d = $xm;
518 0         0 $e = $d;
519             }
520             }
521             else
522             {
523             #
524             # Bounds decreasing too slowly for
525             # quadratic interpolation, use bisection.
526             #
527 9         10 $d = $xm;
528 9         13 $e = $d;
529             }
530              
531             #
532             # Move last best guess to $a.
533             #
534 66         85 $a = $b;
535 66         67 $fa = $fb;
536              
537             #
538             # Calculate the next guess.
539             #
540 66 100       143 $b += (abs($d) > $tol1)? $d: copysign($tol1, $xm);
541 66         175 $fb = &$func($b);
542 66         2767 $iter++;
543             }
544              
545 0 0         carp "Brentzero Exceed Maximum Iterations.\n" if ($iter >= $ITMAX);
546 0           return $a;
547             }
548              
549             1;
550             __END__