)abbrev domain MODMON ModMonic
++ Description:
++ This domain implements quotient of ring of univariate polynomials
++ by ideal generated by modulus polynomial.  Modulus must be monic.
++ Before performing any operations you need to set modulus, subsequent
++ operations use this modulus.
-- following line prevents caching ModMonic
)bo PUSH('ModMonic, $mutableDomains)

ModMonic(R, Rep) : C == T
 where
  R : Ring
  Rep : UnivariatePolynomialCategory(R)
  C == Join(Ring, LeftModule(R), CoercibleFrom(R)) with
    if R has CommutativeRing then CommutativeRing
  --operations
    setPoly : Rep -> Rep
        ++ setPoly(x) sets modulus for subsequent operations.
    modulus : -> Rep
        ++ modulus() returns current modulus.
    reduce : Rep -> %
        ++ reduce(x) reduces x modulo current modulus.
    lift : % -> Rep --reduce lift = identity
        ++ lift(x) returns x as a polynomial.
    Vectorise : % -> Vector(R)
        ++ Vectorise(x) returns vector of coefficients of lift(x).
    UnVectorise : Vector(R) -> %
        ++ UnVectorise(v) converts vector of coefficients to \spad{%}.
    pow : -> PrimitiveArray(%)
        ++ pow() returns precomputed array of powers of variable
        ++ modulo current modulus from defree \spad{d} up to degree
        ++ \spad{2*d  -1}, where d is degree of the modulus.
        ++ Note: element at index 0 is variable to power d.
    if R has FiniteFieldCategory then
       frobenius : % -> %
        ++ frobenius(x) computes \spad{x^q} where \spad{q} is
        ++ the size of \spad{R}.
  --assertions
    if R has Finite then Finite
  T == add

      reductum : % -> %
      leadingCoefficient : % -> R
      degree : % -> NonNegativeInteger
      monomial : (R, NonNegativeInteger) -> %
      computePowers : () -> PrimitiveArray(%)

    --constants
      m : Rep := monomial(1, 1)$Rep --| degree(m) > 0 and LeadingCoef(m) = R$1
      d := degree(m)$Rep
      d1 := (d-1)::NonNegativeInteger
      twod := 2*d1+1
      frobenius? : Boolean := R has FiniteFieldCategory
      --VectorRep := DirectProduct(d: NonNegativeInteger, R)
    --declarations
      x, y : %
      p : Rep
      d, n : Integer
      e, k1, k2 : NonNegativeInteger
      c : R
      --vect: Vector(R)
      power : PrimitiveArray(%)
      frobeniusPower : PrimitiveArray(%)
      computeFrobeniusPowers : () -> PrimitiveArray(%)
    --representations
    --mutable m    --take this out??
    --define
      power := new(0, 0)
      frobeniusPower := new(0, 0)
      setPoly (mon : Rep) ==
        mon =$Rep m => mon
        oldm := m
        leadingCoefficient mon ~= 1 => error "polynomial must be monic"
        -- following copy code needed since FFPOLY can modify mon
        copymon : Rep := 0
        while not zero? mon repeat
           copymon := monomial(leadingCoefficient mon, degree mon)$Rep + copymon
           mon := reductum mon
        m := copymon
        d := degree(m)$Rep
        d1 := (d-1)::NonNegativeInteger
        twod := 2*d1+1
        power := computePowers()
        if frobenius? then
          degree(oldm)>1 and not((oldm exquo$Rep m) case "failed") =>
              for i in 1..d1 repeat
                frobeniusPower(i) := reduce lift frobeniusPower(i)
          frobeniusPower := computeFrobeniusPowers()
        m
      modulus == m
      if R has Finite then
         size == d * size$R
         random == UnVectorise([random()$R for i in 0..d1])
         index(np : PositiveInteger) : %  ==
             n := np - 1
             s1 := size$R
             res : % := 0
             for i in 0..(d - 1) repeat
                 c1 := n rem s1
                 cp := qcoerce(c1 + 1)$PositiveInteger
                 res := res + monomial(index(cp)$R, i)
                 n := n quo s1
             res
         lookup(x : %) : PositiveInteger ==
             res : Integer := 0
             s1 := size$R
             for dd in (d - 1)..0 by -1 repeat
                 dd1 := degree(x)
                 c1 : R :=
                     dd = dd1 =>
                         c1 := leadingCoefficient(x)
                         x := reductum(x)
                         c1
                     0
                 res := res*s1 + lookup(c1)$R - 1
             qcoerce(res + 1)@PositiveInteger
      0 == 0$Rep
      1 == 1$Rep
      c * x == c *$Rep x
      n * x == (n::R) *$Rep x
      coerce(c : R) : % == monomial(c, 0)$Rep
      coerce(x : %) : OutputForm == coerce(x)$Rep
      coefficient(x, e) : R == coefficient(x, e)$Rep
      reductum(x) == reductum(x)$Rep
      leadingCoefficient x == (leadingCoefficient x)$Rep
      degree x == (degree x)$Rep
      lift(x) == x pretend Rep
      reduce(p) == (monicDivide(p, m)$Rep).remainder
      x = y == x =$Rep y
      x + y == x +$Rep y
      - x == -$Rep x
      x * y ==
        p := x *$Rep y
        ans := 0$Rep
        while (n := degree p)>d1 repeat
           ans := ans + leadingCoefficient(p)*power.(n-d)
           p := reductum p
        ans+p
      Vectorise(x) == [coefficient(lift(x), i) for i in 0..d1]
      UnVectorise(vect) ==
        reduce(+/[monomial(vect.(i+1), i) for i in 0..d1])
      computePowers ==
           mat : PrimitiveArray(%) := new(d, 0)
           mat.0 := reductum(-m)$Rep
           w : % := monomial$Rep (1, 1)
           for i in 1..d1 repeat
              mat.i := w *$Rep mat.(i-1)
              if degree mat.i = d then
                mat.i := reductum mat.i + leadingCoefficient mat.i * mat.0
           mat
      if frobenius? then
          computeFrobeniusPowers() ==
            mat : PrimitiveArray(%) := new(d, 1)
            mat.1 := mult := monomial(1, size$R)
            for i in 2..d1 repeat
               mat.i := mult * mat.(i-1)
            mat

          frobenius(a : %) : % ==
            aq : % := 0
            while a ~= 0 repeat
              aq := aq + leadingCoefficient(a)*frobeniusPower(degree a)
              a := reductum a
            aq

      pow == power
      monomial(c, e)==
         if e<d then monomial(c, e)$Rep
         else
            if e<=twod then
               c * power.(e-d)
            else
               k1 := e quo twod
               k2 := (e-k1*twod)::NonNegativeInteger
               reduce((power.d1 ^k1)*monomial(c, k2))

      if R has Field then

         (x:% exquo y:%):Union(%, "failed") ==
            uv := extendedEuclidean(y, modulus(), x)$Rep
            uv case "failed" => "failed"
            return reduce(uv.coef1)

         recip(y:%):Union(%, "failed") ==  1 exquo y

--Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
--All rights reserved.
--
--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.
--
--    - Neither the name of The Numerical ALgorithms Group Ltd. nor the
--      names of its contributors may be used to endorse or promote products
--      derived from this software without specific prior written permission.
--
--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.
