File Coverage

blib/lib/DCOLLINS/ANN/SimWorld.pm
Criterion Covered Total %
statement 8 10 80.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 14 85.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package DCOLLINS::ANN::SimWorld;
3             BEGIN {
4 2     2   27385 $DCOLLINS::ANN::SimWorld::VERSION = '0.004';
5             }
6 2     2   18 use strict;
  2         5  
  2         177  
7 2     2   12 use warnings;
  2         5  
  2         62  
8             # ABSTRACT: a simulated world for robots to play in
9              
10 2     2   1557 use Moose;
  0            
  0            
11              
12             use Storable qw(dclone);
13             use List::Util qw(max min);
14             use Time::HiRes qw(usleep);
15              
16              
17             has 'map' => (is => 'rw', isa => 'ArrayRef[ArrayRef[Str]]');
18             has 'batt_while_moving' => (is => 'rw', isa => 'Num', default => 0.2);
19             has 'batt_while_not_moving' => (is => 'rw', isa => 'Num', default => 0.1);
20             has 'pain_decrease' => (is => 'rw', isa => 'Num', default => 1.3);
21             has 'pain_overcharge' => (is => 'rw', isa => 'Num', default => 3);
22             has 'batt_charge' => (is => 'rw', isa => 'Num', default => 5);
23             has 'pain_stationary_not_charging' => (is => 'rw', isa => 'Num', default => 1);
24             has 'pain_collision' => (is => 'rw', isa => 'Num', default => 6);
25             has 'show_progress' => (is => 'rw', isa => 'Num', default => 0);
26             has 'show_field' => (is => 'rw', isa => 'Num', default => 0);
27             has 'fitness_function' => (is => 'rw', isa => 'CodeRef', default =>
28             sub { sub { log($_[0]->{'age'}) * ( 1 +
29             -10*(min($_[0]->{'total_pain'}/$_[0]->{'age'}, 0.03)) + # 0 to -0.3
30             0.4*(min($_[0]->{'total_battery'}/$_[0]->{'age'}, 0.7)) + # 0 to 0.3
31             -0.2*(sort(grep {defined $_} @{$_[0]->{'weights'}}))[int(0.95*grep {defined $_} @{$_[0]->{'weights'}})] +
32             log(max(($_[0]->{'uniq_squares'}-7)/2,1))/log(2000) # 0 to 0.4
33             )
34             }});
35              
36             around BUILDARGS => sub {
37             my $orig = shift;
38             my $class = shift;
39             my %input = @_;
40             for (my $i = 0; $i <= 9; $i++) {
41             for (my $j = 0; $j <= 9; $j++) {
42             $input{'map'}->[$i]->[$j] = '.';
43             }
44             }
45             $input{'map'}->[0]->[0] = 'C';
46             return $class->$orig(%input);
47             };
48              
49              
50             sub run_robot {
51             my $self = shift;
52             my $robot = shift;
53             my $max_age = shift;
54             my $pain = 0;
55             my $battery = 100;
56             my $x = 0;
57             my $y = 0;
58             my $xmax = $#{$self->{'map'}};
59             my $ymax = $#{$self->{'map'}->[0]};
60             my $disscale = max($xmax, $ymax)*1.4;
61             my $dir = "N";
62             my $diffpain = 0;
63             my $diffbatt = 0;
64             my $moved = 0;
65             my $return = { age => 0, total_pain => 0, total_battery => 0,
66             battery_used => 0, pain_given => 0 };
67             my %diroffs = ( "N" => 8,
68             "NE" => 9,
69             "E" => 2,
70             "SE" => 3,
71             "S" => 4,
72             "SW" => 5,
73             "W" => 6,
74             "NW" => 7);
75             my %dirs = reverse %diroffs;
76             my @dis;
77             my $inputs = [];
78             my $outputs = [];
79             my $visited = [[]];
80             my $visited_count = 0;
81             my $rotate = [ "\\", "|", "/", "-" ];
82             my $rotateoffset = 0;
83             while ($pain < 100 && $battery > 0) {
84             $inputs->[0] = $battery/100;
85             $inputs->[1] = $pain/100;
86             $inputs->[2] = $diffbatt;
87             $inputs->[3] = $diffpain;
88             $dis[0] = $y; # North
89             $dis[1] = 1.4*min($xmax-$x, $y);
90             $dis[2] = $xmax-$x;
91             $dis[3] = 1.4*min($xmax-$x, $ymax-$y);
92             $dis[4] = $ymax-$y;
93             $dis[5] = 1.4*min($x, $ymax-$y);
94             $dis[6] = $x;
95             $dis[7] = 1.4*min($x, $y); # NorthWest
96             @dis[8..15] = @dis[0..7];
97             @dis = map { min($_, 3) / 3 } @dis;
98             @{$inputs}[4..8] = @dis[($diroffs{$dir}-2)..($diroffs{$dir}+2)];
99             $inputs->[9] = $x;
100             $inputs->[10] = $y;
101             $inputs->[11] = ($dir =~ /N/ ? 1 : 0);
102             $inputs->[12] = ($dir =~ /S/ ? 1 : 0);
103             $inputs->[13] = ($dir =~ /E/ ? 1 : 0);
104             $inputs->[14] = ($dir =~ /W/ ? 1 : 0);
105             $outputs = $robot->execute($inputs);
106             $diffbatt = $diffpain = $moved = 0;
107             #print STDERR join("|", @$inputs) . "\n";
108             #print STDERR join("|", @$outputs) . "\n";
109             #my ($a, $b, $c) = $robot->get_state();
110             #print STDERR join("|", @$a) . "\n";
111             #print STDERR join("|", @$b) . "\n";die;
112             #print STDERR join("|", @$c) . "\n";
113              
114             my $maxout = max(@$outputs);
115             if ($maxout >= 1) {
116             if ($outputs->[0] == $maxout) {
117             my $newdir = $diroffs{$dir}-1;
118             $newdir = 9 if $newdir == 1;
119             $dir = $dirs{$newdir};
120             # $diffbatt = -1 if rand() < $self->{'batt_while_moving'};
121             $diffbatt -= $self->{'batt_while_moving'};
122             # $moved = 1;
123             } elsif ($outputs->[1] == $maxout) {
124             my $newdir = $diroffs{$dir}+1;
125             $newdir = 2 if $newdir == 10;
126             $dir = $dirs{$newdir};
127             # $diffbatt = -1 if rand() < $self->{'batt_while_moving'};
128             $diffbatt -= $self->{'batt_while_moving'};
129             # $moved = 1;
130             } elsif ($outputs->[2] == $maxout) {
131             if ($dis[$diroffs{$dir}+0] == 0) {
132             $diffpain = $self->{'pain_collision'};
133             } else {
134             $x++ if $dir =~ /E/;
135             $x-- if $dir =~ /W/;
136             $y++ if $dir =~ /S/;
137             $y-- if $dir =~ /N/;
138             }
139             # $diffbatt = -1 if rand() < $self->{'batt_while_moving'};
140             $diffbatt -= $self->{'batt_while_moving'};
141             $moved = 1;
142             } elsif ($outputs->[3] == $maxout) {
143             if ($dis[$diroffs{$dir}+4] == 0) {
144             $diffpain = $self->{'pain_collision'};
145             } else {
146             $x-- if $dir =~ /E/;
147             $x++ if $dir =~ /W/;
148             $y-- if $dir =~ /S/;
149             $y++ if $dir =~ /N/;
150             }
151             # $diffbatt = -1 if rand() < $self->{'batt_while_moving'};
152             $diffbatt -= $self->{'batt_while_moving'};
153             $moved = 1;
154             }
155             }
156             if ($moved == 0) {
157             # $diffbatt = -1 if rand() < $self->{'batt_while_not_moving'};
158             $diffbatt -= $self->{'batt_while_not_moving'};
159             }
160             if ($self->{'map'}->[$x]->[$y] =~ /C/) {
161             $diffbatt += $self->{'batt_charge'};
162             if ($battery + $diffbatt > 100) {
163             $diffpain += $self->{'pain_overcharge'};
164             $diffbatt = 100 - $battery;
165             }
166             }
167             if ($moved == 0 && ($self->{'map'}->[$x]->[$y] !~ /C/)) {
168             $diffpain += $self->{'pain_stationary_not_charging'};
169             }
170             if ($diffpain == 0 && $pain > 0) {
171             # $diffpain = -1 if rand() < $self->{'pain_decrease'};
172             $diffpain -= $self->{'pain_decrease'};
173             }
174             if ($pain + $diffpain < 0) {
175             $diffpain = 0 - $pain;
176             }
177             if (not defined $visited->[$x]->[$y]) {
178             $visited_count++;
179             }
180             $visited->[$x]->[$y]=$return->{'age'};
181             $pain += $diffpain;
182             $battery += $diffbatt;
183             if ($return->{'age'} % 50 == 0) {
184             print $rotate->[$rotateoffset], chr(8) if $self->{'show_progress'};
185             $rotateoffset++;
186             if ($rotateoffset == 4) {$rotateoffset = 0}
187             }
188             $return->{'age'}++;
189             $return->{'total_battery'} += $battery;
190             $return->{'total_pain'} += $pain;
191             $return->{'battery_used'} -= $diffbatt if $diffbatt < 0;
192             $return->{'pain_given'} += $diffpain if $diffpain > 0;
193             if ($self->{'show_field'}) {
194             my $map = dclone($self->{'map'});
195             $map->[$x]->[$y] = '@';
196             my $output = '+-'.'-'x$xmax.'+'."\n";
197             foreach my $y (0..$ymax) {
198             $output .= '|';
199             foreach my $x (0..$xmax) {
200             $output .= $map->[$x]->[$y];
201             }
202             $output .= "|\n";
203             }
204             $output .= '+-'.'-'x$xmax.'+'."\n";
205             $output .= sprintf "NNO: %5.3f %5.3f %5.3f %5.3f %5.3f\n", @$outputs;
206             $output .= sprintf "B: %05.1f, P: %05.1f, D: %2s, A: %u\n", $battery, $pain, $dir, $return->{'age'};
207             `clear`;
208             print $output;
209             usleep(30000);
210             }
211             if ($max_age && $return->{'age'} > $max_age) {last}
212             }
213             my $internals = $robot->get_internals();
214             my @weights;
215             foreach my $hash (@$internals) {
216             foreach my $input (0..$#{$hash->{'inputs'}}) {
217             push @weights, $hash->{'inputs'}->[$input];
218             }
219             foreach my $neuron (0..$#{$hash->{'neurons'}}) {
220             push @weights, $hash->{'neurons'}->[$neuron];
221             }
222             }
223             $return->{'uniq_squares'} = $visited_count;
224             $return->{'weights'} = \@weights;
225             $return->{'fitness'} = &{$self->{'fitness_function'}}($return);
226             if ($self->{'show_field'}) {
227             printf "Final fitness function is %02.3f\n", $return->{'fitness'};
228             }
229             return $return;
230             }
231              
232             __PACKAGE__->meta->make_immutable;
233              
234             1;
235              
236             __END__
237             =pod
238              
239             =head1 NAME
240              
241             DCOLLINS::ANN::SimWorld - a simulated world for robots to play in
242              
243             =head1 VERSION
244              
245             version 0.004
246              
247             =head1 SYNOPSIS
248              
249             =head1 METHODS
250              
251             =head2 new
252              
253             DCOLLINS::ANN::SimWorld::new( )
254              
255             Creates a DCOLLINS::ANN::SimWorld object ready to test robots.
256              
257             Has many parameters. Important may be fitness_function, a coderef to
258             a function that takes age, total_pain, total_battery, battery_used, pain_given, weights, uniq_squares
259             For standardization, these are the parameters that SimWorld will pass to the
260             network:
261             Current battery power (0-1)
262             Current pain value (0-1)
263             Differential battery power ((-1)-1)
264             Differential pain value ((-1)-1)
265             Proximity readings, -45, 0, 45 degrees (0-1)
266             Current X location (0-1)
267             Current Y location (0-1)
268             Currently facing: N, S, E, W (0-1)
269              
270             These are the parameters that SimWorld will expect as outputs from the network:
271             Rotate L
272             Rotate R
273             Forwards
274             Reverse
275             Stop
276             The largest value will be accepted. If no output is greater than 1, SimWorld
277             will interpret as a stop.
278              
279             =head2 run_robot
280              
281             $environment->run_robot($robot);
282              
283             Returns a hashref with the following information:
284             fitness => Num
285             age => Num
286             total_pain => Num
287             total_battery => Num
288             battery_used => Num
289             pain_given => Num
290              
291             =head1 AUTHOR
292              
293             Dan Collins <dcollin1@stevens.edu>
294              
295             =head1 COPYRIGHT AND LICENSE
296              
297             This software is Copyright (c) 2011 by Dan Collins.
298              
299             This is free software, licensed under:
300              
301             The GNU General Public License, Version 3, June 2007
302              
303             =cut
304