File Coverage

blib/lib/AI/Gene/Simple.pm
Criterion Covered Total %
statement 194 212 91.5
branch 72 76 94.7
condition 53 65 81.5
subroutine 16 20 80.0
pod 14 14 100.0
total 349 387 90.1


line stmt bran cond sub pod time code
1             package AI::Gene::Simple;
2             require 5.6.0;
3 1     1   802 use strict;
  1         2  
  1         30  
4 1     1   4 use warnings;
  1         2  
  1         35  
5              
6             BEGIN {
7 1     1   4 use Exporter ();
  1         2  
  1         106  
8 1     1   3 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
9 1         2 $VERSION = 0.20;
10 1         17 @ISA = qw(Exporter);
11 1         2 @EXPORT = ();
12 1         2 %EXPORT_TAGS = ();
13 1         2838 @EXPORT_OK = qw();
14             }
15             our @EXPORT_OK;
16              
17             my ($probs,$mut_keys) = _normalise( { map {$_ => 1}
18             qw(insert remove overwrite
19             duplicate minor major
20             switch shuffle reverse) } );
21             ##
22             # calls mutation method at random
23             # 0: number of mutations to perform
24             # 1: ref to hash of probs to use (otherwise uses default mutations and probs)
25              
26             sub mutate {
27 60     60 1 4305 my $self = shift;
28 60   100     850 my $num_mutates = +$_[0] || 1;
29 60         212 my $rt = 0;
30 60         56 my ($hr_probs, $muts);
31 60 100       203 if (ref $_[1] eq 'HASH') { # use non standard mutations or probs
32 30         69 ($hr_probs, $muts) = _normalise($_[1]);
33             }
34             else { # use standard mutations and probs
35 30         32 $hr_probs = $probs;
36 30         33 $muts = $mut_keys;
37             }
38              
39 60         112 MUT_CYCLE: for (1..$num_mutates) {
40 60         80 my $rand = rand;
41 60         52 foreach my $mutation (@{$muts}) {
  60         195  
42 297 100       1769 next unless $rand < $hr_probs->{$mutation};
43 60         225 my $mut = 'mutate_' . $mutation;
44 60         168 $rt += $self->$mut(1);
45 60         177 next MUT_CYCLE;
46             }
47             }
48 60         1194 return $rt;
49             }
50              
51             ##
52             # creates a normalised and cumulative prob distribution for the
53             # keys of the referenced hash
54              
55             sub _normalise {
56 31     31   38 my $hr = $_[0];
57 31         41 my $h2 = {};
58 31         36 my $muts = [keys %{$hr}];
  31         123  
59 31         102 my $sum = 0;
60 31         32 foreach (values %{$hr}) {
  31         70  
61 249         424 $sum += $_;
62             }
63 31 50       92 if ($sum <= 0) {
64 0         0 die "Cannot randomly mutate with bad probability distribution";
65             }
66             else {
67 31         30 my $cum;
68 31         92 @{$h2}{ @{$muts} } = map {$cum +=$_; $cum / $sum} @{$hr}{ @{$muts} };
  31         154  
  31         51  
  249         12914  
  249         411  
  31         67  
  31         178  
69 31         106 return ($h2, $muts);
70             }
71             }
72              
73             ##
74             # inserts one element into the sequence
75             # 0: number to perform ( or 1)
76             # 1: position to mutate (undef for random)
77              
78             sub mutate_insert {
79 42     42 1 1085 my $self = shift;
80 42   100     127 my $num = +$_[0] || 1;
81 42         41 my $rt = 0;
82 42         72 for (1..$num) {
83 42         32 my $glen = scalar @{$self->[0]};
  42         71  
84 42 100       95 my $pos = defined($_[1]) ? $_[1] : int rand $glen;
85 42 100       76 next if $pos > $glen; # further than 1 place after gene
86 41         90 my $token = $self->generate_token;
87 41         258 splice @{$self->[0]}, $pos, 0, $token;
  41         104  
88 41         94 $rt++;
89             }
90 42         182 return $rt;
91             }
92              
93             ##
94             # removes element(s) from sequence
95             # 0: number of times to perform
96             # 1: position to affect (undef for rand)
97             # 2: length to affect, undef => 1, 0 => random length
98              
99             sub mutate_remove {
100 45     45 1 2545 my $self = shift;
101 45   50     183 my $num = +$_[0] || 1;
102 45         43 my $rt = 0;
103 45         68 for (1..$num) {
104 45         49 my $glen = scalar @{$self->[0]};
  45         75  
105 45 100 100     169 my $length = !defined($_[2]) ? 1 : ($_[2] || int rand $glen);
106 45 50       173 return $rt if ($glen - $length) <= 0;
107 45 100       173 my $pos = defined($_[1]) ? $_[1] : int rand $glen;
108 45 100       80 next if $pos >= $glen; # outside of gene
109 44         37 splice @{$self->[0]}, $pos, $length;
  44         90  
110 44         106 $rt++;
111             }
112 45         278 return $rt;
113             }
114              
115             ##
116             # copies an element or run of elements into a random place in the gene
117             # 0: number to perform (or 1)
118             # 1: posn to copy from (undef for rand)
119             # 2: posn to splice in (undef for rand)
120             # 3: length (undef for 1, 0 for random)
121              
122             sub mutate_duplicate {
123 42     42 1 1227 my $self = shift;
124 42   50     94 my $num = +$_[0] || 1;
125 42         42 my $rt = 0;
126 42         70 for (1..$num) {
127 42         46 my $glen = scalar @{$self->[0]};
  42         61  
128 42 100 100     161 my $length = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
129 42 100       75 my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
130 42 100       71 my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
131 42 100       92 next if ($pos1 + $length) > $glen;
132 33 100       54 next if $pos2 > $glen;
133 32         34 splice @{$self->[0]}, $pos2, 0, @{$self->[0]}[$pos1..($pos1+$length-1)];
  32         67  
  32         95  
134 32         75 $rt++;
135             }
136 42         214 return $rt;
137             }
138              
139             ##
140             # Duplicates a sequence and writes it on top of some other position
141             # 0: num to perform (or 1)
142             # 1: pos to get from (undef for rand)
143             # 2: pos to start replacement (undef for rand)
144             # 3: length to operate on (undef => 1, 0 => rand)
145              
146             sub mutate_overwrite {
147 41     41 1 1332 my $self = shift;
148 41   50     96 my $num = +$_[0] || 1;
149 41         38 my $rt = 0;
150            
151 41         72 for (1..$num) {
152 41         39 my $glen = scalar @{$self->[0]};
  41         65  
153 41 100 100     155 my $length = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
154 41 100       87 my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
155 41 100       65 my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
156 41 100 100     186 next if ( ($pos1 + $length) >= $glen
157             or $pos2 > $glen);
158 26         59 splice (@{$self->[0]}, $pos2, $length,
  26         75  
159 26         28 @{$self->[0]}[$pos1..($pos1+$length-1)] );
160 26         68 $rt++;
161             }
162              
163 41         193 return $rt;
164             }
165              
166             ##
167             # Takes a run of tokens and reverses their order, is a noop with 1 item
168             # 0: number to perform
169             # 1: posn to start from (undef for rand)
170             # 2: length (undef=>1, 0=>rand)
171              
172             sub mutate_reverse {
173 37     37 1 1203 my $self = shift;
174 37   50     82 my $num = +$_[0] || 1;
175 37         41 my $rt = 0;
176            
177 37         64 for (1..$num) {
178 37         33 my $length = scalar @{$self->[0]};
  37         60  
179 37 100       87 my $pos = defined($_[1]) ? $_[1] : int rand $length;
180 37 100 100     134 my $len = !defined($_[2]) ? 1 : ($_[2] || int rand $length);
181              
182 37 100 100     157 next if ($pos >= $length
183             or $pos + $len > $length);
184              
185 22         52 splice (@{$self->[0]}, $pos, $len,
  22         65  
186 22         21 reverse( @{$self->[0]}[$pos..($pos+$len-1)] ));
187 22         53 $rt++;
188             }
189 37         189 return $rt;
190             }
191              
192             ##
193             # Changes token into one of same type (ie. passes type to generate..)
194             # 0: number to perform
195             # 1: position to affect (undef for rand)
196              
197             sub mutate_minor {
198 36     36 1 1628 my $self = shift;
199 36   100     108 my $num = +$_[0] || 1;
200 36         36 my $rt = 0;
201 36         60 for (1..$num) {
202 36         32 my $glen = scalar @{$self->[0]};
  36         59  
203 36 100       213 my $pos = defined $_[1] ? $_[1] : int rand $glen;
204 36 100       67 next if $pos >= $glen; # pos lies outside of gene
205 35         51 my $type = $self->[0][$pos];
206 35         171 my $token = $self->generate_token($type);
207 35         240 $self->[0][$pos] = $token;
208 35         64 $rt++;
209             }
210 36         208 return $rt;
211             }
212              
213             ##
214             # Changes one token into some other token
215             # 0: number to perform
216             # 1: position to affect (undef for random)
217              
218             sub mutate_major {
219 44     44 1 1315 my $self = shift;
220 44   100     132 my $num = +$_[0] || 1;
221 44         63 my $rt = 0;
222 44         70 for (1..$num) {
223 44         39 my $glen = scalar @{$self->[0]};
  44         74  
224 44 100       96 my $pos = defined $_[1] ? $_[1] : int rand $glen;
225 44 100       80 next if $pos >= $glen ; # outside of gene
226 43         88 my $token = $self->generate_token();
227 43         249 $self->[0][$pos] = $token;
228 43         165 $rt++;
229             }
230 44         172 return $rt;
231             }
232              
233             ##
234             # swaps over two sequences within the gene
235             # any sort of oddness can occur if regions overlap
236             # 0: number to perform
237             # 1: start of first sequence (undef for rand)
238             # 2: start of second sequence (undef for rand)
239             # 3: length of first sequence (undef for 1, 0 for rand)
240             # 4: length of second sequence (undef for 1, 0 for rand)
241              
242             sub mutate_switch {
243 40     40 1 1239 my $self = shift;
244 40   50     88 my $num = $_[0] || 1;
245 40         44 my $rt = 0;
246 40         58 for (1..$num) {
247 40         40 my $glen = scalar @{$self->[0]};
  40         61  
248 40 100       95 my $pos1 = defined $_[1] ? $_[1] : int rand $glen;
249 40 100       74 my $pos2 = defined $_[2] ? $_[2] : int rand length $glen;
250 40 100       80 next if $pos1 == $pos2;
251 36 100 100     124 my $len1 = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
252 36 100 66     114 my $len2 = !defined($_[4]) ? 1 : ($_[4] || int rand $glen);
253              
254 36 100       83 if ($pos1 > $pos2) { # ensure $pos1 comes first
255 28         41 ($pos1, $pos2) = ($pos2, $pos1);
256 28         43 ($len1, $len2) = ($len2, $len1);
257             }
258              
259 36 100 100     169 if ( ($pos1 + $len1) > $pos2 # ensure no overlaps
      66        
260             or ($pos2 + $len2) > $glen
261             or $pos1 >= $glen ) {
262 21         50 next;
263             }
264              
265 15         52 my @chunk1 = splice(@{$self->[0]}, $pos1, $len1,
  15         55  
266 15         19 splice(@{$self->[0]}, $pos2, $len2) );
267 15         25 splice @{$self->[0]}, $pos2 + $len2 - $len1,0, @chunk1;
  15         41  
268 15         45 $rt++;
269             }
270 40         181 return $rt;
271             }
272              
273             ##
274             # takes a sequence, removes it, then inserts it at another position
275             # odd things might occur if posn to replace to lies within area taken from
276             # 0: number to perform
277             # 1: posn to get from (undef for rand)
278             # 2: posn to put (undef for rand)
279             # 3: length of sequence (undef for 1, 0 for rand)
280              
281             sub mutate_shuffle {
282 48     48 1 1725 my $self = shift;
283 48   50     111 my $num = +$_[0] || 1;
284 48         53 my $rt = 0;
285            
286 48         82 for (1..$num) {
287 48         48 my $glen = scalar @{$self->[0]};
  48         80  
288 48 100       113 my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
289 48 100       94 my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
290 48 100 100     170 my $len = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
291              
292 48 100 100     307 next if ($pos1 +$len > $glen # outside gene
      100        
      66        
293             or $pos2 >= $glen # outside gene
294             or ($pos2 < ($pos1 + $len) and $pos2 > $pos1)); # overlap
295              
296 32 100       51 if ($pos1 < $pos2) {
297 16         30 splice (@{$self->[0]}, $pos2-$len, 0,
  16         52  
298 16         18 splice(@{$self->[0]}, $pos1, $len) );
299             }
300             else {
301 16         26 splice(@{$self->[0]}, $pos2, 0,
  16         46  
302 16         15 splice(@{$self->[0]}, $pos1, $len) );
303             }
304 32         91 $rt++;
305             }
306 48         218 return $rt;
307             }
308              
309             # These are intended to be overriden, simple versions are
310             # provided for the sake of testing.
311              
312             # Generates things to make up genes
313             # can be called with a token type to produce, or with none.
314             # if called with a token type, it will also be passed the original
315             # token as the second argument.
316             # should return a two element list of the token type followed by the token itself.
317              
318             sub generate_token {
319 0     0 1 0 my $self = shift;
320 0         0 my $token_type = $_[0];
321 0         0 my $letter = ('a'..'z')[rand 25];
322 0 0       0 unless ($token_type) {
323 0         0 return $letter;
324             }
325 0         0 return $token_type;
326             }
327              
328             ## You might also want to have methods like the following,
329             # they will not be called by the 'sequence' methods.
330              
331             # Default constructor
332             sub new {
333 0     0 1 0 my $gene = [[]]; # leave space for other info
334 0   0     0 return bless $gene, ref $_[0] || $_[0];
335             }
336              
337             # remember that clone method may require deep copying depending on
338             # your specific needs
339              
340             sub clone {
341 312     312 1 24589 my $self = shift;
342 312         451 my $new = [];
343 312         332 $new->[0] = [@{$self->[0]}];
  312         1530  
344 312         918 return bless $new, ref $self;
345             }
346              
347             # You need some way to use the gene you've made and mutated, but
348             # this will let you have a look, if it starts being odd.
349              
350             sub render_gene {
351 0     0 1   my $self = shift;
352 0           my $return = "$self\n";
353 0           $return .= (join ',', @{$self->[0]}). "\n";
  0            
354 0           return $return;
355             }
356              
357             # used for testing
358              
359             sub _test_dump {
360 0     0     my $self = shift;
361 0           my $rt = (join('',@{$self->[0]}));
  0            
362 0           return $rt;
363             }
364             1;
365              
366             __END__;