function x=lincon(a,b,n)
%System of linear congruences
%   X = LINCON(A,B,N) solves the system of linear congruences
%
%       A(1) * X == B(1)  (mod N(1))
%       A(2) * X == B(2)  (mod N(2))
%       ...
%       A(m) * X == B(m)  (mod N(m))
%
%   The solution, X, will be given as a vector [x1 x2] 
%   representing the general solution 
%
%              X == x1  (mod x2)
%  
%   Any specific answer can be found by letting
%   X = x1 + x2 * k  for any integer value of k.
%
%   If no solution exists [NaN NaN] will be returned.
%
%   A scalar input functions as a constant vector of the
%   same size as the other inputs.
%
%   Program is compatible with Variable Precision Integer
%   Arithmetic Toolbox available on File Exchange (#22725)
%   Use of VPI is recommended for large magnitude inputs 
%   or outputs. If VPI is not used and internal calculations
%   exceed largest consecutive flint a warning will be given
%   that results may be inaccurate, along with [NaN NaN].
%
%   Example #1:
%   Solve the following system of linear congruences
%      2x == 2  (mod 6)
%      3x == 2  (mod 7)
%      2x == 4  (mod 8)
%
%   Solution:
%      a=[2 3 2]; b=[2 2 4]; n=[6 7 8];
%      x=lincon(a,b,n)
%
%   Verify:
%      isequal( mod(a*x(1),n) , b)
%
%
%   Example #2:
%   Use of VPI for large magnitude numbers. Solve the following
%   system of linear congruences
%         (1234567)x == 89       (mod 2^32)
%         (9876543)x == 21       (mod 3^50)
%            (5555)x == 62830211 (mod 7^10)
%
%   Solution:
%     a=[1234567 9876543 5555]; b=[89 21 62830211]; 
%     n=[vpi(2)^32 vpi(3)^50 vpi(7)^10];
%     x=lincon(a,b,n)
%
%   Verify:
%     isequal( mod(a*x(1),n) , b)
%

%   Mike Sheppard
%   Last Modified 11-Sep-2011


%Check Inputs
[a,b,n,isvpi]=LinConCheckInputs(a,b,n);

%Initialize
b=mod(b,n);  % Make all positive for consistency
m=length(a); % Number of congruences
x=[0 1];     % Initialize to x==0  (mod 1)
k=0;         % line number

while k<m
  
  k=k+1;
  
  % Go line by line, using previous solutions of X
  % as starting points for each line
  
  % Original k'th Equation, shown as scalar valued:
  %    a * X == b  (mod n)
  % However, X == x1  (mod x2), substituting in for X
  %    a * (x1 + x2*V) == b  (mod n)
  % Simplifying
  %    a * x2 * V == (b - a*x1)  (mod n)
  % Relabel, and now solve for V for the modified equation
  %    ak * V == bk  (mod n)
  
  ak=a(k)*x(2);
  bk=mod(b(k)-a(k)*x(1),n(k)); %Positive for consistency
  nk=n(k);
  
  % Use Linear congruence theorem
  % Solving ak * V = bk  (mod n) for V

  if isvpi
      [gk,ck,dk]=vpigcd2(ak,nk); %Subfunction below
  else
      [gk,ck,dk]=gcd(ak,nk); 
  end
  
  % GK = CK*[AK] + DK*[NK]
  % (Extended Euclidean algorithm)

  
  if mod(bk,gk)==0 % Solution exists
      % V == v1  (mod v2)
      v2=nk/gk; 
      v1=mod(ck*bk/gk,v2); %Positive for consistency
      v=[v1 v2];
  else % No valid solution
      v=[NaN NaN]; 
      k=Inf; %Terminate loop
  end

  % Solution to modified equation is
  %    V == v1  (mod v2)
  % From before, X is given by
  %    X = x1 + x2*V
  % Substituting in for V yields
  %    X == (x1+x2*v1) (mod x2*v2)
  % Redefine new solution for X for use in next iteration
  
  if isfinite(k)
      x=[x(1)+x(2)*v(1) x(2)*v(2)];
  else
      x=[NaN NaN]; %No valid solution
  end

  
end

% Solution is found
%    X == x1  (mod x2)

end



%SUBFUNCTIONS
%------------
function [a,b,n,isvpi]=LinConCheckInputs(a,b,n)

%Make column vectors
a=a(:); b=b(:); n=n(:);

%Check to see if all are integer valued
if any(a~=round(a)) || any(b~=round(b)) || any(n~=round(n))
   error('LINCON : Input vectors must be same size')
end

%Check sizes, they can either be scalar or same-sized vectors
na=length(a); nb=length(b); nn=length(n); m=max([na nb nn]);
if ~all([na nb nn]==m | [na nb nn]==1)
   error('LINCON : Non-scalar arguments must match in size.')  
end

%If scalar, redefine as vectors of same size as other inputs
if na==1, a=a*ones(m,1); end
if nb==1, b=b*ones(m,1); end
if nn==1, n=n*ones(m,1); end

%Check to see if using VPI (Variable Precision Integers)
%Toolbox is available here:
%http://www.mathworks.com/matlabcentral/fileexchange/22725
isvpi=any(strcmp({class(a) class(b) class(n)},'vpi'));

end


function [g,c,d] = vpigcd2(a,b)
% Built-in GCD code given in VPI Toolbox solves
% for just the GCD; however, the coefficients for
% G = C*[A] + D*[B]
% are needed. This subfunction replaces the
% built-in GCD algorithm in VPI with one that
% uses the Extended Euclidean Algorithm
% (Simplified version of Matlab's built-in GCD)

c = 0; d = 0; g = 0;
u = [1 0 abs(a)];
v = [0 1 abs(b)];

while v(3)~=0
    q = quotient(u(3),v(3));
    t = u - v*q;
    u = v;
    v = t;
end
 
c = u(1) * sign(a);
d = u(2) * sign(b);
g = u(3);

end