)abbrev domain U64INT U64Int
++ Description: Domain of unsigned 64-bit integers.  It is used
++  to declare that values of local variables fit into 64 bits.
U64Int : Ring with
    qconvert : Integer -> %
  == Integer add
    qconvert(x) == x pretend %

)abbrev domain U32VEC U32Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of unsigned 32-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
U32Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_U32$Lisp
   Qelt ==> ELT_U32$Lisp
   Qsetelt ==> SETELT_U32$Lisp
   Qnew ==> GETREFV_U32$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)         == Qelt(x, i)
   qsetelt!(x, i, s)          == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s)       == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)



)abbrev domain U32MAT U32Matrix

++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of unsigned 32-bit integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).

U32Matrix : MatrixCategory(Integer,
                         U32Vector,
                         U32Vector)
  == add

    R ==> Integer

    Qelt2 ==> AREF2_U32$Lisp
    Qsetelt2 ==> SETAREF2_U32$Lisp
    Qnrows ==> ANROWS_U32$Lisp
    Qncols ==> ANCOLS_U32$Lisp
    Qnew ==> MAKE_MATRIX_U32$Lisp
    Qnew1 ==> MAKE_MATRIX1_U32$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)




)abbrev domain U16VEC U16Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of unsigned 16-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
U16Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_U16$Lisp
   Qelt ==> ELT_U16$Lisp
   Qsetelt ==> SETELT_U16$Lisp
   Qnew ==> GETREFV_U16$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)         == Qelt(x, i)
   qsetelt!(x, i, s)          == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s)       == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)



)abbrev domain U16MAT U16Matrix

++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of unsigned 16-bit integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).

U16Matrix : MatrixCategory(Integer,
                         U16Vector,
                         U16Vector)
  == add

    R ==> Integer

    Qelt2 ==> AREF2_U16$Lisp
    Qsetelt2 ==> SETAREF2_U16$Lisp
    Qnrows ==> ANROWS_U16$Lisp
    Qncols ==> ANCOLS_U16$Lisp
    Qnew ==> MAKE_MATRIX_U16$Lisp
    Qnew1 ==> MAKE_MATRIX1_U16$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)




)abbrev domain U8VEC U8Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of unsigned 8-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
U8Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_U8$Lisp
   Qelt ==> ELT_U8$Lisp
   Qsetelt ==> SETELT_U8$Lisp
   Qnew ==> GETREFV_U8$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)         == Qelt(x, i)
   qsetelt!(x, i, s)          == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s)       == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)



)abbrev domain U8MAT U8Matrix

++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of unsigned 8-bit integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).

U8Matrix : MatrixCategory(Integer,
                         U8Vector,
                         U8Vector)
  == add

    R ==> Integer

    Qelt2 ==> AREF2_U8$Lisp
    Qsetelt2 ==> SETAREF2_U8$Lisp
    Qnrows ==> ANROWS_U8$Lisp
    Qncols ==> ANCOLS_U8$Lisp
    Qnew ==> MAKE_MATRIX_U8$Lisp
    Qnew1 ==> MAKE_MATRIX1_U8$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)

)abbrev domain I32VEC I32Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of signed 32-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
I32Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_I32$Lisp
   Qelt ==> ELT_I32$Lisp
   Qsetelt ==> SETELT_I32$Lisp
   Qnew ==> GETREFV_I32$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)     == Qelt(x, i)
   qsetelt!(x, i, s)           == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s) == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)

)abbrev domain I32MAT I32Matrix
++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of signed 32-bit integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).

I32Matrix : MatrixCategory(Integer,
                           I32Vector,
                           I32Vector)
  == add

    R ==> Integer

    Qelt2 ==> AREF2_I32$Lisp
    Qsetelt2 ==> SETAREF2_I32$Lisp
    Qnrows ==> ANROWS_I32$Lisp
    Qncols ==> ANCOLS_I32$Lisp
    Qnew ==> MAKE_MATRIX_I32$Lisp
    Qnew1 ==> MAKE_MATRIX1_I32$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)


)abbrev domain I16VEC I16Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of signed 16-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
I16Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_I16$Lisp
   Qelt ==> ELT_I16$Lisp
   Qsetelt ==> SETELT_I16$Lisp
   Qnew ==> GETREFV_I16$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)     == Qelt(x, i)
   qsetelt!(x, i, s)           == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s)  == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)

)abbrev domain I16MAT I16Matrix
++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of 16-bit signed integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).
I16Matrix : MatrixCategory(Integer,
                           I16Vector,
                           I16Vector)
  == add
    R ==> Integer

    Qelt2 ==> AREF2_I16$Lisp
    Qsetelt2 ==> SETAREF2_I16$Lisp
    Qnrows ==> ANROWS_I16$Lisp
    Qncols ==> ANCOLS_I16$Lisp
    Qnew ==> MAKE_MATRIX_I16$Lisp
    Qnew1 ==> MAKE_MATRIX1_I16$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)

)abbrev domain I8VEC I8Vector
++ Description: This is a low-level domain which implements vectors
++ (one dimensional arrays) of signed 8-bit numbers.  Indexing
++ is 0 based, there is no bound checking (unless provided by
++ lower level).
I8Vector() : OneDimensionalArrayAggregate Integer == add
   Qsize ==> QV_LEN_I8$Lisp
   Qelt ==> ELT_I8$Lisp
   Qsetelt ==> SETELT_I8$Lisp
   Qnew ==> GETREFV_I8$Lisp

   #x                          == Qsize x
   minIndex x                  == 0
   empty()                     == Qnew(0$Lisp, 0$Lisp)
   new(n, x)                   == Qnew (n, x)
   qelt(x, i)                  == Qelt(x, i)
   elt(x : %, i : Integer)     == Qelt(x, i)
   qsetelt!(x, i, s)           == Qsetelt(x, i, s)
   setelt!(x : %, i : Integer, s : Integer) == Qsetelt(x, i, s)
   fill!(x, s) == (for i in 0..((Qsize x) - 1) repeat Qsetelt(x, i, s); x)

)abbrev domain I8MAT I8Matrix
++ Description: This is a low-level domain which implements matrices
++ (two dimensional arrays) of 8-bit signed integers.
++ Indexing is 0 based, there is no bound checking (unless
++ provided by lower level).
I8Matrix : MatrixCategory(Integer,
                          I8Vector,
                          I8Vector)
  == add
    R ==> Integer

    Qelt2 ==> AREF2_I8$Lisp
    Qsetelt2 ==> SETAREF2_I8$Lisp
    Qnrows ==> ANROWS_I8$Lisp
    Qncols ==> ANCOLS_I8$Lisp
    Qnew ==> MAKE_MATRIX_I8$Lisp
    Qnew1 ==> MAKE_MATRIX1_I8$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : R == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : R) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)


)abbrev domain PRIMMAT2 PrimitiveTwoDimensionalArray
++ Author: Waldek Hebisch
++ Description: This is a low-level domain which implements matrices
++  (two dimensional arrays) of general values.
++  Indexing is 0 based, there is no bound checking (unless
++  provided by lower level).
PrimitiveTwoDimensionalArray(S : Type
        ) : TwoDimensionalArrayCategory(S,
                                        PrimitiveArray(S),
                                        PrimitiveArray(S)) with
    if S has AbelianMonoid then MatrixCategory(S,
                                     PrimitiveArray(S),
                                     PrimitiveArray(S))
  == add

    Qelt2 ==>  QAREF2$Lisp
    Qsetelt2 ==> QSETAREF2$Lisp
    Qnrows ==> ANROWS$Lisp
    Qncols ==> ANCOLS$Lisp
    Qnew ==> MAKE_MATRIX$Lisp
    Qnew1 ==> MAKE_MATRIX1$Lisp

    minRowIndex x == 0
    minColIndex x == 0
    nrows x == Qnrows(x)
    ncols x == Qncols(x)
    maxRowIndex x == Qnrows(x) - 1
    maxColIndex x == Qncols(x) - 1

    qelt(m, i, j) == Qelt2(m, i, j)
    elt(m : %, i : Integer, j : Integer) : S == Qelt2(m, i, j)
    qsetelt!(m, i, j, r) == Qsetelt2(m, i, j, r)
    setelt!(m : %, i : Integer, j : Integer, r : S) == Qsetelt2(m, i, j, r)

    empty() == Qnew(0$Integer, 0$Integer)
    qnew(rows, cols) == Qnew(rows, cols)
    new(rows, cols, a) == Qnew1(rows, cols, a)


)abbrev package POLYVEC U32VectorPolynomialOperations
++ Description: This is a low-level package which implements operations
++  on vectors treated as univariate modular polynomials.  Most
++  operations takes modulus as parameter.  Modulus is machine
++  sized prime which should be small enough to avoid overflow
++  in intermediate calculations.
U32VectorPolynomialOperations() : Export == Implementation where
    PA ==> U32Vector
    Export ==> with
        copy_first : (PA, PA, Integer) -> Void
          ++ copy_first(v1, v2, n) copies first n elements
          ++ of v2 into n first positions in v1.
        copy_slice : (PA, PA, Integer, Integer) -> Void
          ++ copy_slice(v1, v2, m, n) copies the slice of v2 starting
          ++ at m elements and having n elements into corresponding
          ++ positions in v1.
        eval_at : (PA, Integer, Integer, Integer) -> Integer
          ++ eval_at(v, deg, pt, p) treats v as coefficients of
          ++ polynomial of degree deg and evaluates the
          ++ polynomial at point pt modulo p
        vector_add_mul : (PA, PA, Integer, Integer, Integer, Integer) _
            -> Void
          ++ vector_add_mul(v1, v2, m, n, c, p) sets v1(m), ...,
          ++ v1(n) to corresponding extries in v1 + c*v2
          ++ modulo p.
        mul_by_binomial : (PA, Integer, Integer) -> Void
          ++ mul_by_binomial(v,  pt, p) treats v a polynomial
          ++ and multiplies in place this polynomial by binomial (x + pt).
          ++ Highest coefficient of product is ignored.
        mul_by_binomial : (PA, Integer, Integer, Integer) -> Void
          ++ mul_by_binomial(v, deg, pt, p) treats v as
          ++ coefficients of polynomial of degree deg - 1 and
          ++ multiplies in place this polynomial by binomial (x + pt).
          ++ Highest coefficient of product is ignored.
        mul_by_scalar : (PA, Integer, Integer, Integer) -> Void
          ++ mul_by_scalar(v, deg, c, p) treats v as
          ++ coefficients of polynomial of degree deg and
          ++ multiplies in place this polynomial by scalar c
        mul : (PA, PA, Integer) -> PA
          ++ Polynomial multiplication.
        truncated_multiplication : (PA, PA, Integer, Integer) -> PA
          ++ truncated_multiplication(x, y, d, p) computes
          ++ x*y truncated after degree d.
        truncated_mul_add : (PA, PA, PA, Integer, Integer) -> Void
          ++ truncated_mul_add(x, y, z, d, p) adds to z
          ++ the product x*y truncated after degree d.
        truncated_mul_add2 : (PA, PA, PA, Integer, Integer, Integer) -> Void
          ++ truncated_mul_add2(x, y, z, d1, d2, p) adds to z
          ++ the product x*y and the variable to power d1
          ++ truncated after degree d.
        pow : (PA, PositiveInteger, NonNegativeInteger, Integer) -> PA
          ++ pow(u, n, d, p) returns u^n truncated after degree d, except if
          ++ n=1, in which case u itself is returned
        differentiate : (PA, Integer) -> PA
          ++ Polynomial differentiation.
        differentiate : (PA, NonNegativeInteger, Integer) -> PA
          ++ Polynomial differentiation.
        divide! : (PA, PA, PA, Integer) -> Void
          ++ Polynomial division.
        remainder! : (PA, PA, Integer) -> Void
          ++ Polynomial remainder
        vector_combination : (PA, Integer, PA, Integer, _
                               Integer, Integer, Integer) -> Void
          ++ vector_combination(v1, c1, v2, c2, n, delta, p) replaces
          ++ first n + 1 entries of v1 by corresponding entries of
          ++ c1*v1+c2*x^delta*v2 mod p.
        to_mod_pa : (SparseUnivariatePolynomial Integer, Integer) -> PA
          ++ to_mod_pa(s, p) reduces coefficients of polynomial
          ++ s modulo prime p and converts the result to vector
        pa_to_sup : PA -> SparseUnivariatePolynomial Integer
          ++ pa_to_sup(v) converts vector of coefficients to
          ++ a polynomial
        gcd : (PA, PA, Integer) -> PA
          ++ gcd(v1, v2, p) computes monic gcd of v1 and v2 modulo p.
        gcd : (PrimitiveArray PA, Integer, Integer, Integer) -> PA
          ++ gcd(a, lo, hi, p) computes gcd of elements
          ++ a(lo), a(lo+1), ..., a(hi).
        lcm : (PrimitiveArray PA, Integer, Integer, Integer) -> PA
          ++ lcm(a, lo, hi, p) computes lcm of elements
          ++ a(lo), a(lo+1), ..., a(hi).
        degree : PA -> Integer
          ++ degree(v) is degree of v treated as polynomial
        extended_gcd : (PA, PA, Integer) -> List(PA)
          ++ extended_gcd(v1, v2, p) gives [g, c1, c2] such
          ++ that g is \spad{gcd(v1, v2, p)}, \spad{g = c1*v1 + c2*v2}
          ++ and degree(c1) < max(degree(v2) - degree(g), 0) and
          ++ degree(c2) < max(degree(v1) - degree(g), 1)
        resultant : (PA, PA, Integer) -> Integer
          ++ resultant(v1, v2, p) computes resultant of v1 and v2
          ++ modulo p.

    Implementation ==> add

        Qmuladdmod ==> QSMULADDMOD64_32$Lisp
        Qmuladd  ==> QSMULADD64_32$Lisp
        Qmul ==> QSMULMOD32$Lisp
        Qdot2 ==> QSDOT2MOD64_32$Lisp
        Qrem ==> QSMOD64_32$Lisp
        modInverse ==> invmod

        copy_first(np : PA, op : PA, n : Integer) : Void ==
            ns := qconvert(n)@SingleInteger
            for j in 0..(ns - 1) repeat
                np(j) := op(j)

        copy_slice(np : PA, op : PA, m : Integer, _
                    n : Integer) : Void ==
            ms := qconvert(m)@SingleInteger
            ns := qconvert(n)@SingleInteger
            for j in ms..(ms + ns - 1) repeat
                np(j) := op(j)

        eval_at(v : PA, deg : Integer, pt : Integer, _
               p : Integer) : Integer ==
            i : SingleInteger := qconvert(deg)@SingleInteger
            res : Integer := 0
            while not(i < 0) repeat
                res := Qmuladdmod(pt, res, v(i), p)
                i := i - 1
            res

        to_mod_pa(s : SparseUnivariatePolynomial Integer, p : Integer) : PA ==
            zero?(s) => new(1, 0)$PA
            n0 := qconvert(degree(s))@SingleInteger
            ncoeffs := new(qcoerce(n0+1)@NonNegativeInteger, 0)$PA
            while not(zero?(s)) repeat
                n := degree(s)
                ncoeffs(n) := positiveRemainder(leadingCoefficient(s), p)
                s := reductum(s)
            ncoeffs

        pa_to_sup(v : PA) : SparseUnivariatePolynomial Integer ==
            n := #v
            res : SparseUnivariatePolynomial(Integer) := 0
            for i in 0..(n - 1) repeat
                res := monomial(v(i), i)$SparseUnivariatePolynomial(Integer)
                       + res
            res

        vector_add_mul(v1 : PA, v2 : PA, m : Integer, n : Integer, _
                         c : Integer, p : Integer) : Void ==
            ms := qconvert(m)@SingleInteger
            ns := qconvert(n)@SingleInteger
            for i in ms..ns repeat
                v1(i) := Qmuladdmod(c, v2(i), v1(i), p)

        mul_by_binomial(v : PA, n : Integer, pt : Integer, _
                          p : Integer) : Void ==
            prev_coeff : Integer := 0
            ns := qconvert(n)@SingleInteger
            for i in 0..(ns - 1) repeat
                pp := v(i)
                v(i) := Qmuladdmod(pt, pp, prev_coeff, p)
                prev_coeff := pp

        mul_by_binomial(v : PA, pt : Integer, _
                          p : Integer) : Void ==
            mul_by_binomial(v, #v, pt, p)

        mul_by_scalar(v : PA, n : Integer, c : Integer, _
                        p : Integer) : Void ==
            ns := qconvert(n)@SingleInteger
            for i in 0..ns repeat
                v(i) := Qmul(c, v(i), p)

        degree(v : PA) : Integer ==
            n := #v
            for i in (n - 1)..0 by -1 repeat
                not(v(i) = 0) => return i
            -1

        vector_combination(v1 : PA, c1 : Integer, _
                            v2 : PA, c2 : Integer, _
                            n : Integer, delta : Integer, _
                            p : Integer) : Void ==
            ns := qconvert(n)@SingleInteger
            ds := qconvert(delta)@SingleInteger
            if not(c1 = 1) then
                ns + 1 < ds =>
                    for i in 0..ns repeat
                        v1(i) := Qmul(v1(i), c1, p)
                for i in 0..(ds - 1) repeat
                    v1(i) := Qmul(v1(i), c1, p)
                for i in ds..ns repeat
                    v1(i) := Qdot2(v1(i), c1, v2(i - ds), c2, p)
            else
                for i in ds..ns repeat
                    v1(i) := Qmuladdmod(c2, v2(i - ds), v1(i), p)

        divide!(r0 : PA, r1 : PA, res : PA, p : Integer) : Void ==
            dr0 := qconvert(degree(r0))@SingleInteger
            dr1 := qconvert(degree(r1))@SingleInteger
            dr1 < 0 => error "divide!: zero divisor"
            dr0 < 0 => return void()
            c0 := r1(dr1)
            c0 := modInverse(c0, p)
            dr1 = 0 =>
                while not(dr0 < 0) repeat
                    c1 : Integer := Qmul(c0, r0(dr0), p)
                    res(dr0) := c1
                    r0(dr0) := 0
                    dr0 := dr0 - 1
            while not(dr0 < dr1) repeat
                delta := dr0 - dr1
                c1 : Integer := Qmul(c0, r0(dr0), p)
                res(delta) := c1
                c1 := p - c1
                r0(dr0) := 0
                dr0 := dr0 - 1
                if dr0 < 0 then break
                vector_combination(r0, 1, r1, c1, dr0, delta, p)
                while r0(dr0) = 0 repeat
                    dr0 := dr0 - 1
                    if dr0 < 0 then break

        remainder!(r0 : PA, r1 : PA, p : Integer) : Void ==
            dr0 := qconvert(degree(r0))@SingleInteger
            dr1 := qconvert(degree(r1))@SingleInteger
            dr1 < 0 => error "remainder!: zero divisor"
            dr0 < 0 => return void()
            dr1 = 0 =>
                while not(dr0 < 0) repeat
                    r0(dr0) := 0
                    dr0 := dr0 - 1
            c0 := r1(dr1)
            c0 := modInverse(c0, p)
            while not(dr0 < dr1) repeat
                delta := dr0 - dr1
                c1 : Integer := Qmul(c0, r0(dr0), p)
                c1 := p - c1
                r0(dr0) := 0
                dr0 := dr0 - 1
                if dr0 < 0 then break
                vector_combination(r0, 1, r1, c1, dr0, delta, p)
                while r0(dr0) = 0 repeat
                    dr0 := dr0 - 1
                    if dr0 < 0 then break

        gcd(x : PA, y : PA, p : Integer) : PA ==
            dr0 := qconvert(degree(y))@SingleInteger
            dr1 : SingleInteger
            if dr0 < 0 then
                tmpp := x
                x := y
                y := tmpp
                dr1 := dr0
                dr0 := qconvert(degree(y))@SingleInteger
            else
                dr1 := qconvert(degree(x))@SingleInteger
            dr0 < 0 => return new(1, 0)$PA
            r0 := new(qcoerce(dr0 + 1)@NonNegativeInteger, 0)$PA
            copy_first(r0, y, dr0 + 1)
            dr1 < 0 =>
                c := r0(dr0)
                c := modInverse(c, p)
                mul_by_scalar(r0, dr0, c, p)
                return r0
            r1 := new(qcoerce(dr1 + 1)@NonNegativeInteger, 0)$PA
            copy_first(r1, x, dr1 + 1)
            while 0 < dr1 repeat
                while not(dr0 < dr1) repeat
                    delta := dr0 - dr1
                    c1 : Integer := sub_SI(p, r0(dr0))$Lisp
                    c0 := r1(dr1)
                    if c0 ~= 1 and 30 < delta then
                        c0 :=  modInverse(c0, p)
                        mul_by_scalar(r1, dr1, c0, p)
                        c0 := 1
                    r0(dr0) := 0
                    dr0 := dr0 - 1
                    vector_combination(r0, c0, r1, c1, dr0, delta, p)
                    while r0(dr0) = 0 repeat
                        dr0 := dr0 - 1
                        if dr0 < 0 then break
                tmpp := r0
                tmp := dr0
                r0 := r1
                dr0 := dr1
                r1 := tmpp
                dr1 := tmp
            not(dr1 < 0) =>
                r1(0) := 1
                return r1
            c := r0(dr0)
            c := modInverse(c, p)
            mul_by_scalar(r0, dr0, c, p)
            r0

        gcd(a : PrimitiveArray PA, lo : Integer, hi : Integer, p : Integer) _
               : PA ==
            res := a(lo)
            for i in (lo + 1)..hi repeat
                res := gcd(a(i), res, p)
            res

        lcm2(v1 : PA, v2 : PA, p : Integer) : PA ==
            pp := gcd(v1, v2, p)
            dv2 := qconvert(degree(v2))@SingleInteger
            dpp := qconvert(degree(pp))@SingleInteger
            dv2 = dpp =>
                v1
            dpp = 0 => mul(v1, v2, p)
            tmp1 := new(qcoerce(dv2 + 1)@NonNegativeInteger, 0)$PA
            tmp2 := new(qcoerce(dv2 - dpp + 1)@NonNegativeInteger, 0)$PA
            copy_first(tmp1, v2, dv2 + 1)
            divide!(tmp1, pp, tmp2, p)
            mul(v1, tmp2, p)

        lcm(a : PrimitiveArray PA, lo : Integer, hi : Integer, p : Integer) _
               : PA ==
            res := a(lo)
            for i in (lo + 1)..hi repeat
                res := lcm2(a(i), res, p)
            res

        inner_mul : (PA, PA, PA,  SingleInteger, SingleInteger, _
                      SingleInteger, SingleInteger, Integer) -> Void

        mul(x : PA, y : PA, p : Integer) : PA ==
            xdeg := qconvert(degree(x))@SingleInteger
            ydeg := qconvert(degree(y))@SingleInteger
            if xdeg > ydeg then
                tmpp := x
                tmp := xdeg
                x := y
                xdeg := ydeg
                y := tmpp
                ydeg := tmp
            xcoeffs := x
            ycoeffs := y
            xdeg < 0 => x
            xdeg = 0 and xcoeffs(0) = 1 => copy(y)
            zdeg : SingleInteger := xdeg + ydeg
            zdeg0 := qcoerce((zdeg + 1)::Integer)@NonNegativeInteger
            zcoeffs := new(zdeg0, 0)$PA
            inner_mul(xcoeffs, ycoeffs, zcoeffs, xdeg, ydeg, 0, zdeg, p)
            zcoeffs

        inner_mul(x, y, z, xdeg, ydeg, d, zdeg, p) ==
            -- Logically not necessary, but helps to get better code
            ds : SingleInteger := d
            if ydeg < xdeg then
                tmpp := x
                tmp := xdeg
                x := y
                xdeg := ydeg
                y := tmpp
                ydeg := tmp
            xdeg :=
                zdeg < xdeg => zdeg
                xdeg
            ydeg :=
                zdeg < ydeg => zdeg
                ydeg
            ss : U64Int
            i : SingleInteger
            j : SingleInteger
            for i in 0..xdeg repeat
                ss := qconvert(z(i + ds))
                for j in 0..i repeat
                    ss := Qmuladd(x(i - j), y(j), ss)
                z(i + d) := Qrem(ss, p)
            for i in (xdeg+1)..ydeg repeat
                ss := qconvert(z(i + ds))
                for j in 0..xdeg repeat
                    ss := Qmuladd(x(j), y(i-j), ss)
                z(i + d) := Qrem(ss, p)
            for i in (ydeg+1)..zdeg repeat
                ss := qconvert(z(i + ds))
                for j in (i-xdeg)..ydeg repeat
                    ss := Qmuladd(x(i - j), y(j), ss)
                z(i + ds) := Qrem(ss, p)

        truncated_mul_add(x, y, z, m, p) ==
            xdeg := qconvert(degree(x))@SingleInteger
            ydeg := qconvert(#y - 1)@SingleInteger
            inner_mul(x, y, z, xdeg, ydeg, 0, qconvert(m)@SingleInteger, p)

        truncated_mul_add2(x, y, z, d, m, p) ==
            xdeg := qconvert(#x - 1)@SingleInteger
            ydeg := qconvert(#y - 1)@SingleInteger
            inner_mul(x, y, z, xdeg, ydeg, qconvert(d)@SingleInteger,
                      qconvert(m - d)@SingleInteger, p)

        truncated_multiplication(x, y, m, p) ==
            xdeg := qconvert(#x - 1)@SingleInteger
            ydeg := qconvert(#y - 1)@SingleInteger
            z := new(qcoerce(qconvert(m)@SingleInteger + 1
                            )@NonNegativeInteger, 0)$PA
            inner_mul(x, y, z, xdeg, ydeg, 0, qconvert(m)@SingleInteger, p)
            z

        pow(x : PA, n : PositiveInteger, d : NonNegativeInteger, _
            p : Integer) : PA ==
            one? n => x
            odd?(n)$Integer =>
                truncated_multiplication(x,
                    pow(truncated_multiplication(x, x, d, p),
                        qcoerce(shift(n, -1))@PositiveInteger,
                        d,
                        p),
                    d,
                    p)
            pow(truncated_multiplication(x, x, d, p),
                qcoerce(shift(n, -1))@PositiveInteger,
                d,
                p)

        differentiate(x : PA, p : Integer) : PA ==
            d := #x - 1
            if zero? d then empty()$PA
            else
                r := new(d::NonNegativeInteger, 0)$PA
                for i in 0..d-1 repeat
                    i1 := i+1
                    r.i := Qmul(i1, x.i1, p)
                r

        differentiate(x : PA, n : NonNegativeInteger, p : Integer) : PA ==
            ns := qconvert(n)@SingleInteger
            zero? ns => x
            d := #x - 1
            ds := qconvert(d)@SingleInteger
            if ds < ns then empty()$PA
            else
                res := new(qcoerce(ds - ns + 1)@NonNegativeInteger, 0)$PA
                for i in ns..ds repeat
                    j : SingleInteger := i - ns
                    f := j+1
                    for k in j+2..i repeat f := Qmul(f, k, p)
                    res(j) := Qmul(f, x(i), p)
                res

        extended_gcd(x : PA, y : PA, p : Integer) : List(PA) ==
            dr0 := qconvert(degree(x))@SingleInteger
            dr1 : SingleInteger
            swapped : Boolean := false
            t0 : PA
            if dr0 < 0 then
                (x, y) := (y, x)
                dr1 := dr0
                dr0 := qconvert(degree(x))@SingleInteger
                swapped := true
            else
                dr1 := qconvert(degree(y))@SingleInteger
            dr1 < 0 =>
                dr0 < 0 =>
                    return [new(1, 0)$PA, new(1, 0)$PA, new(1, 1)$PA]
                r0 := new(qcoerce(dr0 + 1)@NonNegativeInteger, 0)$PA
                copy_first(r0, x, dr0 + 1)
                c := r0(dr0)
                c := modInverse(c, p)
                mul_by_scalar(r0, dr0, c, p)
                t0 := new(1, c)$PA
                if swapped then
                    return [r0, new(1, 0)$PA, t0]
                else
                    return [r0, t0, new(1, 0)$PA]
            swapped => error "impossible"
            dt := (dr0 > 0 => dr0 - 1 ; 0)
            ds := (dr1 > 0 => dr1 - 1 ; 0)
            -- invariant: r0 = s0*x + t0*y, r1 = s1*x + t1*y
            r0 := new(qcoerce(dr0 + 1)@NonNegativeInteger, 0)$PA
            t0 := new(qcoerce(dt + 1)@NonNegativeInteger, 0)$PA
            s0 := new(qcoerce(ds + 1)@NonNegativeInteger, 0)$PA
            copy_first(r0, x, dr0 + 1)
            s0(0) := 1
            r1 := new(qcoerce(dr1 + 1)@NonNegativeInteger, 0)$PA
            t1 := new(qcoerce(dt + 1)@NonNegativeInteger, 0)$PA
            s1 := new(qcoerce(ds + 1)@NonNegativeInteger, 0)$PA
            copy_first(r1, y, dr1 + 1)
            t1(0) := 1
            while dr1 > 0 repeat
                while dr0 >= dr1 repeat
                    delta := dr0 - dr1
                    c1 : Integer := sub_SI(p, r0(dr0))$Lisp
                    c0 := r1(dr1)
                    if c0 ~= 1 and delta > 30 then
                        c0 :=  modInverse(c0, p)
                        mul_by_scalar(r1, dr1, c0, p)
                        mul_by_scalar(t1, dt, c0, p)
                        mul_by_scalar(s1, ds, c0, p)
                        c0 := 1
                    r0(dr0) := 0
                    dr0 := dr0 - 1
                    vector_combination(r0, c0, r1, c1, dr0, delta, p)
                    vector_combination(t0, c0, t1, c1, dt, delta, p)
                    vector_combination(s0, c0, s1, c1, ds, delta, p)
                    while r0(dr0) = 0 repeat
                        dr0 := dr0 - 1
                        if dr0 < 0 then break
                (r0, r1) := (r1, r0)
                (dr0, dr1) := (dr1, dr0)
                (s0, s1) := (s1, s0)
                (t0, t1) := (t1, t0)
            dr1 >= 0 =>
                c := r1(0)
                c := modInverse(c, p)
                r1(0) := 1
                mul_by_scalar(s1, ds, c, p)
                mul_by_scalar(t1, dt, c, p)
                return [r1, s1, t1]
            c := r0(dr0)
            c := modInverse(c, p)
            mul_by_scalar(r0, dr0, c, p)
            mul_by_scalar(s0, ds, c, p)
            mul_by_scalar(t0, dt, c, p)
            [r0, s0, t0]

        resultant(x : PA, y : PA, p : Integer) : Integer ==
            dr0 := qconvert(degree(x))@SingleInteger
            dr0 < 0 => 0
            dr1 := qconvert(degree(y))@SingleInteger
            dr1 < 0 => 0
            r0 := new(qcoerce(dr0 + 1)@NonNegativeInteger, 0)$PA
            copy_first(r0, x, dr0 + 1)
            r1 := new(qcoerce(dr1 + 1)@NonNegativeInteger, 0)$PA
            copy_first(r1, y, dr1 + 1)
            res : SingleInteger := 1
            repeat
                dr0 < dr1 =>
                    (r0, r1) := (r1, r0)
                    (dr0, dr1) := (dr1, dr0)
                c0 := r1(dr1)
                dr1 = 0 =>
                    while 0 < dr0 repeat
                        res := Qmul(res, c0, p)
                        dr0 := dr0 - 1
                    return res
                delta := dr0 - dr1
                c1 : Integer := sub_SI(p, r0(dr0))$Lisp
                if c0 ~= 1 then
                    c1 :=  Qmul(c1, modInverse(c0, p), p)
                r0(dr0) := 0
                dr0 := dr0 - 1
                vector_combination(r0, 1, r1, c1, dr0, delta, p)
                res := Qmul(res, c0, p)
                while r0(dr0) = 0 repeat
                    dr0 := dr0 - 1
                    dr0 < 0 => return 0
                    res := Qmul(res, c0, p)

)abbrev package POLVECP VectorPolynomialOperationsP
VectorPolynomialOperationsP(p : PositiveInteger
                           ) : Export == Implementation where
    K ==> PrimeField(p)
    PA ==> PrimitiveArray(K)
    Export ==> with
        degree : PA -> Integer
          ++ degree(v) is degree of v treated as polynomial
        to_mod_pa : SparseUnivariatePolynomial(Integer) -> PA
          ++ to_mod_pa(s) reduces coefficients of polynomial
          ++ s modulo prime p and converts the result to vector
        pa_to_sup : PA -> SparseUnivariatePolynomial(Integer)
          ++ pa_to_sup(v) converts vector of coefficients to
          ++ a polynomial

    Implementation ==> add

        degree(v : PA) : Integer ==
            n := #v
            for i in (n - 1)..0 by -1 repeat
                not(v(i) = 0) => return i
            -1

        to_mod_pa(s : SparseUnivariatePolynomial(Integer)) : PA ==
            zero?(s) => new(1, 0)$PA
            n0 := qconvert(degree(s))@SingleInteger
            ncoeffs := new(qcoerce(n0+1)@NonNegativeInteger, 0)$PA
            while not(zero?(s)) repeat
                n := degree(s)
                ncoeffs(n) := leadingCoefficient(s)::K
                s := reductum(s)
            ncoeffs

        pa_to_sup(v : PA) : SparseUnivariatePolynomial(Integer) ==
            n := #v
            res : SparseUnivariatePolynomial(Integer) := 0
            for i in 0..(n - 1) repeat
                res := monomial(convert(v(i))@Integer, i
                               )$SparseUnivariatePolynomial(Integer)
                       + res
            res


)abbrev package POLVECF VectorPolynomialOperationsF
VectorPolynomialOperationsF(K : FiniteFieldCategory
                           ) : Export == Implementation where
    PA ==> PrimitiveArray(K)
    Export ==> with
        degree : PA -> Integer
          ++ degree(v) is degree of v treated as polynomial
        to_mod_pa : SparseUnivariatePolynomial(K) -> PA
          ++ to_mod_pa(s) converts polynomial to vector
        pa_to_sup : PA -> SparseUnivariatePolynomial(K)
          ++ pa_to_sup(v) converts vector of coefficients to
          ++ a polynomial

    Implementation ==> add

        degree(v : PA) : Integer ==
            n := #v
            for i in (n - 1)..0 by -1 repeat
                not(v(i) = 0) => return i
            -1

        to_mod_pa(s : SparseUnivariatePolynomial(K)) : PA ==
            zero?(s) => new(1, 0)$PA
            n0 := qconvert(degree(s))@SingleInteger
            ncoeffs := new(qcoerce(n0+1)@NonNegativeInteger, 0)$PA
            while not(zero?(s)) repeat
                n := degree(s)
                ncoeffs(n) := leadingCoefficient(s)
                s := reductum(s)
            ncoeffs

        pa_to_sup(v : PA) : SparseUnivariatePolynomial(K) ==
            n := #v
            res : SparseUnivariatePolynomial(K) := 0
            for i in 0..(n - 1) repeat
                res := monomial(v(i), i
                               )$SparseUnivariatePolynomial(K)
                       + res
            res


