File Coverage

blib/lib/Algorithm/SpiralSearch.pm
Criterion Covered Total %
statement 65 65 100.0
branch 16 22 72.7
condition 9 9 100.0
subroutine 5 5 100.0
pod 0 1 0.0
total 95 102 93.1


line stmt bran cond sub pod time code
1             package Algorithm::SpiralSearch;
2              
3             #use 5.004;
4 1     1   19758 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         1  
  1         25  
6 1     1   6 use Carp;
  1         4  
  1         97  
7             #use Math::Gradient 0.01;
8 1     1   764 use Math::Gradient;
  1         953  
  1         663  
9              
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14             our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
15             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
16             our @EXPORT = qw(spiral_search);
17             our $VERSION = '1.20';
18              
19             sub spiral_search {
20 18     18 0 19592 my $usage = '($opt_x, $opt_y) = spiral_search($lower_boundx,$upper_boundx,' .
21             '$lower_boundy,$upper_boundy,$iterations,$function,' .
22             "'MAX|MIN')";
23              
24 18         40 my ($lbx, $ubx, $lby, $uby, $iters, $f, $max_or_min) = @_;
25              
26 18 50       77 croak 'A valid input/output funtion reference must be passed in'
27             unless $f =~ /CODE/;
28              
29 18 50       36 croak 'Two or more iterations are required : ' if $iters < 2;
30 18 50       34 croak 'Upper boundary on first parameter must be non-zero : ' if $ubx == 0.0;
31 18 50       30 croak 'Upper boundary on second parameter must be non-zero : '
32             if $uby == 0.0;
33              
34 18 50       92 croak 'Final parameter must be set to MAX or MIN : '
35             unless $max_or_min =~ /MAX|MIN/i;
36              
37             # Set the initial start points to half the distances of the search space
38             # extrema.
39 18         45 my $x_init = ($ubx - $lbx) / 2;
40 18         21 my $y_init = ($uby - $lby) / 2;
41              
42             # Find the gradients of each axis.
43 18         49 my @grad_x = Math::Gradient::gradient($lbx, $ubx, $iters);
44 18         1431 my @grad_y = Math::Gradient::gradient($lby, $uby, $iters);
45              
46 18         1519 my @x = ();
47 18         21 my @y = ();
48 18         22 my @nrv_ary = ();
49 18         30 my $x_inc = $grad_x[1] - $grad_x[0];
50 18         21 my $y_inc = $grad_y[1] - $grad_y[0];
51 18 100       74 my $maximize = $max_or_min =~ /^\s*MIN\s*$/i ? -1 : 1;
52 18         23 my $ret_val = 0;
53 18         19 my $new_ret_val = 0;
54 18         17 my $theta = 0;
55 18         22 my $best_x = 0;
56 18         22 my $best_y = 0;
57 18         18 my $out_of_bounds = 0;
58              
59             # Increase the radius of the search by the following factor
60             # if a better function evaluation is not found.
61 18         17 my $rad_inc = 1.2;
62              
63 18         49 my $bounded = 1;
64 18         17 my $radius_x = 1;
65 18         21 my $radius_y = 1;
66              
67 18         45 for (my $t = $iters; $t >= 1; $t--) {
68             # Follow the spiral inwards.
69 900         1650 $theta = atan2($grad_y[$t-1], $grad_x[$t-1]);
70 900         1939 $x[$t-1] = $x_init + $radius_x * exp(-0.1 * $theta) * cos($t);
71 900         1600 $y[$t-1] = $y_init + $radius_y * exp(-0.1 * $theta) * sin($t);
72              
73             # Make sure our spiral is within boundaries.
74 900 50       2635 if ($bounded) {
75 900 100 100     6729 if ($x[$t-1] < $lbx || $x[$t-1] > $ubx || $y[$t-1] < $lby ||
      100        
      100        
76             $y[$t-1] > $uby)
77             {
78 182         259 $out_of_bounds = 1;
79             } else {
80 718         1005 $out_of_bounds = 0;
81             }
82             }
83              
84             # If our new evaluation point is out of bounds, do not proceed with the
85             # function evaluation. Otherwise, continue.
86 900 100       1285 if (! $out_of_bounds) {
87             # Evaluate the new parameters.
88 718         973 $nrv_ary[$t-1] = $new_ret_val = &{$f}($x[$t-1], $y[$t-1]);
  718         1514  
89              
90             # If the new result was better than the previous, increase the spiral's
91             # radius.
92 718 100       5701 if ($maximize * $new_ret_val > $maximize * $ret_val) {
93 397         427 $radius_x += $rad_inc * $x_inc;
94 397         407 $radius_y += $rad_inc * $y_inc;
95              
96 397         454 $x_init = $x[$t-1];
97 397         547 $y_init = $y[$t-1];
98             }
99              
100 718         1644 $ret_val = $new_ret_val;
101             } else {
102 182         411 $nrv_ary[$t-1] = 0;
103             }
104             }
105              
106             # Find the best return value and its corresponding input coordinates.
107             {
108 18         18 my $m = 0;
  18         18  
109              
110 18         41 for (my $i = 0; $i < @nrv_ary; $i++) {
111 900 100       2456 if ($maximize * $nrv_ary[$i] >= $maximize * $m) {
112 112         150 $best_x = $x[$i];
113 112         122 $best_y = $y[$i];
114 112         263 $m = $nrv_ary[$i];
115             }
116             }
117             }
118              
119 18         271 return($best_x, $best_y);
120             }
121              
122             1;
123              
124             __END__