)if false
\documentclass{article}
\usepackage{axiom, amsthm, amsmath, url}
\newtheorem{ToDo}{ToDo}[section]

\newcommand{\Axiom}{{\tt Axiom}}
\newcommand{\Rate}{{\tt Rate}}
\newcommand{\GFUN}{{\tt GFUN}}
\begin{document}
\title{mantepse.spad}
\author{Martin Rubey}
\maketitle
\begin{abstract}
  The packages defined in this file enable {\Axiom} to guess formulas for
  sequences of, for example, rational numbers or rational functions, given the
  first few terms.  It extends and complements Christian Krattenthaler's
  program \Rate\ and the relevant parts of Bruno Salvy and Paul Zimmermann's
  \GFUN.  An article describing this package can be found at
  \url{http://arxiv.org/abs/math.CO/0702086}.
\end{abstract}
\tableofcontents
\section{domain UFPS UnivariateFormalPowerSeries}
)endif

)abbrev domain UFPS UnivariateFormalPowerSeries
UnivariateFormalPowerSeries(Coef : Ring) ==
    UnivariateTaylorSeries(Coef, 'x, 0$Coef)


)abbrev package UFPS1 UnivariateFormalPowerSeriesFunctions
UnivariateFormalPowerSeriesFunctions(Coef : Ring) : Exports == Implementation
  where

    UFPS ==> UnivariateFormalPowerSeries Coef

    Exports == with

      hadamard : (UFPS, UFPS) -> UFPS

    Implementation == add

      hadamard(f, g) ==
        series map((z1 : Coef, z2 : Coef) : Coef +-> z1*z2, coefficients f, coefficients g)
                  $StreamFunctions3(Coef, Coef, Coef)

)abbrev package STNSR StreamTensor
StreamTensor(R : Type) : with

        tensorMap : (Stream R, R -> List R) -> Stream R
          ++ tensorMap([s1, s2, ...], f) returns the stream consisting of all
          ++ elements of f(s1) followed by all elements of f(s2) and so on.

    == add

        tensorMap(s, f) ==
            empty? s => empty()
            concat([f first s], delay tensorMap(rest s, f))

)if false
\section{domain GOPT GuessOption}
)endif

)abbrev domain GOPT GuessOption
++ Author: Martin Rubey
++ Description: GuessOption is a domain whose elements are various options used
++ by \spadtype{Guess}.
GuessOption() : Exports == Implementation where

  Exports == SetCategory with

    maxDerivative : Union(NonNegativeInteger, "arbitrary") -> %
      ++ maxDerivative(d) specifies the maximum derivative in an algebraic
      ++ differential equation.  This option is expressed in the form
      ++ \spad{maxDerivative == d}.

    maxShift : Union(NonNegativeInteger, "arbitrary") -> %
      ++ maxShift(d) specifies the maximum shift in a recurrence
      ++ equation.  This option is expressed in the form \spad{maxShift == d}.

    maxSubst : Union(PositiveInteger, "arbitrary") -> %
      ++ maxSubst(d) specifies the maximum degree of the monomial substituted
      ++ into the function we are looking for.  That is, if \spad{maxSubst ==
      ++ d}, we look for polynomials such that $p(f(x), f(x^2), ...,
      ++ f(x^d))=0$.  equation.  This option is expressed in the form
      ++ \spad{maxSubst == d}.

    maxPower : Union(PositiveInteger, "arbitrary") -> %
      ++ maxPower(d) specifies the maximum degree in an algebraic differential
      ++ equation. For example, the degree of (f'')^3 f' is 4. maxPower(-1)
      ++ specifies that the maximum exponent can be arbitrary. This option is
      ++ expressed in the form \spad{maxPower == d}.

    homogeneous : Union(PositiveInteger, Boolean) -> %
      ++ homogeneous(d) specifies whether we allow only homogeneous algebraic
      ++ differential equations.  This option is expressed in the form
      ++ \spad{homogeneous == d}.  If true, then maxPower must be
      ++ set, too, and ADEs with constant total degree are allowed.
      ++ If a PositiveInteger is given, only ADE's with this total degree are
      ++ allowed.

    Somos : Union(PositiveInteger, Boolean) -> %
      ++ Somos(d) specifies whether we want that the total degree of the
      ++ differential operators is constant, and equal to d, or maxDerivative
      ++ if true. If true, maxDerivative must be set, too.

    maxLevel : Union(NonNegativeInteger, "arbitrary") -> %
      ++ maxLevel(d) specifies the maximum number of recursion levels operators
      ++ guessProduct and guessSum will be applied. This option is expressed in
      ++ the form spad{maxLevel == d}.

    maxDegree : Union(NonNegativeInteger, "arbitrary") -> %
      ++ maxDegree(d) specifies the maximum degree of the coefficient
      ++ polynomials in an algebraic differential equation or a recursion with
      ++ polynomial coefficients. For rational functions with an exponential
      ++ term, \spad{maxDegree} bounds the degree of the denominator
      ++ polynomial.
      ++ This option is expressed in the form \spad{maxDegree == d}.

    maxMixedDegree : NonNegativeInteger -> %
      ++ maxMixedDegree(d) specifies the maximum q-degree of the coefficient
      ++ polynomials in a recurrence with polynomial coefficients, in the case
      ++ of mixed shifts.  Although slightly inconsistent, maxMixedDegree(0)
      ++ specifies that no mixed shifts are allowed. This option is expressed
      ++ in the form \spad{maxMixedDegree == d}.

    allDegrees : Boolean -> %
      ++ allDegrees(d) specifies whether all possibilities of the degree vector
      ++ - taking into account maxDegree - should be tried. This is mainly
      ++ interesting for rational interpolation. This option is expressed in
      ++ the form \spad{allDegrees == d}.

    safety : NonNegativeInteger -> %
      ++ safety(d) specifies the number of values reserved for testing any
      ++ solutions found. This option is expressed in the form \spad{safety ==
      ++ d}.

    check : Union("skip", "MonteCarlo", "deterministic") -> %
      ++ check(d) specifies how we want to check the solution.  If
      ++ the value is "skip", we return the solutions found by the
      ++ interpolation routine without checking.  If the value is
      ++ "MonteCarlo", we use a probabilistic check.  This option is
      ++ expressed in the form \spad{check == d}

    checkExtraValues : Boolean -> %
      ++ checkExtraValues(d) specifies whether we want to check the
      ++ solution beyond the order given by the degree bounds. This
      ++ option is expressed in the form \spad{checkExtraValues == d}

    one : Boolean -> %
      ++ one(d) specifies whether we are happy with one solution. This option
      ++ is expressed in the form \spad{one == d}.

    debug : Boolean -> %
      ++ debug(d) specifies whether we want additional output on the
      ++ progress. This option is expressed in the form \spad{debug == d}.

    functionName : Symbol -> %
      ++ functionName(d) specifies the name of the function given by the
      ++ algebraic differential equation or recurrence. This option is
      ++ expressed in the form \spad{functionName == d}.

    functionNames : List(Symbol) -> %
      ++ functionNames(d) specifies the names for the function in
      ++ algebraic dependence. This option is
      ++ expressed in the form \spad{functionNames == d}.

    variableName : Symbol -> %
      ++ variableName(d) specifies the variable used in by the algebraic
      ++ differential equation. This option is expressed in the form
      ++ \spad{variableName == d}.

    indexName : Symbol -> %
      ++ indexName(d) specifies the index variable used for the formulas. This
      ++ option is expressed in the form \spad{indexName == d}.

    displayKind : Symbol -> %
      ++ displayKind(d) specifies kind of the result: generating function,
      ++ recurrence or equation. This option should not be set by the
      ++ user, but rather by the HP-specification.

    option : (List %, Symbol) -> Union(Any, "failed")
      ++ option(l, option) returns which options are given.

  Implementation ==> add
    import from AnyFunctions1(Boolean)
    import from AnyFunctions1(Symbol)
    import from AnyFunctions1(NonNegativeInteger)
    import from AnyFunctions1(Union(NonNegativeInteger, "arbitrary"))
    import from AnyFunctions1(Union(PositiveInteger, "arbitrary"))
    import from AnyFunctions1(Union(PositiveInteger, Boolean))
    import from AnyFunctions1(Union("skip", "MonteCarlo", "deterministic"))

    Rep := Record(keyword : Symbol, value : Any)

    maxLevel d       == ['maxLevel,       d::Any]
    maxDerivative d  == ['maxDerivative,  d::Any]
    maxShift d       == maxDerivative d
    maxSubst d       ==
        if d case PositiveInteger
        then maxDerivative((d::Integer-1)::NonNegativeInteger)
        else maxDerivative d
    maxDegree d        == ['maxDegree,        d::Any]
    maxMixedDegree d   == ['maxMixedDegree,   d::Any]
    allDegrees d       == ['allDegrees,       d::Any]
    maxPower d         == ['maxPower,         d::Any]
    safety d           == ['safety,           d::Any]
    homogeneous d      == ['homogeneous,      d::Any]
    Somos d            == ['Somos,            d::Any]
    debug d            == ['debug,            d::Any]
    check d            == ['check,            d::Any]
    checkExtraValues d == ['checkExtraValues, d::Any]
    one d              == ['one,              d::Any]
    functionName d     == ['functionName,     d::Any]
    functionNames d ==
        ['functionNames, coerce(d)$AnyFunctions1(List(Symbol))]
    variableName d     == ['variableName,     d::Any]
    indexName d        == ['indexName,        d::Any]
    displayKind d      == ['displayKind,      d::Any]

    coerce(x : %) : OutputForm == x.keyword::OutputForm = x.value::OutputForm
    x : % = y : %              == x.keyword = y.keyword and x.value = y.value

    option(l, s) ==
      for x in l repeat
        x.keyword = s => return(x.value)
      "failed"


)abbrev package GOPT0 GuessOptionFunctions0
++ Author: Martin Rubey
++ Description: GuessOptionFunctions0 provides operations that extract the
++ values of options for \spadtype{Guess}.
GuessOptionFunctions0() : Exports == Implementation where

  LGOPT ==> List GuessOption

  Exports == SetCategory with

    maxDerivative : LGOPT -> Union(NonNegativeInteger, "arbitrary")
      ++ maxDerivative returns the specified maxDerivative.

    maxShift : LGOPT -> Union(NonNegativeInteger, "arbitrary")
      ++ maxShift returns the specified maxShift.

    maxSubst : LGOPT -> Union(PositiveInteger, "arbitrary")
      ++ maxSubst returns the specified maxSubst.

    maxPower : LGOPT -> Union(PositiveInteger, "arbitrary")
      ++ maxPower returns the specified maxPower.

    homogeneous : LGOPT -> Union(PositiveInteger, Boolean)
      ++ homogeneous returns whether we allow only homogeneous algebraic
      ++ differential equations, default being false

    Somos : LGOPT -> Union(PositiveInteger, Boolean)
      ++ Somos returns whether we allow only Somos-like operators, default
      ++ being false

    maxLevel : LGOPT -> Union(NonNegativeInteger, "arbitrary")
      ++ maxLevel returns the specified maxLevel.

    maxDegree : LGOPT -> Union(NonNegativeInteger, "arbitrary")
      ++ maxDegree returns the specified maxDegree.

    maxMixedDegree : LGOPT -> NonNegativeInteger
      ++ maxMixedDegree returns the specified maxMixedDegree.

    allDegrees : LGOPT -> Boolean
      ++ allDegrees returns whether all possibilities of the degree vector
      ++ should be tried, the default being false.

    safety : LGOPT -> NonNegativeInteger
      ++ safety returns the specified safety or 1 as default.

    check : LGOPT -> Union("skip", "MonteCarlo", "deterministic")
      ++ check(d) specifies how we want to check the solution.  If
      ++ the value is "skip", we return the solutions found by the
      ++ interpolation routine without checking.  If the value is
      ++ "MonteCarlo", we use a probabilistic check.  The default is
      ++ "deterministic".

    checkExtraValues : LGOPT -> Boolean
      ++ checkExtraValues(d) specifies whether we want to check the
      ++ solution beyond the order given by the degree bounds.  The
      ++ default is true.

    one : LGOPT -> Boolean
      ++ one returns whether we need only one solution, default being true.

    functionName : LGOPT -> Symbol
      ++ functionName returns the name of the function given by the algebraic
      ++ differential equation, default being f

    functionNames : LGOPT -> List(Symbol)
      ++ functionNames returns the names for the function in the algebraic
      ++ dependence, default being %f1, %f2, ...

    variableName : LGOPT -> Symbol
      ++ variableName returns the name of the variable used in by the
      ++ algebraic differential equation, default being x

    indexName : LGOPT -> Symbol
      ++ indexName returns the name of the index variable used for the
      ++ formulas, default being n

    displayKind : LGOPT -> Symbol
      ++ displayKind(d) specifies kind of the result: generating function,
      ++ recurrence or equation. This option should not be set by the
      ++ user, but rather by the HP-specification.

    debug : LGOPT -> Boolean
      ++ debug returns whether we want additional output on the progress,
      ++ default being false

    checkOptions : LGOPT -> Void
      ++ checkOptions checks whether the given options are consistent, and
      ++ yields an error otherwise

  Implementation == add

    maxLevel l ==
        if (opt := option(l, 'maxLevel)) case "failed" then
            "arbitrary"
        else
            retract(opt::Any)$AnyFunctions1(Union(NonNegativeInteger,
                                                  "arbitrary"))

    maxDerivative l ==
        if (opt := option(l, 'maxDerivative)) case "failed" then
            "arbitrary"
        else
            retract(opt::Any)$AnyFunctions1(Union(NonNegativeInteger,
                                                  "arbitrary"))

    maxShift l == maxDerivative l

    maxSubst l ==
        d := maxDerivative l
        if d case NonNegativeInteger
        then (d+1)::PositiveInteger
        else d

    maxDegree l ==
        if (opt := option(l, 'maxDegree)) case "failed" then
            "arbitrary"
        else
            retract(opt::Any)$AnyFunctions1(Union(NonNegativeInteger,
                                                  "arbitrary"))

    maxMixedDegree l ==
        if (opt := option(l, 'maxMixedDegree)) case "failed" then
            0
        else
            retract(opt::Any)$AnyFunctions1(NonNegativeInteger)

    allDegrees l ==
        if (opt := option(l, 'allDegrees)) case "failed" then
            false
        else
            retract(opt::Any)$AnyFunctions1(Boolean)

    maxPower l ==
        if (opt := option(l, 'maxPower)) case "failed" then
            "arbitrary"
        else
            retract(opt::Any)$AnyFunctions1(Union(PositiveInteger, "arbitrary"))

    safety l ==
        if (opt := option(l, 'safety)) case "failed" then
            1$NonNegativeInteger
        else
            retract(opt::Any)$AnyFunctions1(NonNegativeInteger)

    check l ==
        if (opt := option(l, 'check)) case "failed" then
            "deterministic"
        else
            retract(opt::Any)$AnyFunctions1(Union("skip",
                                "MonteCarlo", "deterministic"))

    checkExtraValues l ==
        if (opt := option(l, 'checkExtraValues)) case "failed" then
            true
        else
            retract(opt::Any)$AnyFunctions1(Boolean)

    one l ==
        if (opt := option(l, 'one)) case "failed" then
            true
        else
            retract(opt::Any)$AnyFunctions1(Boolean)

    debug l ==
        if (opt := option(l, 'debug)) case "failed" then
            false
        else
            retract(opt::Any)$AnyFunctions1(Boolean)

    homogeneous l ==
        if (opt := option(l, 'homogeneous)) case "failed" then
            false
        else
            retract(opt::Any)$AnyFunctions1(Union(PositiveInteger, Boolean))

    Somos l ==
        if (opt := option(l, 'Somos)) case "failed" then
            false
        else
            retract(opt::Any)$AnyFunctions1(Union(PositiveInteger, Boolean))

    variableName l ==
        if (opt := option(l, 'variableName)) case "failed" then
            'x
        else
            retract(opt::Any)$AnyFunctions1(Symbol)

    functionName l ==
        if (opt := option(l, 'functionName)) case "failed" then
            'f
        else
            retract(opt::Any)$AnyFunctions1(Symbol)

    functionNames l ==
        if (opt := option(l, 'functionNames)) case "failed" then
            []
        else
            retract(opt::Any)$AnyFunctions1(List Symbol)

    indexName l ==
        if (opt := option(l, 'indexName)) case "failed" then
            'n
        else
            retract(opt::Any)$AnyFunctions1(Symbol)

    displayKind l ==
        if (opt := option(l, 'displayKind)) case "failed" then
            error "GuessOption: displayKind not set"
        else
            retract(opt::Any)$AnyFunctions1(Symbol)

    NNI ==> NonNegativeInteger
    PI  ==> PositiveInteger

    checkOptions l ==
        maxD := maxDerivative l
        maxP := maxPower l
        homo := homogeneous l
        Somo := Somos l

        if Somo case PI then
            if one? Somo then
                error "Guess: Somos must be Boolean or at least two"

            if maxP case PI and one? maxP then
                error "Guess: Somos requires that maxPower is at least two"

            if maxD case NNI and maxD > Somo then
                error "Guess: if Somos is an integer, it should be larger"
                  " than maxDerivative/maxShift or at least as big as maxSubst"
        else
            if Somo then
                if maxP case PI and one? maxP then
                    error "Guess: Somos requires that maxPower is at least two"

                if not (maxD case NNI) or zero? maxD or one? maxD then
                    error "Guess: Somos==true requires that maxDerivative/"
                      "maxShift is an integer, at least two, or maxSubst is"
                        " an integer, at least three"

                if not (maxP case PI) and homo case Boolean and not homo then
                    error "Guess: Somos requires that maxPower is set or"
                          " homogeneous is not false"

        if homo case PI then
            if maxP case PI and maxP ~= homo then
                error "Guess: only one of homogeneous and maxPower may be"
                      " an integer"

            if maxD case NNI and zero? maxD then
                error "Guess: homogeneous requires that maxShift/maxDerivative"
                      " is at least one or maxSubst is at least two"
        else
            if homo then
                if not maxP case PI then
                    error "Guess: homogeneous==true requires that maxPower"
                          " is an integer"

                if maxD case NNI and zero? maxD then
                    error "Guess: homogeneous requires that maxShift/"
                          "maxDerivative is at least one or maxSubst is"
                            " at least two"


)abbrev package GUESSEB GuessExpBin
)boot $tryRecompileArguments := nil
++ Author: Martin Rubey
++ Description: This package implements guessing GuessExpRat and
++ GuessBinRat functions of guessing package.
GuessExpBin(F, S, EXPRR, retract, coerce) : Exports == Implementation where
  -- for example : FRAC POLY PF 5
  F : Join(Field, PolynomialFactorizationExplicit)
  -- in F we interpolate and check

  S : GcdDomain

-- in guessExpRat I would like to determine the roots of polynomials in F. When
-- F is a quotient field, I can get rid of the denominator.  In this case F is
-- roughly QFCAT S

  -- results are given as elements of EXPRR
  EXPRR : Join(FunctionSpace Integer, IntegralDomain,
                RetractableTo Symbol,
                RetractableTo Integer, CombinatorialOpsCategory,
                PartialDifferentialRing Symbol) with
              _* : (%, %) -> %
              _/ : (%, %) -> %
              _^ : (%, %) -> %
              numerator : % -> %
              denominator : % -> %
              ground? : % -> Boolean

  -- EXPRR exists, in case at some point there is support for EXPR PF 5.

  -- the following I really would like to get rid of

  retract : EXPRR -> F                      -- eg.: i+->i
  coerce : F -> EXPRR                       -- eg.: i+->i

  LGOPT ==> List GuessOption
  GOPT0 ==> GuessOptionFunctions0

  NNI ==> NonNegativeInteger
  PI ==> PositiveInteger
  GUESSRESULT ==> List EXPRR

  SUP ==> SparseUnivariatePolynomial

  FFFG ==> FractionFreeFastGaussian

  DIFFSPECN ==> EXPRR -> EXPRR             -- eg.: i+->q^i

  GUESSER ==> (List F, LGOPT) -> GUESSRESULT

  FSUPF ==> Fraction SUP F

  V ==> OrderedVariableList(['a1, 'A])
  POLYF ==> SparseMultivariatePolynomial(F, V)
  FPOLYF ==> Fraction POLYF
  FSUPFPOLYF ==> Fraction SUP FPOLYF
  POLYS ==> SparseMultivariatePolynomial(S, V)
  FPOLYS ==> Fraction POLYS
  FSUPFPOLYS ==> Fraction SUP FPOLYS

  Exports == with

    guessExpRat : (List F, LGOPT) -> GUESSRESULT
      ++ \spad{guessExpRat(l, options)} tries to find a function of the
      ++ form n+->(a+b n)^n r(n), where r(n) is a rational function, that
      ++ fits l.

    guessBinRat : (List F, LGOPT) -> GUESSRESULT
      ++ \spad{guessBinRat(l, options)} tries to find a function of the
      ++ form n+->binomial(a+b n, n) r(n), where r(n) is a rational
      ++ function, that fits l.

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        guessExpRat : Symbol -> GUESSER
          ++ \spad{guessExpRat q} returns a guesser that tries to find a
          ++ function of the form n+->(a+b q^n)^n r(q^n), where r(q^n) is a
          ++ q-rational function, that fits l.

        guessBinRat : Symbol -> GUESSER
          ++ \spad{guessBinRat q} returns a guesser that tries to find a
          ++ function of the form n+->qbinomial(a+b n, n) r(n), where r(q^n)
          ++ is a q-rational function, that fits l.

  Implementation == add

    -- The following expressions for order and degree of the resultants
    -- [[res1]] and [[res2]] in [[guessExpRatAux]] were first guessed,
    -- partially with the aid of [[guessRat]], and then proven to be correct.

    ord1(x : List Integer, i : Integer) : Integer ==
        n := #x - 3 - i
        x.(n+1)*reduce(_+, [x.j for j in 1..n], 0) + _
            2*reduce(_+, [reduce(_+, [x.k*x.j for k in 1..j-1], 0) _
                          for j in 1..n], 0)

    ord2(x : List Integer, i : Integer) : Integer ==
        if zero? i then
            n := #x - 3 - i
            ord1(x, i) + reduce(_+, [x.j for j in 1..n], 0)*(x.(n+2)-x.(n+1))
        else
            ord1(x, i)

    deg1(x : List Integer, i : Integer) : Integer ==
        m := #x - 3
        (x.(m+3)+x.(m+1)+x.(1+i))*reduce(_+, [x.j for j in 2+i..m], 0) + _
            x.(m+3)*x.(m+1) + _
            2*reduce(_+, [reduce(_+, [x.k*x.j for k in 2+i..j-1], 0) _
                          for j in 2+i..m], 0)

    deg2(x : List Integer, i : Integer) : Integer ==
        m := #x - 3
        deg1(x, i) + _
            (x.(m+3) + reduce(_+, [x.j for j in 2+i..m], 0)) * _
            (x.(m+2)-x.(m+1))

    checkResult(res : EXPRR, n : Symbol, l : Integer, list : List F) : NNI ==
        for i in l..1 by -1 repeat
            den := eval(denominator res, n::EXPRR, (i-1)::EXPRR)
            if den = 0 then return i::NNI
            num := eval(numerator res, n::EXPRR, (i-1)::EXPRR)
            if list.i ~= retract(num/den)@F
            then return i::NNI
        0$NNI

    SUPS2SUPF(p : SUP S) : SUP F ==
      if F is S then
        p pretend SUP(F)
      else if F is Fraction S then
        map((z1 : S) : F +-> coerce(z1)$Fraction(S), p)
          $SparseUnivariatePolynomialFunctions2(S, F)
      else error "Guess: type parameter F should be either equal to S or"
                 " equal to Fraction S"

    -- convertion routines

    F2FPOLYS(p : F) : FPOLYS ==
      if F is S then
        p::POLYF::FPOLYF pretend FPOLYS
      else if F is Fraction S then
        numer(p)$Fraction(S)::POLYS/denom(p)$Fraction(S)::POLYS
      else error "Guess: type parameter F should be either equal to S or"
                 " equal to Fraction S"

    MPCSF ==> MPolyCatFunctions2(V, IndexedExponents V,
                                    IndexedExponents V, S, F,
                                    POLYS, POLYF)

    SUPF2EXPRR(xx : Symbol, p : SUP F) : EXPRR ==
      zero? p => 0
      (coerce(leadingCoefficient p))::EXPRR * (xx::EXPRR)^degree p
         + SUPF2EXPRR(xx, reductum p)

    FSUPF2EXPRR(xx : Symbol, p : FSUPF) : EXPRR ==
      (SUPF2EXPRR(xx, numer p)) / (SUPF2EXPRR(xx, denom p))


    POLYS2POLYF(p : POLYS) : POLYF ==
      if F is S then
        p pretend POLYF
      else if F is Fraction S then
        map((z1 : S) : F +-> coerce(z1)$Fraction(S), p)$MPCSF
      else error "Guess: type parameter F should be either equal to S or"
                 " equal to Fraction S"

    SUPPOLYS2SUPF(p : SUP POLYS, a1v : F, Av : F) : SUP F ==
      zero? p => 0
      lc : POLYF := POLYS2POLYF leadingCoefficient(p)
      monomial(retract(eval(lc, [index(1)$V, index(2)$V]::List V,
                                [a1v, Av])),
               degree p)
        + SUPPOLYS2SUPF(reductum p, a1v, Av)


    SUPFPOLYS2FSUPPOLYS(p : SUP FPOLYS) : Fraction SUP POLYS  ==
      cden := splitDenominator(p)
             $UnivariatePolynomialCommonDenominator(POLYS, FPOLYS, SUP FPOLYS)

      pnum : SUP POLYS
           := map((z1 : FPOLYS) : POLYS +-> retract(z1 * cden.den)$FPOLYS, p)
                 $SparseUnivariatePolynomialFunctions2(FPOLYS, POLYS)
      pden : SUP POLYS := (cden.den)::SUP POLYS

      pnum/pden


    -- this needs documentation. In particular, why is V appearing here?
    GF ==> GeneralizedMultivariateFactorize(SingletonAsOrderedSet,
                                            NonNegativeInteger, F, F,
                                            SUP F)

    -- mimicking $q$-analoga

    defaultD : DIFFSPECN
    defaultD(expr : EXPRR) : EXPRR == expr

    -- applies n+->q^n or whatever DN is to i
    DN2DL : (DIFFSPECN, Integer) -> F
    DN2DL(DN, i) == retract(DN(i::EXPRR))@F

    evalResultant(p1 : POLYS, p2 : POLYS, o : Integer, d : Integer,
                  va1 : V, vA : V) : List S ==
        res : List S := []
        d1 := degree(p1, va1)
        d2 := degree(p2, va1)
        lead : S
        -- Since we also have an lower bound for the order of the resultant,
        -- we need to evaluate it only at $d-o+1$ points. Furthermore,
        -- we can divide by $k^o$ and still obtain a polynomial.
        for k in 1..d-o+1 repeat
            p1atk := univariate eval(p1, vA, k::S)
            p2atk := univariate eval(p2, vA, k::S)

        -- It may happen, that the leading coefficients of one or both of
        -- the polynomials changes, when we evaluate it at $k$. In this
        -- case, we need to correct this by multiplying with the
        -- corresponding power of the leading coefficient of the other
        -- polynomial.

        -- Consider the Sylvester matrix of the original polynomials. We
        -- want to evaluate it at $A = k$. If the first few leading
        -- coefficients of $p2$ vanish, the first few columns of the
        -- Sylvester matrix have triangular shape, with the leading
        -- coefficient of $p1$ on the diagonal. The same thing happens, if
        -- we exchange the roles of $p1$ and $p2$, only that we have to
        -- take care of the sign, too.

            d1atk := degree p1atk
            d2atk := degree p2atk

    --      output("k: " string(k))$OutputPackage
    --      output("d1: " string(d1) " d1atk: " string(d1atk))$OutputPackage
    --      output("d2: " string(d2) " d2atk: " string(d2atk))$OutputPackage


            if d2atk < d2 then
                if  d1atk < d1
                then lead := 0$S
                else lead := (leadingCoefficient p1atk)^((d2-d2atk)::NNI)
            else
                if  d1atk < d1
                then lead := (-1$S)^d2 *
                       (leadingCoefficient p2atk)^((d1-d1atk)::NNI)
                else lead := 1$S

            if zero? lead
            then res := cons(0, res)
            else res := cons(lead * (resultant(p1atk, p2atk)$SUP(S) exquo
                                    (k::S)^(o::NNI))::S,
                             res)
        reverse res

    -- The degree of [[poly3]] is governed by $(a_0+x_m a_1)^{x_m}$.
    -- Therefore, we substitute $A-x_m a1$ for $a_0$, which reduces
    -- the degree in $a_1$ by $x_m-x_{i+1}$.

    p_subst(xm : Integer, i : Integer, va1 : V, vA : V, basis : DIFFSPECN
           ) : FPOLYS ==
        vA::POLYS::FPOLYS + va1::POLYS::FPOLYS _
                           * F2FPOLYS(DN2DL(basis, i) - DN2DL(basis, xm))

    p2_subst(xm : Integer, i : Symbol, a1v : F, Av : F, basis : DIFFSPECN
            ) : EXPRR ==
        coerce(Av) + coerce(a1v)*(basis(i::EXPRR) - basis(xm::EXPRR))

    guessExpRatAux(xx : Symbol, list : List F, basis : DIFFSPECN,
                   xValues : List Integer, options : LGOPT) : List EXPRR ==
        import from Factored(SUP F)

        a1 : V := index(1)$V
        A : V := index(2)$V

        len : NNI := #list
        if len < 4 then return []
                   else len := (len-3)::NNI

        xlist := [F2FPOLYS DN2DL(basis, xValues.i) for i in 1..len]
        x1 := F2FPOLYS DN2DL(basis, xValues.(len+1))
        x2 := F2FPOLYS DN2DL(basis, xValues.(len+2))
        x3 := F2FPOLYS DN2DL(basis, xValues.(len+3))

        -- We try to fit the data $(s1, s2, \dots)$ to the model
        -- $(a+b n)^n y(n)$, $r$ being a rational function. To obtain
        -- $y$, we compute $y(n)=s_n*(a+b n)^{-n}$.

        y : NNI -> FPOLYS :=
            (z1 : NNI) : FPOLYS +-> F2FPOLYS(list.z1) * _
            p_subst(last xValues, (xValues.z1)::Integer, a1, A, basis)^_
                (-(xValues.z1)::Integer)

        ylist : List FPOLYS := [y i for i in 1..len]

        y1 := y(len+1)
        y2 := y(len+2)
        y3 := y(len+3)

        res := []::List EXPRR
        if (maxD := maxDegree(options)$GOPT0) case NNI
        then maxDeg := min(maxD, len-1)
        else maxDeg := len-1

        for i in 0..maxDeg repeat
            if debug(options)$GOPT0 then
                output("Guess: degree ExpRat ", i::OutputForm)$OutputPackage

            if debug(options)$GOPT0 then
                output("Guess: interpolating...")$OutputPackage

            ri : FSUPFPOLYS
               := interpolate(xlist, ylist, (len-1-i)::NNI) _
                             $FFFG(FPOLYS, SUP FPOLYS)

    -- for experimental fraction free interpolation
    --        ri: Fraction SUP POLYS
    --           := interpolate(xlist, ylist, (len-1-i)::NNI) _
    --                         $FFFG(POLYS, SUP POLYS)

            if debug(options)$GOPT0 then
    --            output(hconcat("xlist: ", xlist::OutputForm))$OutputPackage
    --            output(hconcat("ylist: ", ylist::OutputForm))$OutputPackage
    --            output(hconcat("ri: ", ri::OutputForm))$OutputPackage
                output("Guess: polynomials...")$OutputPackage

            poly1 : POLYS := numer(elt(ri, x1)$SUP(FPOLYS) - y1)
            poly2 : POLYS := numer(elt(ri, x2)$SUP(FPOLYS) - y2)
            poly3 : POLYS := numer(elt(ri, x3)$SUP(FPOLYS) - y3)

    -- for experimental fraction free interpolation
    --        ri2: FSUPFPOLYS := map(#1::FPOLYS, numer ri)                 _
    --                $SparseUnivariatePolynomialFunctions2(POLYS, FPOLYS) _
    --                         /map(#1::FPOLYS, denom ri)                  _
    --                $SparseUnivariatePolynomialFunctions2(POLYS, FPOLYS)
    --
    --        poly1: POLYS := numer(elt(ri2, x1)$SUP(FPOLYS) - y1)
    --        poly2: POLYS := numer(elt(ri2, x2)$SUP(FPOLYS) - y2)
    --        poly3: POLYS := numer(elt(ri2, x3)$SUP(FPOLYS) - y3)

            n : Integer := len - i
            o1 : Integer := ord1(xValues, i)
            d1 : Integer := deg1(xValues, i)
            o2 : Integer := ord2(xValues, i)
            d2 : Integer := deg2(xValues, i)

    -- another compiler bug: using i as iterator here makes the loop break

            if debug(options)$GOPT0 then
                output("Guess: interpolating resultants...")$OutputPackage

            res1 : SUP S
                 := newton(evalResultant(poly1, poly3, o1, d1, a1, A))
                          $NewtonInterpolation(S)

            res2 : SUP S
                 := newton(evalResultant(poly2, poly3, o2, d2, a1, A))
                          $NewtonInterpolation(S)

    --        if debug(options)$GOPT0 then
    --            res1: SUP S := univariate(resultant(poly1, poly3, a1))
    --            res2: SUP S := univariate(resultant(poly2, poly3, a1))
    --            if res1 ~= res1res or res2 ~= res2res then
    --            output(hconcat("poly1 ", poly1::OutputForm))$OutputPackage
    --                output(hconcat("poly2 ", poly2::OutputForm))$OutputPackage
    --            output(hconcat("poly3 ", poly3::OutputForm))$OutputPackage
    --                output(hconcat("res1 ", res1::OutputForm))$OutputPackage
    --                output(hconcat("res2 ", res2::OutputForm))$OutputPackage
    --            output("n/i: " string(n) " " string(i))$OutputPackage
    --            output("res1 ord: " string(o1) " " string(minimumDegree res1))
    --                  $OutputPackage
    --            output("res1 deg: " string(d1) " " string(degree res1))
    --                  $OutputPackage
    --            output("res2 ord: " string(o2) " " string(minimumDegree res2))
    --                  $OutputPackage
    --            output("res2 deg: " string(d2) " " string(degree res2))
    --                  $OutputPackage

            if debug(options)$GOPT0 then
                output("Guess: computing gcd...")$OutputPackage

    -- we want to solve over F
    -- for polynomial domains S this seems to be very costly!
            res3 : SUP F := SUPS2SUPF(primitivePart(gcd(res1, res2)))

            if debug(options)$GOPT0 then
                output("Guess: solving...")$OutputPackage

    -- res3 is a polynomial in A=a0+(len+3)*a1
    -- now we have to find the roots of res3

            for f in factorList factor(res3)$GF | degree f.factor = 1 repeat
    -- we are only interested in the linear factors
    --             if debug(options)$GOPT0 then
    --                 output(hconcat("f: ", f::OutputForm))$OutputPackage

                Av : F := -coefficient(f.factor, 0)
                         / leadingCoefficient f.factor

    -- FIXME: in an earlier version, we disregarded vanishing Av
    --    maybe we intended to disregard vanishing a1v? Either doesn't really
    --    make sense to me right now.

                evalPoly := eval(POLYS2POLYF poly3, A, Av)
                if zero? evalPoly
                then evalPoly := eval(POLYS2POLYF poly1, A, Av)
    -- Note that it really may happen that poly3 vanishes when specializing
    -- A. Consider for example guessExpRat([1, 1, 1, 1]).

    -- FIXME: We check poly1 below, too. I should work out in what cases poly3
    -- vanishes.

                for g in factorList factor(univariate evalPoly)$GF
                          | degree g.factor = 1 repeat
    --                 if debug(options)$GOPT0 then
    --                     output("g: ", g::OutputForm)$OutputPackage

                    a1v : F := -coefficient(g.factor, 0)
                              / leadingCoefficient g.factor

    -- check whether poly1 and poly2 really vanish. Note that we could have
    -- found an extraneous solution, since we only computed the gcd of the
    -- two resultants.

                    t1 := eval(POLYS2POLYF poly1, [a1, A]::List V,
                                                   [a1v, Av]::List F)
                    not(zero? t1) => "iterate"
                    t2 := eval(POLYS2POLYF poly2, [a1, A]::List V,
                                                   [a1v, Av]::List F)
                    if zero? t2 then

                        ri1 : Fraction SUP POLYS
                          := SUPFPOLYS2FSUPPOLYS(numer ri)
                            / SUPFPOLYS2FSUPPOLYS(denom ri)

    -- for experimental fraction free interpolation
    --                         ri1: Fraction SUP POLYS := ri

                        numr : SUP F := SUPPOLYS2SUPF(numer ri1, a1v, Av)
                        denr : SUP F := SUPPOLYS2SUPF(denom ri1, a1v, Av)

                        if not zero? denr then
                            res4 : EXPRR := eval(FSUPF2EXPRR(xx, numr/denr),
                                                 kernel(xx), basis(xx::EXPRR))
                                       *p2_subst(last xValues, xx, a1v,
                                                  Av, basis)^xx::EXPRR
                            res := cons(res4, res)
                        else if zero? numr and debug(options)$GOPT0 then
                            output("Guess: numerator and denominator vanish!"
                                  )$OutputPackage

    -- If we are only interested in one solution, we do not try other
    -- degrees if we have found already some solutions. I.e., the
    -- indentation here is correct.

            if not(empty?(res)) and one(options)$GOPT0 then return res

        res

    guessExpRatAux0(list : List F, basis : DIFFSPECN, options : LGOPT
                   ) : GUESSRESULT ==
        if zero? safety(options)$GOPT0 then
            error "Guess: guessExpRat does not support zero safety"
    -- guesses Functions of the Form (a1*n+a0)^n*rat(n)
        xx := indexName(options)$GOPT0

    -- restrict to safety

        len : Integer := #list
        if len-safety(options)$GOPT0+1 < 0 then return []

        shortlist : List F := first(list, (len-safety(options)$GOPT0+1)::NNI)

    -- remove zeros from list

        zeros : EXPRR := 1
        newlist : List F
        xValues : List Integer

        i : Integer := -1
        for x in shortlist repeat
            i := i+1
            if x = 0 then
                zeros := zeros * (basis(xx::EXPRR) - basis(i::EXPRR))

        i := -1
        for x in shortlist repeat
            i := i+1
            if x ~= 0 then
                newlist := cons(x/retract(eval(zeros, xx::EXPRR, i::EXPRR))@F,
                                newlist)
                xValues := cons(i, xValues)

        newlist := reverse newlist
        xValues := reverse xValues

        res : List EXPRR
            := [eval(zeros * f, xx::EXPRR, xx::EXPRR) _
                for f in guessExpRatAux(xx, newlist, basis, xValues, options)]

        select(z1 +-> checkResult(z1, xx, len, list) <
                         len-safety(options)$GOPT0, res)

    guessExpRat(list : List F, options : LGOPT) : GUESSRESULT ==
        guessExpRatAux0(list, defaultD, options)

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        guessExpRat(q : Symbol) : GUESSER ==
            (z1 : List F, z2 : LGOPT) : GUESSRESULT +->
                guessExpRatAux0(z1, (i1 : EXPRR) : EXPRR +-> q::EXPRR^i1, z2)


    EXT ==> (Integer, V, V) -> FPOLYS
    EXTEXPR ==> (Symbol, F, F) -> EXPRR

    binExt : EXT
    binExt(i : Integer, va1 : V, vA : V) : FPOLYS ==
        numl : List POLYS := [(vA::POLYS) + i * (va1::POLYS) - (l::POLYS) _
                             for l in 0..i-1]
        num : POLYS := reduce(_*, numl, 1)

        num/(factorial(i)::POLYS)

    binExtEXPR : EXTEXPR
    binExtEXPR(i : Symbol, a1v : F, Av : F) : EXPRR ==
        binomial(coerce Av + coerce a1v * (i::EXPRR), i::EXPRR)


    guessBinRatAux(xx : Symbol, list : List F,
                   basis : DIFFSPECN, ext : EXT, extEXPR : EXTEXPR,
                   xValues : List Integer, options : LGOPT) : List EXPRR ==
        import from Factored(SUP F)

        a1 : V := index(1)$V
        A : V := index(2)$V

        len : NNI := #list
        if len < 4 then return []
                   else len := (len-3)::NNI

        xlist := [F2FPOLYS DN2DL(basis, xValues.i) for i in 1..len]
        x1 := F2FPOLYS DN2DL(basis, xValues.(len+1))
        x2 := F2FPOLYS DN2DL(basis, xValues.(len+2))
        x3 := F2FPOLYS DN2DL(basis, xValues.(len+3))

        -- We try to fit the data $(s1, s2, \dots)$ to the model
        -- $\binom{a+b n}{n} y(n)$, $r$ being a rational function.
        -- To obtain $y$, we compute $y(n)=s_n*\binom{a+bn}{n}^-1$.

        y : NNI -> FPOLYS :=
            (z1 : NNI) : FPOLYS +-> F2FPOLYS(list.z1) / _
            ext((xValues.z1)::Integer, a1, A)

        ylist : List FPOLYS := [y i for i in 1..len]

        y1 := y(len+1)
        y2 := y(len+2)
        y3 := y(len+3)

        res := []::List EXPRR
        if (maxD := maxDegree(options)$GOPT0) case NNI
        then maxDeg := min(maxD, len-1)
        else maxDeg := len-1

        for i in 0..maxDeg repeat
    --        if debug(options)$GOPT0 then
    --            output(hconcat("degree BinRat "::OutputForm, i::OutputForm))
    --                $OutputPackage

    --        if debug(options)$GOPT0 then
    --            output("interpolating..."::OutputForm)$OutputPackage

            ri : FSUPFPOLYS
               := interpolate(xlist, ylist, (len-1-i)::NNI) _
                             $FFFG(FPOLYS, SUP FPOLYS)

    --        if debug(options)$GOPT0 then
    --            output(hconcat("ri ", ri::OutputForm))$OutputPackage

            poly1 : POLYS := numer(elt(ri, x1)$SUP(FPOLYS) - y1)
            poly2 : POLYS := numer(elt(ri, x2)$SUP(FPOLYS) - y2)
            poly3 : POLYS := numer(elt(ri, x3)$SUP(FPOLYS) - y3)

    --        if debug(options)$GOPT0 then
    --            output(hconcat("poly1 ", poly1::OutputForm))$OutputPackage
    --            output(hconcat("poly2 ", poly2::OutputForm))$OutputPackage
    --            output(hconcat("poly3 ", poly3::OutputForm))$OutputPackage


            n : Integer := len - i
            res1 : SUP S := univariate(resultant(poly1, poly3, a1))
            res2 : SUP S := univariate(resultant(poly2, poly3, a1))
    --        if debug(options)$GOPT0 then
    --            output(hconcat("res1 ", res1::OutputForm))$OutputPackage
    --            output(hconcat("res2 ", res2::OutputForm))$OutputPackage

    --            if res1 ~= res1res or res2 ~= res2res then
    --            output(hconcat("poly1 ", poly1::OutputForm))$OutputPackage
    --                output(hconcat("poly2 ", poly2::OutputForm))$OutputPackage
    --            output(hconcat("poly3 ", poly3::OutputForm))$OutputPackage
    --                output(hconcat("res1 ", res1::OutputForm))$OutputPackage
    --                output(hconcat("res2 ", res2::OutputForm))$OutputPackage
    --            output("n/i: " string(n) " " string(i))$OutputPackage
    --            output("res1 ord: " string(minimumDegree res1))
    --                  $OutputPackage
    --            output("res1 deg: " string(degree res1))
    --                  $OutputPackage
    --            output("res2 ord: " string(minimumDegree res2))
    --                  $OutputPackage
    --            output("res2 deg: " string(degree res2))
    --                  $OutputPackage

            if debug(options)$GOPT0 then
                output("Guess: computing gcd...")$OutputPackage

    -- we want to solve over F
            res3 : SUP F := SUPS2SUPF(primitivePart(gcd(res1, res2)))

    --        if debug(options)$GOPT0 then
    --            output(hconcat("res3 ", res3::OutputForm))$OutputPackage

    -- res3 is a polynomial in A=a0+(len+3)*a1
    -- now we have to find the roots of res3

            for f in factorList factor(res3)$GF | degree f.factor = 1 repeat
    -- we are only interested in the linear factors
    --             if debug(options)$GOPT0 then
    --                 output(hconcat("f: ", f::OutputForm))$OutputPackage

                Av : F := -coefficient(f.factor, 0)
                         / leadingCoefficient f.factor

    --             if debug(options)$GOPT0 then
    --                 output(hconcat("Av: ", Av::OutputForm))$OutputPackage

    -- FIXME: in an earlier version, we disregarded vanishing Av
    --        maybe we intended to disregard vanishing a1v? Either doesn't really
    --        make sense to me right now.

                evalPoly := eval(POLYS2POLYF poly3, A, Av)
                if zero? evalPoly
                then evalPoly := eval(POLYS2POLYF poly1, A, Av)
    -- Note that it really may happen that poly3 vanishes when specializing
    -- A. Consider for example guessExpRat([1, 1, 1, 1]).

    -- FIXME: We check poly1 below, too. I should work out in what cases poly3
    -- vanishes.

                for g in factorList factor(univariate evalPoly)$GF
                          | degree g.factor = 1 repeat
    --                 if debug(options)$GOPT0 then
    --                     output(hconcat("g: ", g::OutputForm))$OutputPackage

                    a1v : F := -coefficient(g.factor, 0)
                              / leadingCoefficient g.factor

    --                 if debug(options)$GOPT0 then
    --                     output(hconcat("a1v: ", a1v::OutputForm))$OutputPackage

    -- check whether poly1 and poly2 really vanish. Note that we could have found
    -- an extraneous solution, since we only computed the gcd of the two
    -- resultants.

                    t1 := eval(POLYS2POLYF poly1, [a1, A]::List V,
                                                   [a1v, Av]::List F)

    --                 if debug(options)$GOPT0 then
    --                     output(hconcat("t1: ", t1::OutputForm))$OutputPackage

                    not(zero? t1) => "iterate"
                    t2 := eval(POLYS2POLYF poly2, [a1, A]::List V,
                                                  [a1v, Av]::List F)

    --                     if debug(options)$GOPT0 then
    --                         output(hconcat("t2: ", t2::OutputForm))$OutputPackage

                    if zero? t2 then

                        ri1 : Fraction SUP POLYS
                                 := SUPFPOLYS2FSUPPOLYS(numer ri)
                                  / SUPFPOLYS2FSUPPOLYS(denom ri)

    --                         if debug(options)$GOPT0 then
    --                             output(hconcat("ri1: ", ri1::OutputForm))$OutputPackage

                        numr : SUP F := SUPPOLYS2SUPF(numer ri1, a1v, Av)
                        denr : SUP F := SUPPOLYS2SUPF(denom ri1, a1v, Av)

    --                  if debug(options)$GOPT0 then
    --                      output(hconcat("numr: ", numr::OutputForm)
    --                            )$OutputPackage
    --                      output(hconcat("denr: ", denr::OutputForm)
    --                            )$OutputPackage

                        if not zero? denr then
                            res4 : EXPRR := eval(FSUPF2EXPRR(xx, numr/denr),
                                                 kernel(xx), basis(xx::EXPRR))
                                             * extEXPR(xx, a1v, Av)

    --                             if debug(options)$GOPT0 then
    --                                 output(hconcat("res4: ", res4::OutputForm))$OutputPackage

                            res := cons(res4, res)
                        else if zero? numr and debug(options)$GOPT0 then
                            output("Guess: numerator and denominator vanish!"
                                  )$OutputPackage

    -- If we are only interested in one solution, we do not try other degrees if we
    -- have found already some solutions. I.e., the indentation here is correct.

            if not(empty?(res)) and one(options)$GOPT0 then return res

        res

    guessBinRatAux0(list : List F,
                    basis : DIFFSPECN, ext : EXT, extEXPR : EXTEXPR,
                    options : LGOPT) : GUESSRESULT ==

        if zero? safety(options)$GOPT0 then
            error "Guess: guessBinRat does not support zero safety"
    -- guesses Functions of the form binomial(a+b*n, n)*rat(n)
        xx := indexName(options)$GOPT0

    -- restrict to safety

        len : Integer := #list
        if len-safety(options)$GOPT0+1 < 0 then return []

        shortlist : List F := first(list, (len-safety(options)$GOPT0+1)::NNI)

    -- remove zeros from list

        zeros : EXPRR := 1
        newlist : List F
        xValues : List Integer

        i : Integer := -1
        for x in shortlist repeat
            i := i+1
            if x = 0 then
                zeros := zeros * (basis(xx::EXPRR) - basis(i::EXPRR))

        i := -1
        for x in shortlist repeat
            i := i+1
            if x ~= 0 then
                newlist := cons(x/retract(eval(zeros, xx::EXPRR, i::EXPRR))@F,
                                newlist)
                xValues := cons(i, xValues)

        newlist := reverse newlist
        xValues := reverse xValues

        res : List EXPRR
            := [eval(zeros * f, xx::EXPRR, xx::EXPRR) _
                for f in guessBinRatAux(xx, newlist, basis, ext,
                                        extEXPR, xValues, options)]

        select(z1 +-> checkResult(z1, xx, len, list) <
                           len-safety(options)$GOPT0, res)

    guessBinRat(list : List F, options : LGOPT) : GUESSRESULT ==
        guessBinRatAux0(list, defaultD, binExt, binExtEXPR, options)


    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        qD : Symbol -> DIFFSPECN
        qD q == (z1 : EXPRR) : EXPRR +-> (q::EXPRR)^z1

        qBinExtAux(q : Symbol, i : Integer, va1 : V, vA : V) : FPOLYS ==
            fl : List FPOLYS
                 := [(1$FPOLYS - _
                      va1::POLYS::FPOLYS * (vA::POLYS::FPOLYS)^(i-1) * _
                      F2FPOLYS(q::F)^l) / (1-F2FPOLYS(q::F)^l) _
                     for l in 1..i]
            reduce(_*, fl, 1)

        qBinExt : Symbol -> EXT
        qBinExt q == (z1 : Integer, z2 : V, z3 : V) : FPOLYS +->
                        qBinExtAux(q, z1, z2, z3)

        qBinExtEXPRaux(q : Symbol, i : Symbol, a1v : F, Av : F) : EXPRR ==
            l : Symbol := 'l
            product((1$EXPRR - _
                     coerce a1v * (coerce Av) ^ (coerce i - 1$EXPRR) * _
                     (q::EXPRR) ^ coerce(l)) / _
                    (1$EXPRR - (q::EXPRR) ^ coerce(l)), _
                    equation(l, 1$EXPRR..i::EXPRR))

        qBinExtEXPR : Symbol -> EXTEXPR
        qBinExtEXPR q == (z1 : Symbol, z2 : F, z3 : F) : EXPRR +->
                            qBinExtEXPRaux(q, z1, z2, z3)

        guessBinRat(q : Symbol) : GUESSER ==
             (z1 : List F, z2 : LGOPT) : GUESSRESULT +->
                guessBinRatAux0(z1, qD q, qBinExt q, qBinExtEXPR q, z2)
)boot $tryRecompileArguments := true

)if false
\section{package GUESS Guess}
)endif

)abbrev package GUESS Guess
)boot $tryRecompileArguments := nil
++ Author: Martin Rubey
++ Description: This package implements guessing of sequences. Packages for the
++ most common cases are provided as \spadtype{GuessInteger},
++ \spadtype{GuessPolynomial}, etc.
Guess(F, S, EXPRR, retract, coerce) : Exports == Implementation where
  -- for example : FRAC POLY PF 5
  F : Join(Field, PolynomialFactorizationExplicit)
  -- in F we interpolate and check

  S : GcdDomain

-- in guessExpRat I would like to determine the roots of polynomials in F. When
-- F is a quotient field, I can get rid of the denominator.  In this case F is
-- roughly QFCAT S

  -- results are given as elements of EXPRR
  EXPRR : Join(FunctionSpace Integer, IntegralDomain,
                RetractableTo Symbol,
                RetractableTo Integer, CombinatorialOpsCategory,
                PartialDifferentialRing Symbol) with
              _* : (%, %) -> %
              _/ : (%, %) -> %
              _^ : (%, %) -> %
              numerator : % -> %
              denominator : % -> %
              ground? : % -> Boolean

  -- EXPRR exists, in case at some point there is support for EXPR PF 5.

  -- the following I really would like to get rid of

  retract : EXPRR -> F                      -- eg.: i+->i
  coerce : F -> EXPRR                       -- eg.: i+->i

  LGOPT ==> List GuessOption
  GOPT0 ==> GuessOptionFunctions0

  NNI ==> NonNegativeInteger
  PI ==> PositiveInteger
  EXPRI ==> Expression Integer
  GUESSRESULT ==> List EXPRR

  UFPSF ==> UnivariateFormalPowerSeries F
  UFPS1 ==> UnivariateFormalPowerSeriesFunctions

  UFPSS ==> UnivariateFormalPowerSeries S

  SUP ==> SparseUnivariatePolynomial

  SMPF ==> SparseMultivariatePolynomial(F, NNI)

  UFPSSUPF ==> UnivariateFormalPowerSeries SMPF

  FFFG ==> FractionFreeFastGaussian
  FFFGF ==> FractionFreeFastGaussianFractions

  -- CoeffAction
  DIFFSPECA ==> (NNI, NNI, SUP S) -> S

  DIFFSPECAF ==> (NNI, NNI, UFPSSUPF) -> SMPF

  DIFFSPECAX ==> (NNI, Symbol, EXPRR) -> EXPRR

  -- the diagonal of the C-matrix
  DIFFSPECC ==> NNI -> List S

)if false

We need to create several streams.  Let $P$ be the univariate power series
whose first few coefficients [[list]] are given.  As an example, consider the
differentiation setting:
\begin{itemize}
\item the elements of [[guessStream]] consist of $P$ differentiated and taken
  to some power
\item the elements of [[degreeStream]] are integers that tell us how many terms
  less than in [[list]] are valid in the corresponding element of
  [[guessStream]]
\item the elements of [[testStream]] are very similar to those of
  [[guessStream]], with the difference that they are derived from $P$ with an
  transcendental element added, which corresponds to $O(x^d)$
\item finally, the elements of [[exprStream]] contain representations of the
  transformations applied to $P$ as expressions.
\end{itemize}

)endif

  FPSGEN ==> UFPSSUPF -> Vector UFPSSUPF
  -- the second argument is the modulus, the last argument is q
  SEQGEN ==> (List(U32Vector), Integer, Integer) -> Vector(U32Vector)

  HPSPEC ==> Record(degreeStream : Stream NNI,
                      guessStream : UFPSF -> Stream UFPSF,
                      guessModGen : NNI -> SEQGEN,
                      testGen : List PositiveInteger -> FPSGEN,
                      exprStream :   (EXPRR, Symbol) -> Stream EXPRR,
                      kind : Symbol,
                      qvar : Symbol,
                      A :  DIFFSPECA,
                      AF : DIFFSPECAF,
                      AX : DIFFSPECAX,
                      C :  DIFFSPECC)

  -- note that empty?(guessStream.o) has to return always. In other
  -- words, if the stream is finite, empty? should recognize it.

  DIFFSPECN ==> EXPRR -> EXPRR             -- eg.: i+->q^i

  GUESSER ==> (List F, LGOPT) -> GUESSRESULT

  FSUPS ==> Fraction SUP S
  FSUPF ==> Fraction SUP F

  Exports == with

        guess : List F -> GUESSRESULT
          ++ \spad{guess l} applies recursively \spadfun{guessRat} to the
          ++ successive differences and quotients of the list.  Default options
          ++ as described in \spadtype{GuessOptionFunctions0} are used.

        guess : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guess(l, options)} applies recursively \spadfun{guessRat}
          ++ to the successive differences and quotients of the list.  The
          ++ given options are used.

        guess : (List F, List GUESSER, List Symbol) -> GUESSRESULT
          ++ \spad{guess(l, guessers, ops)} applies recursively the given
          ++ guessers to the successive differences if ops contains the symbol
          ++ guessSum and quotients if ops contains the symbol guessProduct to
          ++ the list.  Default options as described in
          ++ \spadtype{GuessOptionFunctions0} are used.

        guess : (List F, List GUESSER, List Symbol, LGOPT) -> GUESSRESULT
          ++ \spad{guess(l, guessers, ops)} applies recursively the given
          ++ guessers to the successive differences if ops contains the symbol
          ++ \spad{guessSum} and quotients if ops contains the symbol
          ++ \spad{guessProduct} to the list.  The given options are used.

        guessExpRat : List F -> GUESSRESULT
          ++ \spad{guessExpRat l} tries to find a function of the form
          ++ n+->(a+b n)^n r(n), where r(n) is a rational function, that fits
          ++ l.

        guessExpRat : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessExpRat(l, options)} tries to find a function of the
          ++ form n+->(a+b n)^n r(n), where r(n) is a rational function, that
          ++ fits l.

        guessBinRat : List F -> GUESSRESULT
          ++ \spad{guessBinRat(l, options)} tries to find a function of the
          ++ form n+->binomial(a+b n, n) r(n), where r(n) is a rational
          ++ function, that fits l.

        guessBinRat : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessBinRat(l, options)} tries to find a function of the
          ++ form n+->binomial(a+b n, n) r(n), where r(n) is a rational
          ++ function, that fits l.

        if F has RetractableTo Symbol and S has RetractableTo Symbol then

            guessExpRat : Symbol -> GUESSER
              ++ \spad{guessExpRat q} returns a guesser that tries to find a
              ++ function of the form n+->(a+b q^n)^n r(q^n), where r(q^n)
              ++ is a q-rational function, that fits l.

            guessBinRat : Symbol -> GUESSER
              ++ \spad{guessBinRat q} returns a guesser that tries to find a
              ++ function of the form n+->qbinomial(a+b n, n) r(n), where
              ++ r(q^n) is a q-rational function, that fits l.

        guessAlgDep : (List List F) -> GUESSRESULT
          ++ \spad{guessAlgDep ll} tries to find an algebraic dependence
          ++ between several power series whose first Taylor
          ++ coefficients are given by members of ll, using the default
          ++ options described in \spadtype{GuessOptionFunctions0}.

        guessAlgDep : (List List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessAlgDep ll} tries to find an algebraic dependence
          ++ between several power series whose first Taylor
          ++ coefficients are given by members of ll, using the
          ++ given options.

        guessADE : List F -> GUESSRESULT
          ++ \spad{guessADE l} tries to find an algebraic differential equation
          ++ for a generating function whose first Taylor coefficients are
          ++ given by l, using the default options described in
          ++ \spadtype{GuessOptionFunctions0}.

        guessADE : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessADE(l, options)} tries to find an algebraic
          ++ differential equation for a generating function whose first Taylor
          ++ coefficients are given by l, using the given options.

        guessFE : List F -> GUESSRESULT
          ++ \spad{guessFE l} tries to find an algebraic substitution equation
          ++ for a generating function whose first Taylor coefficients are
          ++ given by l, using the default options described in
          ++ \spadtype{GuessOptionFunctions0}.

        guessFE : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessFE(l, options)} tries to find an algebraic
          ++ substitution equation for a generating function whose first Taylor
          ++ coefficients are given by l, using the given options.

        guessAlg : List F -> GUESSRESULT
          ++ \spad{guessAlg l} tries to find an algebraic equation for a
          ++ generating function whose first Taylor coefficients are given by
          ++ l, using the default options described in
          ++ \spadtype{GuessOptionFunctions0}. It is equivalent to
          ++ \spadfun{guessADE}(l, maxDerivative == 0).

        guessAlg : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessAlg(l, options)} tries to find an algebraic equation
          ++ for a generating function whose first Taylor coefficients are
          ++ given by l, using the given options. It is equivalent to
          ++ \spadfun{guessADE}(l, options) with \spad{maxDerivative == 0}.

        guessHolo : List F -> GUESSRESULT
          ++ \spad{guessHolo l} tries to find an ordinary linear differential
          ++ equation for a generating function whose first Taylor coefficients
          ++ are given by l, using the default options described in
          ++ \spadtype{GuessOptionFunctions0}. It is equivalent to
          ++ \spadfun{guessADE}\spad{(l, maxPower == 1)}.

        guessHolo : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessHolo(l, options)} tries to find an ordinary linear
          ++ differential equation for a generating function whose first Taylor
          ++ coefficients are given by l, using the given options. It is
          ++ equivalent to \spadfun{guessADE}\spad{(l, options)} with
          ++ \spad{maxPower == 1}.

        guessPade : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessPade(l, options)} tries to find a rational function
          ++ whose first Taylor coefficients are given by l, using the given
          ++ options. It is equivalent to \spadfun{guessADE}\spad{(l,
          ++ maxDerivative == 0, maxPower == 1, allDegrees == true)}.

        guessPade : List F -> GUESSRESULT
          ++ \spad{guessPade(l, options)} tries to find a rational function
          ++ whose first Taylor coefficients are given by l, using the default
          ++ options described in \spadtype{GuessOptionFunctions0}. It is
          ++ equivalent to \spadfun{guessADE}\spad{(l, options)} with
          ++ \spad{maxDerivative == 0, maxPower == 1, allDegrees == true}.

        guessRec : List F -> GUESSRESULT
          ++ \spad{guessRec l} tries to find an ordinary difference equation
          ++ whose first values are given by l, using the default options
          ++ described in \spadtype{GuessOptionFunctions0}.

        guessRec : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessRec(l, options)} tries to find an ordinary difference
          ++ equation whose first values are given by l, using the given
          ++ options.

        guessPRec : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessPRec(l, options)} tries to find a linear recurrence
          ++ with polynomial coefficients whose first values are given by l,
          ++ using the given options. It is equivalent to
          ++ \spadfun{guessRec}\spad{(l, options)} with \spad{maxPower == 1}.

        guessPRec : List F -> GUESSRESULT
          ++ \spad{guessPRec l} tries to find a linear recurrence with
          ++ polynomial coefficients whose first values are given by l, using
          ++ the default options described in
          ++ \spadtype{GuessOptionFunctions0}. It is equivalent to
          ++ \spadfun{guessRec}\spad{(l, maxPower == 1)}.

        guessRat : (List F, LGOPT) -> GUESSRESULT
          ++ \spad{guessRat(l, options)} tries to find a rational function
          ++ whose first values are given by l, using the given options. It is
          ++ equivalent to \spadfun{guessRec}\spad{(l, maxShift == 0, maxPower
          ++ == 1, allDegrees == true)}.

        guessRat : List F -> GUESSRESULT
          ++ \spad{guessRat l} tries to find a rational function whose first
          ++ values are given by l, using the default options described in
          ++ \spadtype{GuessOptionFunctions0}. It is equivalent to
          ++ \spadfun{guessRec}\spad{(l, maxShift == 0, maxPower == 1,
          ++ allDegrees == true)}.

        algDepHP : (List List F, LGOPT) -> HPSPEC
          ++ \spad{algDepHP(list, options)} returns a specification
          ++ for Hermite-Pade approximation looking for algebraic
          ++ dependencies

        diffHP : LGOPT -> HPSPEC
          ++ \spad{diffHP options} returns a specification for Hermite-Pade
          ++ approximation with the differential operator

        substHP : LGOPT -> HPSPEC
          ++ \spad{substHP options} returns a specification for Hermite-Pade
          ++ approximation with the substitution operator

        shiftHP : LGOPT -> HPSPEC
          ++ \spad{shiftHP options} returns a specification for Hermite-Pade
          ++ approximation with the shift operator

        if F has RetractableTo Symbol and S has RetractableTo Symbol then

            shiftHP : Symbol -> (LGOPT -> HPSPEC)
              ++ \spad{shiftHP options} returns a specification for
              ++ Hermite-Pade approximation with the $q$-shift operator, or,
              ++ if \spad{maxMixedDegree > 0} for mixed shifts

            diffHP : Symbol -> (LGOPT -> HPSPEC)
              ++ \spad{diffHP options} returns a specification for Hermite-Pade
              ++ approximation with the $q$-dilation operator

            guessRec : Symbol -> GUESSER
              ++ \spad{guessRec q} returns a guesser that finds an ordinary
              ++ q-difference equation whose first values are given by l, using
              ++ the given options.

            guessPRec : Symbol -> GUESSER
              ++ \spad{guessPRec q} returns a guesser that tries to find
              ++ a linear q-recurrence with polynomial coefficients whose first
              ++ values are given by l, using the given options. It is
              ++ equivalent to \spadfun{guessRec}\spad{(q)} with
              ++ \spad{maxPower == 1}.

            guessRat : Symbol -> GUESSER
              ++ \spad{guessRat q} returns a guesser that tries to find a
              ++ q-rational function whose first values are given by l, using
              ++ the given options. It is equivalent to \spadfun{guessRec} with
              ++ \spad{(l, maxShift == 0, maxPower == 1, allDegrees == true)}.

            guessADE : Symbol -> GUESSER
              ++ \spad{guessADE q} returns a guesser that tries to find an
              ++ algebraic differential equation for a generating function
              ++ whose first Taylor coefficients are given by l, using the
              ++ given options.

            guessHolo : Symbol -> GUESSER
              ++ \spad{guessHolo q} returns a guesser that tries to find a
              ++ linear differential equation for a generating function whose
              ++ first Taylor coefficients are given by l, using the given
              ++ options.

  Implementation == add

    GUESSEB ==> GuessExpBin(F, S, EXPRR, retract, coerce)

    guessExpRat(list : List F) : GUESSRESULT ==
        guessExpRat(list, [])$GUESSEB

    guessExpRat(list : List F, options : LGOPT) : GUESSRESULT ==
        guessExpRat(list, options)$GUESSEB

    guessBinRat(list : List F) : GUESSRESULT ==
        guessBinRat(list, [])$GUESSEB

    guessBinRat(list : List F, options : LGOPT) : GUESSRESULT ==
        guessBinRat(list, options)$GUESSEB

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        guessExpRat(q : Symbol) : GUESSER ==
            guessExpRat(q)$GUESSEB

        guessBinRat(q : Symbol) : GUESSER ==
            guessBinRat(q)$GUESSEB


    -- general utilities

    getVariables(lF : List F) : List EXPRR ==
        lvar : List Symbol := []

        if S has variables : S -> List Symbol then
            if F is S then
                ll : List List Symbol
                   := map((f : S) : (List Symbol) +->
                              variables(f)$S, lF pretend List S
                         )$ListFunctions2(S, List Symbol)

            else if F is Fraction S then
                ll : List List Symbol
                   :=  map((g : F) : (List Symbol) +->
                               setUnion(variables(numer(g)$Fraction(S)),
                                        variables(denom(g)$Fraction(S))), lF
                          )$ListFunctions2(F, List Symbol)

            else error "Guess: type parameter F should be either"
                       " equal to S or equal to Fraction S"

            lvar := reduce((a : List Symbol, b : List Symbol
                           ) : List Symbol +-> setUnion(a, b), ll)
        else lvar := []

        map((mys : Symbol) : EXPRR +-> mys::EXPRR,
            lvar)$ListFunctions2(Symbol, EXPRR)

    SUPS2SUPF(p : SUP S) : SUP F ==
      if F is S then
        p pretend SUP(F)
      else if F is Fraction S then
        map((z1 : S) : F +-> coerce(z1)$Fraction(S), p)
          $SparseUnivariatePolynomialFunctions2(S, F)
      else error "Guess: type parameter F should be either"
                 " equal to S or equal to Fraction S"

    -- Types for Operators

    -- some useful types for Ore operators that work on series

    -- the differentiation operator
    DIFFSPECX ==> (EXPRR, Symbol, NonNegativeInteger) -> EXPRR
                                           -- eg.: f(x), k +-> f(q^k*x)
                                           --      f(x), k +-> D(f, x, k)
    DIFFSPECS ==> (UFPSF, NonNegativeInteger) -> UFPSF
                                           -- eg.: f(x), k +-> f(q^k*x)
                                           -- eg.: f(x), k +-> f(x)/x^k

    DIFFSPECSF ==> (UFPSSUPF, NonNegativeInteger) -> UFPSSUPF
                                           -- eg.: f(x), k +-> f(q^k*x)

    -- the third argument is the modulus, the last q
    DIFFSPECSV ==> (U32Vector, NonNegativeInteger, Integer, Integer
                   ) -> U32Vector

    -- the constant term for the inhomogeneous case

    DIFFSPEC1X ==> Symbol -> EXPRR

    DIFFSPEC1 ==> UFPSF

    DIFFSPEC1F ==> UFPSSUPF

    DIFFSPEC1V ==> U32Vector

)if false

\subsubsection{Streams}\label{sec : streams}

In this section we define some functions that provide streams for
[[HermitePade]].

\spad{FilteredPartitionStream} returns a stream of partitions.  We will
interpret each partition $(\lambda_1, \lambda_2, \dots)$ as the expression
$\prod_i D(f, \lambda_i-1)$, where the meaning of $D$ can be differentiation,
shift, or substitution.  For given $n$ we will then try to find a linear
combination of the first $n$ expressions in the stream, such that its Taylor
coefficients match the given data.

It is not clear whether we should \lq prefer\rq\ shifting and
differentiation over powering. Currently, we produce the stream
\begin{equation*}
  \begin{array}{rrrrrrrrr}
    \emptyset& 1& 11 & 2 & 111& 2 1 & 3  & 1111\\
            1& f& f^2& f'& f^3& f f'& f''& f^4 &\dots
  \end{array}
\end{equation*}

Maybe it would be better to produce
\begin{equation*}
  \begin{array}{rrrrrrrrr}
    \emptyset& 1& 2 & 11 & 3  & 21  & 111& 4\\
            1& f& f'& f^2& f''& f f'& f^3& f''' &\dots
  \end{array}
\end{equation*}
instead, i.e., to leave the partitions unconjugated. Note however, that
shifting and differentiation decrease the number of valid terms, while
powering does not.

\begin{ToDo}
  \begin{itemize}
  \item should we eliminate lists that have nontrivial gcd?
  \item should \spad{Somos} imply that there at least one partition contains a
    part equal to one?
  \end{itemize}
\end{ToDo}

)endif

    FilteredPartitionStream(options : LGOPT) : Stream List Integer ==
        maxP := maxPower(options)$GOPT0
        homo := homogeneous(options)$GOPT0
        if homo case Boolean then
            homo => error "Guess: internal error - homogeneous should not"
                          " be true in FilteredPartitionStream"
        else
            maxP := homo::PI

        maxD := maxDerivative(options)$GOPT0
        Somo : Union(PositiveInteger, Boolean) := Somos(options)$GOPT0
        if Somo case Boolean then
            Somo => error "Guess: internal error - Somos should not be"
                          " true in FilteredPartitionStream"
        else
            if maxD case NNI then
                maxD := min(maxD::NNI, Somo::PI::NNI)
            else
                maxD := Somo::PI::NNI

        s : Stream List Integer
        if maxD case NNI and maxP case PI then
            s := partitions(maxD::NNI+1, maxP::PI)$PartitionsAndPermutations
        else
            s1 : Stream Integer := stream(inc, 1)$Stream(Integer)
            s2 : Stream Stream List Integer :=
                maxD case NNI =>
                   map(z1 +-> partitions(maxD + 1, z1, z1)
                                        $PartitionsAndPermutations,
                       s1)$StreamFunctions2(Integer, Stream List Integer)

                maxP case PI =>
                   map(z1 +-> partitions(z1, maxP, z1)
                                        $PartitionsAndPermutations,
                       s1)$StreamFunctions2(Integer, Stream List Integer)

                map(z1 +-> partitions(z1)$PartitionsAndPermutations,
                    s1)$StreamFunctions2(Integer, Stream List Integer)

            s3 : Stream List Integer
                := concat(s2)$StreamFunctions1(List Integer)
            s := cons([], s3)

        s := conjugates(s)$PartitionsAndPermutations

        if homo case PI then
            s := select(z1 +-> # z1 = (maxP::PI), s)

        if Somo case PI then
            s := select(z1 +-> reduce(_+, z1, 0) = Somo::PI::NNI + # z1, s)

        s

)if false

The following three functions [[termAsEXPRR]], [[termAsUFPSF]],
[[termAsUFPSSUPF]] transform a partition into a product of derivatives of
[[f]], using the given operators.  We need to provide the same functionality
for expressions, series and series with a transcendental element.  Only for
expressions we do not provide a version using the Hadamard product, although it
would be quite easy to define an appropriate operator on expressions.

A partition $(\lambda_1^{p_1}, \lambda_2^{p_2}, \dots)$ is transformed into the
expression $(f^{(\lambda_1-1)})^{p_1}(f^{(\lambda_2-1)})^{p_2}\cdots$, i.e.,
the size of the part is interpreted as derivative, the exponent as power.

)endif

    termAsEXPRR(f : EXPRR, xx : Symbol, partition : List Integer,
                DX : DIFFSPECX, D1X : DIFFSPEC1X) : EXPRR ==
        if empty? partition then D1X(xx)
        else
            ll : List List Integer := powers(partition)$Partition

            fl : List EXPRR := [DX(f, xx, (first part-1)::NonNegativeInteger)
                               ^ second(part)::NNI for part in ll]
            reduce(_*, fl)

    termAsU32VectorDiff : (U32Vector, List Integer, Integer,
                           NonNegativeInteger) ->  U32Vector
    termAsU32VectorDiff(f, partition, prime, degreeLoss) ==
        if empty? partition then
            construct(cons(1, [0 for i in degreeLoss+2..#f]))$U32Vector
        else
            d := (#f - degreeLoss - 1)::NonNegativeInteger
            ll : List List Integer := powers(partition)$Partition
    -- first of each element of ll is the derivative, second is the power

            fl : List U32Vector
               := [pow(differentiate(f, (first part -1)::NonNegativeInteger,
                                prime)$U32VectorPolynomialOperations,
                       second(part)::PI,
                       d,
                       prime)$U32VectorPolynomialOperations for part in ll]

            reduce((f1, f2) +-> truncated_multiplication(f1, f2, d, prime
                                   )$U32VectorPolynomialOperations, fl)

    termAsU32VectorShift : (U32Vector, List Integer, Integer,
                            NonNegativeInteger) -> U32Vector
    termAsU32VectorShift(f, partition, prime, degreeLoss) ==
        if empty? partition then
            construct([1 for i in degreeLoss+1..#f])$U32Vector
        else
            d := (#f - degreeLoss - 1)::NonNegativeInteger
            ll : List List Integer := powers(partition)$Partition

    -- first of each element of ll is the derivative, second is the power
            fl : List U32Vector
               := [construct([f(j)^(second(part)::PI) rem prime _
                              for j in first part -1..first part -1+d]
                            )$U32Vector _
                   for part in ll]

            r := new(d+1, 0)$U32Vector
            copy_first(r, first fl, d+1)$U32VectorPolynomialOperations
            for i in 0..d repeat
                for e in rest fl repeat
                    r(i) := QSMULMOD32(r(i), e(i), prime)$Lisp

            r

    termAsUFPSF(f : UFPSF, partition : List Integer, DS : DIFFSPECS,
                D1 : DIFFSPEC1) : UFPSF ==
        if empty? partition then D1
        else
            ll : List List Integer := powers(partition)$Partition

    -- first of each element of ll is the derivative, second is the power

            fl : List UFPSF := [DS(f, (first part -1)::NonNegativeInteger) _
                               ^ second(part)::NNI for part in ll]

            reduce(_*, fl)

    -- returns \prod f^(partition.i), but using the Hadamard product
    termAsUFPSF2(f : UFPSF, partition : List Integer,
                 DS : DIFFSPECS, D1 : DIFFSPEC1) : UFPSF ==
        if empty? partition then D1
        else
            ll : List List Integer := powers(partition)$Partition

    -- first of each element of ll is the derivative, second is the power

            fl : List UFPSF
                := [map(z1 +-> z1 ^ second(part)::NNI,
                        DS(f, (first part -1)::NNI))
                    for part in ll]

            reduce(hadamard$UFPS1(F), fl)

    -- returns \prod f^(partition.i), but using the Hadamard product
    termAsUFPSSUPF2(f : UFPSSUPF, partition : List Integer,
                    DSF : DIFFSPECSF, D1F : DIFFSPEC1F) : UFPSSUPF ==
        if empty? partition then D1F
        else
            ll : List List Integer := powers(partition)$Partition

    -- first of each element of ll is the derivative, second is the power

            fl : List UFPSSUPF
               := [map(z1 +-> z1 ^ second(part)::NNI,
                       DSF(f, (first part -1)::NNI)) for part in ll]

            reduce(hadamard$UFPS1(SMPF), fl)

    -- The following functions transform the stream of partitions into the
    -- corresponding streams of power series or expressions.

    -- for functions
    ADEguessStream(f : UFPSF, partitions : Stream List Integer,
                   DS : DIFFSPECS, D1 : DIFFSPEC1) : Stream UFPSF ==
        map((z1 : List Integer) : UFPSF +-> termAsUFPSF(f, z1, DS, D1),
            partitions)$StreamFunctions2(List Integer, UFPSF)

    -- for coefficients, i.e., using the Hadamard product
    ADEguessStream2(f : UFPSF, partitions : Stream List Integer,
                    DS : DIFFSPECS, D1 : DIFFSPEC1) : Stream UFPSF ==
        map((z1 : List Integer) : UFPSF +-> termAsUFPSF2(f, z1, DS, D1),
            partitions)$StreamFunctions2(List Integer, UFPSF)

    PAIR ==> List Integer

    guessModGenCached(cache : PrimitiveArray U32Vector,
                      np : NonNegativeInteger,
                      sigma : Integer,
                      addOne : Boolean,
                      maxD : NonNegativeInteger,
                      prodl : List PAIR,
                      terml : List Integer,
                      prime : Integer) : Vector U32Vector ==
        for i in maxD+1..maxD+np for pp in prodl repeat
            cache(i) := truncated_multiplication(cache first pp,
                              cache second pp, sigma-1, prime
                              )$U32VectorPolynomialOperations
        nt := # terml
        if addOne then
            res := new(nt + 1, empty()$U32Vector)$Vector(U32Vector)
            res1 := new(sigma::NNI, 0)$U32Vector
            res1(0) := 1
            res(1) := res1
            for i in 2..nt+1 for r in terml repeat
                res(i) := cache r
        else
            res := new(nt, empty()$U32Vector)$Vector(U32Vector)
            for i in 1..nt for r in terml repeat
                res(i) := cache r
        res

    DEPguessModGenCached(fl : List(U32Vector),
                         addOne : Boolean,
                         maxD : NonNegativeInteger,
                         prodl : List PAIR,
                         terml : List Integer,
                         prime : Integer) : Vector U32Vector ==
        #fl < maxD + 1 =>
            error "DEPguessModGenCached: #fl < maxD + 1"
        f := first(fl)
        sigma := #f
        np := # prodl
        cache := new(maxD + 1 + np, empty()$U32Vector
                    )$(PrimitiveArray U32Vector)
        for i in 0..maxD for f in fl repeat
            cache(i) := f
        guessModGenCached(cache, np, sigma, addOne, maxD, prodl,
                          terml, prime)

    ADEguessModGenCached(fl : List(U32Vector),
                         addOne : Boolean,
                         maxD : NonNegativeInteger,
                         prodl : List PAIR,
                         terml : List Integer,
                         prime : Integer) : Vector U32Vector ==
        #fl ~= 1 =>
            error "ADEguessModGenCached: #fl ~= 1"
        f := first(fl)
        sigma := #f - maxD
        np := # prodl
        cache := new(maxD + 1 + np, empty()$U32Vector
                    )$(PrimitiveArray U32Vector)
        cache(0) := f
        for i in 1..maxD repeat
            cache(i) := differentiate(cache(i-1), prime)
                                     $U32VectorPolynomialOperations
        guessModGenCached(cache, np, sigma, addOne, maxD, prodl,
                          terml, prime)

    ADEtestGenCached(f : UFPSSUPF,
                     addOne : Boolean,
                     maxD : NonNegativeInteger,
                     prodl : List PAIR,
                     terml : List Integer,
                     DSF : DIFFSPECSF,
                     D1F : DIFFSPEC1F) : Vector UFPSSUPF ==
        np := # prodl
        cache := new(maxD + 1 + np, 0$UFPSSUPF)$(PrimitiveArray UFPSSUPF)
        cache(0) := f
        for i in 1..maxD repeat
            cache(i) := DSF(cache(0), i)

        for i in maxD+1..maxD+np for pp in prodl repeat
            cache(i) := cache first pp * cache second pp

        nt := # terml
        if addOne then
            res := new(nt + 1, 0$UFPSSUPF)$Vector(UFPSSUPF)
            res(1) := D1F
            for i in 2..nt+1 for r in terml repeat
                res(i) := cache r
        else
            res := new(nt, 0$UFPSSUPF)$Vector(UFPSSUPF)
            for i in 1..nt for r in terml repeat
                res(i) := cache r
        res

    multiplyExponents : (U32Vector, NNI) -> U32Vector
    multiplyExponents(f, n) ==
        nn := #f
        f0 := new(nn, 0)$U32Vector
        deg := nn - 1
        for i in 0..deg while n*i <= deg repeat
            f0.(n*i) := f.i
        f0

    FEguessModGenCached(fl : List(U32Vector),
                         addOne : Boolean,
                         maxD : NonNegativeInteger,
                         prodl : List PAIR,
                         terml : List Integer,
                         prime : Integer) : Vector U32Vector ==
        #fl ~= 1 =>
            error "FEguessModGenCached: #fl ~= 1"
        f := first(fl)
        sigma := #f
        np := # prodl
        cache := new(maxD + 1 + np, empty()$U32Vector
                    )$(PrimitiveArray U32Vector)
        cache(0) := f
        for i in 1..maxD repeat
            cache(i) := multiplyExponents(cache(0), i+1)
    --                                 $U32VectorPolynomialOperations
        guessModGenCached(cache, np, sigma, addOne, maxD, prodl,
                          terml, prime)

    -- checks whether partition is a product of two elememts in partitions
    optimiseFactor(partition : Partition,
                   partitions : List Partition) : Union(PAIR, "failed") ==
        n := # partitions
        for p in partitions for i in 1..n repeat
             r := subtractIfCan(partition, p)
             if r case Partition then
                 j := position(r, partitions)
                 if j > 0 then return [i, j]
        "failed"

    -- destructively adds rule for computing p to prods0 and parts0
    optimiseProducts1(p : Partition, _
                      prods0 : Reference List PAIR, _
                      parts0 : Reference List Partition) : Void ==
        r : Union(PAIR, "failed")
        if (r := optimiseFactor(p, deref parts0)) case "failed" then
            optimiseProducts1(partition(rest(p::List Integer))$Partition,
                              prods0, parts0)
            r := optimiseFactor(p, deref parts0)

        r case "failed" =>
                error "Internal error: failed adding intermediate partition"

        lp0 := #(deref parts0)
        setref(prods0, cons([lp0-first(r::PAIR), lp0-second(r::PAIR)], _
                            deref prods0))
        setref(parts0, cons(p, deref parts0))

    -- addOne says that the constant term is part of the list
    -- maxDiff is the maximal derivative encountered
    -- cache.i = prods.j.1 * prods.j.2
    -- terms is the list of indices in cache actually appearing,
    --       where 0 to maxD refer to derivatives
    -- we assume that partitions is a set
    optimiseProducts(partitions : List List Integer)
                     : Record(addOne : Boolean,
                             maxDiff : NonNegativeInteger,
                             prods : List PAIR,
                             terms : List Integer) ==

        addOne0 := empty? first partitions
        if addOne0 then
            partitions := rest partitions
            if empty? partitions then return [addOne0, 0, [], []]
    -- this is actually degree+1
        maxD0 : Integer := reduce(max, map((p : List Integer) : Integer +->
                                           first p, partitions)
                                         $ListFunctions2(List Integer, Integer))
        prods0 : Reference List PAIR := ref []
        terms0 : List Integer := []

    -- we need the list of partitions we have already in the cache to find out
    -- whether the next partition is a product of two previous ones
        parts0 : Reference List Partition := _
             ref [partition([i]) for i in maxD0..1 by -1]
        for p in partitions repeat
            one?(# p) =>
                terms0 := cons(first(p)-1, terms0)
            optimiseProducts1(partition(p)$Partition, prods0, parts0)
            terms0 := cons(#(deref parts0) - 1, terms0)

        [addOne0, (maxD0-1)::NNI, reverse! deref prods0, reverse! terms0]

    ADEguessModOptimisedGen : (List List Integer) -> SEQGEN
    ADEguessModOptimisedGen(partitions) : SEQGEN ==
        r := optimiseProducts(partitions)
        (fl : List(U32Vector), prime : Integer, q : Integer) _
           : Vector U32Vector +-> _
            ADEguessModGenCached(fl, r.addOne, r.maxDiff, r.prods,
                                 r.terms, prime)

    FEguessModOptimisedGen : (List List Integer) -> SEQGEN
    FEguessModOptimisedGen(partitions) : SEQGEN ==
        r := optimiseProducts(partitions)
        (fl : List(U32Vector), prime : Integer, q : Integer) _
           : Vector U32Vector +-> _
            FEguessModGenCached(fl, r.addOne, r.maxDiff, r.prods,
                                r.terms, prime)

    DEPguessModOptimisedGen : (List List Integer) -> SEQGEN
    DEPguessModOptimisedGen(partitions) : SEQGEN ==
        r := optimiseProducts(partitions)
        (fl : List(U32Vector), prime : Integer, q : Integer) _
           : Vector(U32Vector) +-> _
              DEPguessModGenCached(fl, r.addOne, r.maxDiff, r.prods,
                                   r.terms, prime)

    ADEguessModGen2 : (List List Integer, NonNegativeInteger) -> SEQGEN
    ADEguessModGen2(partitions, d) : SEQGEN ==
        (fl, prime, q) +->
             #fl ~= 1 =>
                 error "ADEguessModGen2, lambda: #fl ~= 1"
             f := first(fl)
             vector([termAsU32VectorShift(f, p, prime, d) _
                         for p in partitions])$(Vector(U32Vector))

    ADEtestOptimisedGen : (List List Integer, DIFFSPECSF, DIFFSPEC1F) -> FPSGEN
    ADEtestOptimisedGen(partitions, DSF, D1F) : FPSGEN ==
        r := optimiseProducts(partitions)
        (f : UFPSSUPF) : Vector UFPSSUPF +-> _
           ADEtestGenCached(f, r.addOne, r.maxDiff, r.prods, r.terms, DSF, D1F)

    ADEtestGen2 : (List List Integer, DIFFSPECSF, DIFFSPEC1F) -> FPSGEN
    ADEtestGen2(partitions, DSF, D1F) : FPSGEN ==
        (f : UFPSSUPF) : Vector UFPSSUPF +-> _
            vector([termAsUFPSSUPF2(f, p, DSF, D1F) _
                    for p in partitions])$(Vector UFPSSUPF)

    ADEEXPRRStream(f : EXPRR, xx : Symbol, partitions : Stream List Integer,
                   DX : DIFFSPECX, D1X : DIFFSPEC1X) : Stream EXPRR ==
        map((z1 : List Integer) : EXPRR +-> termAsEXPRR(f, xx, z1, DX, D1X),
            partitions)$StreamFunctions2(List Integer, EXPRR)

)if false

The entries of the following stream indicate how many terms we loose when
applying one of the power and shift or differentiation operators. More
precisely, the $n$\textsuperscript{th} entry of the stream takes into account
all partitions up to index $n$. Thus, the entries of the stream are weakly
increasing.

)endif

    ADEdegreeStream(partitions : Stream List Integer) : Stream NNI ==
        scan(0, (z1 : List Integer, z2 : NNI) : NNI +->
               max((if empty? z1 then 0 else (first z1 - 1)::NNI), z2),
             partitions)$StreamFunctions2(List Integer, NNI)

)if false

\subsubsection{Operators}

We need to define operators that transform series for differentiation and
shifting. We also provide operators for $q$-analogs. The functionality
corresponding to powering and taking the Hadamard product is provided by the
streams, see Section~\ref{sec : streams}.

We have to provide each operator in three versions:
\begin{itemize}
\item for expressions,
\item for series, and
\item for series with an additional transcendental element.
\end{itemize}

The latter makes it possible to detect lazily whether a computed coefficient of
a series is valid or not.

Furthermore, we have to define for each operator how to extract the coefficient
of $x^k$ in $z^l f(x)$, where multiplication with $z$ is defined depending on
the operator. Again, it is necessary to provide this functionality for
expressions, series and series with a transcendental element.

Finally, we define a function that returns the diagonal elements $c_{k, k}$ in
the expansion $\langle x^k\rangle z f(x) = \sum_{i=0}^k c_{k, i} \langle
x^i\rangle f(x)$, and an expression that represents the constant term for the
inhomogeneous case.

)endif

    -- The Differentiation Setting
    -- In this setting, we have $z f(x) := xf(x)$.

    diffDX : DIFFSPECX
    diffDX(expr, x, n) == D(expr, x, n)

    diffDS : DIFFSPECS
    diffDS(s, n) == D(s, n)

    diffDSF : DIFFSPECSF
    diffDSF(s, n) ==
    -- I have to help the compiler to choose the right signature...
        if SMPF has _*: (NonNegativeInteger, SMPF) -> SMPF
        then D(s, n)


    -- The next three functions extract the coefficient of $x^k$ in
    -- $z^l f(x)$. Only, for expressions, we rather need
    -- $\sum_{k\ge0} \langle x^k\rangle z^l f(x)$, i.e., the function
    -- itself, which is by definition equal to $x^l f(x)$.

    diffAX : DIFFSPECAX
    diffAX(l : NNI, x : Symbol, f : EXPRR) : EXPRR ==
        (x::EXPRR)^l * f

    diffA : DIFFSPECA
    diffA(k : NNI, l : NNI, f : SUP S) : S ==
        DiffAction(k, l, f)$FFFG(S, SUP S)

    diffAF : DIFFSPECAF
    diffAF(k : NNI, l : NNI, f : UFPSSUPF) : SMPF ==
        DiffAction(k, l, f)$FFFG(SMPF, UFPSSUPF)

    diffC : DIFFSPECC
    diffC(total : NNI) : List S == DiffC(total)$FFFG(S, SUP S)

    diff1X : DIFFSPEC1X
    diff1X(x : Symbol)== 1$EXPRR

    diffHP(options : LGOPT) ==
        if not zero? maxMixedDegree(options)$GOPT0 then
            error "Guess: no support for mixed shifts in differential"
                  " equations"
        dk := displayKind(options)$GOPT0
        if dk = 'displayAsGF then
            partitions := FilteredPartitionStream options
            degrees := ADEdegreeStream partitions
            [degrees, _
             (z1 : UFPSF) : Stream UFPSF +-> _
                 ADEguessStream(z1, partitions, diffDS, 1$UFPSF), _
             (o : NNI) : SEQGEN +-> _
               ADEguessModOptimisedGen entries complete first(partitions, o), _
             (l : List PositiveInteger) : FPSGEN +-> _
                 ADEtestOptimisedGen([partitions.i for i in l], diffDSF,
                                     1$UFPSSUPF), _
             (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                 ADEEXPRRStream(z1, z2, partitions, diffDX, diff1X), _
             'diffHP, 'dummy, diffA, diffAF, diffAX, diffC]$HPSPEC
        else
            error "Guess: guessADE supports only displayAsGF"

    monomialAsEXPRR(fl0 : List EXPRR, partition : List Integer
                    ) : EXPRR ==
        if empty? partition then 1
        else
            ll : List List Integer := powers(partition)$Partition
            fl : List EXPRR := [fl0((first part)::PositiveInteger) _
                               ^ second(part)::NNI for part in ll]
            reduce(_*, fl)

    DEPEXPRRStream(fl : List EXPRR, partitions : Stream List Integer
                  ) : Stream EXPRR ==
        map((z1 : List Integer) : EXPRR +-> monomialAsEXPRR(fl, z1),
            partitions)$StreamFunctions2(List Integer, EXPRR)

    list2UFPSF : List F -> UFPSF
    list2UFPSSUPF : List F -> UFPSSUPF

    get_fnames(n : Integer, options : LGOPT) : List EXPRR ==
        syms := functionNames(options)$GOPT0
        n1 := #syms
        n1 > 0 and n1 ~=n =>
            error "numbers of sequences and names are different"
        if n1 = 0 then
            snums : List String :=  [convert(q) for q in 1..n]
            syms :=
                [concat("%", concat("f", i))::Symbol  for i in snums]
        [sym::EXPRR for sym in syms]

    algDepHP(lists, options) ==
        if not zero? maxMixedDegree(options)$GOPT0 then
            error "Guess: no support for mixed shifts in algebraic"
                  " dependencies"
        dk := displayKind(options)$GOPT0
        dk ~= 'displayAsEQ =>
            error "Guess: guessAlgDep supports only displayAsEQ"
        partitions := FilteredPartitionStream options
        degrees := ADEdegreeStream partitions
        fl := get_fnames(#lists, options)

        -- We use fake derivatives to pass rest of the list (second
        -- term and later) to other routines.  However, we need
        -- to pass the real list to the modular solver.
        fakeDS := (w1 : UFPSF, w2 : NNI) : UFPSF +-> _
                      list2UFPSF(lists.(w2+1))
        fakeDSF := (w1 : UFPSSUPF, w2 : NNI) : UFPSSUPF +-> _
                      list2UFPSSUPF(lists.(w2+1))
        [map((c : List Integer) : NNI +-> 0, partitions _
            )$StreamFunctions2(List Integer, NNI), _
             (z1 : UFPSF) : Stream UFPSF +-> _
                 ADEguessStream(z1, partitions, fakeDS, 1$UFPSF), _
             (o : NNI) : SEQGEN +-> _
               DEPguessModOptimisedGen entries complete first(partitions, o), _
             (l : List PositiveInteger) : FPSGEN +-> _
                 ADEtestOptimisedGen([partitions.i for i in l], _
                                     fakeDSF, 1$UFPSSUPF), _
             (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                 DEPEXPRRStream(fl, partitions), _
             'diffHP, 'dummy, diffA, diffAF, diffAX, diffC]$HPSPEC


    -- The Substitution - Functional Equation Setting
    -- In this setting, we have $z f(x) := x f(x)$, but instead of
    -- differentiation, we substitute powers of $x$ into $f$.

    substDX : DIFFSPECX
    substDX(expr, x, n) == eval(expr, x::EXPRR, (x::EXPRR)^(n+1))

    substDS : DIFFSPECS
    substDS(s, n) == multiplyExponents(s, (n+1)::PositiveInteger)

    substDSF : DIFFSPECSF
    substDSF(s, n) == multiplyExponents(s, (n+1)::PositiveInteger)

    substHP(options : LGOPT) ==
        if not zero? maxMixedDegree(options)$GOPT0
        then error "Guess: no support for mixed shifts in functional equations"
        dk := displayKind(options)$GOPT0
        if dk = 'displayAsGF then
            partitions := FilteredPartitionStream options
            [map((c : List Integer) : NNI +-> 0, partitions
                )$StreamFunctions2(List Integer, NNI), _
             (z1 : UFPSF) : Stream UFPSF +-> _
                 ADEguessStream(z1, partitions, substDS, 1$UFPSF), _
             (o : NNI) : SEQGEN +-> _
               FEguessModOptimisedGen entries complete first(partitions, o), _
             (l : List PositiveInteger) : FPSGEN +-> _
                 ADEtestOptimisedGen([partitions.i for i in l], substDSF,
                                     1$UFPSSUPF), _
             (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                 ADEEXPRRStream(z1, z2, partitions, substDX, diff1X), _
             'diffHP, 'dummy, diffA, diffAF, diffAX, diffC]$HPSPEC
        else
            error "Guess: guessSubst supports only displayAsGF"


    -- $q$-dilation
    -- In this setting, we also have $z f(x) := x f(x)$, therefore
    -- we can reuse some of the functions of the previous paragraph.
    -- Differentiation is defined by $D_q f(x, q) = f(qx, q)$.

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        qDiffDX(q : Symbol, expr : EXPRR, x : Symbol, n : NonNegativeInteger
               ) : EXPRR == eval(expr, x::EXPRR, (q::EXPRR)^n*x::EXPRR)

        qDiffDS(q : Symbol, s : UFPSF, n : NonNegativeInteger) : UFPSF ==
            multiplyCoefficients((z1 : Integer) : F +->
                                     (q::F)^((n*z1)::NonNegativeInteger), s)

        qDiffDSV(f : U32Vector, n : NonNegativeInteger, prime : Integer,
                 q : Integer) : U32Vector ==
            (zero? n) => f
            d := #f - 1
            r := new(d::NNI+1, 0)$U32Vector
            r.0 := f.0
            if d > 0 then
                Q : Integer := 1
                qn := q^n rem prime
                for i in 1..d repeat
                    Q := QSMULMOD32(qn, Q, prime)$Lisp
                    r.i := QSMULMOD32(f.i, Q, prime)$Lisp
            r

        qDiffDSF(q : Symbol, s : UFPSSUPF, n : NonNegativeInteger
                ) : UFPSSUPF ==
            multiplyCoefficients((z1 : Integer) : SMPF +->
                        (q::F::SMPF)^((n*z1)::NonNegativeInteger), s)


        termAsU32VectorqDiff : (U32Vector, List Integer, Integer, Integer,
                                NNI) -> U32Vector
        termAsU32VectorqDiff(f, partition, prime, q, dl) ==
            if empty? partition then
                construct(cons(1, [0 for i in dl+2..#f]))$U32Vector
            else
                d := (#f - dl - 1)::NonNegativeInteger
                ll : List List Integer := powers(partition)$Partition
        -- first of each element of ll is the derivative, second is the power

                fl : List U32Vector
                   := [pow(qDiffDSV(f, (first part -1)::NonNegativeInteger,
                                    prime, q),
                           second(part)::PI,
                           d,
                           prime)$U32VectorPolynomialOperations for part in ll]

                reduce((f1, f2) +->
                    truncated_multiplication(f1, f2, d, prime
                                        )$U32VectorPolynomialOperations, fl)

        ADEguessModGenq : (List List Integer, NonNegativeInteger) -> SEQGEN
        ADEguessModGenq(partitions, d) : SEQGEN ==
            (fl, prime, q) +->
                #fl ~= 1 =>
                    error "ADEguessModGenq, lambda: #fl ~= 1"
                f := first(fl)
                vector([termAsU32VectorqDiff(f, p, prime, q, d) _
                            for p in partitions])$(Vector(U32Vector))

        diffHP(q : Symbol) : (LGOPT -> HPSPEC) ==
            (l1 : LGOPT) : HPSPEC +->
                if not zero? maxMixedDegree(l1)$GOPT0 then
                    error "Guess: no support for mixed shifts in"
                          " differential equations"
                if displayKind(l1)$GOPT0 = 'displayAsGF then
                    partitions := FilteredPartitionStream l1
                    [map((c : List Integer) : NNI +-> 0, partitions
                        )$StreamFunctions2(List Integer, NNI), _
                     (z1 : UFPSF) : Stream UFPSF +-> _
                         ADEguessStream(z1, partitions, _
                                        (w1 : UFPSF, w2 : NNI) : UFPSF +-> _
                                            qDiffDS(q, w1, w2), 1$UFPSF), _
                     (o : NNI) : SEQGEN +-> _
                         ADEguessModGenq(entries complete first(partitions,
                                                                o), 0), _
                     (l : List PositiveInteger) : FPSGEN +-> _
                         ADEtestOptimisedGen([partitions.i for i in l], _
                         (w1 : UFPSSUPF, w2 : NNI) : UFPSSUPF +-> _
                             qDiffDSF(q, w1, w2), _
                         1$UFPSSUPF), _
                     (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                         ADEEXPRRStream(z1, z2, partitions, _
                             (w1 : EXPRR, w2 : Symbol, w3 : NNI) : EXPRR +-> _
                                            qDiffDX(q, w1, w2, w3), diff1X), _
                     'qdiffHP, q, diffA, diffAF, diffAX, diffC]$HPSPEC
                else
                    error "Guess: guessADE supports only displayAsGF"


)if false

\paragraph{Shifting} The shift operator transforms a sequence $u(k)$ into
$u(k+1)$.  We also provide operators [[ShiftSXGF]], [[ShiftAXGF]] that act on
the power series, as long as no powering is involved.  In this case, shifting
transforms $f(x)$ into $\frac{f(x)-f(0)}{x}$.

Multiplication with $z$ transforms the coefficients $u(n)$ of the series into
$z u(n) := n u(n)$.  The description in terms of power series is given by
$xDf(x)$.

% The coefficients of $x^n$ are $1, f(n), f(n+1), f(n)^2, f(n)f(n+1), \dots$
% What does this remark mean?

)endif

    ShiftSX(expr : EXPRR, x : Symbol, n : NNI) : EXPRR ==
        eval(expr, x::EXPRR, x::EXPRR+n::EXPRR)

    ShiftSXGF(expr : EXPRR, x : Symbol, n : NNI) : EXPRR ==
        if zero? n then expr
        else
            l := [eval(D(expr, x, i)/factorial(i)::EXPRR, x::EXPRR, 0$EXPRR)_
                  *(x::EXPRR)^i for i in 0..n-1]
            (expr-reduce(_+, l))/(x::EXPRR^n)

    ShiftSS(s : UFPSF, n : NNI) : UFPSF ==
        (((z1 : UFPSF) : UFPSF +-> (quoByVar z1))^n)$MappingPackage1(UFPSF) (s)

    ShiftSF(s : UFPSSUPF, n : NNI) : UFPSSUPF ==
        (((z1 : UFPSSUPF) : UFPSSUPF +-> quoByVar z1)^n
        )$MappingPackage1(UFPSSUPF) (s)


    -- As before, next three functions extract the coefficient of
    -- $x^k$ in $z^l f(x)$.

    ShiftAX(l : NNI, n : Symbol, f : EXPRR) : EXPRR ==
        n::EXPRR^l * f

    ShiftAXGF(l : NNI, x : Symbol, f : EXPRR) : EXPRR ==
        -- I need to help the compiler here, unfortunately
        if zero? l then f
        else
            s := [stirling2(l, i)$IntegerCombinatoricFunctions(Integer)
                     ::EXPRR * (x::EXPRR)^i*D(f, x, i) for i in 1..l]
            reduce(_+, s)

    ShiftA(k : NNI, l : NNI, f : SUP S) : S ==
        ShiftAction(k, l, f)$FFFG(S, SUP S)

    ShiftAF(k : NNI, l : NNI, f : UFPSSUPF) : SMPF ==
        ShiftAction(k, l, f)$FFFG(SMPF, UFPSSUPF)

    ShiftC(total : NNI) : List S ==
        ShiftC(total)$FFFG(S, SUP S)

    shiftHP(options : LGOPT) ==
        if not zero? maxMixedDegree(options)$GOPT0
        then error "Guess: need a symbol for mixed Shifting"
        partitions := FilteredPartitionStream options
        dk := displayKind(options)$GOPT0
        dk ~= 'displayAsRec =>
    --    if maxPower(options)$GOPT0 = 1 then
    --            [(z1: UFPSF): Stream UFPSF +-> ADEguessStream(z1, partitions, ShiftSS, (1-monomial(1, 1))^(-1)), _
    --             (o: NNI): SEQGEN +-> _
    --                 ADEguessModGen(entries complete first(partitions, o), _
    --                          shiftSV, construct([1, 1, 1, 1])$U32Vector), _
    --             ADEdegreeStream partitions, _
    --             (z1: UFPSSUPF): Stream UFPSSUPF +-> _
    --                 ADEtestStream(z1, partitions, ShiftSF, (1-monomial(1, 1))^(-1)), _
    --             (z1: EXPRR, z2: Symbol): Stream EXPRR +-> _
    --                 ADEEXPRRStream(z1, z2, partitions, ShiftSXGF, (w1: Symbol): EXPRR +-> 1/(1-w1::EXPRR)), _
    --             'shiftHP, 'dummy, ShiftA, ShiftAF, ShiftAXGF, ShiftC]$HPSPEC
    --    else
            error "Guess: no support for the Shift operator with displayAsGF"
    --                   and maxPower>1"
        degrees := ADEdegreeStream partitions
        [degrees, _
            (z1 : UFPSF) : Stream UFPSF +-> _
                ADEguessStream2(z1, partitions, ShiftSS,
                                (1-monomial(1, 1))^(-1)), _
            (o : NNI) : SEQGEN +-> _
                ADEguessModGen2(entries complete first(partitions, o),
                                degrees.o), _
            (l : List PositiveInteger) : FPSGEN +->
                ADEtestGen2([partitions.i for i in l], ShiftSF,
                             (1-monomial(1, 1))^(-1)), _
            (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                ADEEXPRRStream(z1, z2, partitions, ShiftSX, diff1X), _
            'shiftHP, 'dummy, ShiftA, ShiftAF, ShiftAX, ShiftC]$HPSPEC

)if false

\paragraph{$q$-Shifting and mixed $q$-Shifting} The $q$-shift also transforms
$u(n)$ into $u(n+1)$, and we can reuse the corresponding functions of the
previous paragraph.  However, this time multiplication with $z$ is defined
differently : the coefficient of $x^k$ in $z u(n)$ is $q^n u(n)$.  We do not
define the corresponding functionality for power series.

To guess mixed shift and $q$-shift equations we use ordinary shifting, but on a
different sequence of power series.  Namely, for each partition, we produce
[[maxMixedDegree]] power series, corresponding to $u(n)$, $q^n u(n)$, $q^{2n}
u(n)$ and so on.
\begin{ToDo}
  The minimal number of series should be [[maxMixedDegree]] here\dots
\end{ToDo}

)endif

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        qShiftAX(q : Symbol, l : NNI, n : Symbol, f : EXPRR) : EXPRR ==
            (q::EXPRR)^(l*n::EXPRR) * f

        qShiftA(q : Symbol, k : NNI, l : NNI, f : SUP S) : S ==
            qShiftAction(q::S, k, l, f)$FFFG(S, SUP S)

        qShiftAF(q : Symbol, k : NNI, l : NNI, f : UFPSSUPF) : SMPF ==
            qShiftAction(q::F::SMPF, k, l, f)$FFFG(SMPF, UFPSSUPF)

        qShiftC(q : Symbol, total : NNI) : List S ==
            qShiftC(q::S, total)$FFFG(S, SUP S)

        qSubstUFPSF(q : Symbol, s : UFPSF, maxMD : NNI) : List UFPSF ==
            [s((q::F)^i*monomial(1, 1)) for i in 0..maxMD]

    --  qSubstUFPSSUPF(q: Symbol, s: UFPSSUPF, maxMD: NNI): List UFPSSUPF ==
    --      [s(((q::F)^i)::SUP(F)*monomial(1, 1)) for i in 0..maxMD]

        qSubstEXPRR(q : Symbol, xx : Symbol, s : EXPRR, maxMD : NNI
                   ) : List EXPRR ==
            [(q::EXPRR)^(i*xx::EXPRR)*s for i in 0..maxMD]

        ADEguessStreamMixShift(q : Symbol, f : UFPSF, _
                               partitions : Stream List Integer, _
                               maxMD : NNI) : Stream UFPSF ==
            s := ADEguessStream2(f, partitions, ShiftSS,
                                 (1-monomial(1, 1))^(-1))
            tensorMap(s, (z1 : UFPSF) : List UFPSF +->
                            qSubstUFPSF(q, z1, maxMD))$StreamTensor(UFPSF)

        ADEguessModGen2Mixed : (Stream List Integer, NonNegativeInteger,
                                NNI, NNI) -> SEQGEN
        ADEguessModGen2Mixed(partitions, dl, maxMD, o) : SEQGEN ==
            (fl, prime, q) +->
                #fl ~= 1 =>
                    error "ADEguessModGen2Mixed: #fl ~= 1"
                f := first(fl)

                d := (#f - dl - 1)::NNI
                qv := new(d+1, 1)$U32Vector
                for i in 1..d repeat
                    qv(i) := (q^i) rem prime

                O := (ceiling(o/(maxMD+1))$Fraction(Integer))::NNI
                vv1 : List U32Vector := [termAsU32VectorShift(f, p, prime, dl)
                                        for p in entries complete
                                           first(partitions, O)]
                l : List Vector U32Vector := []
                vv : List U32Vector
                vn : U32Vector
                lastv : U32Vector := first vv1
                for v in vv1 for w in rest vv1 repeat
                    lastv := w
                    vv := [v]
                    vn := v
                    for i in 1..maxMD repeat
                        vn := copy vn
                        for e in 1..d repeat
                            vn(e) := QSMULMOD32(vn(e), qv(e), prime)$Lisp
                        vv := cons(vn, vv)

                    l := cons(vector reverse! vv, l)

                vv := [lastv]
                vn := lastv
                r := (o rem (maxMD+1))-1
                if r = -1 then r := maxMD
                for i in 1..r repeat
                    vn := copy vn
                    for e in 1..d repeat
                        vn(e) := QSMULMOD32(vn(e), qv(e), prime)$Lisp
                    vv := cons(vn, vv)

                l := cons(vector reverse! vv, l)

                concat(reverse! l)$(Vector U32Vector)

        ADEdegreeStreamMixShift : (Stream List Integer, NNI) -> Stream NNI
        ADEdegreeStreamMixShift(partitions, maxMD) ==
            s := ADEdegreeStream partitions
            tensorMap(s, (z1 : NNI) : List NNI +-> [z1 for i in 0..maxMD]
                     )$StreamTensor(NNI)

        ADEtestMixShiftGen : (Symbol, Stream List Integer, NNI,
                              List PositiveInteger) -> FPSGEN
        ADEtestMixShiftGen(q, partitions, maxMD, l) : FPSGEN ==
            (f : UFPSSUPF) : Vector UFPSSUPF +->
                res : List UFPSSUPF  := []
                f0 : UFPSSUPF
                exponents : List NNI := []
                oldPartition : PositiveInteger :=
                    (ceiling((first l)/(maxMD+1))$Fraction(Integer)
                    )::PositiveInteger

                newPartition : PositiveInteger
                for p in l repeat
                    newPartition := (ceiling(p/(maxMD+1))$Fraction(Integer)
                                    )::PositiveInteger
                    if newPartition > oldPartition then
                        f0 := termAsUFPSSUPF2(f, partitions.oldPartition,
                                              ShiftSF, (1-monomial(1, 1))^(-1))
                        res := concat([f0(((q::F)^i)::SMPF*monomial(1, 1))
                                       for i in exponents],
                                      res)
                        exponents := []
                        oldPartition := newPartition

                    exponents := cons(((p::Integer-1) rem (maxMD+1))::NNI,
                                      exponents)

                f0 := termAsUFPSSUPF2(f, partitions.oldPartition,
                                      ShiftSF, (1-monomial(1, 1))^(-1))
                res := concat([f0(((q::F)^i)::SMPF*monomial(1, 1))
                               for i in exponents],
                              res)

                vector reverse! res

        ADEEXPRRStreamMixShift : (Symbol, EXPRR, Symbol, Stream List Integer,
                                  NNI) -> Stream EXPRR
        ADEEXPRRStreamMixShift(q, f, xx, partitions, maxMD) : Stream EXPRR ==
            s := ADEEXPRRStream(f, xx, partitions, ShiftSX, diff1X)
            tensorMap(s, (z1 : EXPRR) : List EXPRR +->
                           qSubstEXPRR(q, xx, z1, maxMD))$StreamTensor(EXPRR)

        shift_hp_aux(q : Symbol, l1 : LGOPT) : HPSPEC ==
            displayKind(l1)$GOPT0 ~= 'displayAsRec =>
                error "Guess: no support for the qShift operator"
                      " with displayAsGF"
            partitions := FilteredPartitionStream l1
            maxMD : NNI := maxMixedDegree(l1)$GOPT0
            if zero?(maxMD) then
                degrees := ADEdegreeStream partitions
                [degrees, _
                 (z1 : UFPSF) : Stream UFPSF +-> _
                    ADEguessStream2(z1, partitions, ShiftSS, _
                                    (1-monomial(1, 1))^(-1)), _
                 (o : NNI) : SEQGEN +-> _
                    ADEguessModGen2(entries complete first(partitions, o), _
                                       degrees.o), _
                 (l : List PositiveInteger) : FPSGEN +-> _
                    ADEtestGen2([partitions.i for i in l],
                                 ShiftSF, (1-monomial(1, 1))^(-1)), _
                 (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                    ADEEXPRRStream(z1, z2, partitions, ShiftSX, diff1X), _
                 'qshiftHP, q, _
                      (z1 : NNI, z2 : NNI, z3 : SUP S) : S +-> _
                             qShiftA(q, z1, z2, z3), _
                      (z1 : NNI, z2 : NNI, z3 : UFPSSUPF) : SMPF +-> _
                             qShiftAF(q, z1, z2, z3), _
                      (z1 : NNI, z2 : Symbol, z3 : EXPRR) : EXPRR +-> _
                             qShiftAX(q, z1, z2, z3), _
                      (z1 : NNI) : List S +-> qShiftC(q, z1)]$HPSPEC
            else
                degrees := ADEdegreeStreamMixShift(partitions, maxMD)
                [degrees, _
                 (z1 : UFPSF) : Stream UFPSF +-> _
                    ADEguessStreamMixShift(q, z1, partitions, maxMD), _
                 (o : NNI) : SEQGEN +-> _
                    ADEguessModGen2Mixed(partitions, degrees.o, maxMD, o), _
                 (l : List PositiveInteger) : FPSGEN +-> _
                    ADEtestMixShiftGen(q, partitions, maxMD, l), _
                 (z1 : EXPRR, z2 : Symbol) : Stream EXPRR +-> _
                    ADEEXPRRStreamMixShift(q, z1, z2, partitions, maxMD), _
                 'qmixed, q, ShiftA, ShiftAF, ShiftAX, ShiftC]$HPSPEC

        shiftHP(q : Symbol) : (LGOPT -> HPSPEC) ==
            (l1 : LGOPT) : HPSPEC +-> shift_hp_aux(q, l1)


    -- The following operation uses the given action of $z$ on a
    -- function to multiply an $f$ with a polynomial.

    makeEXPRR(DAX : DIFFSPECAX, x : Symbol, p : SUP F, expr : EXPRR) : EXPRR ==
        if zero? p then 0$EXPRR
        else
            coerce(leadingCoefficient p)::EXPRR * DAX(degree p, x, expr) _
            + makeEXPRR(DAX, x, reductum p, expr)

)if false

\subsubsection{Utilities}

[[list2UFPSF]] and [[list2UFPSSUPF]] transform the list passed to the guessing
functions into a series. One might be tempted to transform the list into a
polynomial instead, but the present approach makes computing powers and
derivatives much cheaper, since, because of laziness, only coefficients that
are actually used are computed.

The second of the two procedures augments the list with transcendental
elements.

)endif

    list2UFPSF(list : List F) : UFPSF == series(list::Stream F)$UFPSF

    list2UFPSSUPF(list : List F) : UFPSSUPF ==
        l1 := [e::SMPF for e in list]::Stream SMPF
        s1 : Stream Integer := stream(inc, 0)$Stream(Integer)
        l2 := map(i +-> monomial(1, monomial(1, i::NNI)
                                            $IndexedExponents(NNI))
                                $SMPF, s1)$StreamFunctions2(Integer, SMPF)
        series(concat(l1, l2))$UFPSSUPF


    -- [[SUPF2SUPSUPF]] interprets each coefficient as a univariate polynomial.

    SUPF2SUPSUPF(p : SUP F) : SUP SMPF ==
        map((z1 : F) : SMPF +-> z1::SMPF, p
           )$SparseUnivariatePolynomialFunctions2(F, SMPF)


    -- [[getListSUPF]] truncates each element of the list after degree [[deg]].

    UFPSF2SUPF(f : UFPSF, deg : NNI) : SUP F ==
        import from UnivariatePolynomial('x, F)
        makeSUP univariatePolynomial(f, deg)

    getListSUPF(s : List UFPSF, deg : NNI) : List SUP F ==
        map((z1 : UFPSF) : SUP F +-> UFPSF2SUPF(z1, deg), s)
           $ListFunctions2(UFPSF, SUP F)

)if false

\subsubsection{The main routines}

We factor the main routine into two subroutines:
\begin{itemize}
\item [[guessHPaux]] determines the search space, and
\item [[guessInterpolate]] calls the appropriate solver and prepares the
  solutions.
\end{itemize}

[[guessHPaux]] determines the search space, i.e., the set of power series of
which we try to find a vanishing linear combination and the sum of the degrees
of the coefficient polynomials.  For every such configuration it calls
[[guessInterpolate]].

[[guessInterpolate]] decides which solver to use, and then converts the
solution into a [[GUESSRESULT]].

There are three different kinds of checks:
\begin{itemize}
\item a priori, any proposed solution has to interpolate correctly up to (but
  excluding) degree [[sigma]].  These checks should be done by the solver.
\item depending on the solution, it may happen that we can check a few more
  terms.  For example, if $f'(z)$ is part of the linear combination, a priori
  we loose one term for checking.  But if the solver returns $z f'(z)$, we
  don't.  The modular solver can do these checks before lifting the solution.

  Unfortunately we do not know of an easy and efficient way to find out how
  many terms are valid.  As an example, consider
  $$(f(z)f'(z)-f'(z)-f(z)+1-z^2=(f(z)-1)f'(z)-f(z)+1-z^2$$
  with initial value $f(0)=1$.  A priori we would expect to loose one term, but
  it turns out that we don't because of cancellation.

  One possibility that is (probably) exact is to augment the truncated power
  series with an additional transcendental, element representing $O(x^d)$.  We
  then compute the proposed linear combination using the augmented series.  If
  its lowest degree term contains the transcendental element, it depends on
  data we do not have, and the solution is correct.  Unfortunately, it turns
  out that this method is too inefficient for our purpose.

  Therefore, we rely on a probabilistic check : instead of a transcendental
  element, we add a random value and recompute the linear combination.
\item finally, there are certain solutions which we may want to reject under
  some circumstances:
  \begin{itemize}
  \item a trailing zero implies that [[guessPade]] will \emph{always} find a
    solution
  \item solutions may have unattainable points, like $(n-1)f(n)=(n-1)n$ can
    have any value for $f(1)$.
  \end{itemize}
\end{itemize}


% THIS IS NOT UP TO DATE ANYMORE
%
% In the case of [[generalInterpolation]] from [[FFFG]], for one vector of
% degrees, namely [[eta]].  [[testDegree]] indicates the degree up to which any
% given linear combination of elements in [[guessList]] should be correct.
%
% We now have to distinguish between the case where we try all combination of
% degrees and the case where we try only an (nearly) evenly distributed vector
% of degrees.
%
% In the first case, [[guessInterpolate2]] is going to look at all degree
% combinations with [[o]] elements with sum of entries [[guessDegree+2-o]].  We
% give up as soon as the order [[o]] is greater than the number of available
% terms plus one.  In the extreme case, i.e., when [[guessDegree+2 = o]], we
% allow for example constant coefficients for all terms of the linear
% combination.  It seems that it is a bit arbitrary at what point we stop,
% however, we would like to be consistent with the evenly distributed case.
%
% We want to pass only the minimal number of terms to [[FFFG]].

The sum of degrees of the coefficient polynomials has to be at most
[[guessDegree]].  [[sigma-1]] is the degree up to which any solution should be
correct a priori.

)endif

    import from ModularHermitePadeSolver(F, S)

    checkInterpolant : (List F, NNI, List SUP S, HPSPEC, LGOPT, Integer)
                         -> Union("good", "reject", "no_solution")

    guessInterpolateModular(lists : List List F, degs : List Integer, _
                            o : NNI, degreeLoss : Integer, _
                            guessDegree : NNI, D : HPSPEC, options : LGOPT)
                            : Union(Matrix SUP S, Boolean) ==
        list := first(lists)
        sigma : NNI := (#list - degreeLoss)::NNI

        leadingZeros : Integer := 0
        for el in list while zero? el repeat
            leadingZeros := leadingZeros + 1

        if leadingZeros > degreeLoss then
            del := leadingZeros - degreeLoss
            del > guessDegree => return false
            guessDegree := (guessDegree - del)::NNI

        if not(allDegrees(options)$GOPT0) then
            if (maxD := maxDegree(options)$GOPT0) case NNI and
               maxD*o < guessDegree then
                degs := [maxD for i in 1..o]
            else
                deg := divide(guessDegree, o)
                degs := [(if i <= deg.remainder   _
                          then deg.quotient + 1   _
                          else deg.quotient) for i in 1..o]
        check : ((List SUP S) -> Union("good", "reject", "no_solution"))
        check := x +-> checkInterpolant(list, o, x, D, options, sigma)
        if check(options)$GOPT0 case "MonteCarlo" then
            check := gen_Monte_Carlo_check(check, lists, _
                       D.kind, D.qvar, sigma, (D.guessModGen)(o))
        else
            if check(options)$GOPT0 case "skip" then
                if debug(options)$GOPT0 then
                    output("Guess: skipping checks")$OutputPackage
                check := x +-> "good"
            else
                -- stupid compiler
                check := x +-> checkInterpolant(list, o, x, D, options, sigma)

        HP_solve(lists, degs, D.kind, D.qvar, sigma, _
                  (D.guessModGen)(o), check)

    -- If we cannot use the modular solver, we fall back on [[FFFG]].

    -- We distribute the parameters as evenly as possible.  Is it
    -- better to have higher degrees at the end or at the beginning?

    guessInterpolateFFFG(lists : List List F, degs : List Integer, o : NNI, _
                         guessDegree : NNI, D : HPSPEC, options : LGOPT)
                         : Matrix SUP S ==
        list := first(lists)
        if debug(options)$GOPT0 then
            output("Guess: using FFFG")$OutputPackage

        M : Matrix SUP S
        eta : List NNI
        if allDegrees(options)$GOPT0 then
            eta := [d::NNI for d in degs]
            eta(1) := (eta(1) + 1)
        else
            if (maxD := maxDegree(options)$GOPT0) case NNI and
               maxD*o < guessDegree then
                eta := [(if i < o _
                         then maxD + 1 _
                         else maxD)::NNI for i in 1..o]
            else
                deg := divide((guessDegree+o-1)::NNI, o)
                eta := [(if i <= deg.remainder _
                         then deg.quotient + 1 _
                         else deg.quotient)::NNI for i in 1..o]
        sumEta := reduce(_+, eta)

        guessS  := (D.guessStream)(list2UFPSF list)
        guessList : List UFPSF  := entries complete first(guessS, o)
        guessPolys : List SUP F := getListSUPF(guessList, (sumEta-1)::NNI)

        if F is S then
            vguessList : Vector SUP S := vector(guessPolys pretend List SUP S)
            M := generalInterpolation((D.C)(sumEta), D.A,
                                      vguessList, eta)$FFFG(S, SUP S)
        else if F is Fraction S then
            vguessListF : Vector SUP F := vector(guessPolys)
            M := generalInterpolation((D.C)(sumEta), D.A,
                                      vguessListF, eta)$FFFGF(S, SUP S, SUP F)

        else error "Guess: type parameter F should be either equal to"
                   " S or equal to Fraction S"

        rl : List Integer := []
        for i in 1..ncols M repeat
            ce := entries column(M, i)
            if checkInterpolant(list, o, entries column(M, i), D, _
                                options, guessDegree) case "good" then
                rl := cons(i, rl)

        empty?(rl) => empty()
        rl := reverse!(rl)
        m := nrows M
        Mr := new(m, #rl, 0)$Matrix(SUP S)
        for i1 in 1..#rl for i2 in rl repeat
            c : S
            if S has Field then
                c := 0
                for j in 1..m while zero? c repeat
                    c := leadingCoefficient M(j, i2)
            else
    -- This does not really do the right thing when, for example, S is POLY AN.
    -- Eg. [[content univariate(((33+66*x)::SUP AN)*x+33)]] gives 1, not 33.
                cl : List S := [content(M(j, i2))$(SUP S) for j in 1..m]
                c := gcd cl
            for j in 1..m repeat
                Mr(j, i1) := (M(j, i2) exquo c)::(SUP S)
        Mr

    -- [[guessInterpolate3]] calls the appropriate [[guessInterpolate]]
    -- for all degree vectors in the stream [[vs]].

    guessInterpolate3(lists : List List F, o : NNI, degreeLoss : Integer, _
                      guessDegree : NNI, D : HPSPEC, options : LGOPT, _
                      vs : Stream List Integer) _
                      : Stream Matrix SUP S == delay
        explicitlyEmpty? vs or empty? vs => empty()$Stream(Matrix SUP S)
        degs := frst vs
        M0 := guessInterpolateModular(lists, degs, o, _
                                      degreeLoss, guessDegree, D, options)
        M : Matrix SUP S :=
            M0 case Boolean =>
                M0::Boolean and every?(x +-> x >= 0, degs) =>
                    guessInterpolateFFFG(lists, degs, o,
                                         guessDegree, D, options)
                return guessInterpolate3(lists, o, degreeLoss,
                                         guessDegree, D, options, rst vs)
            M0::Matrix SUP S
        concat(M, guessInterpolate3(lists, o, degreeLoss,
                                    guessDegree, D, options, rst vs))

    listDecr(l : List(NNI)) : List(Integer) ==
        map(x +-> x - 1, l)$ListFunctions2(NNI, Integer)

    guessInterpolate2(lists : List List F, o : NNI, degreeLoss : Integer, _
                      guessDegree : NNI, D : HPSPEC, options : LGOPT) _
                      : Stream Matrix SUP S ==
        vs : Stream List Integer
        vs :=
            allDegrees(options)$GOPT0 =>
                maxD0 := maxDegree(options)$GOPT0
                maxD : NNI :=
                    maxD0 case NNI => maxD0::NNI
                    qcoerce(guessDegree + o - 2)
                vs0 := genVectorStream(guessDegree + o, maxD + 1,
                                       o)$FFFG(S, SUP S)
                map(listDecr, vs0)$StreamFunctions2(List NNI, List Integer)
            concat(empty()$List(Integer),
                   empty()$Stream(List Integer))
        guessInterpolate3(lists, o, degreeLoss, guessDegree, D, _
                     options, vs)

)if false

[[checkInterpolant]] checks whether [[resi]] is really a solution.  This is one
of the most time consuming parts of the whole procedure.  For efficiency, we
special case the linear recurrences:

In both routines, [["no_solution"]] means that modular solver produced
something which is not a solution to Hermite-Pade problem, so (at least in
principle) modular solver should work harder to find correct solution to the
Hermite-Pade problem, which [[checkInterpolant]] or [[precCheck]] then will
verify.

[["reject"]] means that the modular solver solved the Hermite-Pade problem, but
we discard the solution.

)endif

    precCheck(list : List F, resi : List SUP S,
              options : LGOPT, sigma : Integer, bad : Boolean) _
              : Union("good", "reject", "no_solution") ==
        homp := homogeneous(options)$GOPT0
        homo : Boolean :=
                          homp case Boolean => homp
                          true
        resv := vector(resi)$Vector(SUP S)
        m := #resv
        gl0 := vector(list)$Vector(F)
        degree_loss : Integer :=
            homo => m - 1
            m - 2
        if checkExtraValues(options)$GOPT0 then
            max_ind := #gl0 - degree_loss - 1
        else
            max_ind := sigma - 1

        svar := create()$SingletonAsOrderedSet
        j0 : Integer :=
                        homo => 1
                        2
        order : Integer := 0
        p2 : SUP S
        import from Fraction(S)
        F is Fraction(S) =>
            gl := gl0
            for i in 0..max_ind repeat
                res1 : S := 0
                cden : S := 1
                si := i::S
                if not(homo) then
                    res1 := retract(eval(resv(1), svar, si))@S
                for j in j0..m repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    vi := gl(i + j - j0 + 1)
                    vd := denom(vi)
                    pp := gcd(cden, vd)
                    c1 := (cden exquo pp)::S
                    c2 := (vd exquo pp)::S
                    res1 := c2*res1 + p2val*c1*numer(vi)
                    cden := cden*c2
                res1 ~= 0 =>
                    i < sigma =>
                        return "no_solution"
                    return "reject"
            if not checkExtraValues(options)$GOPT0 then return "good"
            bad => return "reject"
            for i in 1..degree_loss repeat
                si := (i + max_ind)::S
                for j in (m - i)..m repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    p2val ~= 0 => return "good"
                res2 : Fraction(S)  := 0
                if homo then
                    res2 := (retract(eval(resv(1), svar, si))@S)::Fraction(S)
                for j in j0..(m - i) repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    res2 := res2 + p2val*gl(i + max_ind + j - j0 + 1)
                res2 ~= 0 => return "reject"
            return "good"
        F is S =>
            gl := gl0 pretend Vector(S)
            for i in 0..max_ind repeat
                res1 : S := 0
                si := i::S
                if not(homo) then
                    res1 := retract(eval(resv(1), svar, si))@S
                for j in j0..m repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    res1 := res1 + p2val*gl(i + j - j0 + 1)
                res1 ~= 0 =>
                    i < sigma =>
                        return "no_solution"
                    return "reject"
            if not checkExtraValues(options)$GOPT0 then return "good"
            bad => return "reject"
            for i in 1..degree_loss repeat
                si := (i + max_ind)::S
                for j in (m - i)..m repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    p2val ~= 0 => return "good"
                res2 : S  := 0
                if homo then
                    res2 := retract(eval(resv(1), svar, si))@S
                for j in j0..(m - i) repeat
                    p2 := resv(j)
                    p2val := retract(eval(p2, svar, si))@S
                    res2 := res2 + p2val*gl(i + max_ind + j - j0 + 1)
                res2 ~= 0 => return "reject"
            return "good"
        error "F must be S or Fraction(S)"

)if false

Unfortunately, we cannot simply discard solutions where the corresponding list
of expressions has a non-trivial gcd, since it is quite possible that a linear
combination of such solutions will be the one we are actually looking for.

In fact, we \emph{should} not discard any solutions at all, because the
result of the modular solver is a basis of a vector space, reduced and
normalised.  However, the set of interpolating solutions is \emph{not}
a vector space : consider two solutions $r_1$ and $r_2$, one interpolating,
the other not interpolating but having higher order than the first.  Then
the sum $r_1+r_2$ of the two is interpolating, but $(r_1+r_2) - r_1$ is not.

However, heuristically it still seems to be useful to discard non-interpolating
basis vectors and solutions that consist of a single term only.

Thus, we check for each coefficient whether the proposed linear combination
vanishes.  When the first coefficient that does not vanish contains the
transcendental element we accept the solution.

\begin{ToDo}
  possibly we should add the first interpolating solution to all the others, or
  at least to all the solutions which are not interpolating.
\end{ToDo}

We are not allowed to skip checking the low degrees when using the modular
solver, since it is possible to get spurious solution after modular reduction.
If at least one modular reduction has no spurious solutions, the solver will
discover that other reductions are bad and discard them.  But it is possible
(or at least without deeper analysis we cannot exlude the possiblity) that all
reductions are bad.  In this case the solver would reconstruct something which
is not a solution to Hermite-Pade problem.

)endif

    checkInterpolant : (List F, NNI, List SUP S, HPSPEC, LGOPT, Integer)
                      -> Union("good", "reject", "no_solution")
    checkInterpolant(list, o, resi, D, options, sigma) ==
        if debug(options)$GOPT0 then
            output("Guess: checking solution")$OutputPackage


       -- FIXME: we should also check that the non-zero terms contain the
       -- generating function

        nonZeroCoefficients : Union("one", "several", "none") := "none"
        reject_one_term := false

        for i in 1..#resi repeat
            if not zero? resi.i then
                if nonZeroCoefficients case "none" then
                    nonZeroCoefficients := "one"
                else
                    nonZeroCoefficients := "several"
                    break

        if not((nonZeroCoefficients case "several") or every?(zero?, list))
        then
            reject_one_term := true
            if debug(options)$GOPT0 then
                output("Guess: encountered single-term solution")
                      $OutputPackage


    -- FIXME: we really should special case the check of recurrences also if
    --        it *happens* to be a linear recurrence.

        len := #list
        maxPow : Integer
        maxP : Union(PositiveInteger, "arbitrary") :=
            maxPower(options)$GOPT0
        if maxP case PositiveInteger then
            maxPow := maxP
        else
            maxPow := len+2

        import from Symbol
        (one? maxPow and D.kind = 'shiftHP) =>
            precCheck(list, resi, options, sigma, reject_one_term)

        flist : List PositiveInteger := [i::PositiveInteger for i in 1..o _
                                         | not zero? resi.i]
        testList := ((D.testGen)(flist))(list2UFPSSUPF list)
        resiSUPF := [SUPF2SUPSUPF SUPS2SUPF(resi.i) for i in flist]

        c : SMPF
        order : Integer := 0
        dmax := 10*#list
        import from OutputForm
        for d in 0.. repeat
            if not checkExtraValues(options)$GOPT0 and d = sigma then
                return "good"
            if reject_one_term and d = sigma then
                return "reject"

            c := generalCoefficient(D.AF, testList,
                                    d, vector resiSUPF)
                                   $FFFG(SMPF, UFPSSUPF)
            if not zero? c then
                order := d
                break

            if d > dmax then
                print(message("bailing out from checkInterpolant"))
                print(message("please report the input to "
                       "fricas-devel@googlegroups.com"))
                order := d
                break

        order < sigma =>
            print(hconcat(message("Order too low: "), order::OutputForm))
            print(hconcat(message("sigma: "), sigma::OutputForm))
            return "no_solution"

        ground? c =>
            if debug(options)$GOPT0 then
                print(hconcat(
                    message("Proposed solution does not fit coefficient "),
                              order::OutputForm))
                print(hconcat(message("sigma: "), sigma::OutputForm))
            return "reject"

        "good"

    wrapInterpolant(resi : List SUP S,
                    exprList : List EXPRR,
                    initials : List F,
                    D : HPSPEC,
                    op : BasicOperator, options : LGOPT) : EXPRR ==

        dk := displayKind(options)$GOPT0
        dk = 'displayAsGF or dk = 'displayAsEQ =>
            ex : List EXPRR
               := [makeEXPRR(D.AX, variableName(options)$GOPT0, SUPS2SUPF p, e)
                   for p in resi for e in exprList]

            eq := numerator reduce(_+, ex)
            dk = 'displayAsEQ => eq
            makeFEq(op, variableName(options)$GOPT0,
                    indexName(options)$GOPT0,
                    eq,
                    getVariables initials,
                    map(g +-> coerce(g)@EXPRR,
                        initials::(Stream F))$StreamFunctions2(F, EXPRR))
                   $RecurrenceOperator(Integer, EXPRR)
        dk = 'displayAsRec =>
            ex : List EXPRR
               := [makeEXPRR(D.AX, indexName(options)$GOPT0, SUPS2SUPF p, e)
                   for p in resi for e in exprList]

            makeRec(op, indexName(options)$GOPT0,
                    numerator reduce(_+, ex),
                    getVariables initials,
                    map(g +-> coerce(g)@EXPRR,
                        initials::(Stream F))$StreamFunctions2(F, EXPRR))
                   $RecurrenceOperator(Integer, EXPRR)
        error "wrapInterpolant: unsupported display kind"

)if false

The following is the main guessing routine, called by all others -- except
[[guessExpRat]] and [[guessBinRat]].

We consider the elements of list as the first few coefficients of a formal
power series $f$.  We use the coefficients up to and including degree
[[listDegree]] to determine a vanishing linear combination of power series
derived from $f$, eg. by taking derivatives or powers.  The remaining elements,
whose number is given by the option [[safety]], are used as a plausibility
check.  Originally we demanded to have at least two elements for guessing.
However, we want to be able to induce from $[c, c]$ that the function equals
$c$ with [[safety]] one.  This is important if we apply, for example,
[[guessRat]] recursively.  In this case, [[listDegree]] equals zero.

We call the number of terms [[o]] of the linear combination its \emph{order}.
We consider linear combinations of at least two terms, since otherwise the
function would have to vanish identically\dots

We need to determine the number of parameters we can determine using the given
coefficients.  Note that it may happen that we loose valid terms when we
proceed in the stream of derived powerseries.  For example, when trying to find
a linear ordinary differential equation, we are looking for a linear
combination of $f, f^\prime, f^{\prime\prime}, \dots$.  Suppose [[listDegree]]
equals $2$, i.e. we have
\begin{align*}
  f                &= l_0 + l_1 x + l_2 x^2\\
  f^\prime         &= l_1 + 2l_2 x\\
  f^{\prime\prime} &= 2l_2.
\end{align*}
Thus, each time we differentiate the series, we loose one term for guessing.
Therefore, we cannot use the coefficient of $x^2$ of $f^{\prime\prime}$ for
generating a linear combination.

[[guessDegree]] will contain the maximal sum of degrees of the coefficient
polynomials we are going to determine.  Note that [[listDegree-degreeS.o]] is
the degree up to which any linear combination of the [[o]] selected series is
certainly correct, assuming [[safety]] equals zero.

If [[maxDegree]] is set, we skip the first few partitions, until
[[guessDegree]] would drop below [[o*maxDegree]].  Of course we also want to
make sure that for each sequence of partitions $\lambda_1 \subseteq \lambda_2
\subseteq \dots$ occurring in the stream, exactly one is actually tried.  To
achieve this we assume that [[guessDegree]] is weakly decreasing and that the
lists of partitions are closed under inclusion.

)endif

    guessHPaux(lists : List List F, D : HPSPEC, options : LGOPT
              ) : GUESSRESULT ==
        reslist : GUESSRESULT := []
        list := first(lists)
        listDegree := #list-1-safety(options)$GOPT0
        if listDegree < 0 then return reslist

        op : BasicOperator := operator functionName(options)$GOPT0

        degreeS := D.degreeStream
        dk := displayKind(options)$GOPT0
        exprS :=
            dk = 'displayAsGF or dk = 'displayAsEQ =>
                (D.exprStream)(op((variableName(options)$GOPT0)::EXPRR),
                               variableName(options)$GOPT0)
            dk = 'displayAsRec =>
                (D.exprStream)(op((indexName(options)$GOPT0)::EXPRR),
                               indexName(options)$GOPT0)

        for o in 2.. repeat
            empty? rest(degreeS, (o-1)::NNI) => break
            degreeLoss := (degreeS.o)::Integer
            guessDegree : Integer := listDegree + 2 - degreeLoss - o

            if guessDegree < 0 then
                if debug(options)$GOPT0 then
                    output("Guess: not enough values for guessing"
                          )$OutputPackage
                return reslist

            if debug(options)$GOPT0 then
                output(hconcat([message("Guess: trying order "), o::OutputForm,
                                message(", guessDegree is "),
                                guessDegree::OutputForm]))$OutputPackage

            if (maxD := maxDegree(options)$GOPT0) case NNI and _
               not empty? rest(degreeS, o) and _
               maxD*o     <  guessDegree  and _
               maxD*(o+1) <= listDegree+2-(degreeS.(o+1))::Integer-(o+1)
            then
                if debug(options)$GOPT0
                then output("Guess: iterating")$OutputPackage
            else
                exprList : List EXPRR := []
                if debug(options)$GOPT0 then
                    exprList := entries complete first(exprS, o)
                    output("Guess: The list of expressions is")$OutputPackage
                    output(exprList::OutputForm)$OutputPackage

                MS : Stream Matrix SUP S
                   := guessInterpolate2(lists, o,
                                        degreeLoss,
                                        guessDegree::NNI,
                                        D, options)

                repeat
                    (empty? MS) => break
                    M := first MS
                    MS := rst MS
                    if empty?(exprList) then
                        exprList := entries complete first(exprS, o)
                    import from Vector SUP S
                    for i in 1..ncols M repeat
                        res := wrapInterpolant(entries column(M, i),
                                               exprList,
                                               list,
                                               D, op, options)

                        if not member?(res, reslist)
                        then reslist := cons(res, reslist)
                    if not empty? reslist and one(options)$GOPT0
                    then return reslist

        reslist


    -- For convenience we provide some specialisations that cover
    -- the most common situations.

    processOptions(options : LGOPT) : List LGOPT ==
        if (Som? := Somos(options)$GOPT0) case Boolean and Som? then
            maxD : NNI := (maxDerivative(options)$GOPT0)::NNI
            maxSomos : PI
            if maxPower(options)$GOPT0 case PI then
                maxSomos := maxD::PI * (maxPower(options)$GOPT0)::PI
            else if homogeneous(options)$GOPT0 case PI then
                maxSomos := maxD::PI * (homogeneous(options)$GOPT0)::PI
            else error "Guess: internal error - inconsistent options"

            return concat [processOptions cons(Somos(i::PI)$GuessOption, _
                                               options) _
                           for i in 2..maxSomos]

        if (hom? := homogeneous(options)$GOPT0) case Boolean and hom? then
            minP : PI
            if Somos(options)$GOPT0 case PI then
                minP := 2
            else
                minP := 1
            return [concat([homogeneous(i::PI)$GuessOption, _
                            maxPower(i::PI)$GuessOption], _
                           options) _
                    for i in minP..(maxPower(options)$GOPT0)::PI]

        [options]

    guessAlgDep(lists : List List F, options : LGOPT) : GUESSRESULT ==
        checkOptions(options)$GOPT0

        empty?(lists) => error "guessAlgDep: need at least one list"
        l := first(lists)
        rl := rest(lists)
        nn := #l
        for li in rl repeat
            #li ~= nn => error "guessAlgDep: lists must have equal length"

        lopts := processOptions append([displayKind('displayAsEQ)$GuessOption, _
                              maxSubst((#lists)::PI)$GuessOption], _
                              options)
        lres : GUESSRESULT := []
        res : GUESSRESULT := []

        for opts in lopts repeat
            res := guessHPaux(lists, algDepHP(lists, opts), opts)
            if not empty? res and one(options)$GOPT0 then
                return res
            lres := append(res, lres)

        reverse! lres

    guessAlgDep(lists : List List F) : GUESSRESULT ==
        guessAlgDep(lists, [])

    guessADE(list : List F, options : LGOPT) : GUESSRESULT ==
        checkOptions(options)$GOPT0

        lopts := processOptions cons(displayKind('displayAsGF)$GuessOption,
                                     options)
        lres : GUESSRESULT := []
        res : GUESSRESULT := []

        for opts in lopts repeat
            res := guessHPaux([list], diffHP opts, opts)
            if not empty? res and one(options)$GOPT0 then
                return res
            lres := append(res, lres)

        reverse! lres

    guessADE(list : List F) : GUESSRESULT == guessADE(list, [])

    guessAlg(list : List F, options : LGOPT) ==
        guessADE(list, cons(maxDerivative(0)$GuessOption, options))

    guessAlg(list : List F) : GUESSRESULT == guessAlg(list, [])

    guessHolo(list : List F, options : LGOPT) : GUESSRESULT ==
        guessADE(list, cons(maxPower(1)$GuessOption, options))

    guessHolo(list : List F) : GUESSRESULT == guessHolo(list, [])

    guessPade(list : List F, options : LGOPT) : GUESSRESULT ==
        opts := concat([[maxDerivative(0)$GuessOption,
                         maxPower(1)$GuessOption], options,
                        [allDegrees(true)$GuessOption]])
        guessADE(list, opts)

    guessPade(list : List F) : GUESSRESULT == guessPade(list, [])

    guessFE(list : List F, options : LGOPT) : GUESSRESULT ==
        checkOptions(options)$GOPT0


        lopts := processOptions cons(displayKind('displayAsGF)$GuessOption,
                                     options)
        lres : GUESSRESULT := []
        res : GUESSRESULT := []

        for opts in lopts repeat
             res := guessHPaux([list], substHP opts, opts)
             if not empty? res and one(options)$GOPT0 then
                 return res
             lres := append(res, lres)

        reverse! lres

    guessFE(list : List F) : GUESSRESULT == guessFE(list, [])

    -- $q$-generating functions}


    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        guessADE(q : Symbol) : GUESSER ==
            (list : List F, options : LGOPT) : GUESSRESULT +->
                checkOptions(options)$GOPT0

                lopts := processOptions cons(displayKind('displayAsGF
                                                        )$GuessOption,
                                             options)
                lres : GUESSRESULT := []
                res : GUESSRESULT := []

                for opts in lopts repeat
                     res := guessHPaux([list], (diffHP q)(opts), opts)
                     if not empty? res and one(options)$GOPT0 then
                         return res
                     lres := append(res, lres)

                reverse! lres

        guessHolo(q : Symbol) : GUESSER ==
            (z1 : List F, z2 : LGOPT) : GUESSRESULT +->
                guessADE(q)(z1, cons(maxPower(1)$GuessOption, z2))


    -- coefficients

    guessRec(list : List F, options : LGOPT) : GUESSRESULT ==
        checkOptions(options)$GOPT0

        lopts := processOptions cons(displayKind('displayAsRec)$GuessOption,
                                     options)
        lres : GUESSRESULT := []
        res : GUESSRESULT := []

        for opts in lopts repeat
             res := guessHPaux([list], shiftHP opts, opts)
             if not empty? res and one(options)$GOPT0 then
                 return res
             lres := append(res, lres)

        reverse! lres

    guessRec(list : List F) : GUESSRESULT == guessRec(list, [])

    guessPRec(list : List F, options : LGOPT) : GUESSRESULT ==
        guessRec(list, cons(maxPower(1)$GuessOption, options))

    guessPRec(list : List F) : GUESSRESULT == guessPRec(list, [])

    guessRat(list : List F, options : LGOPT) : GUESSRESULT ==
        opts := concat([[maxShift(0)$GuessOption,
                         maxPower(1)$GuessOption],
                         options, [allDegrees(true)$GuessOption]])
        guessRec(list, opts)

    guessRat(list : List F) : GUESSRESULT == guessRat(list, [])


    -- $q$-coefficients

    if F has RetractableTo Symbol and S has RetractableTo Symbol then

        guessRec(q : Symbol) : GUESSER ==
            (list : List F, options : LGOPT) : GUESSRESULT +->
                checkOptions(options)$GOPT0

                lopts := processOptions cons(
                    displayKind('displayAsRec)$GuessOption, options)
                lres : GUESSRESULT := []
                res : GUESSRESULT := []

                for opts in lopts repeat
                     res := guessHPaux([list], shiftHP(q)(opts), opts)
                     if not empty? res and one(options)$GOPT0 then
                         return res
                     lres := append(res, lres)

                reverse! lres

        guessPRec(q : Symbol) : GUESSER ==
            (z1 : List F, z2 : LGOPT) : GUESSRESULT +->
                guessRec(q)(z1, cons(maxPower(1)$GuessOption, z2))

        guessRat(q : Symbol) : GUESSER ==
            (z1 : List F, z2 : LGOPT) : GUESSRESULT +->
                guessRec(q)(z1, concat([[maxShift(0)$GuessOption,
                                         maxPower(1)$GuessOption], z2,
                                        [allDegrees(true)$GuessOption]]))

)if false

\subsection{[[guess]] -- applying operators recursively}

The main observation made by Christian Krattenthaler in designing his program
\Rate\ is the following : it occurs frequently that although a sequence of
numbers is not generated by a rational function, the sequence of successive
quotients is.

We slightly extend upon this idea, and apply recursively one or both of the two
following operators:
\begin{description}
\item[$\Delta_n$] the differencing operator, transforming $f(n)$ into
  $f(n)-f(n-1)$, and
\item[$Q_n$] the operator that transforms $f(n)$ into $f(n)/f(n-1)$.
\end{description}

)endif

    guess(list : List F, guessers : List GUESSER, ops : List Symbol,
          options : LGOPT) : GUESSRESULT ==
        newMaxLevel := maxLevel(options)$GOPT0
        xx := (indexName(options)$GOPT0)::EXPRR
        if debug(options)$GOPT0 and newMaxLevel case NNI then
            output(hconcat(message("Guess: guessing level "),
                           newMaxLevel::OutputForm))$OutputPackage

        res : GUESSRESULT := []
        len := #list :: PositiveInteger
        if len <= 1 then return res

        for guesser in guessers repeat
            res := append(guesser(list, options), res)

    --        if debug(options)$GOPT0 then
    --            output(hconcat("res ", res::OutputForm))$OutputPackage

            if one(options)$GOPT0 and not empty? res then return res

        if newMaxLevel case NNI then
            if zero? newMaxLevel
            then return res
            else newMaxLevel := (newMaxLevel::Integer - 1)::NNI

        if member?('guessProduct, ops) then

            if member?(0$F, list) then
                0 -- this must be here due to bug #430
                if debug(options)$GOPT0 then
                    output("Guess: cannot use guessProduct because of zeros"
                          )$OutputPackage
            else
                prodList : List F := [(list.(i+1))/(list.i) for i in 1..(len-1)]

                if not every?(one?, prodList) then
                    var : Symbol := subscript('p, [len::OutputForm])
                    prodGuess : List EXPRR := []
                    for guess in guess(prodList, guessers, ops,
                                       append([indexName(var)$GuessOption,
                                               maxLevel(newMaxLevel
                                                       )$GuessOption],
                                               options))$% repeat
    --                    o := checkResult(guess, var, #prodList, prodList)
    --                    init: EXPRR := coerce(list.(o+1))
    --                    prod: EXPRR := product(guess, _
    --                                           equation(var, o::EXPRR..xx-1))
                        init : EXPRR := coerce(list.1)
                        prod : EXPRR := product(guess, _
                                               equation(var, 0$EXPRR..xx-1))
                        prodGuess := cons(init*prod, prodGuess)

    --                if debug(options)$GOPT0 then
    --                    output(hconcat("prodGuess "::OutputForm,
    --                                   prodGuess::OutputForm))
    --                          $OutputPackage

                    for guess in prodGuess | not member?(guess, res) repeat
                        res := cons(guess, res)

        if one(options)$GOPT0 and not empty? res then return res

        if member?('guessSum, ops) then
            sumList : List F := [(list.(i+1))-(list.i) for i in 1..(len-1)]

            if not every?((z1 : F) : Boolean +-> z1 = sumList.1, sumList) then
                var : Symbol := subscript('s, [len::OutputForm])
                sumGuess : List EXPRR := []
                for guess in guess(sumList, guessers, ops,
                                   append([indexName(var)$GuessOption,
                                           maxLevel(newMaxLevel)$GuessOption],
                                           options))$% repeat
    --                o := checkResult(guess, var, #sumList, sumList)
    --                init: EXPRR := coerce(list.(o+1))
    --                summ: EXPRR := summation(guess, _
    --                                 equation(var, o::Integer::EXPRR..xx-1))
                    init : EXPRR := coerce(list.1)
                    summ : EXPRR := summation(guess, _
                                             equation(var, 0$EXPRR..xx-1))
                    sumGuess := cons(init + summ, sumGuess)

                for guess in sumGuess | not member?(guess, res) repeat
                    res := cons(guess, res)

        res

    guess(list : List F) : GUESSRESULT ==
        guess(list, [guessRat$%], ['guessProduct, 'guessSum], [])

    guess(list : List F, options : LGOPT) : GUESSRESULT ==
        guess(list, [guessRat$%], ['guessProduct, 'guessSum], options)

    guess(list : List F, guessers : List GUESSER, ops : List Symbol
         ) : GUESSRESULT ==
        guess(list, guessers, ops, [])
)boot $tryRecompileArguments := true

-- concerning algebraic functions, it may make sense to look at A.J.van der
-- Poorten, Power Series Representing Algebraic Functions, Section 6.
)abbrev package GUESSINT GuessInteger
++ Description:
++ This package exports guessing of sequences of rational numbers
GuessInteger() == Guess(Fraction Integer, Integer, Expression Integer,
                     retract$Expression(Integer),
                     coerce$Expression(Integer))


)abbrev package GUESSF1 GuessFiniteFunctions
++ Description:
++ This package exports coercion and retraction from EXPR INT to
++ finite fields
GuessFiniteFunctions(F : Join(FiniteFieldCategory, ConvertibleTo Integer)):
  Exports == Implementation where

    EXPRR ==> Expression Integer

    Exports == with

      F2EXPRR : F -> EXPRR
      EXPRR2F : EXPRR -> F

    Implementation == add

      F2EXPRR(p : F) : EXPRR == convert(p)@Integer::EXPRR
      EXPRR2F(p : EXPRR) : F == retract(p)@Fraction(Integer)::F


)abbrev package GUESSF GuessFinite
++ Description:
++ This package exports guessing of sequences of numbers in a finite field
GuessFinite(F : Join(FiniteFieldCategory, ConvertibleTo Integer)) ==
  Guess(F, F, Expression Integer, EXPRR2F$GuessFiniteFunctions(F),
        F2EXPRR$GuessFiniteFunctions(F))


)abbrev package GUESSP1 GuessPolynomialFunctions
++ Description:
++ This package exports coercion and retraction from EXPR INT to
++ polynomials
GuessPolynomialFunctions(R : Ring):
  Exports == Implementation where

    EXPRR ==> Expression Integer

    Exports == with

      F2EXPRR : Fraction Polynomial R -> EXPRR
      EXPRR2F : EXPRR -> Fraction Polynomial R

      iim2 : R -> EXPRR
      ++ should be local but conditional
      iiretractVar : Kernel EXPRR -> Polynomial R
      ++ should be local but conditional

    Implementation == add

      m1(var : Symbol) : EXPRR == coerce(var)$EXPRR@EXPRR

      m3(var : Kernel EXPRR) : EXPRR == var::EXPRR

      if EXPRR has RetractableTo R then
          iim2(coeff : R) : EXPRR == coeff::EXPRR

          iiretractVar(v : Kernel EXPRR) : Polynomial R ==
              v2 := m3 v
              (r := retractIfCan(v2)@Union(R, "failed")) case "failed" =>
                  retract(v2)@Symbol::Polynomial(R)
              r::R::Polynomial(R)

      else
          if R has ConvertibleTo Integer then
              iim2(coeff : R) : EXPRR == (convert(coeff)@Integer)::EXPRR

              iiretractVar(v : Kernel EXPRR) : Polynomial R ==
                  v2 := m3 v
                  (r := retractIfCan(v2)@Union(Integer, "failed")) case "failed" =>
                      retract(v2)@Symbol::Polynomial(R)
                  r::Integer::R::Polynomial(R)
          else
              error "retraction and coercion to EXPR INT is not implemented for this Ring"


      PL1 ==> PolynomialCategoryLifting(IndexedExponents(Symbol), Symbol,
                                        R, Polynomial(R), EXPRR)


      S2EXPRR(p : Polynomial R) : EXPRR ==
          map((var : Symbol) : EXPRR +-> m1 var,
              (coeff : R) : EXPRR +-> iim2 coeff, p)$PL1

      F2EXPRR(p : Fraction Polynomial R) : EXPRR ==
          S2EXPRR(numer p) / S2EXPRR(denom p)

      SMP ==> SparseMultivariatePolynomial(Integer, Kernel EXPRR)
      PL2 ==> PolynomialCategoryLifting(IndexedExponents Kernel EXPRR,
                                        Kernel EXPRR, Integer, SMP,
                                        Polynomial R)

      SMP2S(p : SMP) : Polynomial R ==
          map((var : Kernel EXPRR) : Polynomial(R) +-> iiretractVar(var),
              (coeff : Integer) : Polynomial(R) +-> coeff::R::Polynomial(R), p)$PL2

      EXPRR2F(p : EXPRR) : Fraction Polynomial R ==
          SMP2S(numer p)/SMP2S(denom p)


)abbrev package GUESSP GuessPolynomial
++ Description:
++ This package exports guessing of sequences of rational functions
GuessPolynomial(R : Ring) ==
    Guess(Fraction Polynomial R, Polynomial R, Expression Integer,
          EXPRR2F$GuessPolynomialFunctions(R),
          F2EXPRR$GuessPolynomialFunctions(R))


)abbrev package GUESSPI GuessPolynomialInteger
++ Description:
++ This package exports guessing of sequences of rational functions over the
++ integers
GuessPolynomialInteger() == GuessPolynomial Integer


)abbrev package GUESSAN GuessAlgebraicNumber
++ Description:
++ This package exports guessing of sequences of rational functions
GuessAlgebraicNumber() == Guess(AlgebraicNumber, AlgebraicNumber,
                                Expression Integer,
                                retract$Expression(Integer),
                                coerce$Expression(Integer))


--Copyright (c) 2006-2007, Martin Rubey <Martin.Rubey@univie.ac.at>
--
--Redistribution and use in source and binary forms, with or without
--modification, are permitted provided that the following conditions are
--met:
--
--    - Redistributions of source code must retain the above copyright
--      notice, this list of conditions and the following disclaimer.
--
--    - Redistributions in binary form must reproduce the above copyright
--      notice, this list of conditions and the following disclaimer in
--      the documentation and/or other materials provided with the
--      distribution.
--
--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
--IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
--TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
--OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
--EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
--PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
--PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
--LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
--NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
