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