File Coverage

blib/lib/AI/Genetic.pm
Criterion Covered Total %
statement 12 119 10.0
branch 0 34 0.0
condition 0 19 0.0
subroutine 4 20 20.0
pod 14 14 100.0
total 30 206 14.5


line stmt bran cond sub pod time code
1              
2             package AI::Genetic;
3              
4 1     1   6704 use strict;
  1         4  
  1         46  
5 1     1   6 use Carp;
  1         3  
  1         104  
6              
7 1     1   7 use vars qw/$VERSION/;
  1         7  
  1         69  
8              
9             $VERSION = 0.05;
10              
11 1     1   608 use AI::Genetic::Defaults;
  1         12  
  1         1433  
12              
13             # new AI::Genetic. More modular.
14             # Not too many checks are done still.
15              
16             ##### Shared private vars
17             # this hash predefines some strategies
18              
19             my %_strategy = (
20             rouletteSinglePoint => \&AI::Genetic::Defaults::rouletteSinglePoint,
21             rouletteTwoPoint => \&AI::Genetic::Defaults::rouletteTwoPoint,
22             rouletteUniform => \&AI::Genetic::Defaults::rouletteUniform,
23              
24             tournamentSinglePoint => \&AI::Genetic::Defaults::tournamentSinglePoint,
25             tournamentTwoPoint => \&AI::Genetic::Defaults::tournamentTwoPoint,
26             tournamentUniform => \&AI::Genetic::Defaults::tournamentUniform,
27              
28             randomSinglePoint => \&AI::Genetic::Defaults::randomSinglePoint,
29             randomTwoPoint => \&AI::Genetic::Defaults::randomTwoPoint,
30             randomUniform => \&AI::Genetic::Defaults::randomUniform,
31             );
32              
33             # this hash maps the genome types to the
34             # classes they're defined in.
35              
36             my %_genome2class = (
37             bitvector => 'AI::Genetic::IndBitVector',
38             rangevector => 'AI::Genetic::IndRangeVector',
39             listvector => 'AI::Genetic::IndListVector',
40             );
41              
42             ##################
43              
44             # sub new():
45             # This is the constructor. It creates a new AI::Genetic
46             # object. Options are:
47             # -population: set the population size
48             # -crossover: set the crossover probability
49             # -mutation: set the mutation probability
50             # -fitness: set the fitness function
51             # -type: set the genome type. See docs.
52             # -terminate: set termination sub.
53              
54             sub new {
55 0     0 1   my ($class, %args) = @_;
56              
57 0           my $self = bless {
58             ADDSEL => {}, # user-defined selections
59             ADDCRS => {}, # user-defined crossovers
60             ADDMUT => {}, # user-defined mutations
61             ADDSTR => {}, # user-defined strategies
62             } => $class;
63              
64 0   0 0     $self->{FITFUNC} = $args{-fitness} || sub { 1 };
  0            
65 0   0       $self->{CROSSRATE} = $args{-crossover} || 0.95;
66 0   0       $self->{MUTPROB} = $args{-mutation} || 0.05;
67 0   0       $self->{POPSIZE} = $args{-population} || 100;
68 0   0       $self->{TYPE} = $args{-type} || 'bitvector';
69 0   0 0     $self->{TERM} = $args{-terminate} || sub { 0 };
  0            
70              
71 0           $self->{PEOPLE} = []; # list of individuals
72 0           $self->{GENERATION} = 0; # current gen.
73              
74 0           $self->{INIT} = 0; # whether pop is initialized or not.
75 0           $self->{SORTED} = 0; # whether the population is sorted by score or not.
76 0           $self->{INDIVIDUAL} = ''; # name of individual class to use().
77              
78 0           return $self;
79             }
80              
81             # sub createStrategy():
82             # This method creates a new strategy.
83             # It takes two arguments: name of strategy, and
84             # anon sub that implements it.
85              
86             sub createStrategy {
87 0     0 1   my ($self, $name, $sub) = @_;
88              
89 0 0         if (ref($sub) eq 'CODE') {
90 0           $self->{ADDSTR}{$name} = $sub;
91             } else {
92             # we don't know what this operation is.
93 0           carp <
94             ERROR: Must specify anonymous subroutine for strategy.
95             Strategy '$name' will be deleted.
96             EOC
97             ;
98 0           delete $self->{ADDSTR}{$name};
99 0           return undef;
100             }
101              
102 0           return $name;
103             }
104              
105             # sub evolve():
106             # This method evolves the population using a specific strategy
107             # for a specific number of generations.
108              
109             sub evolve {
110 0     0 1   my ($self, $strategy, $gens) = @_;
111              
112 0 0         unless ($self->{INIT}) {
113 0           carp "can't evolve() before init()";
114 0           return undef;
115             }
116              
117 0           my $strSub;
118 0 0         if (exists $self->{ADDSTR}{$strategy}) {
    0          
119 0           $strSub = $self->{ADDSTR}{$strategy};
120             } elsif (exists $_strategy{$strategy}) {
121 0           $strSub = $_strategy{$strategy};
122             } else {
123 0           carp "ERROR: Do not know what strategy '$strategy' is,";
124 0           return undef;
125             }
126              
127 0   0       $gens ||= 1;
128              
129 0           for my $i (1 .. $gens) {
130 0           $self->sortPopulation;
131 0           $strSub->($self);
132              
133 0           $self->{GENERATION}++;
134 0           $self->{SORTED} = 0;
135              
136 0 0         last if $self->{TERM}->($self);
137              
138             # my @f = $self->getFittest(10);
139             # for my $f (@f) {
140             # print STDERR " Fitness = ", $f->score, "..\n";
141             # print STDERR " Genes are: @{$f->genes}.\n";
142             # }
143             }
144             }
145              
146             # sub sortIndividuals():
147             # This method takes as input an anon list of individuals, and returns
148             # another anon list of the same individuals but sorted in decreasing
149             # score.
150              
151             sub sortIndividuals {
152 0     0 1   my ($self, $list) = @_;
153              
154             # make sure all score's are calculated.
155             # This is to avoid a bug in Perl where a sort is called from whithin another
156             # sort, and they are in different packages, then you get a use of uninit value
157             # warning. See http://rt.perl.org/rt3/Ticket/Display.html?id=7063
158 0           $_->score for @$list;
159              
160 0           return [sort {$b->score <=> $a->score} @$list];
  0            
161             }
162              
163             # sub sortPopulation():
164             # This method sorts the population of individuals.
165              
166             sub sortPopulation {
167 0     0 1   my $self = shift;
168              
169 0 0         return if $self->{SORTED};
170              
171 0           $self->{PEOPLE} = $self->sortIndividuals($self->{PEOPLE});
172 0           $self->{SORTED} = 1;
173             }
174              
175             # sub getFittest():
176             # This method returns the fittest individuals.
177              
178             sub getFittest {
179 0     0 1   my ($self, $N) = @_;
180              
181 0   0       $N ||= 1;
182 0 0         $N = 1 if $N < 1;
183              
184 0 0         $N = @{$self->{PEOPLE}} if $N > @{$self->{PEOPLE}};
  0            
  0            
185              
186 0           $self->sortPopulation;
187              
188 0           my @r = @{$self->{PEOPLE}}[0 .. $N-1];
  0            
189              
190 0 0 0       return $r[0] if $N == 1 && not wantarray;
191              
192 0           return @r;
193             }
194              
195             # sub init():
196             # This method initializes the population to completely
197             # random individuals. It deletes all current individuals!!!
198             # It also examines the type of individuals we want, and
199             # require()s the proper class. Throws an error if it can't.
200             # Must pass to it an anon list that will be passed to the
201             # newRandom method of the individual.
202              
203             # In case of bitvector, $newArgs is length of bitvector.
204             # In case of rangevector, $newArgs is anon list of anon lists.
205             # each sub-anon list has two elements, min number and max number.
206             # In case of listvector, $newArgs is anon list of anon lists.
207             # Each sub-anon list contains possible values of gene.
208              
209             sub init {
210 0     0 1   my ($self, $newArgs) = @_;
211              
212 0           $self->{INIT} = 0;
213              
214 0           my $ind;
215 0 0         if (exists $_genome2class{$self->{TYPE}}) {
216 0           $ind = $_genome2class{$self->{TYPE}};
217             } else {
218 0           $ind = $self->{TYPE};
219             }
220              
221 0           eval "use $ind"; # does this work if package is in same file?
222 0 0         if ($@) {
223 0           carp "ERROR: Init failed. Can't require '$ind': $@,";
224 0           return undef;
225             }
226              
227 0           $self->{INDIVIDUAL} = $ind;
228 0           $self->{PEOPLE} = [];
229 0           $self->{SORTED} = 0;
230 0           $self->{GENERATION} = 0;
231 0           $self->{INITARGS} = $newArgs;
232              
233 0           push @{$self->{PEOPLE}} =>
234 0           $ind->newRandom($newArgs) for 1 .. $self->{POPSIZE};
235              
236 0           $_->fitness($self->{FITFUNC}) for @{$self->{PEOPLE}};
  0            
237              
238 0           $self->{INIT} = 1;
239             }
240              
241             # sub people():
242             # returns the current list of individuals in the population.
243             # note: this returns the actual array ref, so any changes
244             # made to it (ex, shift/pop/etc) will be reflected in the
245             # population.
246              
247             sub people {
248 0     0 1   my $self = shift;
249              
250 0 0         if (@_) {
251 0           $self->{PEOPLE} = shift;
252 0           $self->{SORTED} = 0;
253             }
254              
255 0           $self->{PEOPLE};
256             }
257              
258             # useful little methods to set/query parameters.
259 0 0   0 1   sub size { $_[0]{POPSIZE} = $_[1] if defined $_[1]; $_[0]{POPSIZE} }
  0            
