AI-Genetic

 view release on metacpan or  search on metacpan

Genetic.pm  view on Meta::CPAN

# classes they're defined in.

my %_genome2class = (
		     bitvector   => 'AI::Genetic::IndBitVector',
		     rangevector => 'AI::Genetic::IndRangeVector',
		     listvector  => 'AI::Genetic::IndListVector',
		    );

##################

# sub new():
# This is the constructor. It creates a new AI::Genetic
# object. Options are:
# -population: set the population size
# -crossover:  set the crossover probability
# -mutation:   set the mutation probability
# -fitness:    set the fitness function
# -type:       set the genome type. See docs.
# -terminate:  set termination sub.

sub new {
  my ($class, %args) = @_;

  my $self = bless {
		    ADDSEL => {},   # user-defined selections
		    ADDCRS => {},   # user-defined crossovers
		    ADDMUT => {},   # user-defined mutations
		    ADDSTR => {},   # user-defined strategies
		   } => $class;

  $self->{FITFUNC}    = $args{-fitness}    || sub { 1 };
  $self->{CROSSRATE}  = $args{-crossover}  || 0.95;
  $self->{MUTPROB}    = $args{-mutation}   || 0.05;
  $self->{POPSIZE}    = $args{-population} || 100;
  $self->{TYPE}       = $args{-type}       || 'bitvector';
  $self->{TERM}       = $args{-terminate}  || sub { 0 };

  $self->{PEOPLE}     = [];   # list of individuals
  $self->{GENERATION} = 0;    # current gen.

  $self->{INIT}       = 0;    # whether pop is initialized or not.
  $self->{SORTED}     = 0;    # whether the population is sorted by score or not.
  $self->{INDIVIDUAL} = '';   # name of individual class to use().

  return $self;
}

# sub createStrategy():
# This method creates a new strategy.
# It takes two arguments: name of strategy, and
# anon sub that implements it.

sub createStrategy {
  my ($self, $name, $sub) = @_;

  if (ref($sub) eq 'CODE') {
    $self->{ADDSTR}{$name} = $sub;
  } else {
    # we don't know what this operation is.
    carp <<EOC;
ERROR: Must specify anonymous subroutine for strategy.
       Strategy '$name' will be deleted.
EOC
    ;
    delete $self->{ADDSTR}{$name};
    return undef;
  }

  return $name;
}

# sub evolve():
# This method evolves the population using a specific strategy
# for a specific number of generations.

sub evolve {
  my ($self, $strategy, $gens) = @_;

  unless ($self->{INIT}) {
    carp "can't evolve() before init()";
    return undef;
  }

  my $strSub;
  if      (exists $self->{ADDSTR}{$strategy}) {
    $strSub = $self->{ADDSTR}{$strategy};

Genetic.pm  view on Meta::CPAN

    last if $self->{TERM}->($self);

#    my @f = $self->getFittest(10);
#    for my $f (@f) {
#      print STDERR "    Fitness = ", $f->score, "..\n";
#      print STDERR "    Genes are: @{$f->genes}.\n";
#    }
  }
}

# sub sortIndividuals():
# This method takes as input an anon list of individuals, and returns
# another anon list of the same individuals but sorted in decreasing
# score.

sub sortIndividuals {
  my ($self, $list) = @_;

  # make sure all score's are calculated.
  # This is to avoid a bug in Perl where a sort is called from whithin another
  # sort, and they are in different packages, then you get a use of uninit value
  # warning. See http://rt.perl.org/rt3/Ticket/Display.html?id=7063
  $_->score for @$list;

  return [sort {$b->score <=> $a->score} @$list];
}

# sub sortPopulation():
# This method sorts the population of individuals.

sub sortPopulation {
  my $self = shift;

  return if $self->{SORTED};

  $self->{PEOPLE} = $self->sortIndividuals($self->{PEOPLE});
  $self->{SORTED} = 1;
}

# sub getFittest():
# This method returns the fittest individuals.

sub getFittest {
  my ($self, $N) = @_;

  $N ||= 1;
  $N = 1 if $N < 1;

  $N = @{$self->{PEOPLE}} if $N > @{$self->{PEOPLE}};

  $self->sortPopulation;

  my @r = @{$self->{PEOPLE}}[0 .. $N-1];

  return $r[0] if $N == 1 && not wantarray;

  return @r;
}

