#!/usr/bin/env perl

use 5.022;
use strict;
use warnings;
use Math::DifferenceSet::Planar 1.000;
use Math::Prime::Util qw(euler_phi);

$| = 1;

my $USAGE =
    "usage: pds_info [-D database] [-l limit] [-h] [-a] [--key]... [file]...\n";
$USAGE   .= "       pds_info -k\n";
my @KEYS = qw(
    order order_base order_exponent modulus n_planes n_sets
    start_element peak_elements min_element max_element largest_gap
    eta zeta theta lambda n_mult phi_modulus sigma tau_zero
    n_elems n_princ n_suppl n_deriv n_princ_deriv n_suppl_deriv n_fill
    elems
);
my @SUBKEYS = qw(
    largest_gap_elements largest_gap_difference
);
my @ALIASES = qw(gamma psi);
my %keys = map {($_ => 1)} @KEYS, @SUBKEYS, @ALIASES;
@keys{qw(peak_elements largest_gap largest_gap_elements)} = (2, 3, 2);
my @SUPER = ();
foreach my $key (@SUBKEYS) {
    if ($key =~ /^(.*)_/) {
        my $sk = $1;
        if (@SUPER && $SUPER[-1]->[0] eq $sk) {
            push @{$SUPER[-1]->[1]}, $key;
        }
        else {
            push @SUPER, [$sk, [$key]];
        }
    }
}
my %ALIAS   = qw(gamma largest_gap_difference psi n_princ);

my @cols  = ();
my $head  = 0;
my $all   = 0;
my $db    = undef;
my $limit = 5;
while (@ARGV && $ARGV[0] =~ /^-(.+)/) {
    my $opt = $1;
    shift @ARGV;
    last                        if '-' eq $opt;
    $head  = 1,            next if 'h' eq $opt;
    $all   = 1,            next if 'a' eq $opt;
    $all   = 0,            next if 'n' eq $opt;
    display_keys()              if 'k' eq $opt;
    push(@cols, $1),       next if $opt =~ /^-(\w+)\z/ && exists $keys{$1};
    $db    = shift(@ARGV), next if 'D' eq $opt && @ARGV;
    $db    = $1,           next if $opt =~ /^D(.+)/s;
    $limit = shift(@ARGV), next
        if 'l' eq $opt && @ARGV && $ARGV[0] =~ /^0|[1-9][0-9]*\z/;
    $limit = $1,           next if $opt =~ /^l(0|[1-9][0-9]*)\z/;
    die $USAGE;
}
push @cols, missing_keys(@cols)                if $all;
@cols = @KEYS[0..4]                            if !@cols;
Math::DifferenceSet::Planar->set_database($db) if defined $db;

print join(q[ ], map {$_, ('_') x ($keys{$_} - 1)} @cols), "\n" if $head;
while (<<>>) {
    s/^\s+//;
    my @e = split /\s+/;
    next if !@e;

    die "integer numbers separated by whitespace expected\n"
        if grep { !/^(?:0|[1-9][0-9]*)\z/ } @e;

    my $s = eval { Math::DifferenceSet::Planar->from_elements(@e) };
    if (!$s) {
        print "unknown\n";
        next;
    }
    my @res = map { $_ || 0 } map { $s->$_ } @cols;
    print "@res\n";
}

sub missing_keys {
    my %have = ();
    my @aliased = @ALIAS{ grep { exists $ALIAS{$_} } @_ };
    @have{@_, @aliased} = ();
    foreach my $r (@SUPER) {
        my ($sk, $ks) = @{$r};
        if (exists $have{$sk}) {
            @have{@{$ks}} = ();
        }
        elsif (grep {exists $have{$_}} @{$ks}) {
            $have{$sk} = undef;
        }
    }
    return grep { !exists $have{$_} } @KEYS, @SUBKEYS;
}

sub display_keys {
    print map {; "--$_\n" } @KEYS, @SUBKEYS, @ALIASES;
    exit 0;
}

