The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# multiplicity ?


# Copyright 2010, 2011, 2012, 2013, 2014 Kevin Ryde

# This file is part of Math-NumSeq.
#
# Math-NumSeq is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# Math-NumSeq 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.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with Math-NumSeq.  If not, see <http://www.gnu.org/licenses/>.


# ENHANCE-ME: check the factorization, or a least a few smallish 4n+3 primes


package Math::NumSeq::SumTwoSquares;
use 5.004;
use strict;

use vars '$VERSION', '@ISA';
$VERSION = 72;
use Math::NumSeq 7; # v.7 for _is_infinite()
@ISA = ('Math::NumSeq');
*_is_infinite = \&Math::NumSeq::_is_infinite;

# uncomment this to run the ### lines
#use Smart::Comments;


# use constant name => Math::NumSeq::__('Sum of Two Squares');
use constant description => Math::NumSeq::__('Sum of two squares, ie. all numbers which occur as x^2+y^2 for x>=1 and y>=1.');
use constant characteristic_increasing => 1;
use constant i_start => 1;

use constant parameter_info_array =>
  [ {
     name        => 'distinct',
      type        => 'boolean',
      display     => Math::NumSeq::__('Distinct'),
      default     => 0,
      description => Math::NumSeq::__('Distinct x,y squares, meaning 2*(x^2) is excluded.'),
    },
    {
     name        => 'including_zero',
     type        => 'boolean',
     display     => Math::NumSeq::__('Inc Zero'),
     default     => 0,
     description => Math::NumSeq::__('Including x,y=0, so all plain squares x^2 are included (not just the Pythagorean ones like 5^2=3^2+4^2.'),
    },
  ];

sub values_min {
  my ($self) = @_;
  if ($self->{'distinct'}) {
    if ($self->{'including_zero'}) {
      return 1;
    } else {
      return 5;
    }
  } else {
    if ($self->{'including_zero'}) {
      return 0;
    } else {
      return 2;
    }
  }
}

#------------------------------------------------------------------------------
# cf A024507 x,y>0, x!=y
#    A024509 x^2+y^2 with repetitions for different ways each can occur
#    A025284 x^2+y^2 occurring in exactly one way
#    A001844 2n(n+1)+1 is those hypots with Y=H-1 in X^2+Y^2=H^2
#    A057653 odd numbers x^2+y^2
#    A057961 hypot count as radius increases
#    A014198 count x^2+y^2 <= n excluding 0,0

sub oeis_anum {
  my ($self) = @_;
  if ($self->{'distinct'}) {
    if ($self->{'including_zero'}) {
      return undef;
      # return 'A143575';  # x,y=0, x!=y
      # # OEIS-Catalogue: A143575 distinct=1 including_zero=1
    } else {
      return 'A004431'; # x,y!=0, and x!=y
      # OEIS-Catalogue: A004431 distinct=1
    }
  } else {
    if ($self->{'including_zero'}) {
      return 'A001481'; # x,y=0, x==y, so includes plain squares
      # OEIS-Catalogue: A001481 including_zero=1
    } else {
      return 'A000404'; # x,y!=0, x==y
      # OEIS-Catalogue: A000404
    }
  }
}

#------------------------------------------------------------------------------

sub rewind {
  my ($self) = @_;
  ### SumTwoSquares rewind() ...
  $self->{'i'} = $self->i_start;
  $self->{'prev_hypot'} = 1;
  if ($self->{'distinct'}) {
    if ($self->{'including_zero'}) {
      #                            y=0  y=1
      $self->{'y_next_x'}     = [   0,        ];
      $self->{'y_next_hypot'} = [   0*0+1*1,  ];
    } else {
      #                                  y=1      y=2
      $self->{'y_next_x'}     = [ undef,  2,       3 ];
      $self->{'y_next_hypot'} = [ undef,  2*2+1*1, 2*2+3*3 ];
    }
  } else {
    if ($self->{'including_zero'}) {
      #                            y=0  y=1
      $self->{'y_next_x'}     = [   0,        ];
      $self->{'y_next_hypot'} = [   0*0+0*0,  ];
      $self->{'prev_hypot'} = -1;
    } else {
      #                                  y=1       y=2
      $self->{'y_next_x'}     = [ undef,  1,        2 ];
      $self->{'y_next_hypot'} = [ undef,  1*1+1*1,  2*2+2*2 ];
    }
  }
  ### $self
}
sub next {
  my ($self) = @_;
  my $prev_hypot = $self->{'prev_hypot'};
  my $y_next_x = $self->{'y_next_x'};
  my $y_next_hypot = $self->{'y_next_hypot'};
  my $found_hypot = 4 * $prev_hypot + 4;
  ### $prev_hypot
  for (my $y = $self->{'including_zero'} ? 0 : 1; $y < @$y_next_x; $y++) {
    my $h = $y_next_hypot->[$y];
    ### consider y: $y
    ### $h
    if ($h <= $prev_hypot) {
      if ($y == $#$y_next_x) {
        my $next_y = $y + 1;
        ### extend to: $next_y
        my $x = ($self->{'distinct'} ? $next_y+1 : $next_y);
        push @$y_next_x, $x;
        push @$y_next_hypot, $x*$x + $next_y*$next_y;
        ### $y_next_x
        ### $y_next_hypot
        ### assert: $y_next_hypot->[$next_y] == $next_y*$next_y + $y_next_x->[$next_y]*$y_next_x->[$next_y]
      }
      do {
        $h = $y_next_hypot->[$y] += 2*($y_next_x->[$y]++)+1;
        ### step y: $y
        ### next x: $y_next_x->[$y]
        ### next hypot: $y_next_hypot->[$y]
        ### assert: $y_next_hypot->[$y] == $y*$y + $y_next_x->[$y]*$y_next_x->[$y]
      } while ($h <= $prev_hypot);
    }
    ### $h
    if ($h < $found_hypot) {
      ### lower hypot: $y
      $found_hypot = $h;
    }
  }
  $self->{'prev_hypot'} = $found_hypot;
  ### return: $self->{'i'}, $found_hypot
  return ($self->{'i'}++, $found_hypot);
}

sub pred {
  my ($self, $value) = @_;
  if (_is_infinite($value) || $value != int($value)) {
    return 0;  # don't loop forever if $value is +infinity
  }

  if ($self->{'including_zero'}) {
    my $sqrt = int(sqrt($value));
    if ($value == $sqrt*$sqrt) {
      return 1;
    }
  }

  my $limit = int(sqrt(2*$value)/2);
  for (my $x = 1; $x <= $limit; $x++) {
    my $y = int(sqrt($value - $x*$x));
    if ($x*$x + $y*$y == $value) {
      unless ($self->{'distinct'} && $y == $x) {
        return 1;
      }
    }
  }
  return 0;
}

1;
__END__