# sub init():
# This method initializes the population to completely
# random individuals. It deletes all current individuals!!!
# It also examines the type of individuals we want, and
# require()s the proper class. Throws an error if it can't.
# Must pass to it an anon list that will be passed to the
# newRandom method of the individual.

# In case of bitvector, $newArgs is length of bitvector.
# In case of rangevector, $newArgs is anon list of anon lists.
# each sub-anon list has two elements, min number and max number.
# In case of listvector, $newArgs is anon list of anon lists.
# Each sub-anon list contains possible values of gene.

sub init {
  my ($self, $newArgs) = @_;

  $self->{INIT} = 0;

  my $ind;
  if (exists $_genome2class{$self->{TYPE}}) {
    $ind = $_genome2class{$self->{TYPE}};
  } else {
    $ind = $self->{TYPE};
  }

Genetic.pm  view on Meta::CPAN

  $self->{INITARGS}   = $newArgs;

  push @{$self->{PEOPLE}} =>
    $ind->newRandom($newArgs) for 1 .. $self->{POPSIZE};

  $_->fitness($self->{FITFUNC}) for @{$self->{PEOPLE}};

  $self->{INIT} = 1;
}

# sub people():
# returns the current list of individuals in the population.
# note: this returns the actual array ref, so any changes
# made to it (ex, shift/pop/etc) will be reflected in the
# population.

sub people {
  my $self = shift;

  if (@_) {
    $self->{PEOPLE} = shift;
    $self->{SORTED} = 0;
  }

  $self->{PEOPLE};
}

# useful little methods to set/query parameters.
sub size       { $_[0]{POPSIZE}    = $_[1] if defined $_[1]; $_[0]{POPSIZE}   }
sub crossProb  { $_[0]{CROSSRATE}  = $_[1] if defined $_[1]; $_[0]{CROSSRATE} }
sub mutProb    { $_[0]{MUTPROB}    = $_[1] if defined $_[1]; $_[0]{MUTPROB}   }
sub indType    { $_[0]{INDIVIDUAL} }
sub generation { $_[0]{GENERATION} }

# sub inject():
# This method is used to add individuals to the current population.
# The point of it is that sometimes the population gets stagnant,
# so it could be useful add "fresh blood".
# Takes a variable number of arguments. The first argument is the
# total number, N, of new individuals to add. The remaining arguments
# are genomes to inject. There must be at most N genomes to inject.
# If the number, n, of genomes to inject is less than N, N - n random
# genomes are added. Perhaps an example will help?
# returns 1 on success and undef on error.

sub inject {
  my ($self, $count, @genomes) = @_;

  unless ($self->{INIT}) {
    carp "can't inject() before init()";
    return undef;
  }

  my $ind = $self->{INDIVIDUAL};

  my @newInds;

Genetic.pm  view on Meta::CPAN

        -population => 500,
        -crossover  => 0.9,
        -mutation   => 0.01,
	-terminate  => \&terminateFunc,
       );

     $ga->init(10);
     $ga->evolve('rouletteTwoPoint', 100);
     print "Best score = ", $ga->getFittest->score, ".\n";

     sub fitnessFunc {
         my $genes = shift;

         my $fitness;
         # assign a number to $fitness based on the @$genes
         # ...

         return $fitness;
      }

      sub terminateFunc {
         my $ga = shift;

         # terminate if reached some threshold.
         return 1 if $ga->getFittest->score > $THRESHOLD;
         return 0;
      }

=head1 DESCRIPTION

This module implements a Genetic Algorithm (GA) in pure Perl.

Genetic.pm  view on Meta::CPAN

supported. The user can always transform any desired fractional values
by multiplying and dividing by an appropriate power of 10.

=back

Defaults to I<bitvector>.

=item I<-terminate>

This option allows the definition of a termination subroutine.
It expects a subroutine reference. This sub will be called at
the end of each generation with one argument: the AI::Genetic
object. Evolution terminates if the sub returns a true value.

=back

=item I<$ga>-E<gt>B<createStrategy>(I<strategy_name>, I<sub_ref>)

