Code covered by the BSD License  

Highlights from
sumsqint

from sumsqint by Ben Petschel
Finds all distinct ways of writing a number as the sum of squares, i.e. solve x^2+y^2=n for 0<=x<=y.

y=sumsqint(n)
function y=sumsqint(n)
% SUMSQINT - solve x^2+y^2=n for non-negative integers x,y
%
% usage: y=sumsqint(n)
%
% SUMSQINT(N) returns an kx2 array Y where Y(i,:) is a pair of integer
% solutions [x,y] to x^2+y^2=N with 0<x<=y or [] if no solutions exist.
%
% SUMSQINT(F) where F is an array of positive prime integers returns the
% solutions to x^2+y^2=prod(F).  This is useful for solving equations such
% as x^2+y^2=z^2 where z is known, and extends the maximum feasible N up to
% about 2^100 (provided the prime factors are small enough).
%
% NOTE: for efficiency reasons, if factors of N are given they are assumed
% to be prime otherwise the algorithm might never terminate.
%
% Example:
%   sumsqint(1) % returns [0,1]
%   sumsqint(65) % returns [1,8;4,7]
%   sumsqint(repmat(factor(25e6),1,2)) % solves x^2+y^2=(25e6)^2
%   sumsqint([factor(25e6-1),factor(25e6+1)]) % solves x^2+y^2=(25e6)^2-1
%   sumsqint(vpi(5)^100)  % returns 51x2 vpi array (requires VPI toolbox)
%   sumsqint(vpi(65)^100) % returns 5101x2 vpi array
%
% Algorithm: based on gaussian integers: n = x^2+y^2 = (x+iy)(x-iy).
%
% Every gaussian integer has a unique factorization in terms of gaussian
% primes up to multiplication by units (1,-1,i,-i).  Every p+iq is a
% gaussian prime if and only if either p+iq=u*(4n+3) where u is a unit and
% 4n+3 is a prime integer, or p^2+q^2 is prime.
%
% First, find the integer factorization n=d*p1^k1*...&pm^km where d is 1 or
% a square product of primes that are 3 mod 4, and the pj are primes either
% 2 or 1 mod 4.  Each pj can be factored as (a+ib)(a-ib) for some a,b. This
% is found by table lookup if pj is small, or by brute force (suggestions
% for improvement are welcome!)
%
% Every solution (x,y), x+iy is the product of d and a combination of
% (a+ib) or (a-ib).  Multiplication by a unit does not produce a distinct
% solution, and the choice of (1+i) vs (1-i) does not affect the result (up
% to ordering and sign).
%
% This function is compatible with vpi integers in John D'Errico's Variable
% Precision Integer toolbox (see link on download page), provided the
% largest factor is not larger than intmax (2^31)

% Author:
%   Ben Petschel
% Change history:
%   20/7/2009: first release
%   13/8/2009: fixed bug that occurs when vpi prime factors are > 100
%   13/8/2009: added support for factored inputs
%   16/8/2009: changed list of guassian factors to sparse matrix;
%   16/8/2009: added internal option to remember newly found prime factors
%
% TODO: test quadratic residue method to see if there is a performance gain

persistent pmax pf % a list of small gaussian prime integers and their principal factors

addpf=true; % set addpf to false if you don't want to add newly found prime factors to the persistent list

if isempty(pf),
  p=[2,5,13,17,29,37,41,53,61,73,89,97];
  pmax=max(p);
  pf=sparse(pmax,2);
  pf(p,:)=[1,1;1,2;2,3;1,4;2,5;1,6;4,5;2,7;5,6;3,8;5,8;4,9];
  if any(nonzeros(sum(pf.^2,2))~=p(:)),
    error('check built-in list of prime factors');
  end;
end;

isvpi=isa(n,'vpi');
nmax=2^53;

if  numel(n)==1 && (iscell(n) || (n<0) || (~isvpi&&(n~=floor(n)||~isfinite(n)))),
  error('sumsqint:nonneginteger','n must be a non-negative integer');
  %y=[];
  %return;
elseif  numel(n)>1 && (iscell(n) || any(n<1) || any(n~=floor(n)) || (~isvpi&&any(n~=floor(n)|~isfinite(n)))),
  error('sumsqint:posintegerfactors','factors of n must be positive integers');
  %y=[];
  %return;
elseif numel(n)==1 && ~isvpi && n>=nmax,
  error('sumsqint:doubletoobig','Input n>=2^53 (too large to store exactly as a double).');
elseif numel(n)>1 && ~isvpi && prod(n)>=nmax^2
  error('sumsqint:factorstoobig','Input n>=(2^53)^2 (too large to store components exactly as a double).');
elseif numel(n)==1 && ~isvpi && n>intmax,
  try
    n=vpi(n);
    isvpi=true;
    warning('sumsqint:convertvpi','intmax < n < 2^53; converting to vpi');
  catch
    error('sumsqint:requirevpi','Require Variable Precision Integer toolbox for n>intmax');
    %y=[];
    %return;
  end;
elseif n==0,
  y=[0,0];
  return;
elseif n==1,
  y=[0,1];
  return;
end;

if numel(n)>1
  f=sort(n(n>1));
else
  f = factor(n);
end;
if isvpi,
  % convert to vpi for use with hist
  f=double(f);
  if any(f>=nmax),
    error('sumsqint:largefactor','Prime factors of n are >= 2^53 (too large for current algorithm)');
    %y=[];
    %return;
  end;
