AI-Gene-Sequence
view release on metacpan or search on metacpan
AI/Gene/Simple.pm view on Meta::CPAN
my ($probs,$mut_keys) = _normalise( { map {$_ => 1}
qw(insert remove overwrite
duplicate minor major
switch shuffle reverse) } );
##
# calls mutation method at random
# 0: number of mutations to perform
# 1: ref to hash of probs to use (otherwise uses default mutations and probs)
sub mutate {
my $self = shift;
my $num_mutates = +$_[0] || 1;
my $rt = 0;
my ($hr_probs, $muts);
if (ref $_[1] eq 'HASH') { # use non standard mutations or probs
($hr_probs, $muts) = _normalise($_[1]);
}
else { # use standard mutations and probs
$hr_probs = $probs;
$muts = $mut_keys;
}
MUT_CYCLE: for (1..$num_mutates) {
my $rand = rand;
foreach my $mutation (@{$muts}) {
next unless $rand < $hr_probs->{$mutation};
my $mut = 'mutate_' . $mutation;
$rt += $self->$mut(1);
next MUT_CYCLE;
}
}
return $rt;
}
##
# creates a normalised and cumulative prob distribution for the
# keys of the referenced hash
sub _normalise {
my $hr = $_[0];
my $h2 = {};
my $muts = [keys %{$hr}];
my $sum = 0;
foreach (values %{$hr}) {
$sum += $_;
}
if ($sum <= 0) {
die "Cannot randomly mutate with bad probability distribution";
}
else {
my $cum;
@{$h2}{ @{$muts} } = map {$cum +=$_; $cum / $sum} @{$hr}{ @{$muts} };
return ($h2, $muts);
}
}
##
# inserts one element into the sequence
# 0: number to perform ( or 1)
# 1: position to mutate (undef for random)
sub mutate_insert {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $pos = defined($_[1]) ? $_[1] : int rand $glen;
next if $pos > $glen; # further than 1 place after gene
my $token = $self->generate_token;
splice @{$self->[0]}, $pos, 0, $token;
$rt++;
}
return $rt;
}
##
# removes element(s) from sequence
# 0: number of times to perform
# 1: position to affect (undef for rand)
# 2: length to affect, undef => 1, 0 => random length
sub mutate_remove {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $length = !defined($_[2]) ? 1 : ($_[2] || int rand $glen);
return $rt if ($glen - $length) <= 0;
my $pos = defined($_[1]) ? $_[1] : int rand $glen;
next if $pos >= $glen; # outside of gene
splice @{$self->[0]}, $pos, $length;
$rt++;
}
return $rt;
}
##
# copies an element or run of elements into a random place in the gene
# 0: number to perform (or 1)
# 1: posn to copy from (undef for rand)
# 2: posn to splice in (undef for rand)
# 3: length (undef for 1, 0 for random)
sub mutate_duplicate {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $length = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
next if ($pos1 + $length) > $glen;
next if $pos2 > $glen;
splice @{$self->[0]}, $pos2, 0, @{$self->[0]}[$pos1..($pos1+$length-1)];
$rt++;
}
return $rt;
}
##
# Duplicates a sequence and writes it on top of some other position
# 0: num to perform (or 1)
# 1: pos to get from (undef for rand)
# 2: pos to start replacement (undef for rand)
# 3: length to operate on (undef => 1, 0 => rand)
sub mutate_overwrite {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $length = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
next if ( ($pos1 + $length) >= $glen
or $pos2 > $glen);
splice (@{$self->[0]}, $pos2, $length,
@{$self->[0]}[$pos1..($pos1+$length-1)] );
$rt++;
}
return $rt;
}
##
# Takes a run of tokens and reverses their order, is a noop with 1 item
# 0: number to perform
# 1: posn to start from (undef for rand)
# 2: length (undef=>1, 0=>rand)
sub mutate_reverse {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $length = scalar @{$self->[0]};
my $pos = defined($_[1]) ? $_[1] : int rand $length;
my $len = !defined($_[2]) ? 1 : ($_[2] || int rand $length);
next if ($pos >= $length
or $pos + $len > $length);
splice (@{$self->[0]}, $pos, $len,
reverse( @{$self->[0]}[$pos..($pos+$len-1)] ));
$rt++;
}
return $rt;
}
##
# Changes token into one of same type (ie. passes type to generate..)
# 0: number to perform
# 1: position to affect (undef for rand)
sub mutate_minor {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $pos = defined $_[1] ? $_[1] : int rand $glen;
next if $pos >= $glen; # pos lies outside of gene
my $type = $self->[0][$pos];
my $token = $self->generate_token($type);
$self->[0][$pos] = $token;
$rt++;
}
return $rt;
}
##
# Changes one token into some other token
# 0: number to perform
# 1: position to affect (undef for random)
sub mutate_major {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $pos = defined $_[1] ? $_[1] : int rand $glen;
next if $pos >= $glen ; # outside of gene
my $token = $self->generate_token();
$self->[0][$pos] = $token;
$rt++;
}
return $rt;
}
##
# swaps over two sequences within the gene
# any sort of oddness can occur if regions overlap
# 0: number to perform
# 1: start of first sequence (undef for rand)
# 2: start of second sequence (undef for rand)
# 3: length of first sequence (undef for 1, 0 for rand)
# 4: length of second sequence (undef for 1, 0 for rand)
sub mutate_switch {
my $self = shift;
my $num = $_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $pos1 = defined $_[1] ? $_[1] : int rand $glen;
my $pos2 = defined $_[2] ? $_[2] : int rand length $glen;
next if $pos1 == $pos2;
my $len1 = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
my $len2 = !defined($_[4]) ? 1 : ($_[4] || int rand $glen);
if ($pos1 > $pos2) { # ensure $pos1 comes first
($pos1, $pos2) = ($pos2, $pos1);
($len1, $len2) = ($len2, $len1);
}
if ( ($pos1 + $len1) > $pos2 # ensure no overlaps
or ($pos2 + $len2) > $glen
or $pos1 >= $glen ) {
next;
}
my @chunk1 = splice(@{$self->[0]}, $pos1, $len1,
splice(@{$self->[0]}, $pos2, $len2) );
splice @{$self->[0]}, $pos2 + $len2 - $len1,0, @chunk1;
$rt++;
}
return $rt;
}
##
# takes a sequence, removes it, then inserts it at another position
# odd things might occur if posn to replace to lies within area taken from
# 0: number to perform
# 1: posn to get from (undef for rand)
# 2: posn to put (undef for rand)
# 3: length of sequence (undef for 1, 0 for rand)
sub mutate_shuffle {
my $self = shift;
my $num = +$_[0] || 1;
my $rt = 0;
for (1..$num) {
my $glen = scalar @{$self->[0]};
my $pos1 = defined($_[1]) ? $_[1] : int rand $glen;
my $pos2 = defined($_[2]) ? $_[2] : int rand $glen;
my $len = !defined($_[3]) ? 1 : ($_[3] || int rand $glen);
next if ($pos1 +$len > $glen # outside gene
or $pos2 >= $glen # outside gene
or ($pos2 < ($pos1 + $len) and $pos2 > $pos1)); # overlap
if ($pos1 < $pos2) {
splice (@{$self->[0]}, $pos2-$len, 0,
splice(@{$self->[0]}, $pos1, $len) );
}
else {
splice(@{$self->[0]}, $pos2, 0,
splice(@{$self->[0]}, $pos1, $len) );
}
$rt++;
}
return $rt;
}
# These are intended to be overriden, simple versions are
# provided for the sake of testing.
# Generates things to make up genes
# can be called with a token type to produce, or with none.
# if called with a token type, it will also be passed the original
# token as the second argument.
# should return a two element list of the token type followed by the token itself.
sub generate_token {
my $self = shift;
my $token_type = $_[0];
my $letter = ('a'..'z')[rand 25];
unless ($token_type) {
return $letter;
}
return $token_type;
}
## You might also want to have methods like the following,
# they will not be called by the 'sequence' methods.
# Default constructor
sub new {
my $gene = [[]]; # leave space for other info
return bless $gene, ref $_[0] || $_[0];
}
# remember that clone method may require deep copying depending on
# your specific needs
AI/Gene/Simple.pm view on Meta::CPAN
with length I<len1> and the second at I<pos2> with length
I<len2>. If the two sequences overlap, then no mutation will
be attempted.
=back
The following methods are also provided, but you will probably
want to overide them for your own genetic sequences.
=over 4
=item C<generate_token([current token])>
This is used by the mutation methods when changing tokens or
creating new ones. It is expected to return a single token.
If a minor mutation is being attempted, then the method will
also be passed the current token.
The provided version of this method returns a random character
from 'a'..'z' as both the token type and token.
=item C<clone()>
This returns a copy of the gene as a new object. If you are using
nested genes, or other references as your tokens, then you may need
to produce your own version which will deep copy your structure.
=item C<new>
This returns an empty gene, into which you can put things. If you
want to initialise your gene, or anything useful like that, then
you will need another one of these.
=item C<render_gene>
This is useful for debugging, returns a serialised summary of the
gene.
=back
=head1 AUTHOR
This module was written by Alex Gough (F<alex@rcon.org>).
=head1 SEE ALSO
If you are encoding something which must maintain a correct
syntax (executable code, regular expressions, formal poems)
then you might be better off using AI::Gene::Sequence .
=head1 COPYRIGHT
Copyright (c) 2000 Alex Gough <F<alex@rcon.org>>. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=head1 BUGS
Some methods will do odd things if you pass them weird values,
so try not to do that. So long as you stick to passing
positive integers or C<undef> to the methods then they should
recover gracefully.
While it is easy and fun to write genetic and evolutionary
algorithms in perl, for most purposes, it will be much slower
than if they were implemented in another more suitable language.
There are some problems which do lend themselves to an approach
in perl and these are the ones where the time between mutations
will be large, for instance, when composing music where the
selection process is driven by human whims.
=cut
( run in 2.201 seconds using v1.01-cache-2.11-cpan-d80b1682f3f )