File Coverage

blib/lib/Math/Amoeba.pm
Criterion Covered Total %
statement 127 139 91.3
branch 28 38 73.6
condition 3 3 100.0
subroutine 13 13 100.0
pod 4 4 100.0
total 175 197 88.8


line stmt bran cond sub pod time code
1             # This library file contains Amoeba n-D Minimisation routine for Perl
2             # $Id: Amoeba.pm,v 1.2 1995/12/24 12:37:46 willijar Exp $
3             # $Id: Amoeba.pm,v 1.2 1995/12/24 12:37:46 willijar Exp $
4              
5             package Math::Amoeba;
6              
7 1     1   29387 use strict;
  1         3  
  1         36  
8 1     1   6 use warnings;
  1         2  
  1         42  
9              
10             our $VERSION = 0.05;
11              
12 1     1   4 use Carp;
  1         6  
  1         101  
13 1     1   5 use constant TINY => 1e-16;
  1         1  
  1         85  
14              
15 1     1   4 use Exporter;
  1         2  
  1         1382  
16             our @ISA=qw(Exporter);
17             our @EXPORT_OK=qw(ConstructVertices EvaluateVertices Amoeba MinimiseND);
18              
19             =head1 NAME
20              
21             Math::Amoeba - Multidimensional Function Minimisation
22              
23             =head1 SYNOPSIS
24              
25             use Math::Amoeba qw(ConstructVertices EvaluateVertices Amoeba MinimiseND);
26             my ($vertice,$y)=MinimiseND(\@guess,\@scales,\&func,$tol,$itmax,$verbose);
27             my @vertices=ConstructVertices(\@vector,\@offsets);
28             my @y=EvaluateVertices(\@vertices,\&func);
29             my ($vertice,$y)=Amoeba(\@vertices,\@y,\&func,$tol,$itmax,$verbose);
30              
31             =head1 DESCRIPTION
32              
33             This is an implimenation of the Downhill Simpex Method in
34             Multidimensions (Nelder and Mead) for finding the (local) minimum of a
35             function. Doing this in Perl makes it easy for that function to
36             actually be the output of another program such as a simulator.
37              
38             Arrays and the function are passed by reference to the routines.
39              
40             =over
41              
42             =item C
43              
44             The simplest use is the B function. This takes a reference
45             to an array of guess values for the parameters at the function
46             minimum, a reference to an array of scales for these parameters
47             (sensible ranges around the guess in which to look), a reference to
48             the function, a convergence tolerence for the minimum, the maximum
49             number of iterations to be taken and the verbose flag (default ON).
50             It returns an array consisting of a reference to the function parameters
51             at the minimum and the value there.
52              
53             =item C
54              
55             The B function is the actual implimentation of the Downhill
56             Simpex Method in Multidimensions. It takes a reference to an array of
57             references to arrays which are the initial n+1 vertices (where n is
58             the number of function parameters), a reference to the function
59             valuation at these vertices, a reference to the function, a
60             convergence tolerence for the minimum, the maximum number of
61             iterations to be taken and the verbose flag (default ON).
62             It returns an array consisting of a reference to the function parameters
63             at the minimum and the value there.
64              
65             =item C
66              
67             The B is used by B to construct the
68             initial vertices for B as the initial guess plus the parameter
69             scale parameters as vectors along the parameter axis.
70              
71             =item C
72              
73             The B takes these set of vertices, calling the
74             function for each one and returning the vector of results.
75              
76             =back
77              
78             =head1 EXAMPLE
79              
80             use Math::Amoeba qw(MinimiseND);
81             sub afunc {
82             my ($a,$b)=@_;
83             print "$a\t$b\n";
84             return ($a-7)**2+($b+3)**2;
85             }
86             my @guess=(1,1);
87             my @scale=(1,1);
88             ($p,$y)=MinimiseND(\@guess,\@scale,\&afunc,1e-7,100);
89             print "(",join(',',@{$p}),")=$y\n";
90              
91             produces the output
92              
93             (6.99978191653352,-2.99981241563247)=1.00000008274829
94              
95             =head1 LICENSE
96              
97             PERL
98              
99             =cut
100              
101             my ($ALPHA,$BETA,$GAMMA)=(1.0,0.5,2.0);
102              
103             sub MinimiseND {
104 1     1 1 83 my ($guesses,$scales,$func,$tol,$itmax, $verbose)=@_;
105 1         5 my @p=ConstructVertices($guesses,$scales);
106 1         5 my @y=EvaluateVertices(\@p,$func);
107 1         5 return Amoeba(\@p,\@y,$func,$tol,$itmax, $verbose);
108             }
109              
110             sub ConstructVertices {
111             # given 2 vector references constructs an amoeba
112             # returning the vertices
113 1     1 1 1 my ($vector,$ofs)=@_;
114 1         2 my $n=$#{$vector};
  1         2  
115 1         3 my @vector=@{$vector};
  1         3  
116 1         2 my (@p,@y,$i);
117              
118 1         2 $p[0]=[]; @{$p[0]}=@{$vector};
  1         1  
  1         2  
  1         2  
119 1         4 for($i=0; $i<=$n; $i++) {
120 9         12 my $v=[]; @{$v}=@{$vector};
  9         10  
  9         21  
  9         13  
121 9         16 $v->[$i]+=$ofs->[$i];
122 9         25 $p[$i+1]=$v;
123             }
124 1         6 return @p;
125             }
126              
127             sub EvaluateVertices {
128             # evaluates functions for all vertices of the amoeba
129 1     1 1 3 my ($p,$func)=@_;
130 1         1 my ($i,@y);
131 1         3 for($i=0; $i<=$#{$p}; $i++) {
  11         233  
132 10         11 $y[$i]=&$func(@{$p->[$i]});
  10         21  
133             }
134 1         5 return @y;
135             }
136              
137             sub Amoeba {
138              
139 1     1 1 2 my ($p,$y,$func,$ftol,$itmax, $verbose)=@_;
140            
141 1         9 my $n=$#{$p}; # no points
  1         2  
142            
143             # Default parameters
144 1 50       3 $verbose = (defined($verbose)) ? $verbose : 1;
145 1 50       3 if (!$itmax) { $itmax=200; }
  0         0  
146 1 50       3 if (!$ftol) { $ftol=1e-6; }
  0         0  
147              
148             # Member variables
149 1         2 my ($i,$j);
150 1         2 my $iter=0;
151 1         1 my ($ilo, $inhi, $ihi);
152              
153 0         0 my ($pbar, $pr, $pe, $pc, $ypr, $ype, $ypc);
154              
155             # To control the recalculation of centroid
156 1         2 my $recalc = 1;
157 1         2 my $ihi_o;
158              
159             # Loop until any of stopping conditions hit
160 1         1 while (1)
161             {
162 1482         2185 ($ilo, $inhi, $ihi) = _FindMarkers($y);
163              
164             # Stopping conditions
165 1482         3684 my $rtol = 2*abs($y->[$ihi]-$y->[$ilo])/(abs($y->[$ihi])+abs($y->[$ilo])+TINY);
166 1482 100       2597 if ($rtol<$ftol) { last; }
  1         4  
167 1481 50       2374 if ($iter++>$itmax) {
168 0 0       0 carp "Amoeba exceeded maximum iterations\n" if ($verbose);
169 0         0 last;
170             }
171              
172             # Determine the Centroid
173 1481 100       1934 if ($recalc) {
174 1         4 $pbar = _CalcCentroid($p, $ihi);
175             } else {
176 1480         3058 _AdjustCentroid($pbar, $p, $ihi_o, $ihi);
177             }
178              
179             # Reset the re-calculation flag, and remember the current highest
180 1481         1391 $recalc = 0;
181              
182             # Determine the reflection point, evaluate its value
183 1481         2463 $pr = _CalcReflection($pbar, $p->[$ihi], $ALPHA);
184 1481         3531 $ypr = &$func(@$pr);
185              
186             # if it gives a better value than best point, try an
187             # additional extrapolation by a factor gamma, accept best
188 1481 100       26738 if ($ypr < $y->[$ilo]) {
    100          
189              
190 339         624 $pe = _CalcReflection($pbar, $pr, -$GAMMA);
191 339         843 $ype=&$func(@$pe);
192 339 100       5734 if ($ype < $y->[$ilo]) {
193 105         125 $p->[$ihi] = $pe; $y->[$ihi] = $ype;
  105         176  
194             }
195             else {
196 234         298 $p->[$ihi] = $pr; $y->[$ihi] = $ypr;
  234         405  
197             }
198             }
199             # if reflected point worse than 2nd highest
200             elsif ($ypr >= $y->[$inhi]) {
201              
202             # if it is better than highest, replace it
203 428 100       815 if ($ypr < $y->[$ihi] ) {
204 62         80 $p->[$ihi] = $pr; $y->[$ihi] = $ypr;
  62         102  
205             }
206              
207             # look for an intermediate lower point by performing a
208             # contraction of the simplex along one dimension
209 428         785 $pc = _CalcReflection($pbar, $p->[$ihi], -$BETA);
210 428         962 $ypc = &$func(@$pc);
211            
212             # if contraction gives an improvement, accept it
213 428 50       7316 if ($ypc < $y->[$ihi]) {
214 428         525 $p->[$ihi] = $pc; $y->[$ihi] = $ypc;
  428         666  
215             }
216             # otherwise cant seem to remove high point
217             # so contract around lo (best) point
218             else {
219 0         0 for($i=0; $i<=$n; $i++) {
220 0 0       0 if ($i!=$ilo) {
221 0         0 $p->[$i] = _CalcReflection($p->[$ilo], $p->[$i], -$BETA);
222 0         0 $y->[$i] = &$func(@{$p->[$i]});
  0         0  
223             }
224             }
225 0         0 $recalc = 1;
226             }
227             }
228             # if reflected point is in-between lowest and 2nd highest
229             else {
230 714         803 $p->[$ihi] = $pr; $y->[$ihi] = $ypr;
  714         1065  
231             }
232            
233             # Remember the replacing position and its value
234 1481         1705 $ihi_o = $ihi;
235             }
236              
237 1         8 return ($p->[$ilo],$y->[$ilo]);
238             }
239              
240              
241             # Helper function - find the lowest, 2nd highest and highest position
242             sub _FindMarkers
243             {
244 1482     1482   1531 my $y = shift;
245            
246 1482         1351 my ($ilo, $inhi, $ihi);
247 0         0 my ($i, $n);
248              
249 1482         1442 $n = @$y - 1;
250            
251 1482         1337 $ilo=0;
252 1482 100       2482 if ($y->[0]>$y->[1]) {
253 637         574 $ihi=0; $inhi=1;
  637         686  
254             }
255             else {
256 845         776 $ihi=1; $inhi=0;
  845         862  
257             }
258              
259 1482         2905 for($i = 0; $i <= $n; $i++) {
260 14820 100       27186 if ($y->[$i] < $y->[$ilo]) { $ilo = $i; }
  2728         2571  
261 14820 100 100     59565 if ($y->[$i] > $y->[$ihi]) { $inhi = $ihi; $ihi = $i; }
  2089 100       2031  
  2089         3780  
262 2166         8286 elsif ($y->[$i] > $y->[$inhi] && $ihi != $i) { $inhi = $i; }
263             }
264              
265 1482         3078 return ($ilo, $inhi, $ihi);
266             }
267              
268             # Determine the centroid (except the highest point)
269             sub _CalcCentroid
270             {
271 1     1   2 my ($p, $ihi) = @_;
272 1         1 my ($i, $j, $n);
273            
274 1         2 $n = @$p - 1;
275              
276 1         2 my $pbar = [];
277 1         4 for($j=0; $j<$n; $j++) {
278 9         16 for($i=0; $i<=$n; $i++) {
279 90 100       146 if ($i!=$ihi) {
280 81         171 $pbar->[$j] += $p->[$i][$j];
281             }
282             }
283 9         20 $pbar->[$j] /= $n;
284             }
285              
286 1         3 return $pbar;
287             }
288              
289             # Adjust the centroid only
290             sub _AdjustCentroid
291             {
292 1480     1480   1791 my ($pbar, $p, $ihi_o, $ihi) = @_;
293 1480         1296 my ($j, $n);
294              
295 1480         1378 $n = @$pbar;
296            
297 1480 50       2592 if ($ihi_o != $ihi) {
298 1480         2794 for($j=0; $j<$n; $j++) {
299 13320         32826 $pbar->[$j] += ($p->[$ihi_o][$j] - $p->[$ihi][$j]) / $n;
300             }
301             }
302             }
303              
304             # Determine the reflection point
305             sub _CalcReflection
306             {
307 2248     2248   2595 my ($p1, $p2, $scale) = @_;
308 2248         1878 my $j;
309              
310 2248         2122 my $n = @$p1;
311              
312 2248         2589 my $pr = [];
313 2248         4691 for($j=0; $j<$n; $j++) {
314 20232         51828 $pr->[$j] = $p1->[$j] + $scale*($p1->[$j]-$p2->[$j]);
315             }
316              
317 2248         3673 return $pr;
318             }
319              
320             return 1;
321              
322             __END__