File Coverage

blib/lib/Math/Evol.pm
Criterion Covered Total %
statement 132 234 56.4
branch 55 122 45.0
condition 8 21 38.1
subroutine 5 9 55.5
pod 4 7 57.1
total 204 393 51.9


line stmt bran cond sub pod time code
1             # Math::Evol.pm
2             #########################################################################
3             # This Perl module is Copyright (c) 2002, Peter J Billam #
4             # c/o P J B Computing, www.pjb.com.au #
5             # #
6             # This module is free software; you can redistribute it and/or #
7             # modify it under the same terms as Perl itself. #
8             #########################################################################
9              
10             package Math::Evol;
11 1     1   389 no strict;
  1         2  
  1         215  
12             $VERSION = '1.13';
13             # gives a -w warning, but I'm afraid $VERSION .= ''; would confuse CPAN
14             require Exporter;
15             @ISA = qw(Exporter);
16             @EXPORT = qw(evol select_evol text_evol);
17             @EXPORT_OK = qw(arr2txt gaussn);
18              
19             # various epsilons used in convergence testing ...
20             $ea = 0.00000000000001; # absolute stepsize
21             $eb = 0.000000001; # relative stepsize
22             $ec = 0.0000000000000001; # absolute error
23             $ed = 0.00000000001; # relative error
24              
25             sub new {
26 0     0 0 0 my $arg1 = shift;
27 0   0     0 my $class = ref($arg1) || $arg1; # can be used as class or instance method
28 0         0 my $self = {}; # ref to an empty hash
29 0         0 bless $self, $class;
30 0         0 $self->_initialise();
31 0         0 return $self;
32             }
33              
34              
35 4     4 1 1330 sub evol { my ( $xbref,$smref, $func_ref,$constrain_ref, $tm) = @_;
36 4 50 33     41 if (ref $xbref ne 'ARRAY') {
    50          
    50          
    50          
37 0         0 die "Math::Evol::evol 1st arg must be an array ref\n";
38             } elsif (ref $smref ne 'ARRAY') {
39 0         0 die "Math::Evol::evol 2nd arg must be an array ref\n";
40             } elsif (ref $func_ref ne 'CODE') {
41 0         0 die "Math::Evol::evol 3rd arg must be a function ref\n";
42             } elsif ($constrain_ref && (ref $constrain_ref) ne 'CODE') {
43 0         0 die "Math::Evol::evol 4th arg must be a function ref\n";
44             }
45 4         15 my @xb = @$xbref;
46 4         8 my @sm = @$smref;
47 4 50       12 if (! $tm) { $tm = 10.0; }
  0         0  
48 4         6 $tm = abs $tm;
49              
50 4         7 my $debug = 0;
51 4         8 my $n = scalar @xb; # number of variables
52 1     1   485 my $nm1 = $n + $[ - 1; # last index
  1         242  
  1         1946  
  4         19  
53 4         24 my ($usr_o, $sys_o) = times;
54 4         9 my ($fc, $rel_limit); # used to test convergence
55              
56             # step-size adjustment stuff, courtesy Rechenberg ...
57 4         0 my @l; foreach (1 .. 10) { push @l, $n*$_/5.0; } # 6
  4         12  
  40         65  
58 4         8 my $le = $n+$n; my $lm = 0; my $lc = 0;
  4         7  
  4         7  
59              
60 4 50       11 if ($constrain_ref) { @xb = &$constrain_ref(@xb); }
  4         10  
61 4         53 $fb = &$func_ref(@xb); $fc = $fb;
  4         42  
62 4         6 while (1) {
63 24192         56234 foreach $i ($[ .. $nm1) { $x[$i] = $xb[$i]+&gaussn($sm[$i]); }
  96768         177673  
64 24192 50       49830 if ($constrain_ref) {
65 24192         44994 @x = &$constrain_ref(@x);
66 24192 50       220363 warn 'new @x is '.&arr2txt(@x) if $debug;
67             }
68 24192         43053 $ff = &$func_ref (@x); # find new objective function
69 24192 100       227701 if ($ff <= $fb) { $le++; $fb=$ff; @xb=@x; }
  4756         6556  
  4756         6337  
  4756         9500  
70 24192 100       33173 $lm++; if ($lm < $n) { next; }
  24192         46663  
  18144         25173  
71              
72             # adjust the step sizes ...
73 6048         15848 my $k = 0.85 ** ($n+$n <=> $le-$l[$[]);
74 6048         14041 foreach $i ($[ .. $nm1) {
75 24192         34663 $sm[$i] *= $k;
76 24192         34950 $rel_limit = abs ($eb * $xb[$i]);
77 24192 50       48940 if ($sm[$i] < $rel_limit) { $sm[$i] = $rel_limit; }
  0         0  
78 24192 50       50772 if ($sm[$i] < $ea) { $sm[$i] = $ea; }
  0         0  
79             }
80             # could i_l%10 the index as in lua, rather than shift and push as here
81 6048         8623 shift @l; push (@l, $le); $lm = 0;
  6048         9854  
  6048         8063  
82 6048 50       11599 if ($debug) {
83 0         0 warn "le=$le l[0]=$l[$[] n=$n k=$k\n";
84 0         0 warn "new step sizes sm = ".&arr2txt(@sm);
85 0         0 warn "new l = ".&arr2txt(@l);
86             }
87              
88 6048 50       12008 if (defined $ea) { $ea = abs $ea; }
  6048         8758  
89 6048 50       11161 if (defined $eb) { $eb = abs $eb; }
  6048         8227  
90 6048 100       11294 if (defined $ec) { $ec = abs $ec; }
  300         408  
91 6048 100       11189 if (defined $ed) { $ed = abs $ed; }
  250         353  
92 6048 50       11638 warn sprintf("fb=%g fc=%g ec=%g ed=%g\n",$fb,$fc,$ec,$ed) if $debug;
93 6048         7937 $lc++;
94 6048 100       11580 if ($lc < 25) {
95 5807         14318 my ($usr_n, $sys_n) = times;
96 5807 100       13020 if (($usr_n+$sys_n - $usr_o-$sys_o) > $tm) { # out of time ?
97 1 50       20 warn "exceeded $tm seconds\n" if $debug;
98 1         9 return (\@xb, \@sm, $fb, 0); # return best current value
99 5806         8968 } else { next;
100             }
101             }
102 241 100 100     632 if ((defined $ec) && (($fc-$fb) <= $ec)) {
103 1 50       5 warn "converged absolutely\n" if $debug;
104 1         7 return (\@xb, \@sm, $fb, 1); # 29
105             }
106 240 100 100     604 if ((defined $ed) && (($fc-$fb)/$ed <= abs($fc))) {
107 2 50       7 warn "converged relativelty\n" if $debug;
108 2         12 return (\@xb, \@sm, $fb, 1);
109             }
110 238         370 $lc = 0; $fc = $fb; next;
  238         330  
  238         362  
111             }
112             }
113              
114 1     1 1 795 sub select_evol { my ($xbref,$smref,$func_ref,$constrain_ref,$nchoices) = @_;
115 1 50 33     15 if (ref $xbref ne 'ARRAY') {
    50          
    50          
    50          
116 0         0 die "Math::Evol::select_evol 1st arg must be an array ref\n";
117             } elsif (ref $smref ne 'ARRAY') {
118 0         0 die "Math::Evol::select_evol 2nd arg must be an array ref\n";
119             } elsif (ref $func_ref ne 'CODE') {
120 0         0 die "Math::Evol::select_evol 3rd arg must be a function ref\n";
121             } elsif ($constrain_ref && (ref $constrain_ref) ne 'CODE') {
122 0         0 die "Math::Evol::select_evol 4th arg must be a function ref\n";
123             }
124 1         5 my @xb = @$xbref;
125 1         3 my @sm = @$smref;
126 1 50       4 if (! $nchoices) { $nchoices = 1; }
  0         0  
127              
128 1         2 my $debug = 0;
129 1         3 my $n = scalar @xb; # number of variables
130             # warn and return if $n==0, else $test_every=0 and we get a div by 0
131 1 50       3 if ($n==0) { die("Math::Evol::select_evol 1st arg was an empty array\n"); }
  0         0  
132 1         5 my $nm1 = $n + $[ - 1; # last index
133 1         3 my ($choice, $continue);
134              
135             # step-size adjustment stuff, courtesy Rechenberg
136             # modified from Schwefel's sliding flat window average to an EMA
137 1         4 my $desired_success_rate = 1.0 - 0.8**$nchoices;
138 1         2 my $success_rate = $desired_success_rate;
139             # test every 5*$n binary choices equivalent - 10*$n is just too slow.
140 1         2 my $lm = 0;
141 1         5 my $test_every = 5 * $n * (log 2) / (log ($nchoices+1));
142 1         18 my $ema = exp (-1.0 / $test_every);
143 1         2 my $one_m_ema = 1.0 - $ema;
144 1 50       3 warn sprintf
145             ("n=$n nchoices=$nchoices success_rate=%g test_every=%g ema=%g\n",
146             $success_rate, $test_every, $ema) if $debug;
147              
148 1 50       8 if ($constrain_ref) { @xb = &$constrain_ref(@xb); }
  0         0  
149 1         4 my @func_args; # array of refs to arrays
150 1         2 while () {
151 1142         1547 my @x; #XXX?
152 1142         2005 @func_args = ( \@xb );
153 1142         1827 foreach (1 .. $nchoices) {
154 1142         2369 foreach $i ($[ .. $nm1) {
155 4568         8054 $x[$i] = $xb[$i]+&gaussn($sm[$i]);
156             }
157 1142 50       2291 if ($constrain_ref) { @x = &$constrain_ref(@x); }
  0         0  
158 1142         2419 push @func_args, [ @x ];
159             }
160 1142         2110 ($choice, $continue) = &$func_ref(@func_args);
161 1142 100       23331 if ($choice > $[) {
162 57         85 @xb = @{$func_args[$choice]};
  57         118  
163 57         84 $success_rate = $one_m_ema + $ema*$success_rate;
164             } else {
165 1085         1578 $success_rate = $ema*$success_rate;
166             }
167 1142 50       2289 warn "success_rate=$success_rate\n" if $debug;
168 1142 100       2148 if (!$continue) { return (\@xb, \@sm); }
  1         9  
169 1141 100       1517 $lm++; if ($lm < $test_every) { next; }
  1141         2201  
  1084         1795  
170              
171             # adjust the step sizes ...
172 57         99 my $k = 0.85 ** ($desired_success_rate <=> $success_rate);
173 57 50       112 warn "success_rate=$success_rate k=$k\n" if $debug;
174 57         131 foreach $i ($[ .. $nm1) {
175 228         321 $sm[$i] *= $k;
176 228         346 $rel_limit = abs ($eb * $xb[$i]);
177 228 50       447 if ($sm[$i] < $rel_limit) { $sm[$i] = $rel_limit; }
  0         0  
178 228 50       479 if ($sm[$i] < $ea) { $sm[$i] = $ea; }
  0         0  
179             }
180 57 50       111 warn "new step sizes sm = ".&arr2txt(@sm) if $debug;
181 57         95 $lm = 0;
182             }
183             }
184              
185 0     0 1 0 sub text_evol { my ($text, $nchoices); local ($func_ref);
  0         0  
186 0         0 ($text, $func_ref, $nchoices) = @_;
187 0 0       0 return unless $text;
188 0 0       0 if (ref $func_ref ne 'CODE') {
189 0         0 die "Math::Evol::text_evol 2nd arg must be a function ref\n";
190             }
191 0 0       0 if (! $nchoices) { $nchoices = 1; }
  0         0  
192              
193 0         0 my $debug = 0;
194 0         0 local @text = split ("\n", $text);
195 0         0 my ($linenum,$m,@xb,@sm,@min,@max) = ($[,0);
196 0         0 local (@linenums, @firstbits, @middlebits, $lastbit);
197 0         0 my $n = $[ - 1; foreach (@text) {
  0         0  
198 0 0       0 if (/^(.*?)(-?[\d.]+)(\D+)evol\s+step\s+([\d.]+)(.*)$/) {
199 0         0 $n++; $linenums[$n] = $linenum; $firstbits[$n]=$1;
  0         0  
  0         0  
200 0         0 $xb[$n]=$2; $middlebits[$n]=$3; $sm[$n]=$4; $lastbit = $5;
  0         0  
  0         0  
  0         0  
201 0 0       0 if ($lastbit =~ /min\s+([-\d.]+)/) { $min[$n] = $1; }
  0         0  
202 0 0       0 if ($lastbit =~ /max\s+([-\d.]+)/) { $max[$n] = $1; }
  0         0  
203             }
204 0         0 $linenum++;
205             }
206 0 0       0 warn "xb = ".&arr2txt(@xb) if $debug;
207 0 0       0 warn "sm = ".&arr2txt(@sm) if $debug;
208              
209             # construct the constraint routine
210 0         0 my $some_constraints = 0;
211 0         0 my @sub_constr = ("sub constrain {\n");
212 0         0 my $i = $[; while ($i <= $n) {
  0         0  
213 0 0 0     0 if (defined $min[$i] && defined $max[$i]) {
    0          
    0          
214 0         0 push @sub_constr,"\tif (\$_[$i]>$max[$i]) { \$_[$i]=$max[$i];\n";
215 0         0 push @sub_constr,"\t} elsif (\$_[$i]<$min[$i]) {\$_[$i]=$min[$i];\n";
216 0         0 push @sub_constr,"\t}\n";
217             } elsif (defined $min[$i]) {
218 0         0 push @sub_constr,"\tif (\$_[$i]<$min[$i]) { \$_[$i]=$min[$i]; }\n";
219             } elsif (defined $max[$i]) {
220 0         0 push @sub_constr,"\tif (\$_[$i]>$max[$i]) { \$_[$i]=$max[$i]; }\n";
221             }
222 0 0 0     0 if (defined $min[$i] || defined $max[$i]) { $some_constraints++; }
  0         0  
223 0         0 $i++;
224             }
225 0         0 push @sub_constr, "\treturn \@_;\n}\n";
226 0 0       0 warn join ('', @sub_constr)."\n" if $debug;
227              
228             sub choose_best {
229 0     0 1 0 my $xbref; my $linenum; @texts = ();
  0         0  
230 0         0 while ($xbref = shift @_) {
231 0         0 my @newtext = @text; my $i = $[;
  0         0  
232 0         0 foreach $linenum (@linenums) {
233 0         0 $newtext[$linenum] = $firstbits[$i].sprintf('%g',$$xbref[$i])
234             . $middlebits[$i];
235 0         0 $i++;
236             }
237 0         0 push @texts, join ("\n", @newtext);
238             }
239 0         0 return &$func_ref(@texts);
240             }
241              
242 0         0 my ($xbref, $smref);
243 0 0       0 if ($some_constraints) {
244 0 0       0 eval join '', @sub_constr; if ($@) { die "text_evol: $@\n"; }
  0         0  
  0         0  
245 0         0 ($xbref, $smref) =
246             &select_evol(\@xb, \@sm, \&choose_best, \&constrain, $nchoices);
247             } else {
248 0         0 ($xbref, $smref) = &select_evol(\@xb,\@sm,\&choose_best,0,$nchoices);
249             }
250              
251 0         0 my @new_text = @text; $i = $[;
  0         0  
252 0         0 foreach $linenum (@linenums) {
253 0         0 $new_text[$linenum] = $firstbits[$i] . sprintf ('%g', $$xbref[$i])
254             . $middlebits[$i] . ' evol step '. sprintf ('%g', $$smref[$i]);
255 0 0       0 if (defined $min[$i]) { $new_text[$linenum] .= " min $min[$i]"; }
  0         0  
256 0 0       0 if (defined $max[$i]) { $new_text[$linenum] .= " max $max[$i]"; }
  0         0  
257 0         0 $i++;
258             }
259 0 0       0 warn join ("\n", @new_text)."\n" if $debug;
260 0         0 return join ("\n", @new_text)."\n";
261             }
262              
263             # --------------- infrastructure for evol ----------------
264              
265             sub arr2txt { # neat printing of arrays for debug use
266 0     0 0 0 my @txt; foreach (@_) { push @txt, sprintf('%g',$_); }
  0         0  
  0         0  
267 0         0 return join (' ',@txt)."\n";
268             }
269             my $gaussn_a = rand; # reject 1st call to rand in case it's zero
270             my $gaussn_b;
271             my $gaussn_flag;
272 101336     101336 0 196952 sub gaussn { my $standdev = $_[$[];
273             # returns normal distribution around 0.0 by the Box-Muller rules
274 101336 100       181717 if (! $gaussn_flag) {
275             # $gaussn_a = sqrt(-2.0 * log(rand)); BUG #44777: Log of zero error
276 50668         82952 $gaussn_a = sqrt(-2.0 * log(rand(0.999)+0.001)); # 1.12
277 50668         70567 $gaussn_b = 6.28318531 * rand;
278 50668         67733 $gaussn_flag = 1;
279 50668         109451 return ($standdev * $gaussn_a * sin($gaussn_b));
280             } else {
281 50668         68827 $gaussn_flag = 0;
282 50668         106234 return ($standdev * $gaussn_a * cos($gaussn_b));
283             }
284             }
285             1;
286              
287             __END__