AI-Pathfinding-OptimizeMultiple
view release on metacpan or search on metacpan
lib/AI/Pathfinding/OptimizeMultiple/DataInputObj.pm view on Meta::CPAN
package AI::Pathfinding::OptimizeMultiple::DataInputObj;
$AI::Pathfinding::OptimizeMultiple::DataInputObj::VERSION = '0.0.17';
use strict;
use warnings;
use 5.012;
use MooX qw/late/;
use File::Path qw(mkpath);
use AI::Pathfinding::OptimizeMultiple::Scan ();
use PDL (qw( pdl ));
use PDL::IO::FastRaw (qw( readfraw writefraw ));
has start_board => ( isa => 'Int', is => 'ro', required => 1 );
has num_boards => ( isa => 'Int', is => 'ro', required => 1 );
has selected_scans => (
isa => 'ArrayRef',
is => 'ro',
required => 1,
default => sub {
my ($self) = @_;
return $self->_calc_selected_scan_list();
},
lazy => 1,
);
has _scan_ids_to_indexes => (
isa => 'HashRef[Int]',
is => 'ro',
lazy => 1,
default => sub {
my ($self) = @_;
my $scan_ids = $self->get_scan_ids_aref;
return +{ map { $scan_ids->[$_] => $_ } 0 .. $#$scan_ids };
},
);
sub _slurp
{
my $filename = shift;
open my $in, "<", $filename
or die "Could not open $filename";
binmode $in;
local $/;
my $content = <$in>;
close($in);
return $content;
}
sub _read_text_ints_file
{
my $self = shift;
my $filename = shift;
my $text = _slurp($filename);
return [ split( /[\n\r]+/, $text ) ];
}
# Number of selected scans.
sub _num_sel_scans
{
my $self = shift;
return scalar( @{ $self->selected_scans() } );
}
sub _gen_initial_scans_tensor
{
my $self = shift;
my $extra_dims = shift || [];
return zeroes( $self->num_boards(), $self->_num_sel_scans, @$extra_dims );
}
sub _should_update
{
my ( $self, $src_path, $dest_path ) = @_;
my @orig_stat = stat($src_path);
my @proc_stat = stat($dest_path);
return ( ( !@proc_stat ) || ( $orig_stat[9] > $proc_stat[9] ) );
}
# Number of numbers in the header of the solutions' iteration counts
my $NUM_NUMBERS_IN_HEADER = 3;
my $HEADER_START_BOARD_IDX = 0;
my $HEADER_NUM_BOARDS = 1;
my $HEADER_ITERATIONS_LIMIT = 2;
sub _get_scans_data_helper
{
my $self = shift;
my $selected_scans = $self->selected_scans();
my $start_board = $self->start_board();
my $scans_data = {};
my $scans_lens_data = {};
my $data_dir = ".data-proc";
my $lens_dir = ".data-len-proc";
mkpath( [ $data_dir, $lens_dir ] );
foreach my $scan (@$selected_scans)
{
{
my $dest_path = $data_dir . "/" . $scan->id();
{
if (
$self->_should_update(
$scan->data_file_path(), $dest_path
)
)
{
my $data_s = _slurp( $scan->data_file_path() );
my @array = unpack( "l*", $data_s );
if ( ( $array[$HEADER_START_BOARD_IDX] != 1 )
|| ( $array[$HEADER_NUM_BOARDS] < $self->num_boards ) )
{
die "Incorrect file format in scan "
. $scan->{'id'} . "!\n";
}
my $c = pdl( \@array );
writefraw( $c, $dest_path );
}
}
{
my $start_idx = $NUM_NUMBERS_IN_HEADER + ( $start_board - 1 );
my $scan_vec = readfraw($dest_path);
$scans_data->{ $scan->id() } =
$scan_vec->slice( $start_idx . ":"
. ( $start_idx + $self->num_boards() - 1 ) );
}
}
{
my $src = $scan->data_file_path();
my $dest = "$lens_dir/" . $scan->id();
if ( $self->_should_update( $src, $dest ) )
{
my $data_s = _slurp($src);
my @iters = unpack( "l*", $data_s );
if ( ( $iters[0] != 1 ) || ( $iters[1] < $self->num_boards() ) )
{
die "Incorrect file format in scan " . $scan->id() . "!\n";
}
# Remove the header
splice @iters, 0, $NUM_NUMBERS_IN_HEADER;
my $c = pdl(
[
\@iters,
$self->_read_text_ints_file(
"data/" . $scan->id() . ".fcs.moves.txt"
),
$self->_read_text_ints_file(
"data/" . $scan->id() . ".fcpro.moves.txt"
),
]
);
writefraw( $c, $dest );
}
{
my $scan_vec = readfraw($dest);
$scans_lens_data->{ $scan->id() } = $scan_vec->slice(
sprintf( "%d:%d,:,*",
( $start_board - 1 ),
( ( $self->num_boards() - 1 ) + ( $start_board - 1 ) ) )
)->xchg( 1, 2 );
}
}
}
return { 'scans' => $scans_data, 'with_lens' => $scans_lens_data };
}
sub _get_scans_data_generic
{
my ( $self, $id ) = @_;
return $self->_get_scans_data_helper()->{$id};
}
sub get_scans_iters_pdls
{
my $self = shift;
return $self->_get_scans_data_generic('scans');
}
sub get_scans_lens_iters_pdls
{
my $self = shift;
return $self->_get_scans_data_generic('with_lens');
}
sub _filter_scans_based_on_black_list_ids
{
my ( $scans, $black_list_ids ) = @_;
my %black_list = ( map { /(\d+)/ ? ( $1 => 1 ) : () } @$black_list_ids );
return [ grep { !exists( $black_list{ $_->id() } ) } @$scans ];
}
sub _is_scan_suitable
{
my ( $self, $scan ) = @_;
my @stat = stat( $scan->data_file_path() );
return (
scalar(@stat)
&& ( $stat[7] >=
12 + ( $self->num_boards() + $self->start_board() - 1 ) * 4 )
);
}
sub _get_scans_registry_file_path
{
return "scans.txt";
}
sub _get_all_scans_list_from_file
{
my $self = shift;
my @scans;
my $scans_fn = $self->_get_scans_registry_file_path;
open my $scans_fh, "<", $scans_fn
or die "Could not open '$scans_fn' - $!.";
while ( my $line = <$scans_fh> )
{
chomp($line);
my ( $id, $cmd_line ) = split( /\t/, $line );
push @scans,
AI::Pathfinding::OptimizeMultiple::Scan->new(
id => $id,
cmd_line => $cmd_line
);
}
close($scans_fh);
return \@scans;
}
sub _black_list_ids_list
{
my $self = shift;
open my $black_list_fh, "<", "scans-black-list.txt"
or die "Could not open 'scans-black-list.txt'! $!.";
my @black_list_ids = <$black_list_fh>;
chomp(@black_list_ids);
close($black_list_fh);
return \@black_list_ids;
}
sub _suitable_scans_list
{
my $self = shift;
return [ grep { $self->_is_scan_suitable($_) }
@{ $self->_get_all_scans_list_from_file() } ];
}
sub _calc_selected_scan_list
{
my $self = shift;
return _filter_scans_based_on_black_list_ids(
$self->_suitable_scans_list(),
$self->_black_list_ids_list(),
);
}
sub _get_next_id_file_path
{
return "next-id.txt";
}
sub get_next_id
{
my ($self) = @_;
my $id;
my $fn = $self->_get_next_id_file_path;
use autodie;
open my $in, "<", $fn;
$id = <$in>;
chomp($id);
close($in);
open my $out, ">", $fn;
print {$out} ( $id + 1 );
close($out);
return $id;
}
sub get_prev_scans
{
my ($self) = @_;
my @prev_scans;
my $scans_fn = $self->_get_scans_registry_file_path;
open my $in, "<", $scans_fn
or die "Could not open '$scans_fn' - $!.";
while ( my $line = <$in> )
{
chomp($line);
my ( $scan_id, $cmd_line ) = split( /\t/, $line );
push @prev_scans, { 'id' => $scan_id, 'cmd_line' => $cmd_line };
}
close($in);
return \@prev_scans;
}
sub _get_scan_cmd_line
{
my $self = shift;
my $args = shift;
my $min_board = $args->{'min'} || 1;
my $max_board = $args->{'max'} || 32_000;
my $id = $args->{'id'};
my $argv = $args->{'argv'};
my @fc_num = (
exists( $args->{'freecells_num'} )
? ( "--freecells-num", $args->{'freecells_num'} )
: ()
);
my @variant = (
exists( $args->{'variant'} )
? ( "--variant", $args->{'variant'} )
: ()
);
return [
qw(freecell-solver-fc-pro-range-solve),
$min_board,
$max_board,
"20",
@variant,
'--total-iterations-limit',
( $ENV{FC_SOLVE_RANGE_ITERS_LIMIT} // 100000 ),
'--binary-output-to',
"data/$id.data.bin",
@$argv,
@fc_num,
];
}
sub time_scan
{
my $self = shift;
my $args = shift;
my $min_board = $args->{'min'} || 1;
my $max_board = $args->{'max'} || 32_000;
my $id = $args->{'id'};
my $cmd_line = $self->_get_scan_cmd_line($args);
open my $from_cmd, "-|", @$cmd_line
or die "Could not start '@$cmd_line'";
open my $fcs_out, ">", "data/$id.fcs.moves.txt";
open my $fc_pro_out, ">", "data/$id.fcpro.moves.txt";
$fcs_out->autoflush(1);
$fc_pro_out->autoflush(1);
while ( my $line = <$from_cmd> )
{
print $line;
chomp($line);
if ( $line =~ m{\A\[\[Num FCS Moves\]\]=(.*)\z}o )
{
print {$fcs_out} "$1\n";
}
elsif ( $line =~ m{\A\[\[Num FCPro Moves\]\]=(.*)\z}o )
{
print {$fc_pro_out} "$1\n";
}
}
close($from_cmd);
close($fcs_out);
close($fc_pro_out);
}
sub get_scan_ids_aref
{
my $self = shift;
return [ map { $_->id() } @{ $self->selected_scans } ];
}
sub lookup_scan_idx_based_on_id
{
my ( $self, $scan_id ) = @_;
my $idx = $self->_scan_ids_to_indexes->{$scan_id};
if ( !defined($idx) )
{
die "Index '$idx' does not exist!";
}
return $idx;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
AI::Pathfinding::OptimizeMultiple::DataInputObj - handle the data input.
=head1 VERSION
version 0.0.17
=head1 SUBROUTINES/METHODS
=head2 $self->get_next_id()
Retrieves the next scan ID and increment it.
=head2 $self->get_prev_scans()
Returns an array reference of previous scan hash-refs.
=head2 my $hash_ref = $self->get_scans_iters_pdls()
Returns the hash ref mapping scan IDs/names to iteration PDLs.
=head2 my $hash_ref = $self->get_scans_lens_iters_pdls()
Returns the hash ref mapping scan IDs/names to iteration+lengths PDLs.
=head2 $self->num_boards()
The number of boards.
=head2 $self->selected_scans()
An accessor for the selected scans.
=head2 $self->start_board()
The index of the board to start from.
=head2 $self->time_scan()
( run in 0.610 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )