NAME
AI::Gene::Sequence
SYNOPSIS
A base class for storing and mutating genetic sequences.
package Somegene;
use AI::Gene::Sequence;
our @ISA = qw(AI::Gene::Sequence);
my %things = ( a => [qw(a1 a2 a3 a4 a5)],
b => [qw(b1 b2 b3 b4 b5)],);
sub generate_token {
my $self = shift;
my ($type, $prev) = @_;
if ($type) {
$prev = ${ $things{$type} }[rand @{ $things{$type} }];
}
else {
$type = ('a','b')[rand 2];
$prev = ${$things{$type}}[rand @{$things{$type}}];
}
return ($type, $prev);
}
sub valid_gene {
my $self = shift;
return 0 if $_[0] =~ /(.)\1/;
return 1;
}
sub seed {
my $self = shift;
$self->[0] = 'ababab';
@{$self->[1]} = qw(A1 B1 A2 B2 A3 B3);
}
sub render {
my $self = shift;
return join(' ', @{$self->[1]});
}
# elsewhere
package main;
my $gene = Somegene->new;
$gene->seed;
print $gene->render, "\n";
$gene->mutate(5);
print $gene->render, "\n";
$gene->mutate(5);
print $gene->render, "\n";
DESCRIPTION
This is a class which provides generic methods for the creation and mutation of genetic sequences. Various mutations are provided as is a way to ensure that genes created by mutations remain useful (for instance, if a gene gives rise to code, it can be tested for correct syntax).
If you do not need to keep check on what sort of thing is currently occupying a slot in the gene, you would be better off using the AI::Gene::Simple class instead as this will be somewhat faster. The interface to the mutations is the same though, so if you need to change in future, then it will not be too painful.
This module should not be confused with the bioperl modules which are used to analyse DNA sequences.
It is intended that the methods in this code are inherited by other modules.
Anatomy of a gene
A gene is a sequence of tokens, each a member of some group of simillar tokens (they can of course all be members of a single group). This module encodes genes as a string representing token types, and an array containing the tokens themselves, this allows for arbitary data to be stored as a token in a gene.
For instance, a regular expression could be encoded as:
$self = ['ccartm',['a', 'b', '|', '[A-Z]', '\W', '*?'] ]
Using a string to indicate the sort of thing held at the corresponding part of the gene allows for a simple test of the validity of a proposed gene by using a regular expression.
Using the module
To use the genetic sequences, you must write your own implementations of the following methods:
- generate_token
- valid_gene
You may also want to override the following methods:
- new
- clone
- render_gene
Mutation methods
Mutation methods are all named mutate_*
. In general, the first argument will be the number of mutations required, followed by the positions in the genes which should be affected, followed by the lengths of sequences within the gene which should be affected. If positions are not defined, then random ones are chosen. If lengths are not defined, a length of 1 is assumed (ie. working on single tokens only), if a length of 0 is requested, then a random length is chosen.
Also, if a mutation is suggested but would result in an invalid sequence, then the mutation will not be carried out. If a mutation is attempted which could corrupt your gene (copying from a region beyond the end of the gene for instance) then it will be silently skipped. Mutation methods all return the number of mutations carried out (not the number of tokens affected).
These methods all expect to be passed positive integers, undef or zero, other values could (and likely will) do something unpredictable.
mutate([num, ref to hash of probs & methods])
-
This will call at random one of the other mutation methods. It will repeat itself num times. If passed a reference to a hash as its second argument, it will use that to decide which mutation to attempt.
This hash should contain keys which fit $1 in
mutate_(.*)
and values indicating the weight to be given to that method. The module will normalise this nicely, so you do not have to. This lets you define your own mutation methods in addition to overriding any you do not like in the module. mutate_insert([num, pos])
-
Inserts a single token into the string at position pos. The token will be randomly generated by the calling object's
generate_token
method. mutate_overwrite([num, pos1, pos2, len])
-
Copies a section of the gene (starting at pos1, length len) and writes it back into the gene, overwriting current elements, starting at pos2.
mutate_reverse([num, pos, len])
-
Takes a sequence within the gene and reverses the ordering of the elements within that sequence. Starts at position pos for length len.
mutate_shuffle([num, pos1, pos2, len])
-
This takes a sequence (starting at pos1 length len) from within a gene and moves it to another position (starting at pos2). Odd things might occur if the position to move the sequence into lies within the section to be moved, but the module will try its hardest to cause a mutation.
mutate_duplicate([num, pos1, pos2, length])
-
This copies a portion of the gene starting at pos1 of length length and then splices it into the gene before pos2.
mutate_remove([num, pos, length]))
-
Deletes length tokens from the gene, starting at pos. Repeats num times.
mutate_minor([num, pos])
-
This will mutate a single token at position pos in the gene into one of the same type (as decided by the object's
generate_token
method). mutate_major([num, pos])
-
This changes a single token into a token of any token type. Token at postition pos. The token is produced by the object's
generate_token
method. mutate_switch([num, pos1, pos2, len1, len2])
-
This takes two sequences within the gene and swaps them into each other's position. The first starts at pos1 with length len1 and the second at pos2 with length len2. If the two sequences overlap, then no mutation will be attempted.
The following methods are also provided, but you will probably want to overide them for your own genetic sequences.
generate_token([token type, current token])
-
This is used by the mutation methods when changing tokens or creating new ones. It is expected to return a list consisting of a single character to indicate the token type being produced and the token itself. Where it makes sense to do so the token which is about to be modifed is passed along with the token type. If the calling methods require a token of any type, then no arguments will be passed to this method.
The provided version of this method returns a random character from 'a'..'z' as both the token type and token.
valid_gene(string [, posn])
-
This is used to determine if a proposed mutation is allowed. This method is passed a string of the whole gene's token types, it will also be passed a position in the gene where this makes sense (for instance, if only one token is to change). It is expected to return a true value if a change is acceptable and a false one if it is not.
The provided version of this method always returns true.
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.
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.
render_gene
-
This is useful for debugging, returns a serialised summary of the gene.
AUTHOR
This module was written by Alex Gough (alex@rcon.org).
SEE ALSO
For an illustration of the use of this module, see Regexgene.pm, Musicgene.pm, spamscan.pl and music.pl from the gziped distribution.
COPYRIGHT
Copyright (c) 2000 Alex Gough <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.
BUGS
This is very slow if you do not need to check that your mutations create valid genes, but fast if you do, thems the breaks. There is a AI::Gene::Simple class instead if this bothers you.
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 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.