File Coverage

blib/lib/Algorithm/GoldenSection.pm
Criterion Covered Total %
statement 15 143 10.4
branch 0 34 0.0
condition 0 18 0.0
subroutine 5 14 35.7
pod 0 2 0.0
total 20 211 9.4


line stmt bran cond sub pod time code
1             package Algorithm::GoldenSection;
2              
3 1     1   32439 use warnings;
  1         3  
  1         35  
4 1     1   6 use strict;
  1         6  
  1         37  
5 1     1   5 use Carp;
  1         5  
  1         100  
6 1     1   1156 use Readonly;
  1         4387  
  1         179  
7              
8 1     1   1043 use version; our $VERSION = qv('0.0.2');
  1         2560  
  1         21  
9              
10             =head1 NAME
11              
12             Algorithm::GoldenSection - Golden Section Search Algorithm for one-dimensional minimisation.
13              
14             =cut
15             =head1 VERSION
16              
17             This document describes Algorithm::GoldenSection version 0.0.2
18              
19             =cut
20             =head1 DESCRIPTION
21              
22             This module is an implementation of the Golden Section Search Algorithm for finding minima of a unimodal function.
23             In order to isolate a minimum of a univariate functions the minimum must first be isolated.
24             Consequently the program first bounds a minimum - i.e. the program initially creates a triplet of points:
25             x_low < x_int < x_high, such that f(x_int) is lower than both f(x_low) and f(x_high). Thus we ensure that there
26             is a local minimum within the interval: x_low-x_high. The program then uses the Golde Section Search algorithm
27             to successively narrow down on the bounded region to find the minimum.
28             See http://en.wikipedia.org/wiki/Golden_section_search and
29             http://www.gnu.org/software/gsl/manual/html_node/One-dimensional-Minimization.html.
30              
31             The module provides a Perl5OO interface. Simply construct a Algorithm::GoldenSection object with appropriate parameters
32             - see L. Then call the minimise C. This returns a LIST of the value of x at the minimum, the value of
33             f(x) at the minimum and the number of iterations used to isolate the minimum.
34              
35             =cut
36             =head1 SYNOPSIS
37              
38             use Algorithm::GoldenSection;
39            
40             # Create a Algorithm::GoldenSection object and pass it a CODE reference to the function to be minimised and initials values for x_low and x_int.
41             $gs = Algorithm::GoldenSection->new( { function => sub { my $x = shift; my $b = $x * sin($x) - 2 * cos($x); return $b },
42             x_low => 4,
43             x_int => 4.7,} ) ;
44            
45             # Call minimisation method to bracket and minimise.
46             my ($x_min, $f_min, $iterations) = $gs->minimise;
47              
48             print qq{\nMinimisation results: x a minimum = $x_min, function value at minimum = $f_min. Calculation took $iterations iterations};
49              
50             =cut
51              
52             # package-scoped lexicals
53             Readonly::Scalar my $ouro => 1.618034 ;
54             Readonly::Scalar my $glimite => 100.0 ;
55             Readonly::Scalar my $pequeninho => 1.0e-20 ;
56             Readonly::Scalar my $tolerancia => 3.0e-8; # tolerance
57             Readonly::Scalar my $C => (3-sqrt(5))/2;
58             Readonly::Scalar my $R => 1-$C;
59              
60             #/ I had leaving things for operator precedence. you won´t see A+B*(C-D) whe you mean: A+( B*(C-D) ) - i.e. * binds more tightly that +
61              
62             sub new {
63 0     0 0   my ( $class, $h_ref ) = @_;
64 0 0 0       croak qq{\nArguments must be passed as HASH reference.} if ( ( $h_ref ) && ( ref $h_ref ne q{HASH} ) );
65 0           my $self = {};
66 0           bless $self, $class;
67 0           $self->_check_options($h_ref);
68 0           return $self;
69             }
70              
71             sub _check_options {
72              
73 0     0     my ( $self, $h_ref ) = @_;
74              
75 0 0 0       croak qq{\nOption \x27function\x27 is obrigatory and accepts a CODE reference}
76             if ( ( !exists $h_ref->{function} ) || ( ref $h_ref->{function} ne q{CODE} ) );
77 0 0 0       croak qq{\nOption \x27x_low\x27 requirements a numeric value}
78             if ( ( !exists $h_ref->{x_low} ) || ( $h_ref->{x_low} !~ /\A[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?\z/xms ) );
79 0 0 0       croak qq{\nOption \x27x_low\x27 requirements a numeric value}
80             if ( ( !exists $h_ref->{x_int} ) || ( $h_ref->{x_int} !~ /\A[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?\z/xms ) );
81              
82 0           $self->{function} = $h_ref->{function};
83 0           $self->{x_low} = $h_ref->{x_low};
84 0           $self->{x_int} = $h_ref->{x_int};
85              
86             }
87              
88             sub _switch {
89             # twat did you usual of forgetting @_ and then you didn´t even return from it!
90 0     0     my ( $a, $b, $f_a, $f_b) = @_;
91 0           my $buf = $a;
92 0           my $f_buf = $f_a;
93 0           $a = $b;
94 0           $f_a = $f_b;
95 0           $b = $buf;
96 0           $f_b = $f_buf;
97 0           return ($a, $b, $f_a, $f_b);
98             }
99              
100             sub minimise {
101              
102 0     0 0   my $self = shift;
103            
104             #y bracket interval
105 0           $self->_bracket;
106              
107 0           my $func = $self->{function};
108 0           my $a = $self->{x_low};
109 0           my $b = $self->{x_int};
110 0           my $c = $self->{x_high};
111              
112 0           my $x1;
113             my $x2;
114             # this is not efficient code...
115 0           my $x0 = $self->{x_low};
116 0           my $x3 = $self->{x_high};
117              
118 0 0         if ( abs($c-$b) > abs($b-$a) ) {
119 0           $x1 = $b;
120             #y create new point to try
121 0           $x2 = $b + ( $C * ($c-$b) );
122             }
123             else {
124 0           $x2 = $b;
125             #y create new point to try
126 0           $x1 = $b - ( $C * ($b-$a) );
127             }
128            
129             #y initial function evaluations
130 0           my $f1 = $func->($x1);
131 0           my $f2 = $func->($x2);
132              
133 0           my $counter = 0;
134              
135             #y start iterating...
136 0           while ( abs($x3-$x0) > ( $tolerancia * ( abs($x1) + abs($x2) ) ) ) {
137              
138             #y lets increment here just to make it easier - hence start with 0
139 0           $counter++;
140              
141             #y a possible outcome
142 0 0         if ( $f2 < $f1 ) {
143              
144             #########################################
145             #y choose one of the two - but why the fuck-up with $R multiplication?!?
146             #########################################
147             #y the following is identical to:
148 0           $x0 = $x1;
149 0           $x1 = $x2;
150 0           $x2 = ($R*$x2) + ($C*$x3); #
151 0           $f1 = $f2;
152 0           $f2 = $func->($x2);
153             #########################################
154             # my $x_temp = ($R*$x2) + ($C*$x3);
155             # &_shft3(\$x0,\$x1,\$x2,\$x_temp);
156             # my $f_x_temp = $func->($x2);
157             # &_shft2(\$f1,\$f2,\$f_x_temp);
158             #########################################
159             }
160             #y other possibility
161             else {
162              
163             #########################################
164 0           $x3 = $x2;
165 0           $x2 = $x1;
166 0           $x1 = ($R*$x1) + ($C*$x0);
167 0           $f2 = $f1;
168 0           $f1 = $func->($x1);
169             #########################################
170             # my $x_temp = ($R*$x1) + ($C*$x0);
171             # &_shft3(\$x3,\$x2,\$x1,\$x_temp);
172             # my $f_x_temp = $func->($x1);
173             # &_shft2(\$f2,\$f1,\$f_x_temp);
174             #########################################
175             }
176             }
177              
178 0           my $xmin;
179             my $fmin;
180            
181             #y set final values
182 0 0         if ($f1 < $f2) {
183 0           $xmin = $x1;
184 0           $fmin = $f1;
185             }
186             else {
187 0           $xmin = $x2;
188 0           $fmin = $f2;
189             }
190              
191 0           return $xmin, $fmin, $counter;
192             }
193              
194             sub _bracket {
195            
196 0     0     my $self = shift;
197            
198 0           my $function = $self->{function};
199 0           $a = $self->{x_low};
200 0           $b = $self->{x_int};
201            
202 0           my $f_u;
203 0           my $f_a = $function->($a);
204 0           my $f_b = $function->($b);
205              
206             #y that is downhill
207 0 0         if ($f_b > $f_a ) {
208             #print qq{\n\n**** in this case fb is higher than fa - thus we are going uphill so we need to swap them****\n};
209 0           print qq{\n\nswitch $a, $b, $f_a and $f_b};
210 0           ( $a, $b, $f_a, $f_b) = _switch( $a, $b, $f_a, $f_b);
211 0           print qq{\n\nswitch $a, $b, $f_a and $f_b};
212             }
213              
214             # has higher precedence that + thus: $c = $b+$ouro*($b-$a); is the same as $c = $b+($gold*($b-$a)); - same in C/C++
215              
216             #y WE MAKE A GUESS AT A VALUE OF C
217 0           my $c = $b + ( $ouro * ($b-$a) ); # c 26.18034 and f_c 21.6787847478271
218              
219 0           my $f_c = $function->($c);
220              
221             # (1) by SWAPPING we are sure that f(a) > f(b)! - (2) BUT we must also have f(b) < f(c) in order to have _bracketed our MINIMUM
222              
223 0           while ( $f_b > $f_c ) {
224            
225             #y compute u by parabolic extrapolation - tiny is there just to stop ilegal divisions by 0
226 0           my $r = ($b-$a) * ($f_b-$f_c);
227 0           my $q = ($b-$c) * ($f_b-$f_a);
228 0           my $u = $b - ( ( $b - $c ) * $q - ( $b - $a ) * $r ) / ( 2.0 * &_sign ( &_max ( abs ($q-$r), $pequeninho ), $q-$r ) );
229 0           my $ulim = $b + ( $glimite * ($c-$b) );
230              
231             #y test the possibilities!
232              
233 0 0         if ( ($b-$u)*($u-$c) > 0.0 ) { #y parabolic u is between b and c
    0          
    0          
234 0           $f_u = $function->($u);
235            
236             #y have a minimium between b and c - i.e. is f(u) < f(c) - if so:
237 0 0         if ( $f_u < $f_c ) {
    0          
238              
239 0           $a = $b;
240 0           $b = $u;
241 0           $f_a = $f_b;
242 0           $f_b = $f_u;
243              
244             #/ we´re going to return early here so as we aren´t using any package-scoped vars we will need to feed the object here
245 0           $self->{x_low} = $a;
246 0           $self->{x_int} = $b;
247 0           $self->{x_high} = $c;
248            
249             return
250 0           }
251             elsif ( $f_u > $f_b ) {
252 0           $c = $u;
253 0           $f_c = $f_u;
254            
255             #/ we´re going to return early here so as we aren´t using any package-scoped vars we will need to feed the object here
256 0           $self->{x_low} = $a;
257 0           $self->{x_int} = $b;
258 0           $self->{x_high} = $c;
259            
260             return
261 0           }
262              
263             #y parabolic fit was useless in this case - so we use a default magnification
264 0           $u = $c + ( $ouro * ($c-$b) );
265 0           $f_u = $function->($u);
266             }
267              
268             #y parabolic fit is between c and is not allowed
269             elsif ( ($c-$u)*($u-$ulim) > 0 ) {
270              
271 0           $f_u = $function->($u);
272              
273 0 0         if ( $f_u < $f_c ) {
274              
275 0           my $u_other = $u + ( $ouro * ($u-$c) );
276             #/ this should make b = c, c = u and u = u_other
277 0           &_shft3(\$b,\$c,\$u,$u_other);
278             #/ so as u is now u_other this shouldn´t be a prob
279 0           my $f_u_other = $function->($u_other);
280 0           &_shft3(\$f_b,\$f_c,\$f_u, \$f_u_other);
281             }
282             }
283              
284             #y limit parabolic u to max allowed
285             elsif ( ($u-$ulim)*($ulim-$c) >= 0.0 ) {
286 0           $u = $ulim;
287 0           $f_u = $function->($u);
288             }
289              
290             #y reject parabolic u
291             else {
292 0           $u = $c + ( $ouro * ($c-$b) );
293 0           $f_u = $function->($u);
294             }
295              
296             #y eliminate oldest points and will continue};
297              
298 0           &_shft3(\$a,\$b,\$c,\$u);
299 0           &_shft3(\$f_a,\$f_b,\$f_c,\$f_u);
300            
301             }
302              
303 0 0 0       croak qq{\nThere is a problem - email dsth\@cantab.net.} if ( !$a || !$b || !$c );#|| ( $b > $a ) || ( $b > $c ) );
      0        
304 0           $self->{x_low} = $a;
305 0           $self->{x_int} = $b;
306 0           $self->{x_high} = $c;
307             }
308              
309             sub _sign {
310 0     0     my ($a, $b) = @_;
311 0           my $val = abs $a;
312 0 0         my $sig = $b >= 0 ? q{+} : q{-};
313 0           my $final = $sig.$val;
314             # force numeric context - no real reason
315 0           return 0+$final;
316             }
317              
318             sub _max {
319 0     0     my ($a, $b) = @_;
320 0 0         my $ret = $a >= $b ? $a : $b;
321 0           return $ret;
322             }
323              
324             sub _shft3 {
325 0     0     my ($a, $b, $c, $d) = @_;
326 0           $$a = $$b;
327 0           $$b = $$c;
328 0           $$c = $$d;
329 0           return;
330             }
331              
332             sub _shft2 {
333 0     0     my ($a, $b, $c) = @_;
334 0           $$a = $$b;
335 0           $$b = $$c;
336 0           return;
337             }
338              
339             1; # Magic true value required at end of module
340              
341             __END__