260 0 0   0 1   sub crossProb { $_[0]{CROSSRATE} = $_[1] if defined $_[1]; $_[0]{CROSSRATE} }
  0            
261 0 0   0 1   sub mutProb { $_[0]{MUTPROB} = $_[1] if defined $_[1]; $_[0]{MUTPROB} }
  0            
262 0     0 1   sub indType { $_[0]{INDIVIDUAL} }
263 0     0 1   sub generation { $_[0]{GENERATION} }
264              
265             # sub inject():
266             # This method is used to add individuals to the current population.
267             # The point of it is that sometimes the population gets stagnant,
268             # so it could be useful add "fresh blood".
269             # Takes a variable number of arguments. The first argument is the
270             # total number, N, of new individuals to add. The remaining arguments
271             # are genomes to inject. There must be at most N genomes to inject.
272             # If the number, n, of genomes to inject is less than N, N - n random
273             # genomes are added. Perhaps an example will help?
274             # returns 1 on success and undef on error.
275              
276             sub inject {
277 0     0 1   my ($self, $count, @genomes) = @_;
278              
279 0 0         unless ($self->{INIT}) {
280 0           carp "can't inject() before init()";
281 0           return undef;
282             }
283              
284 0           my $ind = $self->{INDIVIDUAL};
285              
286 0           my @newInds;
287 0           for my $i (1 .. $count) {
288 0           my $genes = shift @genomes;
289              
290 0 0         if ($genes) {
291 0           push @newInds => $ind->newSpecific($genes, $self->{INITARGS});
292             } else {
293 0           push @newInds => $ind->newRandom ($self->{INITARGS});
294             }
295             }
296              
297 0           $_->fitness($self->{FITFUNC}) for @newInds;
298              
299 0           push @{$self->{PEOPLE}} => @newInds;
  0            
300              
301 0           return 1;
302             }
303              
304             __END__