#!/usr/bin/env perl

# Given sample planar difference sets, find the lexically first
# lex-canonical sets of their respective spaces

use strict;
use warnings;
use Math::Prime::Util qw(invmod);
use Math::DifferenceSet::Planar 1.002;

$| = 1;

my $BAR_WIDTH = 64;
my $USAGE = "usage: pds_find_lex_ref [-f] [-p] [-part/parts] [file]...\n";

my $force = 0;
my $pro_b = 0;
my $part  = 1;
my $parts = 1;
while (@ARGV && $ARGV[0] =~ /^-(.+)/s) {
    my $opt = $1;
    shift @ARGV;
    last if '-' eq $opt;
    $force = 1, next if 'f' eq $opt;
    $pro_b = 1, next if 'p' eq $opt;
    if ($opt =~ m{^([1-9][0-9]*)/([1-9][0-9]*)\z}) {
        ($part, $parts) = ($1, $2);
        next if $part <= $parts;
    }
    die $USAGE;
}

while(<<>>) {
    s/^\s+//;
    my @list = split /\s+/;
    next if !@list;
    die "integer numbers separated by whitespace expected\n"
        if grep { !/^(?:0|[1-9][0-9]*)\z/ } @list;

    my $s1 = !$force && Math::DifferenceSet::Planar->lex_reference($#list);
    if (!$s1) {
        my $s2 =
            Math::DifferenceSet::Planar->from_elements_fast(@list)
                ->zeta_canonize;
        my $modulus = $s2->modulus;
        my @pe = $s2->plane_principal_elements;
        if (!@pe) {
            my $it = $s2->iterate_rotators;
            while (my $ro = $it->()) {
                push @pe, $ro;
            }
        }
        if (1 < $parts) {
            my $lo = int( @pe * ($part - 1) / $parts );
            my $hi = int( @pe * $part / $parts );
            @pe = @pe[$lo .. $hi-1];
        }
        my $i = 0;
        progress_bar(0, 0+@pe) if $pro_b;
        next if !@pe;
        foreach my $d (@pe) {
            my $f  = invmod($d, $modulus);
            my $s3 = $s2->multiply($f)->lex_canonize;
            $s1 = $s3 if !$s1 || $s3->compare($s1) < 0;
            progress_bar(++$i, 0+@pe) if $pro_b;
        }
    }

    my @el = $s1->elements_sorted;
    print "@el\n";
}

sub progress_bar {
    my ($i, $ni) = @_;
    my $black = $i && int 0.5 + $BAR_WIDTH * $i / $ni;
    my $white = $BAR_WIDTH - $black;
    print STDERR "\r", 'X' x $black, '.' x $white, " $i/$ni";
    print STDERR "\n" if $i >= $ni;
}

__END__

=head1 NAME

pds_find_lex_ref - find lexically minimal planar difference sets

=head1 SYNOPSIS

  pds_find_lex_ref [-f] [-p] [-part/parts] [file]...

=head1 DESCRIPTION

This example program reads planar difference sets, one per line, as
integer numbers separated by whitespace, finds from each of their
respective spaces the lexically first one of all sets, and prints
the results.  It can be used to get lex reference sets from arbitrary
sample sets.

Option C<-f> forces recalculation of the reference set even if it is
already known.

Option C<-p> displays a progress bar for each set that needs to be
calculated.

Argument dash, number, slash, number limits the search to a partial run,
where the number before the slash specifies the run (starting with 1)
and the other number the total number of runs.  The final result has
then to be chosen as the best one of the partial results.

Partial runs and progress bars only are employed if the result is
actually calculated rather than just looked up.

The algorithm uses the fact that the planes containing a set with elements
0, 1, and 3, are precisely those planes with 1 among their principal
elements.  This greatly reduces the number of planes to be considered
for comparison.  Unlike the search for zeta-canonical reference sets,
this algorithm needs to compute complete sets, however.

=head1 AUTHOR

Martin Becker, E<lt>becker-cpan-mp I<at> cozap.comE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2024-2025 by Martin Becker, Blaubeuren.

This library is free software; you can distribute it and/or modify it
under the terms of the Artistic License 2.0 (see the LICENSE file).

The license grants freedom for related software development but does
not cover incorporating code or documentation into AI training material.
Please contact the copyright holder if you want to use the library whole
or in part for other purposes than stated in the license.

=head1 DISCLAIMER OF WARRANTY

This library is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of merchantability
or fitness for a particular purpose.

=cut