This method allows the creation of a custom-made strategy to be used
during evolution. It expects a unique strategy name, and a subroutine
reference as arguments. The subroutine will be called with one argument:
the AI::Genetic object. It is expected to alter the population at each
generation. See L</"STRATEGIES"> for more information.

Genetic.pm  view on Meta::CPAN

The population is sorted according to the individuals' fitnesses.

=item o

The subroutine corresponding to the named strategy is called with one argument,
the AI::Genetic object. This subroutine is expected to alter the object itself.

=item o

If a termination subroutine is given, it is executed and the return value is
checked. Evolution terminates if this sub returns a true value.

=back

=item I<$ga>-E<gt>B<getFittest>(?I<N>?)

This returns the I<N> fittest individuals. If not specified,
I<N> defaults to 1. As a side effect, it sorts the population by
fitness score. The actual AI::Genetic::Individual objects are returned.
You can use the C<genes()> and C<score()> methods to get the genes and the
scores of the individuals. Please check L<AI::Genetic::Individual> for details.

Genetic.pm  view on Meta::CPAN

More detail on these strategies and how to call them in your own
custom strategies can be found in L<AI::Genetic::OpSelection>,
L<AI::Genetic::OpCrossover> and L<AI::Genetic::OpMutation>.

You can use the functions defined in the above modules in your
own custom-made strategy. Consult their manpages for more info.
A custom-made strategy can be defined using the I<strategy()>
method and is called at the beginning of each generation. The only
argument to it is the AI::Genetic object itself. Note that the
population at this point is sorted accoring to each individual's
fitness score. It is expected that the strategy sub will modify
the population stored in the AI::Genetic object. Here's the
pseudo-code of events:

    for (1 .. num_generations) {
      sort population;
      call strategy_sub;
      if (termination_sub exists) {
        call termination_sub;
        last if returned true value;
      }
    }

=head1 A NOTE ON SPEED/EFFICIENCY

Genetic algorithms are inherently slow.
Perl can be pretty fast, but will never reach the speed of optimized
C code (at least my Perl coding will not). I wrote AI::Genetic mainly

Genetic/Defaults.pm  view on Meta::CPAN


use strict;
use AI::Genetic::OpSelection;
use AI::Genetic::OpCrossover;
use AI::Genetic::OpMutation;

1;

# this implements the default strategies.

sub rouletteSinglePoint {
  # initialize the roulette wheel
  AI::Genetic::OpSelection::initWheel($_[0]->people);

  push @_ => 'vectorSinglePoint', 'rouletteUnique';
  goto &genericStrategy;
}

sub rouletteTwoPoint {
  # initialize the roulette wheel
  AI::Genetic::OpSelection::initWheel($_[0]->people);

  push @_ => 'vectorTwoPoint', 'rouletteUnique';
  goto &genericStrategy;
}

sub rouletteUniform {
  # initialize the roulette wheel
  AI::Genetic::OpSelection::initWheel($_[0]->people);

  push @_ => 'vectorUniform', 'rouletteUnique';
  goto &genericStrategy;
}

sub tournamentSinglePoint {
  push @_ => 'vectorSinglePoint', 'tournament', [$_[0]->people];
  goto &genericStrategy;
}

sub tournamentTwoPoint {
  push @_ => 'vectorTwoPoint', 'tournament', [$_[0]->people];
  goto &genericStrategy;
}

sub tournamentUniform {
  push @_ => 'vectorUniform', 'tournament', [$_[0]->people];
  goto &genericStrategy;
}

sub randomSinglePoint {
    push @_ => 'vectorSinglePoint', 'random', [$_[0]->people];
  goto &genericStrategy;
}

sub randomTwoPoint {
  push @_ => 'vectorTwoPoint', 'random', [$_[0]->people];
  goto &genericStrategy;
}

sub randomUniform {
  push @_ => 'vectorUniform', 'random', [$_[0]->people];
  goto &genericStrategy;
}

