File Coverage

blib/lib/Math/Evol.pm
Criterion Covered Total %
statement 130 232 56.0
branch 54 120 45.0
condition 8 21 38.1
subroutine 5 9 55.5
pod 4 7 57.1
total 201 389 51.6


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   684 no strict;
  1         2  
  1         333  
12             $VERSION = '1.12';
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 1456 sub evol { my ( $xbref,$smref, $func_ref,$constrain_ref, $tm) = @_;
36 4 50 33     52 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         14 my @xb = @$xbref;
46 4         7 my @sm = @$smref;
47 4 50       10 if (! $tm) { $tm = 10.0; }
  0         0  
48 4         5 $tm = abs $tm;
49              
50 4         4 my $debug = 0;
51 4         5 my $n = scalar @xb; # number of variables
52 1     1   1042 my $nm1 = $n + $[ - 1; # last index
  1         490  
  1         4383  
  4         21  
53 4         87 my ($usr_o, $sys_o) = times;
54 4         8 my ($fc, $rel_limit); # used to test convergence
55              
56             # step-size adjustment stuff, courtesy Rechenberg ...
57 0         0 my @l; foreach (1 .. 10) { push @l, $n*$_/5.0; } # 6
  4         10  
  40         58  
58 4         9 my $le = $n+$n; my $lm = 0; my $lc = 0;
  4         6  
  4         5  
59              
60 4 50       9 if ($constrain_ref) { @xb = &$constrain_ref(@xb); }
  4         12  
61 4         55 $fb = &$func_ref(@xb); $fc = $fb;
  4         76  
62 4         7 while (1) {
63 22240         61061 foreach $i ($[ .. $nm1) { $x[$i] = $xb[$i]+&gaussn($sm[$i]); }
  88960         166974  
64 22240 50       48616 if ($constrain_ref) {
65 22240         48518 @x = &$constrain_ref(@x);
66 22240 50       234459 warn 'new @x is '.&arr2txt(@x) if $debug;
67             }
68 22240         45926 $ff = &$func_ref (@x); # find new objective function
69 22240 100       229145 if ($ff <= $fb) { $le++; $fb=$ff; @xb=@x; }
  4356         4645  
  4356         4382  
  4356         9127  
70 22240 100       22869 $lm++; if ($lm < $n) { next; }
  22240         40487  
  16680         19263  
71              
72             # adjust the step sizes ...
73 5560         19985 my $k = 0.85 ** ($n+$n <=> $le-$l[$[]);
74 5560         15078 foreach $i ($[ .. $nm1) {
75 22240         25376 $sm[$i] *= $k;
76 22240         25695 $rel_limit = abs ($eb * $xb[$i]);
77 22240 50       40198 if ($sm[$i] < $rel_limit) { $sm[$i] = $rel_limit; }
  0         0  
78 22240 50       48482 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 5560         7045 shift @l; push (@l, $le); $lm = 0;
  5560         7416  
  5560         5771  
82 5560 50       9653 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 5560 50       10752 if (defined $ea) { $ea = abs $ea; }
  5560         7459  
89 5560 50       9781 if (defined $eb) { $eb = abs $eb; }
  5560         5931  
90 5560 100       9646 if (defined $ec) { $ec = abs $ec; }
  225         229  
91 5560 100       9165 if (defined $ed) { $ed = abs $ed; }
  200         182  
92 5560 50       9359 warn sprintf("fb=%g fc=%g ec=%g ed=%g\n",$fb,$fc,$ec,$ed) if $debug;
93 5560         5216 $lc++;
94 5560 100       9811 if ($lc < 25) {
95 5338         34139 my ($usr_n, $sys_n) = times;
96 5338 100       13816 if (($usr_n+$sys_n - $usr_o-$sys_o) > $tm) { # out of time ?
97 1 50       13 warn "exceeded $tm seconds\n" if $debug;
98 1         11 return (\@xb, \@sm, $fb, 0); # return best current value
99 5337         8014 } else { next;
100             }
101             }
102 222 100 100     531 if ((defined $ec) && (($fc-$fb) <= $ec)) {
103 1 50       6 warn "converged absolutely\n" if $debug;
104 1         10 return (\@xb, \@sm, $fb, 1); # 29
105             }
106 221 100 100     593 if ((defined $ed) && (($fc-$fb)/$ed <= abs($fc))) {
107 2 50       36 warn "converged relativelty\n" if $debug;
108 2         32 return (\@xb, \@sm, $fb, 1);
109             }
110 219         238 $lc = 0; $fc = $fb; next;
  219         236  
  219         295  
111             }
112             }
113              
114 1     1 1 863 sub select_evol { my ($xbref,$smref,$func_ref,$constrain_ref,$nchoices) = @_;
115 1 50 33     21 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         4 my @sm = @$smref;
126 1 50       5 if (! $nchoices) { $nchoices = 1; }
  0         0  
127              
128 1         2 my $debug = 0;
129 1         3 my $n = scalar @xb; # number of variables
130 1         6 my $nm1 = $n + $[ - 1; # last index
131 1         3 my ($choice, $continue);
132              
133             # step-size adjustment stuff, courtesy Rechenberg
134             # modified from Schwefel's sliding flat window average to an EMA
135 1         5 my $desired_success_rate = 1.0 - 0.8**$nchoices;
136 1         2 my $success_rate = $desired_success_rate;
137             # test every 5*$n binary choices equivalent - 10*$n is just too slow.
138 1         3 my $lm = 0;
139 1         4 my $test_every = 5 * $n * (log 2) / (log ($nchoices+1));
140 1         20 my $ema = exp (-1.0 / $test_every);
141 1         3 my $one_m_ema = 1.0 - $ema;
142 1 50       19 warn sprintf
143             ("n=$n nchoices=$nchoices success_rate=%g test_every=%g ema=%g\n",
144             $success_rate, $test_every, $ema) if $debug;
145              
146 1 50       4 if ($constrain_ref) { @xb = &$constrain_ref(@xb); }
  0         0  
147 1         2 my @func_args; # array of refs to arrays
148 1         9 while () {
149 1090         1128 my @x; #XXX?
150 1090         2764 @func_args = ( \@xb );
151 1090         1938 foreach (1 .. $nchoices) {
152 1090         3687 foreach $i ($[ .. $nm1) {
153 4360         8009 $x[$i] = $xb[$i]+&gaussn($sm[$i]);
154             }
155 1090 50       2399 if ($constrain_ref) { @x = &$constrain_ref(@x); }
  0         0  
156 1090         3952 push @func_args, [ @x ];
157             }
158 1090         2819 ($choice, $continue) = &$func_ref(@func_args);
159 1090 100       24023 if ($choice > $[) {
160 41         50 @xb = @{$func_args[$choice]};
  41         176  
161 41         72 $success_rate = $one_m_ema + $ema*$success_rate;
162             } else {
163 1049         1483 $success_rate = $ema*$success_rate;
164             }
165 1090 50       2131 warn "success_rate=$success_rate\n" if $debug;
166 1090 100       1928 if (!$continue) { return (\@xb, \@sm); }
  1         12  
167 1089 100       1204 $lm++; if ($lm < $test_every) { next; }
  1089         2071  
  1035         1672  
168              
169             # adjust the step sizes ...
170 54         108 my $k = 0.85 ** ($desired_success_rate <=> $success_rate);
171 54 50       143 warn "success_rate=$success_rate k=$k\n" if $debug;
172 54         157 foreach $i ($[ .. $nm1) {
173 216         235 $sm[$i] *= $k;
174 216         262 $rel_limit = abs ($eb * $xb[$i]);
175 216 50       418 if ($sm[$i] < $rel_limit) { $sm[$i] = $rel_limit; }
  0         0  
176 216 50       509 if ($sm[$i] < $ea) { $sm[$i] = $ea; }
  0         0  
177             }
178 54 50       107 warn "new step sizes sm = ".&arr2txt(@sm) if $debug;
179 54         84 $lm = 0;
180             }
181             }
182              
183 0     0 1 0 sub text_evol { my ($text, $nchoices); local ($func_ref);
  0         0  
184 0         0 ($text, $func_ref, $nchoices) = @_;
185 0 0       0 return unless $text;
186 0 0       0 if (ref $func_ref ne 'CODE') {
187 0         0 die "Math::Evol::text_evol 2nd arg must be a function ref\n";
188             }
189 0 0       0 if (! $nchoices) { $nchoices = 1; }
  0         0  
190              
191 0         0 my $debug = 0;
192 0         0 local @text = split ("\n", $text);
193 0         0 my ($linenum,$m,@xb,@sm,@min,@max) = ($[,0);
194 0         0 local (@linenums, @firstbits, @middlebits, $lastbit);
195 0         0 my $n = $[ - 1; foreach (@text) {
  0         0  
196 0 0       0 if (/^(.*?)(-?[\d.]+)(\D+)evol\s+step\s+([\d.]+)(.*)$/) {
197 0         0 $n++; $linenums[$n] = $linenum; $firstbits[$n]=$1;
  0         0  
  0         0  
198 0         0 $xb[$n]=$2; $middlebits[$n]=$3; $sm[$n]=$4; $lastbit = $5;
  0         0  
  0         0  
  0         0  
199 0 0       0 if ($lastbit =~ /min\s+([-\d.]+)/) { $min[$n] = $1; }
  0         0  
200 0 0       0 if ($lastbit =~ /max\s+([-\d.]+)/) { $max[$n] = $1; }
  0         0  
201             }
202 0         0 $linenum++;
203             }
204 0 0       0 warn "xb = ".&arr2txt(@xb) if $debug;
205 0 0       0 warn "sm = ".&arr2txt(@sm) if $debug;
206              
207             # construct the constraint routine
208 0         0 my $some_constraints = 0;
209 0         0 my @sub_constr = ("sub constrain {\n");
210 0         0 my $i = $[; while ($i <= $n) {
  0         0  
211 0 0 0     0 if (defined $min[$i] && defined $max[$i]) {
    0          
    0          
212 0         0 push @sub_constr,"\tif (\$_[$i]>$max[$i]) { \$_[$i]=$max[$i];\n";
213 0         0 push @sub_constr,"\t} elsif (\$_[$i]<$min[$i]) { \$_[$i]=$min[$i];\n";
214 0         0 push @sub_constr,"\t}\n";
215             } elsif (defined $min[$i]) {
216 0         0 push @sub_constr,"\tif (\$_[$i]<$min[$i]) { \$_[$i]=$min[$i]; }\n";
217             } elsif (defined $max[$i]) {
218 0         0 push @sub_constr,"\tif (\$_[$i]>$max[$i]) { \$_[$i]=$max[$i]; }\n";
219             }
220 0 0 0     0 if (defined $min[$i] || defined $max[$i]) { $some_constraints++; }
  0         0  
221 0         0 $i++;
222             }
223 0         0 push @sub_constr, "\treturn \@_;\n}\n";
224 0 0       0 warn join ('', @sub_constr)."\n" if $debug;
225              
226             sub choose_best {
227 0     0 1 0 my $xbref; my $linenum; @texts = ();
  0         0  
228 0         0 while ($xbref = shift @_) {
229 0         0 my @newtext = @text; my $i = $[;
  0         0  
230 0         0 foreach $linenum (@linenums) {
231 0         0 $newtext[$linenum] = $firstbits[$i] . sprintf ('%g', $$xbref[$i])
232             . $middlebits[$i];
233 0         0 $i++;
234             }
235 0         0 push @texts, join ("\n", @newtext);
236             }
237 0         0 return &$func_ref(@texts);
238             }
239              
240 0         0 my ($xbref, $smref);
241 0 0       0 if ($some_constraints) {
242 0 0       0 eval join '', @sub_constr; if ($@) { die "text_evol: $@\n"; }
  0         0  
  0         0  
243 0         0 ($xbref, $smref) =
244             &select_evol(\@xb, \@sm, \&choose_best, \&constrain, $nchoices);
245             } else {
246 0         0 ($xbref, $smref) = &select_evol(\@xb,\@sm,\&choose_best,0,$nchoices);
247             }
248              
249 0         0 my @new_text = @text; $i = $[;
  0         0  
250 0         0 foreach $linenum (@linenums) {
251 0         0 $new_text[$linenum] = $firstbits[$i] . sprintf ('%g', $$xbref[$i])
252             . $middlebits[$i] . ' evol step '. sprintf ('%g', $$smref[$i]);
253 0 0       0 if (defined $min[$i]) { $new_text[$linenum] .= " min $min[$i]"; }
  0         0  
254 0 0       0 if (defined $max[$i]) { $new_text[$linenum] .= " max $max[$i]"; }
  0         0  
255 0         0 $i++;
256             }
257 0 0       0 warn join ("\n", @new_text)."\n" if $debug;
258 0         0 return join ("\n", @new_text)."\n";
259             }
260              
261             # --------------- infrastructure for evol ----------------
262              
263             sub arr2txt { # neat printing of arrays for debug use
264 0     0 0 0 my @txt; foreach (@_) { push @txt, sprintf('%g',$_); }
  0         0  
  0         0  
265 0         0 return join (' ',@txt)."\n";
266             }
267             my $gaussn_a = rand; # reject 1st call to rand in case it's zero
268             my $gaussn_b;
269             my $gaussn_flag;
270 93320     93320 0 215790 sub gaussn { my $standdev = $_[$[];
271             # returns normal distribution around 0.0 by the Box-Muller rules
272 93320 100       179302 if (! $gaussn_flag) {
273             # $gaussn_a = sqrt(-2.0 * log(rand)); BUG #44777: Log of zero error
274 46660         84268 $gaussn_a = sqrt(-2.0 * log(rand(0.999)+0.001)); # 1.12
275 46660         58099 $gaussn_b = 6.28318531 * rand;
276 46660         47033 $gaussn_flag = 1;
277 46660         145802 return ($standdev * $gaussn_a * sin($gaussn_b));
278             } else {
279 46660         45730 $gaussn_flag = 0;
280 46660         151449 return ($standdev * $gaussn_a * cos($gaussn_b));
281             }
282             }
283             1;
284              
285             __END__