
(* LinearSystemSolver. by Manuel Kauers *)

LinearSystemSolver`Private`Version = "0.5 2007-11-01";

BeginPackage["LinearSystemSolver`", {"DiscreteMath`Combinatorica`", "Algebra`PolynomialPowerMod`", "NumberTheory`NumberTheoryFunctions`", "Algebra`Horner`"}]

LinSolve::usage = "";
LinSolveQ::usage = "";
LinSolveUniv::usage = "";

Begin["`Private`"]

PrintLevel = 0;

DebugPrint[u_, v__] := If[u <= PrintLevel, Print[v]];

(** ------------------------------------------------------------------- **)
(* Arithmetic *)

List2Poly[coeffs_List, var_] := coeffs . var^(Range[Length[coeffs]]-1);
Poly2List[p_, var_, deg_] := Module[ {l}, 
   l = CoefficientList[p, var]; 
   If[ Length[l] <= deg + 1, 
       Return[Join[l, Table[0, {deg + 1 - Length[l]}]]],
       Return[Take[l, deg + 1]]
   ]];

(**
 * polynomial division
 **)
QuoRem[p_, q_, x_, m_] :=
  Module[ {p0, q0, quo, c, i, j},

    p0 = CoefficientList[p, x]; q0 = CoefficientList[q, x]; quo = {};

    If[ Length[p0] < Length[q0], Return[ {0, p} ] ];

    While[ Length[p0] >= Length[q0],

      c = PolynomialMod[Last[p0]/Last[q0], m];
      PrependTo[quo, c];

      j = Length[p0] - Length[q0] + 1;
      Do[ p0[[j++]] = p0[[j]] - c q0[[i]], {i, 1, Length[q0]} ]; 
      p0 = Most[p0];
    ];

    Return[ {List2Poly[quo, x], List2Poly[p0, x]} ]
  ];

MyPolynomialQuotient[p_, q_, x_, m_] := First[QuoRem[p, q, x, m]];
MyPolynomialRemainder[p_, q_, x_, m_] := Last[QuoRem[p, q, x, m]];

(** ------------------------------------------------------------------- **)
(* Reconstruction *)

(**
 * Reconstruction of rational numbers
 **)