package Math::DifferenceSet::Planar;

use Math::BigInt try => 'GMP';

BEGIN {
    *gamma = \&largest_gap_difference;
    *psi   = \&n_princ;
}

sub n_sets   { Math::BigInt->new($_[0]->modulus) * $_[0]->n_planes }
sub n_mult   { 0 + $_[0]->multipliers }
sub n_elems  { 1 + $_[0]->order }
sub n_princ  { 0 + $_[0]->plane_principal_elements }
sub n_suppl  { 0 + $_[0]->plane_supplemental_elements }
sub n_fill   { 0 + $_[0]->plane_fill_elements }
sub tau_zero { 0 + $_[0]->plane_unit_elements }

sub n_deriv {
    my ($this) = @_;
    return 1 + $this->order - $this->n_princ - $this->n_suppl - $this->n_fill;
}

sub n_princ_deriv {
    my ($this) = @_;
    return $this->plane_principal_elements * ($this->multipliers - 1);
}

sub n_suppl_deriv {
    my ($this) = @_;
    return $this->n_deriv - $this->n_princ_deriv;
}

sub phi_modulus {
    my ($this) = @_;
    return $this->n_planes * $this->multipliers;
}

sub sigma {
    my ($this) = @_;
    my $order    = $this->order;
    my $exponent = $this->order_exponent;
    return euler_phi(3 * $order - 3) * 3 * $exponent / 2;
}

sub elems {
    my ($this) = @_;
    if (!$limit || $this->order < $limit) {
        return join q[,], $this->elements_sorted;
    }
    my @elems = map { $this->element_sorted($_) } 0 .. $limit-1;
    push @elems, q[...];
    return join q[,], @elems;
}

sub largest_gap_elements   { ($_[0]->largest_gap)[0, 1] }
sub largest_gap_difference { ($_[0]->largest_gap)[2]    }

__END__

=encoding utf8

=head1 NAME

pds_info - display information about planar difference sets

=head1 SYNOPSIS

  pds_info [-D database] [-l limit] [-h] [-a] [--key]... [file]...
  pds_info -k

=head1 DESCRIPTION

This example program reads planar difference sets, one per line, as
integer numbers separated by whitespace, and writes information about
these sets to standard output.

The program will write the word "unknown" for each input line not
recognized as a planar difference set.  This may occur for incorrect input
as well as for sets exceeding the implementation-specific size limit.

By default, these attributes will be printed, separated by spaces:
order, order_base, order_exponent, modulus, n_planes.

Attributes of interest can also be specified explicitly by enumerating
attribute names preceded by two dashes.  In addition to the default
attributes, these are: n_sets, start_element, peak_elements (yielding two
output columns), min_element, max_element, largest_gap (yielding three
output colums of two elements and their difference), eta, zeta, theta,
lambda, n_mult, phi_modulus, sigma, tau_zero, n_elems, n_princ, n_suppl,
n_deriv, n_princ_deriv, n_suppl_deriv, n_fill, elems.  An unkown value
of lambda is replaced by zero (which is not an otherwise possible value).

The first two values of largest_gap can be obtained with the
largest_gap_elements attribute and the third value with the
largest_gap_difference attribute.

With option B<-h>, the data is preceded by a header line.

With option B<-a>, all columns mentioned above and not specified
individually are added.

Parameter C<-D> specifies an alternate sample database.

=head2 Difference Set Properties

=over 4

=item order

The order of the set, equal to the number of elements minus one.

=item order_base

If the order is written as a power I<p ** n> with maximal I<n>, the
base I<p>.  Note that I<p> is conjectured to always be a prime number.

=item order_exponent

If the order is written as a power I<p ** n> with maximal I<n>, the
exponent I<n>.

=item modulus

The modulus, equal to I<(q + 1) * q + 1> if the order is I<q>.
Also the number of distinct translates of the set.

=item n_planes

