File Coverage

blib/lib/AI/Pathfinding/OptimizeMultiple.pm
Criterion Covered Total %
statement 102 237 43.0
branch 9 30 30.0
condition 2 16 12.5
subroutine 23 34 67.6
pod 7 7 100.0
total 143 324 44.1


line stmt bran cond sub pod time code
1             package AI::Pathfinding::OptimizeMultiple;
2             $AI::Pathfinding::OptimizeMultiple::VERSION = '0.0.16';
3 2     2   319374 use strict;
  2         8  
  2         62  
4 2     2   17 use warnings;
  2         5  
  2         59  
5              
6 2     2   41 use 5.012;
  2         8  
7              
8 2     2   963 use AI::Pathfinding::OptimizeMultiple::IterState ();
  2         8  
  2         72  
9 2     2   1072 use AI::Pathfinding::OptimizeMultiple::Scan ();
  2         6  
  2         49  
10 2     2   944 use AI::Pathfinding::OptimizeMultiple::ScanRun ();
  2         5  
  2         55  
11 2     2   931 use AI::Pathfinding::OptimizeMultiple::SimulationResults ();
  2         24  
  2         69  
12              
13 2     2   17 use MooX qw/late/;
  2         4  
  2         12  
14              
15 2     2   2126 use PDL;
  2         5  
  2         15  
16 2     2   227077 use Scalar::Util qw/ blessed /;
  2         5  
  2         5932  