ReconstructQ[n_, pq_] := (#[[1]]/#[[2]])&[First[LatticeReduce[{{n, 1}, {pq, 0}}]]];

ReconstructQ0[n_, pq_] := If[ n===0, 0, (((#[[2,2]]/#[[1,2,2]])&)[Internal`HGCD[pq, n]]*2)/2 ];

(**
 * Reconstruction of rational functions
 **)
Do[T[i]=0,{i,1,6}];
ReconstructRatfun[g_, mod_, x_, p_] := 
  Module[ {t, r},
    {t, r} = Timing[MyReconstructRatfun[g, mod, x, p]];
    T[1] += t;
    Return[r];    
  ];

MyReconstructRatfun[g_, mod_, x_, p_] :=
  Module[ {t, f, r0, r1, t0, t1, n, d, q, c, df, drat, dr1, dt1},

    c = PolynomialMod[#, p]&;
    If[ c[g] === 0, Return[ 0 ] ];

    f = If[ ListQ[mod], Product[x - mod[[i]], {i, 1, Length[mod]}], mod ];
    df = Exponent[f, x];
    {r0, r1, t0, t1} = {f, g, 0, 1}; {n, d} = {r1, t1};
    drat = Exponent[n, x] + Exponent[d, x];

    While[ r1 =!= 0,

      t = First[Timing[
      dr1 = Exponent[r1, x]; dt1 = Exponent[t1, x];
      If[ dr1 < df && drat > dr1 + dt1,
        {n, d} = {r1, t1}; drat = dr1 + dt1;
      ]; ]];
      T[5]+=t;

      {t, q} = Timing[MyPolynomialQuotient[r0, r1, x, p]];
      T[2]+=t;
      {t, {r0, r1, t0, t1}} = Timing[{r1, c[r0 - q r1], t1, c[t0 - q t1]}];
      T[4]+=t;
    ];
    
    {t, {n, d}} = Timing[CoefficientList[#, x]& /@ {n, d}];
    T[3]+=t;
    {t, {n, d}} = Timing[Map[c, {n,d}/Last[d], {2}]];
    T[6]+=t;
    {n, d} = List2Poly[#, x]& /@ {n, d};

    Return[n/d];
  ];

(** ------------------------------------------------------------------- **)

(* linear solver for algebraic number fields; coeffs must be in alpha *)
LinSolve[mat_, alpha_Symbol, minpoly_, opts:((_Rule|_RuleDelayed)...)] :=
  Module[ {p, deg, point, sol, lastP, lastSol, mod, lift, i, j, M},

    DebugPrint[0, "algebraic solver."];

    If[ Variables[minpoly] =!= {alpha}, Throw["illegal minpoly specification."] ];
    If[ !MemberQ[{{alpha}, {}}, Variables[mat]], Throw["matrix contains illegal symbols."] ];

    deg = Exponent[minpoly, alpha];
    p = 2^28; point = {};
    M = Cancel[#*PolynomialLCM@@(Denominator/@#)]& /@ Together[mat];
    M = Map[Horner[PolynomialRemainder[#, minpoly, alpha]]&, M, {2}];

    lastP = 0; lastSol = {};

    While[True,

      (* find a modulus where minpoly is square free and factorizes into linear factors *)
      While[ Head[point] =!= Times || Length[point] < deg, 
        point = PolynomialMod[minpoly/Coefficient[minpoly, alpha, deg], (p=PreviousPrime[p])];
        point = Factor[point, Modulus -> p];
      ];
      DebugPrint[1, "modulus ", p, " "];
      point = alpha - List@@point;
      mod = {#}& /@ Thread[alpha -> point];

      (* solve in homomorphic image *)
      sol = NullSpace[#, Modulus -> p]& /@ (M /. mod); (* (Hom[M, p, #]& /@ mod); (this is slower) *)
      If[ Min[Length /@ sol] == 0, Return[ {} ]]; (* no solution *)

      (* interpolate *)
      sol = Map[InterpolatingPolynomial[Transpose[{point, #}], alpha, Modulus -> p]&, 
                 Transpose[sol, {3, 1, 2}], {2}];

      (* check for degenerate cases; covers also special handling of first iteration *)
      If[ Length[sol] > Length[lastSol], lastSol = sol; lastP = p; Continue[] ];
      If[ Length[sol] < Length[lastSol], Continue[] ];

      (* combine with previous solution *)
      sol = Apply[Sum[ChineseRemainder[{Coefficient[#1, alpha, i], Coefficient[#2, alpha, i]}, 
                                       {lastP, p}]alpha^i, {i, 0, deg - 1}]&, 
                  Transpose[{lastSol, sol}, {3, 1, 2}], {2}];      

      lastP *= p; lastSol = sol;      

      (* lift and check for termination *)
      lift = {};
      Do[
        AppendTo[lift, Sum[ReconstructQ[Coefficient[#, alpha, j], lastP]alpha^j, {j, 0, deg-1}]& /@ sol[[i]]];
        If[ !MatchQ[Expand[PolynomialRemainder[#, minpoly, alpha]]& /@ (M . Last[lift]), {0...} ],
          lift = {}; Break[] (* not a basis *)
        ];
      , {i, 1, Length[sol]}];

      If[ Length[lift] == Length[sol], Return[lift] ];
    ];
  ];

(* linear solver for Q via chinese remaindering *)
LinSolveQ[mat_] := Module[ {p, P, A, M, sol, lastSol, v, i, k},

    DebugPrint[0, "Q."];

    A = (#/Max[1,GCD@@#])& /@ ((#*LCM@@(Denominator/@#))& /@ mat); 

    p = PreviousPrime[Developer`$MaxMachineInteger];
    M = PolynomialMod[A, p];
    lastSol = {};

    {t0,t1,t2,t3} = {0,0,0,0};

    While[True, 

       DebugPrint[1, p];

       (* next hom. image *)
       t0 += First[Timing[sol = NullSpace[M, Modulus -> p]]];

       Which[ (* initialization and handling of degenerate cases *)
         sol === {}, Return[sol], (* no solution *)
         lastSol === {} || Length[sol] < Length[lastSol], (* init. or all prev. primes unlucky *)
           lastSol = sol; P = p; p = PreviousPrime[p]; M = PolynomialMod[A, p]; Continue[],
         Length[sol] > Length[lastSol], (* this prime unlucky *)
           p = PreviousPrime[p]; M = PolynomialMod[A, p]; Continue[]
       ];

       (* combination *)
       t1 += First[Timing[
       lastSol = Apply[ChineseRemainder[{##}, {P, p}]&, Transpose[{lastSol, sol}, {3, 1, 2}], {2}];
       ]];
       P *= p;

       (* reconstruct & check *)
       sol = {}; p = PreviousPrime[p]; M = PolynomialMod[A, p];
       t2 += First[Timing[
       Do[
         v = ReconstructQ0[#, P]& /@ lastSol[[i]];
         If[ !MatchQ[PolynomialMod[M . PolynomialMod[v, p], p], {0..}], sol = {}; Break[] ];
         AppendTo[sol, v];
       , {i, 1, Length[lastSol]}];       
       ]];

       If[ Length[sol] > 0, DebugPrint[1, {t0,t1,t2}/Second]; Return[sol] ]; (* done. *)
    ];
];

(* linear solver for Q using hensel lifting *)
LinSolveHensel[mat_] :=
  Module[ {rank, regularCols, pi, piInv, A, At, A0, A0t, B, p, i, s, X},

     p = PreviousPrime[Developer`$MaxMachineInteger];
     A = (#/Max[1,GCD@@#])& /@ ((#*LCM@@(Denominator/@#))& /@ mat);
     At = Transpose[A];
     A0 = DeleteCases[RowReduce[A, Modulus -> p], {0...}];
     A0t = Transpose[A0];
     rank = Length[A0];
     If[ rank === Length[At], Return[ {} ] ]; (* no solution *)
     (* move regular columns to front *)
     regularCols = Position[A0t, {0..., 1, 0...}];
     pi = Join[regularCols, Complement[{#}& /@ Range[Length[A0t]], regularCols]];
     piInv = Table[0, {Length[pi]}]; Do[piInv[[First[pi[[i]]]]]={i}, {i, 1, Length[pi]}];
     A = Transpose[Extract[At, pi]];
     (* find a row subset with maximal rank *)
     Do[
       If[ MatrixRank[Extract[A, s = Transpose[Subsets[Range[Length[A]], {rank}, {i}]]], Modulus -> p] === rank,
         A = Extract[A, s]; Break[]
       ];
     , {i, 1, Binomial[Length[A], rank]}];
     (* call solver *)
     X = LinSolveHensel[Take[#, rank]& /@ A, Drop[#, rank]& /@ -A];
     Return[Transpose[Extract[Join[X, IdentityMatrix[Length[A0t] - rank]], piInv]]];
  ];

(* hensel solver with linear convergence for inhomogeneous systems over Z *)
LinSolveHensel[A_, B_] := (* A must be a regular square matrix *)
  Module[ {pk, p, Ainv, Aq, Bq, mp, mq, X, k, u},

     DebugPrint[0, "HenselZ."];
     pk = p = PreviousPrime[Developer`$MaxMachineInteger]; mp[x_] := PolynomialMod[x, p];
     q = PreviousPrime[p]; mq[x_] := PolynomialMod[x, q]; Aq = mq[A]; Bq = mq[B];

     Ainv = Inverse[A, Modulus -> p];
     u = LinearSolve[A, B, Modulus -> p]; k = 0;

     While[True,

        u -= pk mp[Ainv.Internal`ExactQuotient[A.u - B, pk]]; pk *= p; k++; (* lift *)
        X = Map[ReconstructQ0[#, pk]&, u, {2}]; (* reconstruct *)
        If[ MatchQ[mq[Aq.mq[X] - Bq], {{0...}...}], Break[] ]; (* check *)
     ];

     Return[X];
  ];

(* special solver for univariate polynomials as matrix entries *)
LinSolveUniv[mat_, mod_, x_] :=
  Module[ {p, q, mymat, myTimes, myPlus, myPower, d,
           sol, lastSol, roots, x0, n, m, u, v, i, j, M, Mmat, deg, heads, tails, ker},

    p = If[ mod == 0, (* prime seed *)
            Developer`$MaxMachineInteger, 
            Floor[Developer`$MaxMachineInteger/2] 
        ]; 

    mymat = mat;

    If[mod === 0, DebugPrint[0, "univ."]];

    lastSol = {}; roots = {}; M = 1;

    While[True,

       p = PreviousPrime[p];
       DebugPrint[If[mod === 0, 1, 2], {mod, p}];

       sol = If[mod === 0, 
               LinSolveUniv[mymat, p, x], 
               NullSpace[mymat /. x -> p, Modulus -> mod]
             ];

       Which[ (* degenerate cases and initialization *)
         mod === 0 && Length[roots] == 0,
           Null, (* nothing *)
         Length[sol] < Length[lastSol] || Length[roots] == 0,
           lastSol = sol; roots = {p}; M = If[mod === 0, p, x - p]; Continue[],
         Length[sol] > Length[lastSol],
           Continue[]
       ];

       If[ mod === 0,
         (* chinese remaindering *)
         If[ Length[roots] == 0,
           lastSol = sol
         ,
           {n, m} = Dimensions[sol];
           lastSol = Table[
                {u, v} = #[[i, j]]& /@ {lastSol, sol}; 
                {u, v} = Poly2List[#, x, Max[1, Exponent[u, x], Exponent[v, x]]]& /@ {u, v};
               (ChineseRemainder[#, {M, p}]& /@ Transpose[{u, v}])
                 . x^Range[0, Length[u] - 1]
             , {i, 1, n}, {j, 1, m}]
         ]
       ,
         (* interpolation step *)
         lastSol = Expand[lastSol + (sol - (lastSol /. x -> p))*(M/Times@@(p-roots)), Modulus -> mod];
       ];
       AppendTo[roots, p];
       If[ mod === 0,
         M *= p,
         M = Expand[M*(x-p), Modulus -> mod]
       ];

       (* reconstruction *)
       (* If[ OddQ[Length[roots]], Continue[] ]; *)
       If[ mod != 0 && Mod[Length[roots], 15] != 0, Continue[] ]; (** ************************** **)

       sol = {};
       Do[
         v = lastSol[[i]];
         AppendTo[sol, 
           If[ mod === 0,
             Sum[ReconstructQ[Coefficient[#, x, j], M]*x^j, {j, 0, Max[1, Exponent[#, x]]}]& /@ v
           ,
             (* ReconstructRatfunVector[v, mod, x, M, Floor[Length[roots]/2]] *)
             v = ReconstructRatfun[#, M, x, mod]& /@ v;
             Cancel[v * PolynomialLCM@@Append[Denominator /@ v, Modulus -> mod], Modulus -> mod]
           ]];
         v = Last[sol];
         If[ MatchQ[v, {$Failed...}], sol = {}; Break[] ];         
         p = PreviousPrime[p];
         q = If[ mod > 0, mod, PreviousPrime[p] ];
         If[ !MatchQ[
               PolynomialMod[PolynomialMod[mymat /. x -> p, q] . PolynomialMod[v /. x -> p, q], q],
               {0...}],
           sol = {}; Break[];
         ];
       , {i, 1, Length[lastSol]}];

       If[ Length[sol] > 0, Return[sol] ];
    ];    
  ];

End[]

EndPackage[]


