File Coverage

blib/lib/AI/Pathfinding/OptimizeMultiple.pm
Criterion Covered Total %
statement 105 240 43.7
branch 9 30 30.0
condition 2 16 12.5
subroutine 24 35 68.5
pod 7 7 100.0
total 147 328 44.8


line stmt bran cond sub pod time code
1             package AI::Pathfinding::OptimizeMultiple;
2             $AI::Pathfinding::OptimizeMultiple::VERSION = '0.0.15';
3 2     2   211340 use strict;
  2         5  
  2         51  
4 2     2   10 use warnings;
  2         5  
  2         53  
5              
6 2     2   37 use 5.012;
  2         6  
7              
8 2     2   12 use IO::Handle;
  2         3  
  2         89  
9              
10 2     2   665 use AI::Pathfinding::OptimizeMultiple::IterState;
  2         7  
  2         103  
11 2     2   951 use AI::Pathfinding::OptimizeMultiple::Scan;
  2         9  
  2         56  
12 2     2   724 use AI::Pathfinding::OptimizeMultiple::ScanRun;
  2         8  
  2         73  
13 2     2   999 use AI::Pathfinding::OptimizeMultiple::SimulationResults;
  2         11  
  2         104  
14              
15 2     2   19 use MooX qw/late/;
  2         5  
  2         17  
16              
17 2     2   1998 use PDL;
  2         6  
  2         16  
18 2     2   178171 use Scalar::Util qw/ blessed /;
  2         6  
  2         3988  
