| 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 |