
(* Guess. by Manuel Kauers *)

<< LinearSystemSolver.m

Guess`Private`Version = "0.4 2007-10-16"; 

BeginPackage["Guess`", {"DiscreteMath`", "Algebra`SymmetricPolynomials`", 
                        "NumberTheory`NumberTheoryFunctions`", "NumberTheory`AlgebraicNumberFields`",
                        "Algebra`Horner`", "LinearSystemSolver`"}];

SetAttributes[Algebra`Horner, Listable];

`OperatorGCD::usage = "OperatorGCD[rec1, rec2, f[n]] computes the greatest common right divisor of " <>
   "the two given univariate recurrence operators";
`Padd::usage = "Padd[data, u] transforms something like {{1,2},{1,2,3,4,5}} into something like " <>
   "{{1,2,u,u,u},{1,2,3,4,5}}";
`GuessMultRE::usage = "GuessMultRE[data, strset, vars, deg] computes a vector space basis of the space " <>
   "of all recurrences satisfied by the array data.\n\n" <>
   "data... a d-dimensional rectangular array of numbers or rational functions\n" <>
   "strset... a list of expressions of the form f[lc,lc,...] with f being a symbol and " <>
   "the lc being integer-linear combinations of the variables (see below)\n" <>
   "vars... a list of variables, e.g., {n,m,k}, or a list of blocks, e.g., {{n,m},{k}}, indicating " <>
   "that the polynomial coefficients in the recurrence equations should be symmetric wrt. all the " <>
   "variables belonging to the same block. {n,m,k} is equivalent to {{n},{m},{k}}.\n" <>
   "deg... total degree bound for the polynomial coefficients in the recurrence equations, or " <>
   "a list of individual degree bounds for each block.\n\n" <>
   "Options:\n" <>
   "Modulus -> prime number used for solution shape prediction. (0 to skip this step.)\n" <>
   "AdditionalEquations -> number of extra equations to be used, Infinity to take all data.\n" <>
   "StartPoint -> point corresponding to data[[1,1,1...]], default {0,0,0,...}\n" <>
   "Extension -> minimal polynomial of a single parameter, or {} if there is not algebraic extension.\n" <>
   "Except -> list of terms, e.g., n^3 m f[n+2, m+1], that must not occur in the recurrences.\n" <>
   "MustHaveOneOf -> list of terms, e.g., n^3 m f[n+2,m+1], of which at least one must occur in " <>
   "the recurrences, default All\n" <>
   "Factor -> True or False depending on whether coefficients of resulting recurrences should be factored.";

{AdditionalEquations, StartPoint, Except, MustHaveOneOf};

Begin["`Private`"]

(**
 * INPUT: two P-finite recurrence operators
 * OUTPUT: their greatest common right divisor
 **)
OperatorGCD[rec1_, rec2_, f_[n_], opts___] :=
  Module[ {a, b, d, i},
  
    a = rec1 /. n -> n - Min[Cases[{rec1}, f[n + i_.] -> i, Infinity]];
    If[ OperatorOrd[a, f[n]] === 0, Return[ f[n] ]];
    a = Together[#, opts]& /@ Collect[a, f[_]];
    d = PolynomialLCM[#, opts]&@@(Denominator /@ a);
    a = Expand[Cancel[#*d, opts], opts]& /@ a;

    b = rec2 /. n -> n - Min[Cases[{rec2}, f[n + i_.] -> i, Infinity]];
    If[ OperatorOrd[b, f[n]] === 0, Return[ f[n] ]];
    b = Together[#, opts]& /@ Collect[b, f[_]];
    d = PolynomialLCM[#, opts]&@@(Denominator /@ b);
    b = Expand[Cancel[#*d, opts], opts]& /@ b;

    Return[Block[{$RecursionLimit=10^5}, OperatorEuclid[a, b, f[n], opts]]];
  ];
OperatorOrd[a_, f_[n_]] := Max[Cases[{a}, f[n+i_.] -> i, Infinity]];
OperatorEuclid[a_, b_, f_[n_], opts___] := (* brutal euclid *)
  Module[ {da, db, lta, ltb, lcm, c, dc, g, i},

    If[ b === 0, Return[ a ] ];

    da = OperatorOrd[a, f[n]]; db = OperatorOrd[b, f[n]]; 
    If[ da < db, Return[OperatorEuclid[b, a, f[n], opts]] ];
    Print[ {da, db} ];

    lta = Coefficient[a, f[n + da]]; ltb = Coefficient[b, f[n + db]] /. n -> n + da - db;
    lcm = PolynomialLCM[lta, ltb, opts];
    lta = Cancel[lcm/lta, opts]; ltb = Cancel[lcm/ltb, opts];
    c = Sum[Expand[lta*Coefficient[a, f[n+i]] 
                 - ltb*(Coefficient[b, f[n+i-da+db]] /. n -> n + da - db), opts]f[n+i], {i, 0, da}];

    dc = OperatorOrd[c, f[n]];

    Which[
      dc === -Infinity, Return[b],
      dc === 0, Return[f[n]]
    ];

    g = (List@@c) /. f[_] -> 1;
    g = Fold[PolynomialGCD[#1, #2, opts]&, First[g], Rest[g]];
    c = Sum[Cancel[Coefficient[c, f[n+i]]/g, opts]*f[n+i], {i, 0, dc}];
    Return[OperatorEuclid[c, b, f[n], opts]];
  ];

(**
 * Padd a nested list to a full array
 *
 * INPUT: something like {{1,2},{1,2,3,4,5}}
 * OUTPUT: something like {{1,2,u,u,u},{1,2,3,4,5}}
 **)
Padd[data_, u_] := 
  Module[ {dpt, n},
    dpt = Depth[data /. (a_ /; FreeQ[a, List]) -> 1] - 2;
    n = Max[Map[Length, data, {dpt}]];    
    Map[Join[#, Table[u, {n - Length[#]}]]&, data, {dpt}](*;*)
  ];

std2ff[list_] := 
  Module[ {i, j}, 
    Transpose[Table[StirlingS2[i, j], {i, 0, Length[list] - 1}, {j, 0, Length[list] - 1}]] . list
  ];

(* ************************************ multivariate guesser **************************************** *)

(**
 * Multivariate guessing function
 *
 * GIVEN:
 * -> a d-dimensional array data
 * -> a list of variables 
 *    variables may be grouped for indicating symmetry in the data.
 *    then only coefficients with these symmetries will be searched for.
 * -> a structure set sset of the form {f[lc,lc,lc,lc], ...}
 *    with each lc being an integer linear combination of variables 
 *    (f being a fixed symbol)
 * -> a total degree bound deg
 *
 * FIND:
 * -> a linear combination of the structure set elements with polynomial
 *    coefficients that vanishes for the given data
 * 
 **)
Options[GuessMultRE] = {Modulus -> PreviousPrime[2^28], (* 0 to skip hom. precomputation *) 
  Return -> "sol", (* undocumented option for returning the determinant of the system "det" 
                      or the system "sys" itself instead of the solution space "sol", 
                      or "null" to just make a prediction and then return Null *)
  AdditionalEquations -> 100, StartPoint -> Automatic, Extension -> {}, Except -> {},
  MustHaveOneOf -> All, Factor -> True, Sort -> (OrderedQ[{#1,#2}]&)};
GuessMultRE[data_List, sset_, vars_List, deg_, opts:(_Rule|_RuleDelayed)...] :=
  Module[ {i, j, k, mod, dim, hsset, myvars, f, args, terms, sys, sol, p, dom, ret, 
           forced, termorder, 
           actualTerms, e, spols, params, addEqs, blocks, null, ext, except, points},

    {p, addEqs, null, ext, except, ret, fac, forced, termorder} = 
         {Modulus, AdditionalEquations, StartPoint, Extension, Except, Return, Factor, 
          MustHaveOneOf, Sort}     /. {opts} /. Options[GuessMultRE];
    If[ p!=0 && !PrimeQ[p], Throw["Modulus is not a prime!"]];
    If[ p==0 && forced =!= All,
       Throw["modular computation cannot be skipped when there are forced terms."]
    ];
    If[ Length[Complement[First /@ {opts}, First /@ Options[GuessMultRE]]] > 0,
       Throw["unknown options: " <> 
       ToString[Complement[First /@ {opts}, First /@ Options[GuessMultRE]]]];
    ];

    hsset = Complement[sset, {1}]; (* homogeneous terms of the structure set *)

    (* 1. check consistency of input *)
    If[ !MatchQ[Union[Length /@ hsset], {_Integer}],
      Throw["illegal structure set specification"]
    ];
    dim = First[Union[Length /@ hsset]];
    If[ null === Automatic, null = Table[0, {dim}] ];
    myvars = Union[Flatten[Apply[List, Map[Variables, hsset, {2}], 1]]];
    nvars = Length[myvars];

    If[ Sort[Flatten[vars]] =!= Sort[myvars], Throw["unexpected variable declaration"] ];
    myvars = Flatten[vars];
    blocks = If[ MatchQ[Head /@ vars, {List...}], vars, {#}& /@ vars ];

    If[ ListQ[deg] && Length[blocks] =!= Length[deg], 
      Throw["incompatible degree specification"]
    ];

    (* a. check that data is dim-dimensional *)
    If[ Length[Dimensions[data]] =!= dim,
      Throw["data is not a " <> ToString[dim] <> "-dimensional array"];
    ];
    If[ Length[null] =!= Length[myvars],
      Throw["dimensions of data and startpoint don't fit."];
    ];

    (* b. check that the elements of sset have the right form *)
    args = Flatten[Apply[List, hsset, 1]];
    If[ !MatchQ[hsset, {f_[__]..}] ||
        !MatchQ[args /. Thread[myvars -> 0], {_Integer...}] ||
        !(And@@(IntegerQ/@Flatten[Outer[Coefficient, args, myvars]])),

      Throw["illegal structure set specification"];
    ];
    f = hsset[[1, 0]]; (* function symbol in the structure set *)

    (* 2. ansatz *)
    (* a. terms for the polynomial coefficients *)
    terms = 
      If[ IntegerQ[deg],
        (* no symmetries specified *)
        (*
        Flatten[Table@@Join[
           {Times@@(vars^Array[i,Length[vars]])}, 
           Table[{i[j], 0, deg - Sum[i[k], {k, 1, j - 1}]}, {j, 1, nvars}]]],
        *)
        (* use symmetries in ansatz *)
        Flatten[Table@@Join[
           {f@@Thread[SymPolys[blocks, Array[i, Length[blocks]]]]},
           Table[{i[j], 0, deg - Sum[i[k], {k, 1, j - 1}]}, {j, 1, Length[blocks]}]]
           /. f[i__] :> Outer[Times, i]
        ]
      ,
        Flatten[Table@@Join[
           {f@@Thread[SymPolys[blocks, Array[i, Length[blocks]]]]},
           Table[{i[j], 0, deg[[j]]}, {j, 1, Length[blocks]}]]
           /. f[i__] :> Outer[Times, i]
        ]
      ];
       
    (* b. include the structure set *)
    terms = Flatten[Outer[Times, terms, sset]];
    terms = Complement[terms, except]; (* kick out unwanted terms *)
    If[ forced === All, forced = terms ];
    forced = Intersection[terms, forced]; 
    terms = Join[Sort[Complement[terms, forced], termorder], 
                 Sort[forced, termorder]]; (* put forced terms at the end *)
    Print[Length[terms], " terms"]; 

    (* now every term[[i]] has the form n^a k^b ... f[c n + d k + .., ..] *)

    (* 3. determine the domain of points for which all arguments in the
          structure set are within the range supplied in the data array *)
    dom = Table[0 <= hsset[[i,j]] < Length[Nest[First, data, j - 1]], {i, 1, Length[hsset]}, {j, 1, dim}];
    If[ vars=!=myvars,
      dom = Join[dom, Apply[LessEqual, vars, {1}]];
    ];
    dom = CylindricalDecomposition[Flatten[dom], myvars]; 
    If[ dom === False, Throw[ "insufficient input data." ]];
    dom = GenerateTuples[dom, myvars];
    Which[ 
      Count[data, 0, Infinity]/Times@@Dimensions[data] < 1/100,
        Null,
      Length[dom] < 125000,
        e[point__] := data[[Sequence@@({point}+1)]];
        dom = Select[dom, !MatchQ[Apply[e, hsset /. Thread[vars->#], {1}], {0..}]&],
      True,
        Print["Warning: There are many zeros in the given data."]
    ];
    If[ Length[dom] < Length[terms], Throw[ "insufficient input data." ]];
    
    (* 4. construct modular system *)
    params = Union@@(Variables /@ Flatten[data]);
    mod = Thread[params -> Table[Random[Integer, {0, 20}], {i, 1, Length[params]}]];
    ClearAll[e];
    e[point_][t_] := Module[ {j},
      PolynomialMod[t /. Thread[myvars -> point] 
         /. f[j__] :> data[[Sequence@@({j}+1)]]
         /. mod, p] // Return;
    ];

    k = Length[terms] + If[Head[addEqs]===List, First[addEqs], addEqs];
    points = If[ k > Length[dom],
                 Range[Length[dom]], 
                 First[Subsets[Range[Length[dom]], {k}, {Random[Integer, {1, Binomial[Length[dom], k]}]}]]
                ];
    Clear[k];

    If[ ext === {} && ret =!= "det" && p =!= 0, 

      sys = DeleteCases[Table[e[dom[[points[[i]]]]] /@ terms, {i, 1, Length[points]}], {0..}];

      (* 5. solve modular system *)
      Print["modular system: ", Length[sys], " eqns, ", Length[terms], " vars"];
      If[ Length[sys] < Length[terms], Throw["too many zeros."] ];

      sol = NullSpace[sys, Modulus -> p];
      sol = DeleteCases[sol, Prepend[Table[0, {Length[forced]}], ___]];
      If[ Length[sol] == 0, Print["no solution."]; Return[ {} ] ];
    
      Print[Length[sol], " solutions predicted."];
      If[ ret == "null", Return[ Null ] ];

      (* 6. construct refined system *)
      actualTerms = (Min[#, 1]& /@ Plus@@Map[If[#===0, 0, 1]&, sol, {2}]) * terms;
      (* actualTerms = Map[If[#===0,0,1]&, sol[[3]], {1}] * terms; (* SPECIAL *)  *)
      actualTerms = DeleteCases[actualTerms, 0];
    ,
      actualTerms = terms;
    ];

    Which[ 
      Count[data, 0, Infinity]/Times@@Dimensions[data] < 1/100,
        Null,
      Length[dom] < 125000,
        e[point__] := data[[Sequence@@({point}+1)]];
        hsset = Union[Cases[actualTerms, f[__], Infinity]];
        dom = Select[dom, !MatchQ[Apply[e, hsset /. Thread[vars->#], {1}], {0..}]&],
      True, 
        Print["Warning: There are many zeros in the given data."]
    ];
    k = Length[actualTerms] + If[Head[addEqs]===List, Last[addEqs], addEqs];
    points = If[ k > Length[dom],
                 Range[Length[dom]], 
                 First[Subsets[Range[Length[dom]], {k}, {Random[Integer, {1, Binomial[Length[dom], k]}]}]]
                ];
    Clear[k];
    ClearAll[e];
    e[point_][t_] := Module[ {j},
      t /. Thread[myvars -> point] /. f[j__] :> data[[Sequence@@({j}+1)]] // Return;
    ];
    sys = DeleteCases[Table[e[dom[[points[[i]]]]] /@ actualTerms, {i, 1, Length[points]}], {0..}];
    If[ Length[sys] < Length[actualTerms], Throw["too many zeros."] ];

    Which[ 
      ret == "det", 
        While[ Length[sys] > Length[First[sys]], sys = Delete[sys, Random[Integer, Length[sys]]] ];
        Return[Det[sys]],
      ret == "sys",
        Return[sys];
    ];

    (* 7. solve refined system *)
    Print["refined system: ", Length[sys], " eqns, ", Length[actualTerms], " vars"];

    sol = Which[ 
            ext =!= {}, LinearSystemSolver`LinSolve[sys, First[params], ext],
            Length[params] == 0, LinearSystemSolver`LinSolveQ[sys],
            Length[params] == 1, LinearSystemSolver`LinSolveUniv[sys, 0, First[params]],
            NameQ["RISCComb`LinSolve`M"], RISCComb`LinSolve`EANullSpace[sys],
            True, NullSpace[sys]
          ];

    sol = DeleteCases[sol, Prepend[Table[0, {Length[forced]}], ___]];
    Print[Length[sol], " solutions."]; 
    If[ Length[sol] == 0, Return[{}]];

    (* 8. done. *)    
    Return[(If[fac, Factor, Identity]/@ Collect[#, f[__]])& 
            /@ (sol . actualTerms /. Thread[myvars->myvars-null])
            /. f[j__] :> (f[j] /. Thread[myvars->myvars+null])
    ];
  ];

GenerateTuples[dom_, vars_] :=
  GenerateTuples[dom, vars] = (* caching *)
  Module[ {t, int, rest, i, f, a, b, c},

    If[ Length[vars] == 0, Return[{{}}] ];

    If[ Head[dom] === Or, Return[Union @@ (GenerateTuples[#, vars]& /@ dom)]];

    If[ Length[vars] == 1,
      If[ !MemberQ[{Less,LessEqual,Inequality,Equal}, Head[dom]],
        Throw["illegal domain description encountered: ", dom]
      ];
      int = dom;
      rest = True;
    ,
      If[ Head[dom] === And && !MemberQ[{Less,LessEqual,Inequality,Equal}, Head[First[dom]]],
        Throw["illegal domain description encountered: ", dom]
      ];
      int = First[dom];
      rest = Rest[dom];
    ];

    If[ Head[int] === Equal,
      int = Last[int] <= First[int] <= Last[int];
    ];

    If[ MemberQ[{Less, LessEqual}, Head[int]],
      int = Inequality[First[int], Head[int], int[[2]], Head[int], Last[int]];
    ];

    t = Range[Ceiling[First[int]], Floor[Last[int]]];
    If[IntegerQ[First[int]] && int[[2]] === Less, t = Rest[t]];
    If[IntegerQ[Last[int]] && int[[4]] === Less, t = Most[t]];

    Return[
      Union@@Table[
        Prepend[#, t[[i]]]& /@ GenerateTuples[rest /. First[vars] -> t[[i]], Rest[vars]]
      , {i, 1, Length[t]}]
    ];
  ];

(**
 * A basis for the vector space of all homogeneous symmetric polynomials of total degree d
 **)
SymPolys[v_List, d_Integer] := 
  Module[ {e, i, j, k, p},

    If[ d == 0, Return[{1}] ] ;

    e = Table[SymmetricPolynomial[v, i], {i, 1, Length[v]}];
    p = DiscreteMath`IntegerPartitions`IntegerPartitions[d, Min[d, Length[v]]];

    Product[e[[#[[i, 1]]]]^#[[i, 2]], {i, 1, Length[#]}]& /@ p
  ];

End[]

EndPackage[]

If[$Notebooks,
   CellPrint[Cell[#, "Print", FontColor -> RGBColor[0, 0, 0], 
                              CellFrame -> 0.5, 
                              Background -> RGBColor[0.796887, 0.789075, 0.871107]]]&,
   Print
  ]["Guess  Package by Manuel Kauers " <> 
    "\[LongDash] \[Copyright] RISC Linz \[LongDash] V " <> Guess`Private`Version];