The number of distinct planes of the same order.  Difference sets that
are translates of each other belong to the same plane.  Equal to Euler
phi(modulus) / (3 * order_exponent).

=item n_sets

The total number of distinct planar difference sets of the same order,
equal to I<n_planes * modulus>.

=item start_element

The unique element I<e> of the set with the property that I<e + 1>
is also in the set.

=item peak_elements

Two columns.  The unique pair of elements I<e1, e2> of the set with the
property that I<e2 - e1> is equal to half of the modulus (rounded down).

=item min_element

The smallest element of the set, by residue value between 0 and the
modulus minus one.

=item max_element

The largest element of the set, by residue value between 0 and the
modulus minus one.

=item largest_gap

Three columns.  The unique pair of consecutive elements, when listed
with smallest possible increment and wrap-around, with maximal increment,
and the increment amount.

Example: I<{3, 4, 6} (mod 7)> has largest-gap elements I<6> and I<3>
and the increment I<4>, as I<6 + 4 (mod 7)> is congruent to I<3 (mod 7)>.

=item largest_gap_elements

=item largest_gap_difference

=item gamma

The elements (first two columns) of largest_gap can also be accessed as
largest_gap_elements and the increment amount as largest_gap_difference.
A synonym for largest_gap_difference is gamma.

=item eta

The translation amount equivalent to multiplying the set by I<order_base>.

=item zeta

The translation amount equivalent to multiplying the set by I<order>.

=item theta

The translation amount taking the zeta-canonical representative of
this set's plane to this set.  For the definition of zeta-canonical,
cf. L<Math::DifferenceSet::Planar>.

=item lambda

The smallest multiplication value taking the standard reference
set of this set's order to this set's plane.  The triple I<order>,
I<lambda>, I<theta> is also called the fingerprint, as it uniquely
identifies each set.  For the definition of standard reference set,
cf. L<Math::DifferenceSet::Planar>.

=item n_mult

The number of multipliers of the set.  Multipliers are multiplication
values that take a set to itself or another set within the same plane,
i.e. a translate.  Equal to 3 * order_exponent.

=item n_elems

The cardinality of the set, equal to its order plus one.

=item n_princ

The number of principal elements of a set's plane.
For the definition, cf. L<Math::DifferenceSet::Planar>.

Also the number of principal planes of a set's order.
A synonym for n_princ is psi.

Note that you can get principal and supplemental elements themselves
with the example program F<pds_main_elements>.

=item n_suppl

The number of supplemental elements of a set's plane.
For the definition, cf. L<Math::DifferenceSet::Planar>.

=item n_deriv

The number of elements derived from principal and supplemental elements
of a set's plane.  For the definition, cf. L<Math::DifferenceSet::Planar>.

=item n_princ_deriv

The number of elements derived from the principal elements of a set's
plane.  For the definition, cf. L<Math::DifferenceSet::Planar>.

=item n_suppl_deriv

The number of elements derived from the supplemental elements of a
set's plane.  For the definition, cf. L<Math::DifferenceSet::Planar>.

=item n_fill

The number of fill elements of a set's plane.
For the definition, cf. L<Math::DifferenceSet::Planar>.

Note that I<n_princ + n_suppl + n_deriv + n_fill> is equal to I<n_elems>.

=item phi_modulus

The cardinality of the unit space of the modular integer ring.
Equal to Euler phi(modulus), equal to n_planes * n_mult.

=item sigma

The number of different primitive monic third-degree polynomials over
GF(order) generating the plane of this difference set.  Equal to Euler
phi(3 * order - 3) * n_mult / 2.

=item tau_zero

The number of elements of the zeta-canonical representative of this
set's plane that are coprime to the modulus.  Equal to n_princ * n_mult.

=item elems

The comma-separated list of elements of the set in numerically ascending
order, if the set is small, otherwise the first few elements followed
by three dots.  By default, few means five elements.  The limit can be
changed with the B<-l> parameter.  A limit of 0 means no cut-off.

=back

=head1 AUTHOR

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

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2022-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
