File Coverage

lib/Algorithm/MasterMind/EvoRank.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Algorithm::MasterMind::EvoRank;
2              
3 1     1   552 use warnings;
  1         2  
  1         21  
4 1     1   4 use strict;
  1         1  
  1         22  
5 1     1   4 use Carp;
  1         2  
  1         46  
6              
7 1         4 use lib qw(../../lib ../../../../Algorithm-Evolutionary/lib/
8             ../../Algorithm-Evolutionary/lib/
9 1     1   3 ../../../lib);
  1         2  
10              
11             our $VERSION = sprintf "%d.%03d", q$Revision: 1.15 $ =~ /(\d+)\.(\d+)/g;
12              
13 1     1   286 use base 'Algorithm::MasterMind::Evolutionary_Base';
  1         2  
  1         491  
14              
15             use Algorithm::MasterMind qw(partitions);
16              
17             use Algorithm::Evolutionary qw(Op::QuadXOver
18             Op::String_Mutation
19             Op::Permutation
20             Op::Crossover
21             Op::Canonical_GA_NN
22             Individual::String );
23              
24             use Clone::Fast qw(clone);
25              
26             # ---------------------------------------------------------------------------
27             use constant { MAX_CONSISTENT_SET => 20, # This number 20 was computed in NICSO paper, valid for default 4-6 mastermind
28             MAX_GENERATIONS_RESET => 50,
29             MAX_GENERATIONS_EQUAL => 3} ;
30              
31             sub initialize {
32             my $self = shift;
33             my $options = shift;
34             for my $o ( keys %$options ) {
35             $self->{"_$o"} = clone($options->{$o});
36             }
37             croak "No population" if $self->{'_pop_size'} == 0;
38             # Variation operators
39             my $mutation_rate = $options->{'mutation_rate'} || 1;
40             my $xover_rate = $options->{'xover_rate'} || 2;
41             my $permutation_rate = $options->{'permutation_rate'} || 0;
42             my $max_number_of_consistent = $options->{'consistent_set_card'} || MAX_CONSISTENT_SET;
43             my $m = new Algorithm::Evolutionary::Op::String_Mutation $mutation_rate ; # Rate = 1
44             my $c = Algorithm::Evolutionary::Op::QuadXOver->new( 1, $xover_rate );
45             my $operators = [$m,$c];
46             if ( $permutation_rate > 0 ) {
47             my $p = new Algorithm::Evolutionary::Op::Permutation $permutation_rate;
48             push @$operators, $p;
49             }
50             if (! $self->{'_ga'} ) { # Not given as an option
51             $self->{'_ga'} = new Algorithm::Evolutionary::Op::Canonical_GA_NN( $options->{'replacement_rate'},
52             $operators );
53             }
54              
55             if (!$self->{'_distance'}) {
56             $self->{'_distance'} = 'distance_taxicab';
57             }
58             $self->{'_max_consistent'} = $max_number_of_consistent;
59             }
60              
61             sub compute_fitness {
62             my $pop = shift;
63             #Compute min
64             my $min_distance = 0;
65             for my $p ( @$pop ) {
66             $min_distance = ( $p->{'_distance'} < $min_distance )?
67             $p->{'_distance'}:
68             $min_distance;
69             }
70              
71             for my $p ( @$pop ) {
72             $p->Fitness( $p->{'_distance'}+
73             ($p->{'_partitions'}?$p->{'_partitions'}:0)-
74             $min_distance + 1);
75             }
76             }
77              
78             sub issue_next {
79             my $self = shift;
80             my $rules = $self->number_of_rules();
81             my @alphabet = @{$self->{'_alphabet'}};
82             my $length = $self->{'_length'};
83             my $pop = $self->{'_pop'};
84             my $ga = $self->{'_ga'};
85             my $max_number_of_consistent = $self->{'_max_consistent'};
86              
87             #Recalculate distances, new game
88             my (%consistent );
89             my $partitions;
90             my $distance = $self->{'_distance'};
91             for my $p ( @$pop ) {
92             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
93             # ($p->{'_distance'}, $p->{'_matches'}) = @{$self->distance( $p )};
94             $consistent{$p->{'_str'}} = $p if ($p->{'_matches'} == $rules);
95             }
96              
97             my $number_of_consistent = keys %consistent;
98             if ( $number_of_consistent > 1 ) {
99             $partitions = partitions( keys %consistent );
100             for my $c ( keys %$partitions ) {
101             $consistent{$c}->{'_partitions'} = scalar (keys %{$partitions->{$c}});
102             }
103             }
104             my $generations_equal = 0;
105             my $this_number_of_consistent = $number_of_consistent;
106            
107             while ( $this_number_of_consistent < $max_number_of_consistent ) {
108            
109             #Compute fitness
110             compute_fitness( $pop );
111             # print join( " - ", map( $_->{'_fitness'}, @$pop )), "\n";
112            
113             #Apply GA
114             $ga->apply( $pop );
115            
116             #Compute new distances
117             %consistent = (); # Empty to avoid problems
118             for my $p ( @$pop ) {
119             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
120             # ($p->{'_distance'}, $p->{'_matches'}) = @{$self->distance( $p )};
121             if ($p->{'_matches'} == $rules) {
122             $consistent{$p->{'_str'}} = $p;
123             # print $p->{'_str'}, " -> ", $p->{'_distance'}, " - ";
124             } else {
125             $p->{'_partitions'} = 0;
126             }
127             }
128            
129             #Check termination again, and reset
130             if ($generations_equal == MAX_GENERATIONS_RESET ) {
131             $ga->reset( $pop );
132             for my $p ( @$pop ) {
133             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
134             # ($p->{'_distance'}, $p->{'_matches'}) = @{$self->distance( $p )};
135             }
136             $generations_equal = 0;
137             }
138              
139             #Check termination conditions
140             $this_number_of_consistent = keys %consistent;
141             if ( $this_number_of_consistent == $number_of_consistent ) {
142             $generations_equal++;
143             } else {
144             $generations_equal = 0;
145             $number_of_consistent = $this_number_of_consistent;
146             # Compute number of partitions
147             if ( $number_of_consistent > 1 ) {
148             $partitions = partitions( keys %consistent );
149             for my $c ( keys %$partitions ) {
150             $consistent{$c}->{'_partitions'} = scalar (keys %{$partitions->{$c}});
151             }
152             }
153             }
154             last if ( $generations_equal >= MAX_GENERATIONS_EQUAL ) && ( $this_number_of_consistent >= 1 ) ;
155             # print "G $generations_equal $this_number_of_consistent \n";
156             }
157            
158             $self->{'_consistent'} = \%consistent; #This mainly for outside info
159             # print "After GA combinations ", join( " ", keys %consistent ), "\n";
160             # print "Consistent + => ", join( "-", sort keys %consistent ), "\n\n";
161             if ( $this_number_of_consistent > 1 ) {
162             # print "Consistent ", scalar keys %consistent, "\n";
163             #Use whatever we've got to compute number of partitions
164             # my $partitions = partitions( keys %consistent );
165            
166             my $max_partitions = 0;
167             my %max_c;
168             for my $c ( keys %$partitions ) {
169             my $this_max = keys %{$partitions->{$c}};
170             $max_c{$c} = $this_max;
171             if ( $this_max > $max_partitions ) {
172             $max_partitions = $this_max;
173             }
174             }
175             # Find all partitions with that max
176             my @max_c = grep( $max_c{$_} == $max_partitions, keys %max_c );
177             # Break ties
178             my $string = $max_c[ rand( @max_c )];
179             # Obtain next
180             return $self->{'_last'} = $string;
181             } else {
182             return $self->{'_last'} = (keys %consistent)[0];
183             }
184            
185             }
186              
187             "some blacks, 0 white"; # Magic true value required at end of module
188              
189             __END__