# generic sub that implements everything.
sub genericStrategy {
  my ($ga, $Xop, $selOp, $selArgs) = @_;

  #perhaps args should be:
  # ($ga, [xop, xargs], [selop, selargs]) ?

  my $pop = $ga->people;

  # now double up the individuals, and get top half.
  my $size = $ga->size;
  my $ind  = $ga->indType;

Genetic/Defaults.pm  view on Meta::CPAN

    $mutOp = 'rangeVector';
    push @mutArgs => $pop->[0]->ranges;
  } elsif ($ind =~ /IndListVector/) {
    $mutOp = 'listVector';
    push @mutArgs => $pop->[0]->lists;
  }

  my ($ssub, $xsub, $msub);
  {
    no strict 'refs';
    $ssub = \&{"AI::Genetic::OpSelection::$selOp"};
    $xsub = \&{"AI::Genetic::OpCrossover::$Xop"};
    $msub = \&{"AI::Genetic::OpMutation::$mutOp"};
  }

  for my $i (1 .. $size/2) {
    my @parents = $ssub->(@$selArgs);
    @parents < 2 and push @parents => $ssub->(@$selArgs);

    my @cgenes  = $xsub->($crossProb, map scalar $_->genes, @parents);

    # check if two didn't mate.
    unless (ref $cgenes[0]) {

Genetic/IndBitVector.pm  view on Meta::CPAN


package AI::Genetic::IndBitVector;

use strict;
use base qw/AI::Genetic::Individual/;

1;

sub newRandom {
  my ($class, $length) = @_;

  my $self = bless {
		    GENES   => [],
		    SCORE   => 0,
		    FITFUNC => sub {},
		    CALCED  => 0,
		   } => $class;

  push @{$self->{GENES}} => rand > 0.5 ? 1 : 0
    for 1 .. $length;

  return $self;
}

sub newSpecific {
  my ($class, $genes) = @_;

  my $self = bless {
		    GENES   => $genes,
		    CALCED  => 0,
		    SCORE   => 0,
		    FITFUNC => sub {},
		   } => $class;

  return $self;
}

sub genes {
  my $self = shift;

  return wantarray ? @{$self->{GENES}} : [@{$self->{GENES}}];
}

Genetic/IndListVector.pm  view on Meta::CPAN


package AI::Genetic::IndListVector;

use strict;
use base qw/AI::Genetic::Individual/;

1;

sub newRandom {
  my ($class, $lists) = @_;

  my $self = bless {
		    GENES   => [],
		    SCORE   => 0,
		    FITFUNC => sub {},
		    CALCED  => 0,
		    LISTS   => $lists,
		   } => $class;

  push @{$self->{GENES}} => $_->[rand @$_] for @$lists;

  return $self;
}

sub newSpecific {
  my ($class, $genes, $lists) = @_;

  my $self = bless {
		    GENES   => $genes,
		    CALCED  => 0,
		    SCORE   => 0,
		    FITFUNC => sub {},
		    LISTS   => $lists,
		   } => $class;

  return $self;
}

sub genes {
  my $self = shift;

  return wantarray ? @{$self->{GENES}} : [@{$self->{GENES}}];
}

sub lists { $_[0]{LISTS} }

Genetic/IndRangeVector.pm  view on Meta::CPAN


package AI::Genetic::IndRangeVector;

use strict;
use base qw/AI::Genetic::Individual/;

1;

sub newRandom {
  my ($class, $ranges) = @_;

  my $self = bless {
		    GENES   => [],
		    SCORE   => 0,
		    FITFUNC => sub {},
		    CALCED  => 0,
		    RANGES  => $ranges,
		   } => $class;

  for my $r (@$ranges) {
    my $rand = $r->[0] + int rand($r->[1] - $r->[0] + 1);
    push @{$self->{GENES}} => $rand;
  }

  return $self;
}

sub newSpecific {
  my ($class, $genes, $ranges) = @_;

  my $self = bless {
		    GENES   => $genes,
		    CALCED  => 0,
		    SCORE   => 0,
		    FITFUNC => sub {},
		    RANGES  => $ranges,
		   } => $class;

  return $self;
}

sub genes {
  my $self = shift;

  return wantarray ? @{$self->{GENES}} : [@{$self->{GENES}}];
}

sub ranges { $_[0]{RANGES} }

Genetic/Individual.pm  view on Meta::CPAN

use strict;
use vars qw/$VERSION/;
$VERSION = 0.02;

# this package is to serve as a base package to
# all other individuals. It doesn't do anything
# interesting.

1;

sub new {  # hmm .. do I need this?
  my ($class, $genes) = @_;

  my $self;
  if (ref $class) { # clone mode
    $self = bless {} => ref $class;
    $self->{$_} = $class->{$_} for keys %$class;
    $self->{GENES}  = $genes;
    $self->{CALCED} = 0;

  } else {          # new mode. Genome is given
    goto &newSpecific;
  }

  return $self;
}

sub new_old {  # hmm .. do I need this?
  my ($class, $genes) = @_;

  my $self;
  if (ref $class) { # clone mode
    $self = bless {} => ref $class;
    $self->{$_} = $class->{$_} for keys %$class;
    $self->{GENES}  = $genes;
    $self->{CALCED} = 0;

  } else {          # new mode. Just call newRandom.
    goto &newRandom;
  }

  return $self;
}

# should create default methods.
# those are the only three needed.
sub newRandom   {}
sub newSpecific {}
sub genes       {}

# the following methods shouldn't be overridden.
sub fitness {
  my ($self, $sub) = @_;

  $self->{FITFUNC} = $sub if $sub;
  return $self->{FITFUNC};
}

sub score {
  my $self = shift;

  return $self->{SCORE} if $self->{CALCED};

  $self->{SCORE}  = $self->{FITFUNC}->(scalar $self->genes);
  $self->{CALCED} = 1;

  return $self->{SCORE};
}

sub resetScore { $_[0]{CALCED} = 0 }

# hmmm .. how do I reset {CALCED} in case of mutation?

__END__

=head1 NAME

AI::Genetic::Individual - Base class for AI::Genetic Individuals.

=head1 SYNOPSIS

Genetic/Individual.pm  view on Meta::CPAN

instance.

If called as a class method, then it calls I<newSpecific()>. See below
for details.

=item I<$individual>-E<gt>B<fitness(?anon_sub?)>

This method is used to set/query the anonymous subroutine used to
calculate the individual's fitness. If an argument is given, it expects
a subroutine reference which will be set as the fitness subroutine. In
either case, it'll return the fitness sub ref.

=item I<$individual>-E<gt>B<score()>

This method returns the fitness score of the individual. If the score has
never been calculated before, then the fitness function is executed and
the score saved. Subsequent calls to score() will return the cached value.

=item I<$individual>-E<gt>B<resetScore()>

This method resets the score of the individual such that a subsequent call

Genetic/OpCrossover.pm  view on Meta::CPAN


package AI::Genetic::OpCrossover;

use strict;

1;

# sub vectorSinglePoint():
# Single point crossover.
# arguments are crossover prob, two
# anon lists of genes (parents).
# If crossover occurs, returns two anon lists
# of children genes. If no crossover, returns 0.
# both parents have to be of same length.

sub vectorSinglePoint {
  my ($prob, $mom, $dad) = @_;

  return 0 if rand > $prob;

  # get single index from 1 to $#{$dad}
  my $ind = 1 + int rand $#{$dad};

  my @c1 = (@$mom[0 .. $ind - 1],
	    @$dad[$ind .. $#{$dad}]);
  my @c2 = (@$dad[0 .. $ind - 1],
	    @$mom[$ind .. $#{$dad}]);

  return (\@c1, \@c2);
}

# sub vectorTwoPoint():
# Two point crossover.
# arguments are crossover prob, two
# anon lists of genes (parents).
# If crossover occurs, returns two anon lists
# of children genes. If no crossover, returns 0.
# both parents have to be of same length.

sub vectorTwoPoint {
  my ($prob, $mom, $dad) = @_;

  return 0 if rand > $prob;

  # get first index from 1 to $#{$dad}-1
  my $ind1 = 1 + int rand($#{$dad} - 1);

  # get second index from $ind1 to $#{$dad}
  my $ind2 = $ind1 + 1 + int rand($#{$dad} - $ind1);
  my @c1 = (@$mom[0 .. $ind1 - 1],
	    @$dad[$ind1 .. $ind2 - 1],
	    @$mom[$ind2 .. $#{$dad}]);

  my @c2 = (@$dad[0 .. $ind1 - 1],
	    @$mom[$ind1 .. $ind2 - 1],
	    @$dad[$ind2 .. $#{$dad}]);

  return (\@c1, \@c2);
}

# sub vectorUniform():
# Uniform crossover.
# arguments are crossover prob, two
# anon lists of genes (parents).
# If crossover occurs, returns two anon lists
# of children genes. If no crossover, returns 0.
# both parents have to be of same length.

sub vectorUniform {
  my ($prob, $mom, $dad) = @_;

  return 0 if rand > $prob;

  my (@c1, @c2);
  for my $i (0 .. $#{$dad}) {
    if (rand > 0.5) {
      push @c1 => $mom->[$i];
      push @c2 => $dad->[$i];
    } else {

Genetic/OpMutation.pm  view on Meta::CPAN


package AI::Genetic::OpMutation;

use strict;

1;

# This package implements various mutation
# algorithms. To be used as static functions.

# sub bitVector():
# each gene is a bit: 0 or 1. arguments are mutation
# prob. and anon list of genes.
# returns anon list of mutated genes.

sub bitVector {
  my ($prob, $genes) = @_;

  for my $g (@$genes) {
    next if rand > $prob;

    $g = $g ? 0 : 1;
  }

  return $genes;
}

# sub rangeVector():
# each gene is a floating number, and can be anything
# within a range of two numbers.
# arguments are mutation prob., anon list of genes,
# and anon list of ranges. Each element in $ranges is
# an anon list of two numbers, min and max value of
# the corresponding gene.

sub rangeVector {
  my ($prob, $ranges, $genes) = @_;

  my $i = -1;
  for my $g (@$genes) {
    $i++;
    next if rand > $prob;

    # now randomly choose another value from the range.
    my $abs = $ranges->[$i][1] - $ranges->[$i][0] + 1;
    $g = $ranges->[$i][0] + int rand($abs);
  }

  return $genes;
}

# sub listVector():
# each gene is a string, and can be anything
# from a list of possible values supplied by user.
# arguments are mutation prob., anon list of genes,
# and anon list of value lists. Each element in $lists
# is an anon list of the possible values of
# the corresponding gene.

sub listVector {
  my ($prob, $lists, $genes) = @_;

  my $i = -1;
  for my $g (@$genes) {
    $i++;
    next if rand > $prob;

    # now randomly choose another value from the lists.
    my $new;

Genetic/OpSelection.pm  view on Meta::CPAN


package AI::Genetic::OpSelection;

use strict;

my @wheel;
my $wheelPop;

# sub init():
# initializes the roulette wheel array.
# must be called whenever the population changes.
# only useful for roulette().

sub initWheel {
  my $pop = shift;

  my $tot = 0;
  $tot += $_->score for @$pop;

  # if all population has zero score, then none
  # deserves to be selected.
  $tot = 1 unless $tot;    # to avoid div by zero

  # normalize

Genetic/OpSelection.pm  view on Meta::CPAN


  my $cur = 0;
  for my $i (@norms) {
    push @wheel => [$cur, $cur + $i];
    $cur += $i;
  }

  $wheelPop = $pop;
}

# sub roulette():
# Roulette Wheel selection.
# argument is number of individuals to select (def = 2).
# returns selected individuals.

sub roulette {
  my $num = shift || 2;

  my @selected;

  for my $j (1 .. $num) {
    my $rand = rand;
    for my $i (0 .. $#wheel) {
      if ($wheel[$i][0] <= $rand && $rand < $wheel[$i][1]) {
	push @selected => $wheelPop->[$i];
	last;
      }
    }
  }

  return @selected;
}

# same as roulette(), but returns unique individuals.
sub rouletteUnique {
  my $num = shift || 2;

  # make sure we select unique individuals.
  my %selected;

  while ($num > keys %selected) {
    my $rand = rand;

    for my $i (0 .. $#wheel) {
      if ($wheel[$i][0] <= $rand && $rand < $wheel[$i][1]) {
	$selected{$i} = 1;
	last;
      }
    }
  }

  return map $wheelPop->[$_], keys %selected;
}

# sub tournament():
# arguments are anon list of population, and number
# of individuals in tournament (def = 2).
# return 1 individual.

sub tournament {
  my ($pop, $num) = @_;

  $num ||= 2;

  my %s;
  while ($num > keys %s) {
    my $i = int rand @$pop;
    $s{$i} = 1;
  }

  return (sort {$b->score <=> $a->score}
	  map {$_->score; $_}  # This avoids a bug in Perl. See Genetic.pm.
	  map $pop->[$_], keys %s)[0];
}

# sub random():
# pure random choice of individuals.
# arguments are anon list of population, and number
# of individuals to select (def = 1).
# returns selected individual(s).

sub random {
  my ($pop, $num) = @_;

  $num ||= 1;

  my %s;
  while ($num > keys %s) {
    my $i = int rand @$pop;
    $s{$i} = 1;
  }

  return map $pop->[$_], keys %s;
}

# sub topN():
# fittest N individuals.
# arguments are anon list of pop, and N (def = 1).
# return anon list of top N individuals.

sub topN {
  my ($pop, $N) = @_;

  $N ||= 1;

  # hmm .. are inputs already sorted?
  return [(sort {$b->score <=> $a->score}
	   map {$_->score; $_}  # This avoids a bug in Perl. See Genetic.pm.
	   @$pop)[0 .. $N-1]];
}

README  view on Meta::CPAN

            -population => 500,
            -crossover  => 0.9,
            -mutation   => 0.01,
            -terminate  => \&terminateFunc,
           );

         $ga->init(10);
         $ga->evolve('rouletteTwoPoint', 100);
         print "Best score = ", $ga->getFittest->score, ".\n";

         sub fitnessFunc {
             my $genes = shift;

             my $fitness;
             # assign a number to $fitness based on the @$genes
             # ...

             return $fitness;
          }

          sub terminateFunc {
             my $ga = shift;

             # terminate if reached some threshold.
             return 1 if $ga->getFittest->score > $THRESHOLD;
             return 0;
          }

DESCRIPTION
    This module implements a Genetic Algorithm (GA) in pure Perl. Other Perl
    modules that achieve the same thing (perhaps better, perhaps worse) do

README  view on Meta::CPAN

                    Each gene of a rangevector individual can assume one
                    integer value from a range of possible integer values.
                    Note that only integers are supported. The user can
                    always transform any desired fractional values by
                    multiplying and dividing by an appropriate power of 10.

                Defaults to *bitvector*.

        *-terminate*
                This option allows the definition of a termination
                subroutine. It expects a subroutine reference. This sub will
                be called at the end of each generation with one argument:
                the AI::Genetic object. Evolution terminates if the sub
                returns a true value.

    *$ga*->createStrategy(*strategy_name*, *sub_ref*)
        This method allows the creation of a custom-made strategy to be used
        during evolution. It expects a unique strategy name, and a
        subroutine reference as arguments. The subroutine will be called
        with one argument: the AI::Genetic object. It is expected to alter
        the population at each generation. See the section on "STRATEGIES"

README  view on Meta::CPAN

    strategies can be found in the AI::Genetic::OpSelection manpage, the
    AI::Genetic::OpCrossover manpage and the AI::Genetic::OpMutation
    manpage.

    You can use the functions defined in the above modules in your own
    custom-made strategy. Consult their manpages for more info. A
    custom-made strategy can be defined using the *strategy()* method and is
    called at the beginning of each generation. The only argument to it is
    the AI::Genetic object itself. Note that the population at this point is
    sorted accoring to each individual's fitness score. It is expected that
    the strategy sub will modify the population stored in the AI::Genetic
    object. Here's the pseudo-code of events:

        for (1 .. num_generations) {
          sort population;
          call strategy_sub;
          if (termination_sub exists) {
            call termination_sub;
            last if returned true value;
          }
        }

A NOTE ON SPEED/EFFICIENCY
    Genetic algorithms are inherently slow. Perl can be pretty fast, but
    will never reach the speed of optimized C code (at least my Perl coding
    will not). I wrote AI::Genetic mainly for my own learning experience,
    but still tried to optimize it as much as I can while trying to keep it



( run in 0.322 second using v1.01-cache-2.11-cpan-4d50c553e7e )