19              
20             has chosen_scans => (isa => 'ArrayRef', is => 'rw');
21             has _iter_idx => (isa => 'Int', is => 'rw', default => sub { 0; },);
22             has _num_boards => (isa => 'Int', is => 'ro', init_arg => 'num_boards',);
23             has _orig_scans_data => (isa => 'PDL', is => 'rw');
24             has _optimize_for => (isa => 'Str', is => 'ro', init_arg => 'optimize_for',);
25             has _scans_data => (isa => 'PDL', is => 'rw');
26             has _selected_scans => (isa => 'ArrayRef', is => 'ro', init_arg => 'selected_scans',);
27             has _status => (isa => 'Str', is => 'rw');
28             has _quotas => (isa => 'ArrayRef[Int]', is => 'ro', init_arg => 'quotas');
29             has _total_boards_solved => (isa => 'Int', is => 'rw');
30             has _total_iters => (isa => 'Int', is => 'rw');
31             has _trace_cb => (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 => (isa => 'HashRef', is => 'rw', init_arg => 'scans_iters_pdls');
34             has _stats_factors => (isa => 'HashRef', is => 'ro', init_arg => 'stats_factors', default => sub { return +{}; },);
35              
36             sub BUILD
37             {
38 4     4 1 463 my $self = shift;
39              
40 4         9 my $args = shift;
41              
42             my $scans_data = PDL::cat(
43             map {
44 12         33 my $id = $_->id();
45 12         191 my $pdl = $self->_scans_iters_pdls()->{$id};
46 12         75 my $factor = $self->_stats_factors->{$id};
47 12 100       269 (defined($factor)
48             ? (($pdl >= 0) * (($pdl / $factor)->ceil()) + ($pdl < 0) * $pdl)
49             : $pdl
50             );
51             }
52 4         8 @{$self->_selected_scans()}
  4         16  
53             );
54              
55 4         1105 $self->_orig_scans_data($scans_data);
56 4         164 $self->_scans_data($self->_orig_scans_data()->copy());
57              
58 4         283 return 0;
59             }
60              
61             my $BOARDS_DIM = 0;
62             my $SCANS_DIM = 1;
63             my $STATISTICS_DIM = 2;
64              
65             sub _next_iter_idx
66             {
67 8     8   14 my $self = shift;
68              
69 8         120 my $ret = $self->_iter_idx();
70              
71 8         141 $self->_iter_idx($ret+1);
72              
73 8         188 return $ret;
74             }
75              
76             sub _get_next_quota
77             {
78 8     8   12 my $self = shift;
79              
80 8         18 my $iter = $self->_next_iter_idx();
81              
82 8 50       30 if (ref($self->_quotas()) eq "ARRAY")
83             {
84 8         24 return $self->_quotas()->[$iter];
85             }
86             else
87             {
88 0         0 return $self->_quotas()->($iter);
89             }
90             }
91              
92             sub _calc_get_iter_state_param_method
93             {
94 8     8   13 my $self = shift;
95              
96 8         20 my $optimize_for = $self->_optimize_for();
97              
98 8         31 my %resolve =
99             (
100             len => "_get_iter_state_params_len",
101             minmax_len => "_get_iter_state_params_minmax_len",
102             speed => "_get_iter_state_params_speed",
103             );
104              
105 8         23 return $resolve{$optimize_for};
106             }
107              
108             sub _get_iter_state_params
109             {
110 8     8   14 my $self = shift;
111              
112 8         19 my $method = $self->_calc_get_iter_state_param_method();
113              
114 8         30 return $self->$method();
115             }
116              
117             sub _my_sum_over
118             {
119 0     0   0 my $pdl = shift;
120              
121 0         0 return $pdl->sumover()->slice(":,(0)");
122             }
123              
124             sub _my_xchg_sum_over
125             {
126 0     0   0 my $pdl = shift;
127              
128 0         0 return _my_sum_over($pdl->xchg(0,1));
129             }
130              
131             sub _get_iter_state_params_len
132             {
133 0     0   0 my $self = shift;
134              
135 0         0 my $iters_quota = 0;
136 0         0 my $num_solved_in_iter = 0;
137 0         0 my $selected_scan_idx;
138              
139             # If no boards were solved, then try with a larger quota
140 0         0 while ($num_solved_in_iter == 0)
141             {
142 0         0 my $q_more = $self->_get_next_quota();
143 0 0       0 if (!defined($q_more))
144             {
145 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
146             error => "No q_more",
147             );
148             }
149              
150 0         0 $iters_quota += $q_more;
151              
152 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
153 0         0 my $solved = (($iters <= $iters_quota) & ($iters > 0));
154 0         0 my $num_moves = $self->_scans_data->slice(":,:,2");
155 0         0 my $solved_moves = $solved * $num_moves;
156              
157 0         0 my $solved_moves_sums = _my_sum_over($solved_moves);
158 0         0 my $solved_moves_counts = _my_sum_over($solved);
159 0         0 my $solved_moves_avgs = $solved_moves_sums / $solved_moves_counts;
160              
161 0         0 (undef, undef, $selected_scan_idx, undef) =
162             $solved_moves_avgs->minmaximum()
163             ;
164              
165 0         0 $num_solved_in_iter = $solved_moves_counts->at($selected_scan_idx);
166             }
167              
168             return
169             {
170 0         0 quota => $iters_quota,
171             num_solved => $num_solved_in_iter,
172             scan_idx => $selected_scan_idx,
173             };
174             }
175              
176             sub _get_iter_state_params_minmax_len
177             {
178 0     0   0 my $self = shift;
179              
180 0         0 my $iters_quota = 0;
181 0         0 my $num_solved_in_iter = 0;
182 0         0 my $selected_scan_idx;
183              
184             # If no boards were solved, then try with a larger quota
185 0         0 while ($num_solved_in_iter == 0)
186             {
187 0         0 my $q_more = $self->_get_next_quota();
188 0 0       0 if (!defined($q_more))
189             {
190 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
191             error => "No q_more",
192             );
193             }
194              
195 0         0 $iters_quota += $q_more;
196              
197 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
198 0         0 my $solved = (($iters <= $iters_quota) & ($iters > 0));
199 0         0 my $num_moves = $self->_scans_data->slice(":,:,2");
200 0         0 my $solved_moves = $solved * $num_moves;
201              
202 0         0 my $solved_moves_maxima = $solved_moves->maximum()->slice(":,(0),(0)");
203 0         0 my $solved_moves_counts = _my_sum_over($solved);
204              
205 0         0 (undef, undef, $selected_scan_idx, undef) =
206             $solved_moves_maxima->minmaximum()
207             ;
208              
209 0         0 $num_solved_in_iter = $solved_moves_counts->at($selected_scan_idx);
210             }
211              
212             return
213             {
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   12 my $self = shift;
223              
224 8         14 my $iters_quota = 0;
225 8         13 my $num_solved_in_iter = 0;
226 8         13 my $selected_scan_idx;
227              
228             # If no boards were solved, then try with a larger quota
229 8         23 while ($num_solved_in_iter == 0)
230             {
231 8         18 my $q_more = $self->_get_next_quota();
232 8 50       26 if (!defined($q_more))
233             {
234 0         0 AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas->throw(
235             error => "No q_more"
236             );
237             }
238              
239 8         12 $iters_quota += $q_more;
240              
241 8         113 (undef, $num_solved_in_iter, undef, $selected_scan_idx) =
242             PDL::minmaximum(
243             PDL::sumover(
244             ($self->_scans_data() <= $iters_quota) &
245             ($self->_scans_data() > 0)
246             )
247             );
248             }
249              
250             return
251             {
252 8         957 quota => $iters_quota,
253             num_solved => $num_solved_in_iter->at(0),
254             scan_idx => $selected_scan_idx->at(0),
255             };
256             }
257              
258             sub _get_selected_scan
259             {
260 8     8   14 my $self = shift;
261              
262 8         22 my $iter_state =
263             AI::Pathfinding::OptimizeMultiple::IterState->new(
264             $self->_get_iter_state_params(),
265             );
266              
267 8         2897 $iter_state->attach_to($self);
268              
269 8         28 return $iter_state;
270             }
271              
272             sub _inspect_quota
273             {
274 8     8   16 my $self = shift;
275              
276 8         20 my $state = $self->_get_selected_scan();
277              
278 8         29 $state->register_params();
279              
280 8         26 $state->update_total_iters();
281              
282 8 100       1093 if ($self->_total_boards_solved() == $self->_num_boards())
283             {
284 4         76 $self->_status("solved_all");
285             }
286             else
287             {
288 4         39 $state->update_idx_slice();
289             }
290              
291 8         218 $state->detach();
292             }
293              
294              
295             sub calc_meta_scan
296             {
297 4     4 1 26 my $self = shift;
298              
299 4         63 $self->chosen_scans([]);
300              
301 4         139 $self->_total_boards_solved(0);
302 4         142 $self->_total_iters(0);
303              
304 4         141 $self->_status("iterating");
305             # $self->_inspect_quota() throws ::Error::OutOfQuotas if
306             # it does not have any available quotas.
307             eval
308 4         98 {
309 4         54 while ($self->_status() eq "iterating")
310             {
311 8         66 $self->_inspect_quota();
312             }
313             };
314 4 50       38 if (my $err = Exception::Class->caught('AI::Pathfinding::OptimizeMultiple::Error::OutOfQuotas'))
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              
335             sub _get_num_scans
336             {
337 0     0   0 my $self = shift;
338              
339 0         0 return (($self->_scans_data()->dims())[$SCANS_DIM]);
340             }
341              
342             sub _calc_chosen_scan
343             {
344 8     8   17 my ($self, $selected_scan_idx, $iters_quota) = @_;
345              
346             return
347             AI::Pathfinding::OptimizeMultiple::ScanRun->new(
348             {
349             iters => (
350             $iters_quota
351             * (
352             $self->_stats_factors->{
353 8   100     196 ($self->_selected_scans->[$selected_scan_idx]->id()),
354             } // 1
355             )
356             ),
357             scan_idx => $selected_scan_idx,
358             }
359             );
360             }
361              
362             sub calc_flares_meta_scan
363             {
364 0     0 1 0 my $self = shift;
365              
366 0         0 $self->chosen_scans([]);
367              
368 0         0 $self->_total_boards_solved(0);
369 0         0 $self->_total_iters(0);
370              
371 0         0 $self->_status("iterating");
372              
373 0         0 my $iters_quota = 0;
374 0         0 my $flares_num_iters = PDL::Core::pdl([(0) x $self->_get_num_scans()]);
375             my $ones_constant = PDL::Core::pdl(
376 0         0 [map { [1] } (1 .. $self->_get_num_scans())]
  0         0  
377             );
378              
379 0         0 my $next_num_iters_for_each_scan_x_scan =
380             (($ones_constant x $flares_num_iters));
381              
382              
383 0         0 my $num_moves = $self->_scans_data->slice(":,:,1");
384              
385             # The number of moves for dimension 0,1,2 above.
386 0         0 my $num_moves_repeat = $num_moves->clump(1..2)->xchg(0,1)->dummy(0,$self->_get_num_scans());
387              
388 0         0 my $selected_scan_idx;
389              
390 0         0 my $loop_iter_num = 0;
391              
392 0         0 my $UNSOLVED_NUM_MOVES_CONSTANT = 64 * 1024 * 1024;
393              
394 0         0 my $last_avg = $UNSOLVED_NUM_MOVES_CONSTANT;
395              
396             FLARES_LOOP:
397 0         0 while (my $q_more = $self->_get_next_quota())
398             {
399 0         0 $iters_quota += $q_more;
400              
401             # Next number of iterations for each scan x scan combination.
402 0         0 my $next_num_iters =
403             (($ones_constant x $flares_num_iters) +
404             (PDL::MatrixOps::identity($self->_get_num_scans())
405             * $iters_quota
406             )
407             );
408              
409             # print "\$next_num_iters = $next_num_iters\n";
410              
411 0         0 my $iters = $self->_scans_data()->slice(":,:,0");
412              
413 0         0 my $iters_repeat =
414             $iters->dummy(0,$self->_get_num_scans())->xchg(1,2)->clump(2 .. 3);
415              
416             # print "\$iters_repeat =", join(",",$iters_repeat->dims()), "\n";
417              
418 0         0 my $next_num_iters_repeat =
419             $next_num_iters->dummy(0,$self->_num_boards())->xchg(0,2);
420              
421             # print "\$next_num_iters_repeat =", join(",",$next_num_iters_repeat->dims()), "\n";
422              
423             # A boolean tensor of which boards were solved:
424             # Dimension 0 - Which scan is it. - size - _get_num_scans()
425             # Dimension 1 - Which scan we added the quota to
426             # - size - _get_num_scans()
427             # Dimension 2 - Which board. - size - _num_boards()
428 0         0 my $solved = ($iters_repeat >= 0) * ($iters_repeat < $next_num_iters_repeat);
429              
430             # print "\$num_moves_repeat =", join(",",$num_moves_repeat->dims()), "\n";
431              
432              
433              
434 0         0 my $num_moves_solved =
435             ($solved * $num_moves_repeat) + ($solved->not() * $UNSOLVED_NUM_MOVES_CONSTANT);
436              
437 0         0 my $minimal_num_moves_solved = $num_moves_solved->xchg(0,1)->minimum();
438              
439 0         0 my $which_minima_are_solved =
440             ($minimal_num_moves_solved != $UNSOLVED_NUM_MOVES_CONSTANT)
441             ;
442              
443 0         0 my $minimal_with_zeroes =
444             $which_minima_are_solved * $minimal_num_moves_solved;
445              
446 0         0 my $solved_moves_sums = _my_xchg_sum_over($minimal_with_zeroes);
447 0         0 my $solved_moves_counts = _my_xchg_sum_over($which_minima_are_solved);
448 0         0 my $solved_moves_avgs = $solved_moves_sums / $solved_moves_counts;
449              
450             # print join(",", $solved_moves_avgs->minmaximum()), "\n";
451              
452 0         0 my $min_avg;
453              
454 0         0 ($min_avg, undef, $selected_scan_idx, undef) =
455             $solved_moves_avgs->minmaximum()
456             ;
457              
458 0         0 $last_avg = $min_avg;
459              
460 0         0 push @{$self->chosen_scans()}, $self->_calc_chosen_scan($selected_scan_idx, $iters_quota);
  0         0  
461              
462 0         0 $flares_num_iters->set($selected_scan_idx, $flares_num_iters->at($selected_scan_idx)+$iters_quota);
463 0         0 $self->_selected_scans()->[$selected_scan_idx]->mark_as_used();
464              
465 0         0 $iters_quota = 0;
466              
467 0         0 my $num_solved = $solved_moves_counts->at($selected_scan_idx);
468              
469 0         0 my $flares_num_iters_repeat =
470             $flares_num_iters->dummy(0,$self->_num_boards());
471              
472             # A boolean tensor:
473             # Dimension 0 - board.
474             # Dimension 1 - scans.
475 0         0 my $solved_with_which_iter =
476             ($flares_num_iters_repeat >= $iters->clump(1 .. 2))
477             & ($iters->clump(1 .. 2) >= 0)
478             ;
479              
480 0         0 my $total_num_iters =
481             (
482             ($solved_with_which_iter * $flares_num_iters_repeat)->sum()
483             + ($solved_with_which_iter->not()->andover()
484             * $flares_num_iters->sum())->sum()
485             );
486              
487 0         0 print "Finished ", $loop_iter_num++, " ; #Solved = $num_solved ; Iters = $total_num_iters ; Avg = $min_avg\n";
488 0         0 STDOUT->flush();
489             }
490             }
491              
492              
493             sub calc_board_iters
494             {
495 0     0 1 0 my $self = shift;
496 0         0 my $board = shift;
497              
498 0         0 my $board_iters = 0;
499              
500 0         0 my @info = PDL::list($self->_orig_scans_data()->slice("$board,:"));
501 0         0 my @orig_info = @info;
502              
503 0         0 foreach my $s (@{$self->chosen_scans()})
  0         0  
504             {
505 0 0 0     0 if (($info[$s->scan_idx()] > 0) && ($info[$s->scan_idx()] <= $s->iters()))
506             {
507 0         0 $board_iters += $info[$s->iters()];
508 0         0 last;
509             }
510             else
511             {
512 0 0       0 if ($info[$s->scan_idx()] > 0)
513             {
514 0         0 $info[$s->scan_idx()] -= $s->iters();
515             }
516 0         0 $board_iters += $s->iters();
517             }
518             }
519              
520             return
521             {
522 0         0 'per_scan_iters' => \@orig_info,
523             'board_iters' => $board_iters,
524             };
525             }
526              
527              
528             sub get_final_status
529             {
530 0     0 1 0 my $self = shift;
531              
532 0         0 return $self->_status();
533             }
534              
535              
536             sub simulate_board
537             {
538 0     0 1 0 my ($self, $board_idx, $args) = @_;
539              
540 0 0       0 if ($board_idx !~ /\A[0-9]+\z/)
541             {
542 0         0 die "Board index '$board_idx' is not numeric!";
543             }
544              
545 0   0     0 $args ||= {};
546              
547 0   0     0 my $chosen_scans = ($args->{chosen_scans} || $self->chosen_scans);
548              
549 0         0 my @info = PDL::list($self->_orig_scans_data()->slice("$board_idx,:"));
550              
551 0         0 my $board_iters = 0;
552              
553 0         0 my @scan_runs;
554              
555 0         0 my $status = "Unsolved";
556              
557             my $add_new_scan_run = sub {
558 0     0   0 my $scan_run = shift;
559              
560 0         0 push @scan_runs, $scan_run;
561              
562 0         0 $board_iters += $scan_run->iters();
563              
564 0         0 return;
565 0         0 };
566              
567             SCANS_LOOP:
568 0         0 foreach my $s (@$chosen_scans)
569             {
570 0 0 0     0 if (($info[$s->scan_idx()] > 0) && ($info[$s->scan_idx()] <= $s->iters()))
571             {
572 0         0 $add_new_scan_run->(
573             AI::Pathfinding::OptimizeMultiple::ScanRun->new(
574             {
575             iters => $info[$s->scan_idx()],
576             scan_idx => $s->scan_idx(),
577             },
578             )
579             );
580              
581 0         0 $status = "Solved";
582 0         0 last SCANS_LOOP;
583             }
584             else
585             {
586 0 0       0 if ($info[$s->scan_idx()] > 0)
587             {
588 0         0 $info[$s->scan_idx()] -= $s->iters();
589             }
590              
591 0         0 $add_new_scan_run->(
592             AI::Pathfinding::OptimizeMultiple::ScanRun->new(
593             {
594             iters => $s->iters(),
595             scan_idx => $s->scan_idx(),
596             },
597             )
598             );
599             }
600             }
601              
602             return
603 0         0 AI::Pathfinding::OptimizeMultiple::SimulationResults->new(
604             {
605             status => $status,
606             scan_runs => \@scan_runs,
607             total_iters => $board_iters,
608             }
609             );
610             }
611              
612             sub _trace
613             {
614 8     8   61 my ($self, $args) = @_;
615              
616 8 50       26 if (my $trace_callback = $self->_trace_cb())
617             {
618 0         0 $trace_callback->($args);
619             }
620              
621 8         20 return;
622             }
623              
624              
625             sub get_total_iters
626             {
627 0     0 1 0 my $self = shift;
628              
629 0         0 return $self->_total_iters();
630             }
631              
632             sub _add_to_total_iters
633             {
634 16     16   391 my $self = shift;
635              
636 16         31 my $how_much = shift;
637              
638 16         292 $self->_total_iters($self->_total_iters() + $how_much);
639              
640 16         430 return;
641             }
642              
643             sub _add_to_total_boards_solved
644             {
645 8     8   14 my $self = shift;
646              
647 8         13 my $how_much = shift;
648              
649 8         115 $self->_total_boards_solved($self->_total_boards_solved() + $how_much);
650              
651 8         195 return;
652             }
653              
654             1; # End of AI::Pathfinding::OptimizeMultiple
655              
656             __END__