end;
fu=unique(f);
if numel(fu)==1
  fk=numel(f);
else
  fk=hist(f,fu);
end;
if isvpi,
  % convert back to vpi
  fu=vpi(fu);
end;

% check which factors are gaussian primes
frem3 = mod(fu,4)==3;
if any(mod(fk(frem3),2)),
  % gaussian prime factors must occur even number of times
  y=[]; % no solutions exist
  return;
else
  if ~any(frem3),
    xy=[0,1];
    if isvpi,
      xy=vpi(xy);
    end;
  else
    xy = [0,prod(fu(frem3).^(fk(frem3)/2))];
  end;

  fu = fu(~frem3);
  fk = fk(~frem3);

  for j=1:numel(fu),
    pj=fu(j);
    kj=fk(j);
    % factor pj as pjf*pjfc and determine all unique powers (pjf^m)*(pjfc^(kj-m))
    if pj==2,
      % powers of 2 yield only one possibility
      if mod(kj,2)==1,
        % sum of identical squares
        pjklist=pj^((kj-1)/2)*[1,1];
      else
        % sum of 0 and a square
        pjklist=[0,pj^(kj/2)];
      end;

    else
      if pj<=pmax && pf(pj,1)>0
        % pj has been factored
        pjf=pf(pj,:);
        if isvpi,
          pjf=vpi(pjf);
        end;
        pjfc=gaussconj(pjf);
      else
        % find a,b such that a^2+b^2=pj
        %
        % one method to consider in future versions:
        % Randomly choose x; half the time it will be a quadratic
        % non-residue.  If it is, have x^((p-1)/2) = -1 (legendre symbol),
        % and let y=x^((p-1)/4).  So y^2+1=(y+i)*(y-i) is a multiple of p,
        % and gcd(y+i,p) is a prime factor of p.
        %
        % for now just use brute force
        a=1;
        if isvpi,
          a=vpi(a);
        end;
        keepgoing=true;
        if isvpi
          while keepgoing,
            [b,rem]=sqrt(pj-a^2);
            if rem==0,
              keepgoing=false;
            else
              a=a+1;
            end;
          end;
        else
          while keepgoing,
            b=sqrt(pj-a^2);
            if b==floor(b),
              keepgoing=false;
            else
              a=a+1;
            end;
          end;
        end;
        pjf=[a,b];
        pjfc=[a,-b];
        if addpf
          % add [a,b] to list of gaussian factors
          pf(pj,:)=pjf;
          pmax=max(pmax,pj);
        end;
      end;
      % now multiply by all (pjf^m)*(pjfc^(k-m))
      pjfpow=gausspowall(pjf,kj);
      pjfcpow=gausspowall(pjfc,kj);
      pjklist=zeros(size(pjfpow));
      if isvpi,
        pjklist=vpi(pjklist);
      end;
      for ii=1:kj+1,
        pjklist(ii,:)=gaussprod(pjfpow(ii,:),pjfcpow(kj+2-ii,:));
      end;
    end;
    % have the list of all pjf^ii*pjf^(k-ii); now multiply it with all solutions so far
    nxy=size(xy,1);
    npjk=size(pjklist,1);
    xyold=xy;
    xy=zeros(nxy*npjk,2);
    if isvpi,
      xy=vpi(xy);
    end;
    for ii=1:nxy
      for jj=1:npjk
        % multiply xy(ii) by pjklist(jj)
        xy((ii-1)*npjk+jj,:)=gaussprod(xyold(ii,:),pjklist(jj,:));
      end;
    end;
    % now sort the unique solutions (check if there is some way of only
    % needing to do this once, while saving on memory)
    xy=sort(abs(xy),2);
    xy=unique(xy,'rows');
  end;
  y=xy;
end;

end % main function sumsqint(...)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y=gaussconj(ab)
% calculates conjugate of ab: [a,b]->[a,-b]

y=ab.*[1,-1];

end % helper function gaussconj(...)


% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% function y=gausspow(ab,n)
% % calculates ab^n where ab=(a+bi)
% %   use matrix multiplication: [c,d]x[a,b] = [c,d]*[a,b;-b,a];
% 
% if n==0,
%   y=[1,0];
% else
%   y=ab;
%   if n>1,
%     abmat=[ab;-ab(2),ab(1)];
%   end;
%   for k=2:n,
%     y=y*abmat;
%   end;
% end;
% 
% end % helper function gausspow(...)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y=gausspowall(ab,n)
% calculates all powers ab^0,ab,...,ab^n where ab=[a,b]=(a+bi)
%   use matrix multiplication: [c,d]x[a,b] = [c,d]*[a,b;-b,a];

y=repmat(ab,n+1,1); % saves having to do check if ab is vpi
if n>0,
  y(1,:)=[1,0];
  if n>1,
    abmat=[ab;-ab(2),ab(1)];
  end;
  for k=2:n,
    y(k+1,:)=y(k,:)*abmat;
  end;
end;

end % helper function gausspowall(...)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y=gaussprod(ab,cd)
% calculates [a,b]x[c,d]=[ac-bd,bc+ad];

% see if this can be used:
%y=crossproduct(ab,cd);
y=[ab(1)*cd(1)-ab(2)*cd(2),ab(2)*cd(1)+ab(1)*cd(2)];

end % helper function gaussprod(...)

Contact us at files@mathworks.com