17              
18             has chosen_scans => ( isa => 'ArrayRef', is => 'rw' );
19             has _iter_idx => ( isa => 'Int', is => 'rw', default => sub { 0; }, );
20             has _num_boards => ( isa => 'Int', is => 'ro', init_arg => 'num_boards', );
21             has _orig_scans_data => ( isa => 'PDL', is => 'rw' );
22             has _optimize_for => ( isa => 'Str', is => 'ro', init_arg => 'optimize_for', );
23             has _scans_data => ( isa => 'PDL', is => 'rw' );
24             has _selected_scans =>
25             ( isa => 'ArrayRef', is => 'ro', init_arg => 'selected_scans', );
26             has _status => ( isa => 'Str', is => 'rw' );
27             has _quotas => ( isa => 'ArrayRef[Int]', is => 'ro', init_arg => 'quotas' );
28             has _total_boards_solved => ( isa => 'Int', is => 'rw' );
29             has _total_iters => ( isa => 'Int', is => 'rw' );
30             has _trace_cb =>
31             ( isa => 'Maybe[CodeRef]', is => 'ro', init_arg => 'trace_cb' );
32             has _scans_meta_data => ( isa => 'ArrayRef', is => 'ro', init_arg => 'scans' );
33             has _scans_iters_pdls =>
34             ( isa => 'HashRef', is => 'rw', init_arg => 'scans_iters_pdls' );
35             has _stats_factors => (
36             isa => 'HashRef',
37             is => 'ro',
38             init_arg => 'stats_factors',
39             default => sub { return +{}; },
40             );
41              
42             sub BUILD
43             {
44 4     4 1 462 my $self = shift;
45              
46 4         7 my $args = shift;
47              
48             my $scans_data = PDL::cat(
49             map {
50 12         33 my $id = $_->id();
51 12         237 my $pdl = $self->_scans_iters_pdls()->{$id};
52 12         84 my $factor = $self->_stats_factors->{$id};
53             (
54 12 100       279 defined($factor)
55             ? ( ( $pdl >= 0 ) * ( ( $pdl / $factor )->ceil() ) +
56             ( $pdl < 0 ) * $pdl )
57             : $pdl
58             );
59 4         8 } @{ $self->_selected_scans() }
  4         16  
60             );
61              
62 4         1225 $self->_orig_scans_data($scans_data);
63 4         177 $self->_scans_data( $self->_orig_scans_data()->copy() );
64              
65 4         328 return 0;
66             }
67              
68             my $BOARDS_DIM = 0;
69             my $SCANS_DIM = 1;
70             my $STATISTICS_DIM = 2;
71              
72             sub _next_iter_idx
73             {
74 8     8   14 my $self = shift;
75              
76 8         143 my $ret = $self->_iter_idx();
77              
78 8         163 $self->_iter_idx( $ret + 1 );
79              
80 8         230 return $ret;
81             }
82              
83             sub _get_next_quota
84             {
85 8     8   14 my $self = shift;
86              
87 8         17 my $iter = $self->_next_iter_idx();
88              
89 8 50       29 if ( ref( $self->_quotas() ) eq "ARRAY" )
90             {
91 8         22 return $self->_quotas()->[$iter];
92             }
93             else
94             {
95 0         0 return $self->_quotas()->($iter);
96             }
97             }
98              
99             sub _calc_get_iter_state_param_method
100             {
101 8     8   12 my $self = shift;
102              
103 8         21 my $optimize_for = $self->_optimize_for();
104              
105 8         35 my %resolve = (
106             len => "_get_iter_state_params_len",
107             minmax_len => "_get_iter_state_params_minmax_len",
108             speed => "_get_iter_state_params_speed",
109             );
110              
111 8         27 return $resolve{$optimize_for};
112             }
113              
114             sub _get_iter_state_params
115             {
116 8     8   16 my $self = shift;
117              
118 8         19 my $method = $self->_calc_get_iter_state_param_method();
119              
120 8         28 return $self->$method();
121             }
122              
123             sub _my_sum_over
124             {
125 0     0   0 my $pdl = shift;
126              
127 0         0 return $pdl->sumover()->slice(":,(0)");
128             }
129              
130             sub _my_xchg_sum_over
131             {
132 0     0   0 my $pdl = shift;
133              
134 0         0 return _my_sum_over( $pdl->xchg( 0, 1 ) );
135             }
136              
137             sub _get_iter_state_params_len
138             {
139 0     0   0 my $self = shift;
140              
141 0         0 my $iters_quota = 0;
142 0         0 my $num_solved_in_iter = 0;
143 0         0 my $selected_scan_idx;
144              
145             # If no boards were solved, then try with a larger quota
146 0         0 while ( $num_solved_in_iter == 0 )
147             {
148 0         0 my $q_more = $self->_get_next_quota();
149 0 0       0 if ( !defined($q_more) )
150             {
151 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
152             error => "No q_more", );
153             }
154              
155 0         0 $iters_quota += $q_more;
156              
157 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
158 0         0 my $solved = ( ( $iters <= $iters_quota ) & ( $iters > 0 ) );
159 0         0 my $num_moves = $self->_scans_data->slice(":,:,2");
160 0         0 my $solved_moves = $solved * $num_moves;
161              
162 0         0 my $solved_moves_sums = _my_sum_over($solved_moves);
163 0         0 my $solved_moves_counts = _my_sum_over($solved);
164 0         0 my $solved_moves_avgs = $solved_moves_sums / $solved_moves_counts;
165              
166 0         0 ( undef, undef, $selected_scan_idx, undef ) =
167             $solved_moves_avgs->minmaximum();
168              
169 0         0 $num_solved_in_iter = $solved_moves_counts->at($selected_scan_idx);
170             }
171              
172             return {
173 0         0 quota => $iters_quota,
174             num_solved => $num_solved_in_iter,
175             scan_idx => $selected_scan_idx,
176             };
177             }
178              
179             sub _get_iter_state_params_minmax_len
180             {
181 0     0   0 my $self = shift;
182              
183 0         0 my $iters_quota = 0;
184 0         0 my $num_solved_in_iter = 0;
185 0         0 my $selected_scan_idx;
186              
187             # If no boards were solved, then try with a larger quota
188 0         0 while ( $num_solved_in_iter == 0 )
189             {
190 0         0 my $q_more = $self->_get_next_quota();
191 0 0       0 if ( !defined($q_more) )
192             {
193 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
194             error => "No q_more", );
195             }
196              
197 0         0 $iters_quota += $q_more;
198              
199 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
200 0         0 my $solved = ( ( $iters <= $iters_quota ) & ( $iters > 0 ) );
201 0         0 my $num_moves = $self->_scans_data->slice(":,:,2");
202 0         0 my $solved_moves = $solved * $num_moves;
203              
204 0         0 my $solved_moves_maxima = $solved_moves->maximum()->slice(":,(0),(0)");
205 0         0 my $solved_moves_counts = _my_sum_over($solved);
206              
207 0         0 ( undef, undef, $selected_scan_idx, undef ) =
208             $solved_moves_maxima->minmaximum();
209              
210 0         0 $num_solved_in_iter = $solved_moves_counts->at($selected_scan_idx);
211             }
212              
213             return {
214 0         0 quota => $iters_quota,
215             num_solved => $num_solved_in_iter,
216             scan_idx => $selected_scan_idx,
217             };
218             }
219              
220             sub _get_iter_state_params_speed
221             {
222 8     8   36 my $self = shift;
223              
224 8         15 my $iters_quota = 0;
225 8         11 my $num_solved_in_iter = 0;
226 8         14 my $selected_scan_idx;
227              
228             # If no boards were solved, then try with a larger quota
229 8         21 while ( $num_solved_in_iter == 0 )
230             {
231 8         29 my $q_more = $self->_get_next_quota();
232 8 50       25 if ( !defined($q_more) )
233             {
234 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
235             error => "No q_more" );
236             }
237              
238 8         14 $iters_quota += $q_more;
239              
240 8         133 ( undef, $num_solved_in_iter, undef, $selected_scan_idx ) =
241             PDL::minmaximum(
242             PDL::sumover(
243             ( $self->_scans_data() <= $iters_quota ) &
244             ( $self->_scans_data() > 0 )
245             )
246             );
247             }
248              
249             return {
250 8         1116 quota => $iters_quota,
251             num_solved => $num_solved_in_iter->at(0),
252             scan_idx => $selected_scan_idx->at(0),
253             };
254             }
255              
256             sub _get_selected_scan
257             {
258 8     8   16 my $self = shift;
259              
260 8         18 my $iter_state =
261             AI::Pathfinding::OptimizeMultiple::IterState->new(
262             $self->_get_iter_state_params(),
263             );
264              
265 8         3637 $iter_state->attach_to($self);
266              
267 8         14 return $iter_state;
268             }
269              
270             sub _inspect_quota
271             {
272 8     8   15 my $self = shift;
273              
274 8         19 my $state = $self->_get_selected_scan();
275              
276 8         29 $state->register_params();
277              
278 8         24 $state->update_total_iters();
279              
280 8 100       1235 if ( $self->_total_boards_solved() == $self->_num_boards() )
281             {
282 4         97 $self->_status("solved_all");
283             }
284             else
285             {
286 4         39 $state->update_idx_slice();
287             }
288              
289 8         289 $state->detach();
290             }
291              
292             sub calc_meta_scan
293             {
294 4     4 1 26 my $self = shift;
295              
296 4         72 $self->chosen_scans( [] );
297              
298 4         162 $self->_total_boards_solved(0);
299 4         177 $self->_total_iters(0);
300              
301 4         174 $self->_status("iterating");
302              
303             # $self->_inspect_quota() throws ::Error::OutOfQuotas if
304             # it does not have any available quotas.
305 4         95 eval {
306 4         62 while ( $self->_status() eq "iterating" )
307             {
308 8         67 $self->_inspect_quota();
309             }
310             };
311 4 50       47 if (
312             my $err = Exception::Class->caught(
313             'AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas')
314             )
315             {
316 0         0 $self->_status("out_of_quotas");
317             }
318             else
319             {
320 4         36 $err = Exception::Class->caught();
321 4 50       26 if ($err)
322             {
323 0 0 0     0 if ( not( blessed $err && $err->can('rethrow') ) )
324             {
325 0         0 die $err;
326             }
327 0         0 $err->rethrow;
328             }
329             }
330              
331 4         9 return;
332             }
333              
334             sub _get_num_scans
335             {
336 0     0   0 my $self = shift;
337              
338 0         0 return ( ( $self->_scans_data()->dims() )[$SCANS_DIM] );
339             }
340              
341             sub _calc_chosen_scan
342             {
343 8     8   18 my ( $self, $selected_scan_idx, $iters_quota ) = @_;
344              
345             return AI::Pathfinding::OptimizeMultiple::ScanRun->new(
346             {
347             iters => (
348             $iters_quota * (
349             $self->_stats_factors->{
350 8   100     177 ( $self->_selected_scans->[$selected_scan_idx]->id() ),
351             } // 1
352             )
353             ),
354             scan_idx => $selected_scan_idx,
355             }
356             );
357             }
358              
359             sub calc_flares_meta_scan
360             {
361 0     0 1 0 my $self = shift;
362              
363 0         0 $self->chosen_scans( [] );
364              
365 0         0 $self->_total_boards_solved(0);
366 0         0 $self->_total_iters(0);
367              
368 0         0 $self->_status("iterating");
369              
370 0         0 my $iters_quota = 0;
371 0         0 my $flares_num_iters = PDL::Core::pdl( [ (0) x $self->_get_num_scans() ] );
372             my $ones_constant =
373 0         0 PDL::Core::pdl( [ map { [1] } ( 1 .. $self->_get_num_scans() ) ] );
  0         0  
374              
375 0         0 my $next_num_iters_for_each_scan_x_scan =
376             ( ( $ones_constant x $flares_num_iters ) );
377              
378 0         0 my $num_moves = $self->_scans_data->slice(":,:,1");
379              
380             # The number of moves for dimension 0,1,2 above.
381 0         0 my $num_moves_repeat = $num_moves->clump( 1 .. 2 )->xchg( 0, 1 )
382             ->dummy( 0, $self->_get_num_scans() );
383              
384 0         0 my $selected_scan_idx;
385              
386 0         0 my $loop_iter_num = 0;
387              
388 0         0 my $UNSOLVED_NUM_MOVES_CONSTANT = 64 * 1024 * 1024;
389              
390 0         0 my $last_avg = $UNSOLVED_NUM_MOVES_CONSTANT;
391              
392             FLARES_LOOP:
393 0         0 while ( my $q_more = $self->_get_next_quota() )
394             {
395 0         0 $iters_quota += $q_more;
396              
397             # Next number of iterations for each scan x scan combination.
398 0         0 my $next_num_iters = (
399             ( $ones_constant x $flares_num_iters ) + (
400             PDL::MatrixOps::identity( $self->_get_num_scans() ) *
401             $iters_quota
402             )
403             );
404              
405             # print "\$next_num_iters = $next_num_iters\n";
406              
407 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
408              
409 0         0 my $iters_repeat =
410             $iters->dummy( 0, $self->_get_num_scans() )->xchg( 1, 2 )
411             ->clump( 2 .. 3 );
412              
413             # print "\$iters_repeat =", join(",",$iters_repeat->dims()), "\n";
414              
415 0         0 my $next_num_iters_repeat =
416             $next_num_iters->dummy( 0, $self->_num_boards() )->xchg( 0, 2 );
417              
418             # print "\$next_num_iters_repeat =", join(",",$next_num_iters_repeat->dims()), "\n";
419              
420             # A boolean tensor of which boards were solved:
421             # Dimension 0 - Which scan is it. - size - _get_num_scans()
422             # Dimension 1 - Which scan we added the quota to
423             # - size - _get_num_scans()
424             # Dimension 2 - Which board. - size - _num_boards()
425 0         0 my $solved =
426             ( $iters_repeat >= 0 ) * ( $iters_repeat < $next_num_iters_repeat );
427              
428             # print "\$num_moves_repeat =", join(",",$num_moves_repeat->dims()), "\n";
429              
430 0         0 my $num_moves_solved =
431             ( $solved * $num_moves_repeat ) +
432             ( $solved->not() * $UNSOLVED_NUM_MOVES_CONSTANT );
433              
434 0         0 my $minimal_num_moves_solved =
435             $num_moves_solved->xchg( 0, 1 )->minimum();
436              
437 0         0 my $which_minima_are_solved =
438             ( $minimal_num_moves_solved != $UNSOLVED_NUM_MOVES_CONSTANT );
439              
440 0         0 my $minimal_with_zeroes =
441             $which_minima_are_solved * $minimal_num_moves_solved;
442              
443 0         0 my $solved_moves_sums = _my_xchg_sum_over($minimal_with_zeroes);
444 0         0 my $solved_moves_counts = _my_xchg_sum_over($which_minima_are_solved);
445 0         0 my $solved_moves_avgs = $solved_moves_sums / $solved_moves_counts;
446              
447             # print join(",", $solved_moves_avgs->minmaximum()), "\n";
448              
449 0         0 my $min_avg;
450              
451 0         0 ( $min_avg, undef, $selected_scan_idx, undef ) =
452             $solved_moves_avgs->minmaximum();
453              
454 0         0 $last_avg = $min_avg;
455              
456 0         0 push @{ $self->chosen_scans() },
  0         0  
457             $self->_calc_chosen_scan( $selected_scan_idx, $iters_quota );
458              
459 0         0 $flares_num_iters->set( $selected_scan_idx,
460             $flares_num_iters->at($selected_scan_idx) + $iters_quota );
461 0         0 $self->_selected_scans()->[$selected_scan_idx]->mark_as_used();
462              
463 0         0 $iters_quota = 0;
464              
465 0         0 my $num_solved = $solved_moves_counts->at($selected_scan_idx);
466              
467 0         0 my $flares_num_iters_repeat =
468             $flares_num_iters->dummy( 0, $self->_num_boards() );
469              
470             # A boolean tensor:
471             # Dimension 0 - board.
472             # Dimension 1 - scans.
473 0         0 my $solved_with_which_iter =
474             ( $flares_num_iters_repeat >= $iters->clump( 1 .. 2 ) ) &
475             ( $iters->clump( 1 .. 2 ) >= 0 );
476              
477 0         0 my $total_num_iters = (
478             ( $solved_with_which_iter * $flares_num_iters_repeat )->sum() + (
479             $solved_with_which_iter->not()->andover() *
480             $flares_num_iters->sum()
481             )->sum()
482             );
483              
484 0         0 print "Finished ", $loop_iter_num++,
485             " ; #Solved = $num_solved ; Iters = $total_num_iters ; Avg = $min_avg\n";
486 0         0 STDOUT->flush();
487             }
488             }
489              
490             sub calc_board_iters
491             {
492 0     0 1 0 my $self = shift;
493 0         0 my $board = shift;
494              
495 0         0 my $board_iters = 0;
496              
497 0         0 my @info = PDL::list( $self->_orig_scans_data()->slice("$board,:") );
498 0         0 my @orig_info = @info;
499              
500 0         0 foreach my $s ( @{ $self->chosen_scans() } )
  0         0  
501             {
502 0 0 0     0 if ( ( $info[ $s->scan_idx() ] > 0 )
503             && ( $info[ $s->scan_idx() ] <= $s->iters() ) )
504             {
505 0         0 $board_iters += $info[ $s->iters() ];
506 0         0 last;
507             }
508             else
509             {
510 0 0       0 if ( $info[ $s->scan_idx() ] > 0 )
511             {
512 0         0 $info[ $s->scan_idx() ] -= $s->iters();
513             }
514 0         0 $board_iters += $s->iters();
515             }
516             }
517              
518             return {
519 0         0 'per_scan_iters' => \@orig_info,
520             'board_iters' => $board_iters,
521             };
522             }
523              
524             sub get_final_status
525             {
526 0     0 1 0 my $self = shift;
527              
528 0         0 return $self->_status();
529             }
530              
531             sub simulate_board
532             {
533 0     0 1 0 my ( $self, $board_idx, $args ) = @_;
534              
535 0 0       0 if ( $board_idx !~ /\A[0-9]+\z/ )
536             {
537 0         0 die "Board index '$board_idx' is not numeric!";
538             }
539              
540 0   0     0 $args ||= {};
541              
542 0   0     0 my $chosen_scans = ( $args->{chosen_scans} || $self->chosen_scans );
543              
544 0         0 my @info = PDL::list( $self->_orig_scans_data()->slice("$board_idx,:") );
545              
546 0         0 my $board_iters = 0;
547              
548 0         0 my @scan_runs;
549              
550 0         0 my $status = "Unsolved";
551              
552             my $add_new_scan_run = sub {
553 0     0   0 my $scan_run = shift;
554              
555 0         0 push @scan_runs, $scan_run;
556              
557 0         0 $board_iters += $scan_run->iters();
558              
559 0         0 return;
560 0         0 };
561              
562             SCANS_LOOP:
563 0         0 foreach my $s (@$chosen_scans)
564             {
565 0 0 0     0 if ( ( $info[ $s->scan_idx() ] > 0 )
566             && ( $info[ $s->scan_idx() ] <= $s->iters() ) )
567             {
568 0         0 $add_new_scan_run->(
569             AI::Pathfinding::OptimizeMultiple::ScanRun->new(
570             {
571             iters => $info[ $s->scan_idx() ],
572             scan_idx => $s->scan_idx(),
573             },
574             )
575             );
576              
577 0         0 $status = "Solved";
578 0         0 last SCANS_LOOP;
579             }
580             else
581             {
582 0 0       0 if ( $info[ $s->scan_idx() ] > 0 )
583             {
584 0         0 $info[ $s->scan_idx() ] -= $s->iters();
585             }
586              
587 0         0 $add_new_scan_run->(
588             AI::Pathfinding::OptimizeMultiple::ScanRun->new(
589             {
590             iters => $s->iters(),
591             scan_idx => $s->scan_idx(),
592             },
593             )
594             );
595             }
596             }
597              
598 0         0 return AI::Pathfinding::OptimizeMultiple::SimulationResults->new(
599             {
600             status => $status,
601             scan_runs => \@scan_runs,
602             total_iters => $board_iters,
603             }
604             );
605             }
606              
607             sub _trace
608             {
609 8     8   70 my ( $self, $args ) = @_;
610              
611 8 50       35 if ( my $trace_callback = $self->_trace_cb() )
612             {
613 0         0 $trace_callback->($args);
614             }
615              
616 8         19 return;
617             }
618              
619             sub get_total_iters
620             {
621 0     0 1 0 my $self = shift;
622              
623 0         0 return $self->_total_iters();
624             }
625              
626             sub _add_to_total_iters
627             {
628 16     16   436 my $self = shift;
629              
630 16         28 my $how_much = shift;
631              
632 16         386 $self->_total_iters( $self->_total_iters() + $how_much );
633              
634 16         528 return;
635             }
636              
637             sub _add_to_total_boards_solved
638             {
639 8     8   15 my $self = shift;
640              
641 8         12 my $how_much = shift;
642              
643 8         134 $self->_total_boards_solved( $self->_total_boards_solved() + $how_much );
644              
645 8         242 return;
646             }
647              
648             1; # End of AI::Pathfinding::OptimizeMultiple
649              
650             __END__
651              
652             =pod
653              
654             =encoding UTF-8
655              
656             =head1 NAME
657              
658             AI::Pathfinding::OptimizeMultiple - optimize path finding searches for a large
659             set of initial conditions (for better average performance).
660              
661             =head1 VERSION
662              
663             version 0.0.16
664              
665             =head1 SYNOPSIS
666              
667             use AI::Pathfinding::OptimizeMultiple
668              
669             my @scans =
670             (
671             {
672             name => "first_search"
673             },
674             {
675             name => "second_search",
676             },
677             {
678             name => "third_search",
679             },
680             );
681              
682             my $obj = AI::Pathfinding::OptimizeMultiple->new(
683             {
684             scans => \@scans,
685             num_boards => 32_000,
686             optimize_for => 'speed',
687             scans_iters_pdls =>
688             {
689             first_search => $first_search_pdl,
690             second_search => $second_search_pdl,
691             },
692             quotas => [400, 300, 200],
693             selected_scans =>
694             [
695             AI::Pathfinding::OptimizeMultiple::Scan->new(
696             id => 'first_search',
697             cmd_line => "--preset first_search",
698             ),
699             AI::Pathfinding::OptimizeMultiple::Scan->new(
700             id => 'second_search',
701             cmd_line => "--preset second_search",
702             ),
703             AI::Pathfinding::OptimizeMultiple::Scan->new(
704             id => 'third_search',
705             cmd_line => "--preset third_search",
706             ),
707             ],
708             }
709             );
710              
711             $obj->calc_meta_scan();
712              
713             foreach my $scan_alloc (@{$self->chosen_scans()})
714             {
715             printf "Run %s for %d iterations.\n",
716             $scans[$scan_alloc->scan_idx], $scan_alloc->iters;
717             }
718              
719             =head1 DESCRIPTION
720              
721             This CPAN distribution implements the algorithm described here:
722              
723             =over 4
724              
725             =item * L<https://groups.google.com/group/comp.ai.games/msg/41e899e9beea5583?dmode=source&output=gplain&noredirect>
726              
727             =item * L<http://www.shlomifish.org/lecture/Perl/Lightning/Opt-Multi-Task-in-PDL/>
728              
729             =back
730              
731             Given statistics on the performance of several game AI searches (or scans)
732             across a representative number of initial cases, find a scan
733             that solves most deals with close-to-optimal performance, by using switch
734             tasking.
735              
736             =head1 SUBROUTINES/METHODS
737              
738             =head2 my $chosen_scans_array_ref = $self->chosen_scans()
739              
740             Returns the scans that have been chosen to perform the iteration. Each one is
741             a AI::Pathfinding::OptimizeMultiple::ScanRun object.
742              
743             =head2 $calc_meta_scan->calc_meta_scan()
744              
745             Calculates the meta-scan after initialisation. See here for the details
746             of the algorithm:
747              
748             L<http://www.shlomifish.org/lecture/Freecell-Solver/The-Next-Pres/slides/multi-tasking/best-meta-scan/>
749              
750             =head2 $self->calc_flares_meta_scan()
751              
752             This function calculates the flares meta-scan: i.e: assuming that all atomic
753             scans are run one after the other and the shortest solutions of all
754             successful scans are being picked.
755              
756             =head2 $calc_meta_scan->calc_board_iters($board_idx)
757              
758             Calculates the iterations of the board $board_idx in all the scans.
759              
760             Returns a hash_ref containing the key 'per_scan_iters' for the iterations
761             per scan, and 'board_iters' for the total board iterations when ran in the
762             scans.
763              
764             =head2 my $status = $calc_meta_scan->get_final_status()
765              
766             Returns the status as string:
767              
768             =over 4
769              
770             =item * "solved_all"
771              
772             =item * "iterating"
773              
774             =item * "out_of_quotas"
775              
776             =back
777              
778             =head2 my $sim_results_obj = $calc_meta_scan->simulate_board($board_idx, $args)
779              
780             Simulates the board No $board_idx through the scan. Returns a
781             L<AI::Pathfinding::OptimizeMultiple::SimulationResults> object.
782              
783             $args is an optional hash reference. It may contain a value with the key of
784             C<'chosen_scans'> that may specify an alternative scans to traverse.
785              
786             =head2 my $n = $calc_meta_scan->get_total_iters()
787              
788             Returns the total iterations count so far.
789              
790             =head2 BUILD()
791              
792             Moo leftover. B<INTERNAL USE>.
793              
794             =head1 SEE ALSO
795              
796             =over 4
797              
798             =item * L<Freecell Solver|http://fc-solve.shlomifish.org/>
799              
800             For which this code was first written and used.
801              
802             =item * L<Alternative Implementation in C#/.NET|https://bitbucket.org/shlomif/fc-solve/src/cc5b428ed9bad0132d7a7bc1a14fc6d3650edf45/fc-solve/presets/soft-threads/meta-moves/auto-gen/optimize-seq?at=master>
803              
804             An Alternative implementation in C#/.NET, which was written because the
805             performance of the Perl/PDL code was too slow.
806              
807             =item * L<PDL> - Perl Data Language
808              
809             Used here.
810              
811             =back
812              
813             =head1 AUTHOR
814              
815             Shlomi Fish, L<http://www.shlomifish.org/> .
816              
817             =head1 ACKNOWLEDGEMENTS
818              
819             B<popl> from Freenode's #perl for trying to dig some references to an existing
820             algorithm in the scientific literature.
821              
822             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
823              
824             =head1 SUPPORT
825              
826             =head2 Websites
827              
828             The following websites have more information about this module, and may be of help to you. As always,
829             in addition to those websites please use your favorite search engine to discover more resources.
830              
831             =over 4
832              
833             =item *
834              
835             MetaCPAN
836              
837             A modern, open-source CPAN search engine, useful to view POD in HTML format.
838              
839             L<https://metacpan.org/release/AI-Pathfinding-OptimizeMultiple>
840              
841             =item *
842              
843             RT: CPAN's Bug Tracker
844              
845             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
846              
847             L<https://rt.cpan.org/Public/Dist/Display.html?Name=AI-Pathfinding-OptimizeMultiple>
848              
849             =item *
850              
851             CPANTS
852              
853             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
854              
855             L<http://cpants.cpanauthors.org/dist/AI-Pathfinding-OptimizeMultiple>
856              
857             =item *
858              
859             CPAN Testers
860              
861             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
862              
863             L<http://www.cpantesters.org/distro/A/AI-Pathfinding-OptimizeMultiple>
864              
865             =item *
866              
867             CPAN Testers Matrix
868              
869             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
870              
871             L<http://matrix.cpantesters.org/?dist=AI-Pathfinding-OptimizeMultiple>
872              
873             =item *
874              
875             CPAN Testers Dependencies
876              
877             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
878              
879             L<http://deps.cpantesters.org/?module=AI::Pathfinding::OptimizeMultiple>
880              
881             =back
882              
883             =head2 Bugs / Feature Requests
884              
885             Please report any bugs or feature requests by email to C<bug-ai-pathfinding-optimizemultiple at rt.cpan.org>, or through
886             the web interface at L<https://rt.cpan.org/Public/Bug/Report.html?Queue=AI-Pathfinding-OptimizeMultiple>. You will be automatically notified of any
887             progress on the request by the system.
888              
889             =head2 Source Code
890              
891             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
892             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
893             from your repository :)
894              
895             L<http://github.com/shlomif/fc-solve>
896              
897             git clone ssh://git@github.com/shlomif/fc-solve.git
898              
899             =head1 AUTHOR
900              
901             Shlomi Fish <shlomif@cpan.org>
902              
903             =head1 BUGS
904              
905             Please report any bugs or feature requests on the bugtracker website
906             L<https://github.com/shlomif/ai-pathfinding-optimizemultiple/issues>
907              
908             When submitting a bug or request, please include a test-file or a
909             patch to an existing test-file that illustrates the bug or desired
910             feature.
911              
912             =head1 COPYRIGHT AND LICENSE
913              
914             This software is Copyright (c) 2012 by Shlomi Fish.
915              
916             This is free software, licensed under:
917              
918             The MIT (X11) License
919              
920             =cut