line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Algorithm::Evolve; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
13732
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
57
|
|
4
|
1
|
|
|
1
|
|
6
|
use Carp qw/croak carp/; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
84
|
|
5
|
1
|
|
|
1
|
|
7
|
use List::Util qw/shuffle/; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
392
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our (%SELECTION, %REPLACEMENT); |
8
|
|
|
|
|
|
|
our $VERSION = '0.03'; |
9
|
|
|
|
|
|
|
our $DEBUG = 0; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
my $rand_max = (1 << 31); ## close enough |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
########################### |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub debug { |
16
|
0
|
0
|
|
0
|
0
|
|
print @_, "\n" if $DEBUG; |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub new { |
20
|
0
|
|
|
0
|
1
|
|
my $pkg = shift; |
21
|
|
|
|
|
|
|
|
22
|
0
|
|
|
|
|
|
my $p = bless { |
23
|
|
|
|
|
|
|
generations => 0, |
24
|
|
|
|
|
|
|
parents_per_gen => 2, |
25
|
|
|
|
|
|
|
@_ |
26
|
|
|
|
|
|
|
}, $pkg; |
27
|
|
|
|
|
|
|
|
28
|
0
|
|
0
|
|
|
|
$p->{random_seed} ||= int(rand $rand_max); |
29
|
0
|
|
|
|
|
|
srand( $p->random_seed ); |
30
|
|
|
|
|
|
|
|
31
|
0
|
|
0
|
|
|
|
$p->{selection} ||= $p->{replacement}; |
32
|
0
|
|
0
|
|
|
|
$p->{replacement} ||= $p->{selection}; |
33
|
0
|
|
0
|
|
|
|
$p->{children_per_gen} ||= $p->{parents_per_gen}; |
34
|
|
|
|
|
|
|
|
35
|
0
|
|
|
|
|
|
$p->_validate_args; |
36
|
|
|
|
|
|
|
|
37
|
0
|
|
|
|
|
|
return $p; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub _validate_args { |
41
|
0
|
|
|
0
|
|
|
my $p = shift; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
{ |
44
|
1
|
|
|
1
|
|
6
|
no strict 'refs'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
218
|
|
|
0
|
|
|
|
|
|
|
45
|
0
|
|
|
|
|
|
croak "Invalid selection/replacement criteria" |
46
|
0
|
|
|
|
|
|
unless *{"Algorithm::Evolve::selection::" . $p->selection}{CODE} |
47
|
0
|
0
|
0
|
|
|
|
and *{"Algorithm::Evolve::replacement::" . $p->replacement}{CODE}; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
0
|
0
|
|
|
|
|
croak "Please specify the size of the population" unless $p->size; |
51
|
0
|
0
|
|
|
|
|
croak "parents_per_gen must be even" if $p->parents_per_gen % 2; |
52
|
0
|
0
|
|
|
|
|
croak "parents_per_gen must divide children_per_gen" |
53
|
|
|
|
|
|
|
if $p->children_per_gen % $p->parents_per_gen; |
54
|
0
|
0
|
0
|
|
|
|
croak "parents_per_gen and children_per_gen must be no larger than size" |
55
|
|
|
|
|
|
|
if $p->children_per_gen > $p->size |
56
|
|
|
|
|
|
|
or $p->parents_per_gen > $p->size; |
57
|
|
|
|
|
|
|
|
58
|
0
|
|
|
|
|
|
$p->{children_per_parent} = $p->children_per_gen / $p->parents_per_gen; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
############################ |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub start { |
65
|
0
|
|
|
0
|
0
|
|
my $p = shift; |
66
|
0
|
|
|
|
|
|
$p->_initialize; |
67
|
|
|
|
|
|
|
|
68
|
0
|
|
|
|
|
|
until ($p->is_suspended) { |
69
|
1
|
|
|
1
|
|
5
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2328
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
my @parent_indices |
72
|
0
|
|
|
|
|
|
= ("Algorithm::Evolve::selection::" . $p->selection) |
73
|
|
|
|
|
|
|
->($p, $p->parents_per_gen); |
74
|
|
|
|
|
|
|
|
75
|
0
|
|
|
|
|
|
my @children; |
76
|
0
|
|
|
|
|
|
while (@parent_indices) { |
77
|
0
|
|
|
|
|
|
my @parents = @{$p->critters}[ splice(@parent_indices, 0, 2) ]; |
|
0
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
push @children, $p->critter_class->crossover(@parents) |
80
|
0
|
|
|
|
|
|
for (1 .. $p->children_per_parent); |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
0
|
|
|
|
|
|
$_->mutate for @children; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
my @replace_indices |
86
|
0
|
|
|
|
|
|
= ("Algorithm::Evolve::replacement::" . $p->replacement) |
87
|
|
|
|
|
|
|
->($p, $p->children_per_gen); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
## place the new critters first, then sort. maybe fixme: |
90
|
|
|
|
|
|
|
|
91
|
0
|
|
|
|
|
|
@{$p->critters}[ @replace_indices ] = @children; |
|
0
|
|
|
|
|
|
|
92
|
0
|
0
|
|
|
|
|
@{$p->fitnesses}[ @replace_indices ] = () if $p->use_fitness; |
|
0
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
$p->_sort_critters; |
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
|
$p->{generations}++; |
97
|
0
|
0
|
|
|
|
|
$p->callback->($p) if (ref $p->callback eq 'CODE'); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
################### |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub suspend { |
104
|
0
|
|
|
0
|
1
|
|
my $p = shift; |
105
|
0
|
|
|
|
|
|
$p->{is_suspended} = 1; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub resume { |
109
|
0
|
|
|
0
|
1
|
|
my $p = shift; |
110
|
0
|
|
|
|
|
|
$p->{is_suspended} = 0; |
111
|
0
|
|
|
|
|
|
$p->start; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub best_fit { |
115
|
0
|
|
|
0
|
1
|
|
my $p = shift; |
116
|
0
|
0
|
|
|
|
|
carp "It's hard to pick the most fit when fitness is relative!" |
117
|
|
|
|
|
|
|
unless ($p->use_fitness); |
118
|
0
|
|
|
|
|
|
$p->critters->[-1]; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub avg_fitness { |
122
|
0
|
|
|
0
|
1
|
|
my $p = shift; |
123
|
0
|
|
|
|
|
|
my $sum = 0; |
124
|
0
|
|
|
|
|
|
$sum += $_ for @{$p->fitnesses}; |
|
0
|
|
|
|
|
|
|
125
|
0
|
|
|
|
|
|
return $sum / $p->size; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub selection { |
129
|
0
|
|
|
0
|
1
|
|
my ($p, $method) = @_; |
130
|
0
|
0
|
|
|
|
|
return $p->{selection} unless defined $method; |
131
|
0
|
|
|
|
|
|
$p->{selection} = $method; |
132
|
0
|
|
|
|
|
|
$p->_validate_args; |
133
|
0
|
|
|
|
|
|
return $p->{selection}; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub replacement { |
137
|
0
|
|
|
0
|
1
|
|
my ($p, $method) = @_; |
138
|
0
|
0
|
|
|
|
|
return $p->{replacement} unless defined $method; |
139
|
0
|
|
|
|
|
|
$p->{replacement} = $method; |
140
|
0
|
|
|
|
|
|
$p->_validate_args; |
141
|
0
|
|
|
|
|
|
return $p->{replacement}; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub parents_children_per_gen { |
145
|
0
|
|
|
0
|
1
|
|
my ($p, $parents, $children) = @_; |
146
|
0
|
0
|
0
|
|
|
|
return unless defined $parents and defined $children; |
147
|
0
|
|
|
|
|
|
$p->{parents_per_gen} = $parents; |
148
|
0
|
|
|
|
|
|
$p->{children_per_gen} = $children; |
149
|
0
|
|
|
|
|
|
$p->_validate_args; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
#################### |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub _initialize { |
155
|
0
|
|
|
0
|
|
|
my $p = shift; |
156
|
0
|
0
|
|
|
|
|
return if defined $p->critters; |
157
|
|
|
|
|
|
|
|
158
|
0
|
|
|
|
|
|
$p->{critters} = [ map { $p->critter_class->new } 1 .. $p->size ]; |
|
0
|
|
|
|
|
|
|
159
|
0
|
|
|
|
|
|
$p->{use_fitness} = !! $p->critters->[0]->can('fitness'); |
160
|
0
|
0
|
|
|
|
|
$p->{fitnesses} = [ map { $p->critters->[$_]->fitness } 0 .. $p->size-1 ] |
|
0
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
if ($p->use_fitness); |
162
|
|
|
|
|
|
|
|
163
|
0
|
|
|
|
|
|
$p->_sort_critters; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub _sort_critters { |
168
|
0
|
|
|
0
|
|
|
my $p = shift; |
169
|
|
|
|
|
|
|
|
170
|
0
|
0
|
|
|
|
|
return unless $p->use_fitness; |
171
|
|
|
|
|
|
|
|
172
|
0
|
|
|
|
|
|
my $fitnesses = $p->fitnesses; |
173
|
0
|
|
|
|
|
|
my $critters = $p->critters; |
174
|
0
|
|
|
|
|
|
for (0 .. $p->size-1) { |
175
|
0
|
0
|
|
|
|
|
$fitnesses->[$_] = $critters->[$_]->fitness |
176
|
|
|
|
|
|
|
unless defined $fitnesses->[$_]; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
0
|
|
|
|
|
|
my @sorted_indices = |
180
|
0
|
|
|
|
|
|
sort { $fitnesses->[$a] <=> $fitnesses->[$b] } 0 .. $p->size-1; |
181
|
|
|
|
|
|
|
|
182
|
0
|
|
|
|
|
|
$p->{critters} = [ @{$critters} [ @sorted_indices ] ]; |
|
0
|
|
|
|
|
|
|
183
|
0
|
|
|
|
|
|
$p->{fitnesses} = [ @{$fitnesses}[ @sorted_indices ] ]; |
|
0
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
############################ |
188
|
|
|
|
|
|
|
## picks N indices randomly, using the given weights |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub _pick_n_indices_weighted { |
191
|
0
|
|
|
0
|
|
|
my $num = shift; |
192
|
0
|
|
|
|
|
|
my $relative_prob = shift; |
193
|
|
|
|
|
|
|
|
194
|
0
|
0
|
|
|
|
|
croak("Tried to pick $num items, with only " . @$relative_prob . " choices!") |
195
|
|
|
|
|
|
|
if $num > @$relative_prob; |
196
|
|
|
|
|
|
|
|
197
|
0
|
|
|
|
|
|
my $sum = 0; |
198
|
0
|
|
|
|
|
|
$sum += $_ for @$relative_prob; |
199
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
|
my @indices; |
201
|
|
|
|
|
|
|
|
202
|
0
|
|
|
|
|
|
while ($num--) { |
203
|
0
|
|
|
|
|
|
my $dart = rand($sum); |
204
|
0
|
|
|
|
|
|
my $index = -1; |
205
|
|
|
|
|
|
|
|
206
|
0
|
|
|
|
|
|
$dart -= $relative_prob->[++$index] while ($dart > 0); |
207
|
|
|
|
|
|
|
|
208
|
0
|
|
|
|
|
|
$sum -= $relative_prob->[$index]; |
209
|
0
|
|
|
|
|
|
$relative_prob->[$index] = 0; |
210
|
0
|
|
|
|
|
|
push @indices, $index; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
0
|
|
|
|
|
|
return @indices; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
############################# |
217
|
|
|
|
|
|
|
## Selection / replacement routines: these take a population object and a |
218
|
|
|
|
|
|
|
## number, and return a list of indices. Keep in mind that the critter |
219
|
|
|
|
|
|
|
## array is already sorted by fitness. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
############################# |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
## these two go crazy with negative fitness values. fixme later maybe |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::roulette { |
226
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
227
|
0
|
0
|
|
|
|
|
croak "Can't use roulette selection/replacement without a fitness function" |
228
|
|
|
|
|
|
|
unless ($p->use_fitness); |
229
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ @{$p->fitnesses} ] ); |
|
0
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
}; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::roulette { |
233
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
234
|
0
|
0
|
|
|
|
|
croak "Can't use roulette selection/replacement without a fitness function" |
235
|
|
|
|
|
|
|
unless ($p->use_fitness); |
236
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ map { 1/($_+1) } @{$p->fitnesses} ] ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
}; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
############### |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::rank { |
242
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
243
|
0
|
0
|
|
|
|
|
croak "Can't use rank selection/replacement without a fitness function" |
244
|
|
|
|
|
|
|
unless ($p->use_fitness); |
245
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ 1 .. $p->size ] ); |
246
|
|
|
|
|
|
|
}; |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::rank { |
249
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
250
|
0
|
0
|
|
|
|
|
croak "Can't use rank selection/replacement without a fitness function" |
251
|
|
|
|
|
|
|
unless ($p->use_fitness); |
252
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ reverse(1 .. $p->size) ] ); |
253
|
|
|
|
|
|
|
}; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
############### |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::random { |
258
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
259
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ (1) x $p->size ] ); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::random { |
263
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
264
|
0
|
|
|
|
|
|
_pick_n_indices_weighted( $num, [ (1) x $p->size ] ); |
265
|
|
|
|
|
|
|
}; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
################ |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::absolute { |
270
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
271
|
0
|
0
|
|
|
|
|
croak "Can't use absolute selection/replacement without a fitness function" |
272
|
|
|
|
|
|
|
unless ($p->use_fitness); |
273
|
0
|
|
|
|
|
|
return ( $p->size - $num .. $p->size - 1 ); |
274
|
|
|
|
|
|
|
}; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::absolute { |
277
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
278
|
0
|
0
|
|
|
|
|
croak "Can't use absolute selection/replacement without a fitness function" |
279
|
|
|
|
|
|
|
unless ($p->use_fitness); |
280
|
0
|
|
|
|
|
|
return ( 0 .. $num-1 ); |
281
|
|
|
|
|
|
|
}; |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
################ |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
my @tournament_replace_indices; |
286
|
|
|
|
|
|
|
my $tournament_warn = 0; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::tournament { |
289
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
290
|
0
|
|
|
|
|
|
my $t_size = $p->{tournament_size}; |
291
|
|
|
|
|
|
|
|
292
|
0
|
0
|
0
|
|
|
|
croak "Invalid (or no) tournament size specified" |
|
|
|
0
|
|
|
|
|
293
|
|
|
|
|
|
|
if not defined $t_size or $t_size < 2 or $t_size > $p->size; |
294
|
0
|
0
|
|
|
|
|
croak "Tournament size * #tournaments must be no greater than population size" |
295
|
|
|
|
|
|
|
if ($num/2) * $t_size > $p->size; |
296
|
0
|
0
|
0
|
|
|
|
carp "Tournament selection without tournament replacement is insane" |
297
|
|
|
|
|
|
|
unless ($p->replacement eq 'tournament' or $tournament_warn++); |
298
|
|
|
|
|
|
|
|
299
|
0
|
|
|
|
|
|
my $tournament_groups = $num / 2; |
300
|
0
|
|
|
|
|
|
my @indices = shuffle(0 .. $p->size-1); |
301
|
0
|
|
|
|
|
|
my @tournament_choose_indices = |
302
|
|
|
|
|
|
|
@tournament_replace_indices = (); |
303
|
|
|
|
|
|
|
|
304
|
0
|
|
|
|
|
|
for my $i (0 .. $tournament_groups-1) { |
305
|
0
|
|
|
|
|
|
my $beg = $t_size * $i; |
306
|
0
|
|
|
|
|
|
my $end = $beg + $t_size - 1; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
## the critters are already sorted by fitness within $p->critters -- |
309
|
|
|
|
|
|
|
## so we can sort them by their index number, without having to |
310
|
|
|
|
|
|
|
## consult the fitness function (or fitness array) again. |
311
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
|
my @sorted_group_indices = sort { $b <=> $a } @indices[ $beg .. $end ]; |
|
0
|
|
|
|
|
|
|
313
|
0
|
|
|
|
|
|
push @tournament_choose_indices, @sorted_group_indices[0,1]; |
314
|
0
|
|
|
|
|
|
push @tournament_replace_indices, @sorted_group_indices[-2,-1]; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
0
|
|
|
|
|
|
return @tournament_choose_indices; |
318
|
|
|
|
|
|
|
}; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::tournament { |
321
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
322
|
0
|
0
|
|
|
|
|
croak "parents_per_gen must equal children_per_gen with tournament selection" |
323
|
|
|
|
|
|
|
if @tournament_replace_indices != $num; |
324
|
0
|
0
|
|
|
|
|
croak "Can't use tournament replacement without tournament selection" |
325
|
|
|
|
|
|
|
unless ($p->selection eq 'tournament'); |
326
|
|
|
|
|
|
|
|
327
|
0
|
|
|
|
|
|
return @tournament_replace_indices; |
328
|
|
|
|
|
|
|
}; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
####################################### |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
my @gladitorial_replace_indices; |
333
|
|
|
|
|
|
|
my $gladitorial_warn = 0; |
334
|
|
|
|
|
|
|
my $gladitorial_attempts_warn = 0; |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub Algorithm::Evolve::selection::gladitorial { |
337
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
338
|
|
|
|
|
|
|
|
339
|
0
|
0
|
0
|
|
|
|
carp "Gladitorial selection without gladitorial replacement is insane" |
340
|
|
|
|
|
|
|
unless ($p->replacement eq 'gladitorial' or $gladitorial_warn++); |
341
|
|
|
|
|
|
|
|
342
|
0
|
|
0
|
|
|
|
my $max_attempts = $p->{max_gladitorial_attempts} || 100; |
343
|
0
|
|
|
|
|
|
my $fetched = 0; |
344
|
0
|
|
|
|
|
|
my $attempts = 0; |
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
my @available_indices = 0 .. $#{$p->critters}; |
|
0
|
|
|
|
|
|
|
347
|
0
|
|
|
|
|
|
my @gladitorial_select_indices = |
348
|
|
|
|
|
|
|
@gladitorial_replace_indices = (); |
349
|
|
|
|
|
|
|
|
350
|
0
|
|
|
|
|
|
while ($fetched != $p->parents_per_gen) { |
351
|
0
|
|
|
|
|
|
my ($i1, $i2) = (shuffle @available_indices)[0,1]; |
352
|
|
|
|
|
|
|
|
353
|
0
|
0
|
|
|
|
|
if ($attempts++ > $max_attempts) { |
354
|
0
|
0
|
|
|
|
|
carp "Max gladitorial attempts exceeded -- choosing at random" |
355
|
|
|
|
|
|
|
unless $gladitorial_attempts_warn++; |
356
|
0
|
|
|
|
|
|
my $remaining = $p->parents_per_gen - @gladitorial_select_indices; |
357
|
|
|
|
|
|
|
|
358
|
0
|
|
|
|
|
|
push @gladitorial_replace_indices, |
359
|
|
|
|
|
|
|
(shuffle @available_indices)[0 .. $remaining-1]; |
360
|
0
|
|
|
|
|
|
push @gladitorial_select_indices, |
361
|
|
|
|
|
|
|
(shuffle @available_indices)[0 .. $remaining-1]; |
362
|
|
|
|
|
|
|
|
363
|
0
|
|
|
|
|
|
last; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
0
|
|
|
|
|
|
my $cmp = $p->critter_class->compare( @{$p->critters}[$i1, $i2] ); |
|
0
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
|
368
|
0
|
0
|
|
|
|
|
next if $cmp == 0; ## tie |
369
|
|
|
|
|
|
|
|
370
|
0
|
0
|
|
|
|
|
my ($select, $remove) = $cmp > 0 ? ($i1,$i2) : ($i2,$i1); |
371
|
0
|
|
|
|
|
|
@available_indices = grep { $_ != $remove } @available_indices; |
|
0
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
|
373
|
0
|
|
|
|
|
|
push @gladitorial_replace_indices, $remove; |
374
|
0
|
|
|
|
|
|
push @gladitorial_select_indices, $select; |
375
|
0
|
|
|
|
|
|
$fetched++; |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
0
|
|
|
|
|
|
return @gladitorial_select_indices; |
379
|
|
|
|
|
|
|
}; |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub Algorithm::Evolve::replacement::gladitorial { |
382
|
0
|
|
|
0
|
|
|
my ($p, $num) = @_; |
383
|
0
|
0
|
|
|
|
|
croak "parents_per_gen must equal children_per_gen with gladitorial selection" |
384
|
|
|
|
|
|
|
if @gladitorial_replace_indices != $num; |
385
|
0
|
0
|
|
|
|
|
croak "Can't use gladitorial replacement without gladitorial selection" |
386
|
|
|
|
|
|
|
unless ($p->selection eq 'gladitorial'); |
387
|
|
|
|
|
|
|
|
388
|
0
|
|
|
|
|
|
return @gladitorial_replace_indices; |
389
|
|
|
|
|
|
|
}; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
####################################### |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
BEGIN { |
394
|
|
|
|
|
|
|
## creates very basic readonly accessors - very loosely based on an |
395
|
|
|
|
|
|
|
## idea by Juerd in http://perlmonks.org/index.pl?node_id=222941 |
396
|
|
|
|
|
|
|
|
397
|
1
|
|
|
1
|
|
5
|
my @fields = qw/critters size generations callback critter_class |
398
|
|
|
|
|
|
|
random_seed is_suspended use_fitness fitnesses |
399
|
|
|
|
|
|
|
parents_per_gen children_per_gen children_per_parent/; |
400
|
|
|
|
|
|
|
|
401
|
1
|
|
|
1
|
|
7
|
no strict 'refs'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
91
|
|
402
|
1
|
|
|
|
|
2
|
for my $f (@fields) { |
403
|
12
|
0
|
|
0
|
|
101
|
*$f = sub { carp "$f method is readonly" if $#_; $_[0]->{$f} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
########################################## |
408
|
|
|
|
|
|
|
########################################## |
409
|
|
|
|
|
|
|
########################################## |
410
|
|
|
|
|
|
|
1; |
411
|
|
|
|
|
|
|
__END__ |