"""
ntl_lzz_pX.pyx
Wraps NTL's zz_pX type for SAGE
AUTHORS:
- Craig Citro
"""
include "../../ext/interrupt.pxi"
include "../../ext/stdsage.pxi"
include "../../ext/cdefs.pxi"
include 'misc.pxi'
include 'decl.pxi'
from sage.rings.integer import Integer
from sage.rings.integer_ring import IntegerRing
from sage.rings.integer cimport Integer
from sage.rings.integer_ring cimport IntegerRing_class
from sage.rings.finite_rings.integer_mod cimport IntegerMod_gmp, IntegerMod_int, IntegerMod_int64
from sage.libs.ntl.ntl_lzz_pContext import ntl_zz_pContext
from sage.libs.ntl.ntl_lzz_pContext cimport ntl_zz_pContext_class
from sage.libs.ntl.ntl_lzz_p import ntl_zz_p
from sage.libs.ntl.ntl_lzz_p cimport ntl_zz_p
ZZ_sage = IntegerRing()
cdef class ntl_zz_pX:
r"""
The class \class{zz_pX} implements polynomial arithmetic modulo $p$,
for p smaller than a machine word.
Polynomial arithmetic is implemented using the FFT, combined with
the Chinese Remainder Theorem. A more detailed description of the
techniques used here can be found in [Shoup, J. Symbolic
Comp. 20:363-397, 1995].
Small degree polynomials are multiplied either with classical
or Karatsuba algorithms.
"""
def __init__(self, ls=[], modulus=None):
"""
EXAMPLES:
sage: f = ntl.zz_pX([1,2,5,-9],20)
sage: f
[1, 2, 5, 11]
sage: g = ntl.zz_pX([0,0,0],20); g
[]
sage: g[10]=5
sage: g
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5]
sage: g[10]
5
sage: f = ntl.zz_pX([10^30+1, 10^50+1], 100); f
[1, 1]
"""
if modulus is None:
raise ValueError, "You must specify a modulus."
cdef long n
cdef Py_ssize_t i
cdef long temp
if PY_TYPE_CHECK(modulus, Integer):
p_sage = modulus
else:
p_sage = Integer(self.c.p)
n = len(ls)
if (n == 0):
return
self.x.SetMaxLength(n+1)
for i from 0 <= i < n:
a = ls[i]
if PY_TYPE_CHECK(a, IntegerMod_int):
if (self.c.p == (<IntegerMod_int>a).__modulus.int32):
zz_pX_SetCoeff_long(self.x, i, (<IntegerMod_int>a).ivalue)
else:
raise ValueError, \
"Mismatched modulus for converting to zz_pX."
elif PY_TYPE_CHECK(a, IntegerMod_int64):
if (self.c.p == (<IntegerMod_int64>a).__modulus.int64):
zz_pX_SetCoeff_long(self.x, i, (<IntegerMod_int64>a).ivalue)
else:
raise ValueError, \
"Mismatched modulus for converting to zz_pX."
elif PY_TYPE_CHECK(a, IntegerMod_gmp):
if (p_sage == (<IntegerMod_gmp>a).__modulus.sageInteger):
zz_pX_SetCoeff_long(self.x, i, mpz_get_si((<IntegerMod_gmp>a).value))
else:
raise ValueError, \
"Mismatched modulus for converting to zz_pX."
elif PY_TYPE_CHECK(a, Integer):
zz_pX_SetCoeff_long(self.x, i, mpz_fdiv_ui((<Integer>a).value, self.c.p))
elif PY_TYPE_CHECK(a, int):
temp = a
zz_pX_SetCoeff_long(self.x, i, temp%self.c.p)
else:
a = Integer(a)
zz_pX_SetCoeff_long(self.x, i, mpz_fdiv_ui((<Integer>a).value, self.c.p))
return
def __cinit__(self, v=None, modulus=None):
if modulus is None:
zz_pX_construct(&self.x)
return
if PY_TYPE_CHECK( modulus, ntl_zz_pContext_class ):
self.c = <ntl_zz_pContext_class>modulus
elif PY_TYPE_CHECK( modulus, Integer ):
self.c = <ntl_zz_pContext_class>ntl_zz_pContext(modulus)
elif PY_TYPE_CHECK( modulus, long ):
self.c = <ntl_zz_pContext_class>ntl_zz_pContext(modulus)
else:
try:
modulus = int(modulus)
except:
raise ValueError, "%s is not a valid modulus."%modulus
self.c = <ntl_zz_pContext_class>ntl_zz_pContext(modulus)
self.c.restore_c()
zz_pX_construct(&self.x)
def __dealloc__(self):
zz_pX_destruct(&self.x)
def __reduce__(self):
"""
TESTS:
sage: f = ntl.zz_pX([10,10^30+1], 20)
sage: f == loads(dumps(f))
True
"""
return make_zz_pX, (self.list(), self.c)
def __repr__(self):
"""
Return the string representation of self.
EXAMPLES:
sage: f = ntl.zz_pX([3,5], 17)
sage: f.__repr__()
'[3, 5]'
"""
return str(self.list())
def __getitem__(self, i):
"""
Return the ith coefficient of f.
EXAMPLES:
sage: f = ntl.zz_pX(range(7), 71)
sage: f[3] ## indirect doctest
3
sage: f[-5]
0
sage: f[27]
0
"""
cdef ntl_zz_p y
y = PY_NEW(ntl_zz_p)
y.c = self.c
self.c.restore_c()
if not PY_TYPE_CHECK( i, long ):
i = long(i)
y.x = zz_pX_GetCoeff(self.x, i)
return y
def __setitem__(self, i, val):
"""
Set the ith coefficient of self to val. If
i is out of range, raise an exception.
EXAMPLES:
sage: f = ntl.zz_pX([], 7)
sage: f[3] = 2 ; f
[0, 0, 0, 2]
sage: f[-1] = 5
Traceback (most recent call last):
...
ValueError: index (=-1) is out of range
"""
cdef long zero = 0L
if not PY_TYPE_CHECK( i, long ):
i = long(i)
if (i < zero):
raise ValueError, "index (=%s) is out of range"%i
if not PY_TYPE_CHECK( val, long ):
val = long(val)
self.c.restore_c()
zz_pX_SetCoeff_long(self.x, i, val)
return
cdef ntl_zz_pX _new(self):
"""
Quick and dirty method for creating a new object with the
same zz_pContext as self.
EXAMPLES:
sage: f = ntl.zz_pX([1], 20)
sage: f.square() ## indirect doctest
[1]
"""
cdef ntl_zz_pX y
y = PY_NEW(ntl_zz_pX)
y.c = self.c
return y
def __add__(ntl_zz_pX self, other):
"""
Return self + other.
EXAMPLES:
sage: ntl.zz_pX(range(5),20) + ntl.zz_pX(range(6),20) ## indirect doctest
[0, 2, 4, 6, 8, 5]
sage: ntl.zz_pX(range(5),20) + ntl.zz_pX(range(6),50)
Traceback (most recent call last):
...
ValueError: arithmetic operands must have the same modulus.
"""
cdef ntl_zz_pX y
if not PY_TYPE_CHECK(other, ntl_zz_pX):
other = ntl_zz_pX(other, modulus=self.c)
elif self.c is not (<ntl_zz_pX>other).c:
raise ValueError, "arithmetic operands must have the same modulus."
y = self._new()
self.c.restore_c()
zz_pX_add(y.x, self.x, (<ntl_zz_pX>other).x)
return y
def __sub__(ntl_zz_pX self, other):
"""
Return self - other.
EXAMPLES:
sage: ntl.zz_pX(range(5),32) - ntl.zz_pX(range(6),32)
[0, 0, 0, 0, 0, 27]
sage: ntl.zz_pX(range(5),20) - ntl.zz_pX(range(6),50) ## indirect doctest
Traceback (most recent call last):
...
ValueError: arithmetic operands must have the same modulus.
"""
cdef ntl_zz_pX y
if not PY_TYPE_CHECK(other, ntl_zz_pX):
other = ntl_zz_pX(other, modulus=self.c)
elif self.c is not (<ntl_zz_pX>other).c:
raise ValueError, "arithmetic operands must have the same modulus."
self.c.restore_c()
y = self._new()
zz_pX_sub(y.x, self.x, (<ntl_zz_pX>other).x)
return y
def __mul__(ntl_zz_pX self, other):
"""
EXAMPLES:
sage: ntl.zz_pX(range(5),20) * ntl.zz_pX(range(6),20) ## indirect doctest
[0, 0, 1, 4, 10, 0, 10, 14, 11]
sage: ntl.zz_pX(range(5),20) * ntl.zz_pX(range(6),50)
Traceback (most recent call last):
...
ValueError: arithmetic operands must have the same modulus.
"""
cdef ntl_zz_pX y
if not PY_TYPE_CHECK(other, ntl_zz_pX):
other = ntl_zz_pX(other, modulus=self.c)
elif self.c is not (<ntl_zz_pX>other).c:
raise ValueError, "arithmetic operands must have the same modulus."
self.c.restore_c()
y = self._new()
sig_on()
zz_pX_mul(y.x, self.x, (<ntl_zz_pX>other).x)
sig_off()
return y
def __div__(ntl_zz_pX self, other):
"""
Compute quotient self / other, if the quotient is a polynomial.
Otherwise an Exception is raised.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3],17) * ntl.zz_pX([4,5],17)**2
sage: g = ntl.zz_pX([4,5],17)
sage: f/g ## indirect doctest
[4, 13, 5, 15]
sage: ntl.zz_pX([1,2,3],17) * ntl.zz_pX([4,5],17)
[4, 13, 5, 15]
sage: f = ntl.zz_pX(range(10),17); g = ntl.zz_pX([-1,0,1],17)
sage: f/g
Traceback (most recent call last):
...
ArithmeticError: self (=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) is not divisible by other (=[16, 0, 1])
sage: ntl.zz_pX(range(5),20) / ntl.zz_pX(range(6),50)
Traceback (most recent call last):
...
ValueError: arithmetic operands must have the same modulus.
"""
cdef long divisible
cdef ntl_zz_pX q
if not PY_TYPE_CHECK(other, ntl_zz_pX):
other = ntl_zz_pX(other, modulus=self.c)
elif self.c is not (<ntl_zz_pX>other).c:
raise ValueError, "arithmetic operands must have the same modulus."
self.c.restore_c()
q = self._new()
sig_on()
divisible = zz_pX_divide(q.x, self.x, (<ntl_zz_pX>other).x)
sig_off()
if not divisible:
raise ArithmeticError, "self (=%s) is not divisible by other (=%s)"%(self, other)
return q
def __mod__(ntl_zz_pX self, other):
"""
Given polynomials a, b in ZZ[X], there exist polynomials q, r
in QQ[X] such that a = b*q + r, deg(r) < deg(b). This
function returns q if q lies in ZZ[X], and otherwise raises an
Exception.
EXAMPLES:
sage: f = ntl.zz_pX([2,4,6],17); g = ntl.zz_pX([2],17)
sage: f % g ## indirect doctest
[]
sage: f = ntl.zz_pX(range(10),17); g = ntl.zz_pX([-1,0,1],17)
sage: f % g
[3, 8]
"""
cdef ntl_zz_pX y
if not PY_TYPE_CHECK(other, ntl_zz_pX):
other = ntl_zz_pX(other, modulus=self.c)
elif self.c is not (<ntl_zz_pX>other).c:
raise ValueError, "arithmetic operands must have the same modulus."
self.c.restore_c()
y = self._new()
sig_on()
zz_pX_mod(y.x, self.x, (<ntl_zz_pX>other).x)
sig_off()
return y
def __pow__(ntl_zz_pX self, long n, ignored):
"""
Return the n-th nonnegative power of self.
EXAMPLES:
sage: g = ntl.zz_pX([-1,0,1],20)
sage: g**10 ## indirect doctest
[1, 0, 10, 0, 5, 0, 0, 0, 10, 0, 8, 0, 10, 0, 0, 0, 5, 0, 10, 0, 1]
"""
if n < 0:
raise ValueError, "Only positive exponents allowed."
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
sig_on()
zz_pX_power(y.x, self.x, n)
sig_off()
return y
def quo_rem(ntl_zz_pX self, ntl_zz_pX right):
"""
Returns the quotient and remainder when self is divided by right.
Specifically, this return r, q such that $self = q * right + r$
EXAMPLES:
sage: f = ntl.zz_pX(range(7), 19)
sage: g = ntl.zz_pX([2,4,6], 19)
sage: f // g
[1, 1, 15, 16, 1]
sage: f % g
[17, 14]
sage: f.quo_rem(g)
([1, 1, 15, 16, 1], [17, 14])
sage: (f // g) * g + f % g
[0, 1, 2, 3, 4, 5, 6]
"""
cdef ntl_zz_pX q = self._new()
cdef ntl_zz_pX r = self._new()
self.c.restore_c()
sig_on()
zz_pX_divrem(q.x, r.x, self.x, right.x)
sig_off()
return q, r
def __floordiv__(ntl_zz_pX self, ntl_zz_pX right):
"""
Returns the whole part of $self / right$.
EXAMPLE:
sage: f = ntl.zz_pX(range(10), 19); g = ntl.zz_pX([1]*5, 19)
sage: f // g ## indirect doctest
[8, 18, 18, 18, 18, 9]
"""
cdef ntl_zz_pX q = self._new()
self.c.restore_c()
sig_on()
zz_pX_div(q.x, self.x, right.x)
sig_off()
return q
def __lshift__(ntl_zz_pX self, long n):
"""
Shifts this polynomial to the left, which is multiplication by $x^n$.
EXAMPLE:
sage: f = ntl.zz_pX([2,4,6], 17)
sage: f << 2 ## indirect doctest
[0, 0, 2, 4, 6]
"""
cdef ntl_zz_pX r = self._new()
self.c.restore_c()
zz_pX_LeftShift(r.x, self.x, n)
return r
def __rshift__(ntl_zz_pX self, long n):
"""
Shifts this polynomial to the right, which is division by $x^n$ (and truncation).
EXAMPLE:
sage: f = ntl.zz_pX([1,2,3], 17)
sage: f >> 2 ## indirect doctest
[3]
"""
cdef ntl_zz_pX r = self._new()
self.c.restore_c()
zz_pX_RightShift(r.x, self.x, n)
return r
def diff(self):
"""
The formal derivative of self.
EXAMPLE:
sage: f = ntl.zz_pX(range(10), 17)
sage: f.diff()
[1, 4, 9, 16, 8, 2, 15, 13, 13]
"""
cdef ntl_zz_pX r = self._new()
self.c.restore_c()
zz_pX_diff(r.x, self.x)
return r
def reverse(self):
"""
Returns self with coefficients reversed, i.e. $x^n self(x^{-n})$.
EXAMPLE:
sage: f = ntl.zz_pX([2,4,6], 17)
sage: f.reverse()
[6, 4, 2]
"""
cdef ntl_zz_pX r = self._new()
self.c.restore_c()
zz_pX_reverse(r.x, self.x)
return r
def __neg__(self):
"""
Return the negative of self.
EXAMPLES:
sage: f = ntl.zz_pX([2,0,0,1],20)
sage: -f
[18, 0, 0, 19]
"""
cdef ntl_zz_pX y
y = self._new()
self.c.restore_c()
sig_on()
zz_pX_negate(y.x, self.x)
sig_off()
return y
def __cmp__(ntl_zz_pX self, other):
"""
Decide whether or not self and other are equal.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3],20)
sage: g = ntl.zz_pX([1,2,3,0],20)
sage: f == g
True
sage: g = ntl.zz_pX([0,1,2,3],20)
sage: f == g
False
"""
if not PY_TYPE_CHECK(other, ntl_zz_pX):
return cmp(ntl_zz_pX, other.parent())
if not (self.c is (<ntl_zz_pX>other).c):
return cmp(self.c.p, (<ntl_zz_pX>other).c.p)
self.c.restore_c()
if (NTL_zz_pX_DOUBLE_EQUALS(self.x, (<ntl_zz_pX>other).x)):
return 0
else:
return -1
def list(self):
"""
Return list of entries as a list of python ints.
EXAMPLES:
sage: f = ntl.zz_pX([23, 5,0,1], 10)
sage: f.list()
[3, 5, 0, 1]
sage: type(f.list()[0])
<type 'int'>
"""
cdef long i
self.c.restore_c()
return [ zz_p_rep(zz_pX_GetCoeff(self.x, i)) for i from 0 <= i <= zz_pX_deg(self.x) ]
def degree(self):
"""
Return the degree of this polynomial. The degree of the 0
polynomial is -1.
EXAMPLES:
sage: f = ntl.zz_pX([5,0,1],50)
sage: f.degree()
2
sage: f = ntl.zz_pX(range(100),50)
sage: f.degree()
99
sage: f = ntl.zz_pX([],10)
sage: f.degree()
-1
sage: f = ntl.zz_pX([1],77)
sage: f.degree()
0
"""
self.c.restore_c()
return zz_pX_deg(self.x)
def leading_coefficient(self):
"""
Return the leading coefficient of this polynomial.
EXAMPLES:
sage: f = ntl.zz_pX([3,6,9],19)
sage: f.leading_coefficient()
9
sage: f = ntl.zz_pX([],21)
sage: f.leading_coefficient()
0
"""
self.c.restore_c()
return zz_p_rep(zz_pX_LeadCoeff(self.x))
def constant_term(self):
"""
Return the constant coefficient of this polynomial.
EXAMPLES:
sage: f = ntl.zz_pX([3,6,9],127)
sage: f.constant_term()
3
sage: f = ntl.zz_pX([], 12223)
sage: f.constant_term()
0
"""
self.c.restore_c()
return zz_p_rep(zz_pX_ConstTerm(self.x))
def square(self):
"""
Return f*f.
EXAMPLES:
sage: f = ntl.zz_pX([-1,0,1],17)
sage: f*f
[1, 0, 15, 0, 1]
"""
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
sig_on()
zz_pX_sqr(y.x, self.x)
sig_off()
return y
def truncate(self, long m):
"""
Return the truncation of this polynomial obtained by
removing all terms of degree >= m.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3,4,5],70)
sage: f.truncate(3)
[1, 2, 3]
sage: f.truncate(8)
[1, 2, 3, 4, 5]
sage: f.truncate(1)
[1]
sage: f.truncate(0)
[]
sage: f.truncate(-1)
[]
sage: f.truncate(-5)
[]
"""
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
if m <= 0:
y.x = zz_pX_zero()
else:
sig_on()
zz_pX_trunc(y.x, self.x, m)
sig_off()
return y
def multiply_and_truncate(self, ntl_zz_pX other, long m):
"""
Return self*other but with terms of degree >= m removed.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3,4,5],20)
sage: g = ntl.zz_pX([10],20)
sage: f.multiply_and_truncate(g, 2)
[10]
sage: g.multiply_and_truncate(f, 2)
[10]
"""
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
if m <= 0:
y.x = zz_pX_zero()
else:
sig_on()
zz_pX_MulTrunc(y.x, self.x, other.x, m)
sig_off()
return y
def square_and_truncate(self, long m):
"""
Return self*self but with terms of degree >= m removed.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3,4,5],20)
sage: f.square_and_truncate(4)
[1, 4, 10]
sage: (f*f).truncate(4)
[1, 4, 10]
"""
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
if m <= 0:
y.x = zz_pX_zero()
else:
sig_on()
zz_pX_SqrTrunc(y.x, self.x, m)
sig_off()
return y
def invert_and_truncate(self, long m):
"""
Compute and return the inverse of self modulo $x^m$.
The constant term of self must be 1 or -1.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3,4,5,6,7],20)
sage: f.invert_and_truncate(20)
[1, 18, 1, 0, 0, 0, 0, 8, 17, 2, 13, 0, 0, 0, 4, 0, 17, 10, 9]
sage: g = f.invert_and_truncate(20)
sage: g * f
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 4, 4, 3]
"""
if m < 0:
raise ArithmeticError, "m (=%s) must be positive"%m
n = self.constant_term()
if n != 1 and n != -1:
raise ArithmeticError, \
"The constant term of self must be 1 or -1."
cdef ntl_zz_pX y = self._new()
self.c.restore_c()
if m <= 0:
y.x = zz_pX_zero()
else:
sig_on()
zz_pX_InvTrunc(y.x, self.x, m)
sig_off()
return y
def is_zero(self):
"""
Return True exactly if this polynomial is 0.
EXAMPLES:
sage: f = ntl.zz_pX([0,0,0,20],5)
sage: f.is_zero()
True
sage: f = ntl.zz_pX([0,0,1],30)
sage: f
[0, 0, 1]
sage: f.is_zero()
False
"""
self.c.restore_c()
return zz_pX_IsZero(self.x)
def is_one(self):
"""
Return True exactly if this polynomial is 1.
EXAMPLES:
sage: f = ntl.zz_pX([1,1],101)
sage: f.is_one()
False
sage: f = ntl.zz_pX([1],2)
sage: f.is_one()
True
"""
self.c.restore_c()
return zz_pX_IsOne(self.x)
def is_monic(self):
"""
Return True exactly if this polynomial is monic.
EXAMPLES:
sage: f = ntl.zz_pX([2,0,0,1],17)
sage: f.is_monic()
True
sage: g = f.reverse()
sage: g.is_monic()
False
sage: g
[1, 0, 0, 2]
sage: f = ntl.zz_pX([1,2,0,3,0,2],717)
sage: f.is_monic()
False
"""
self.c.restore_c()
if zz_pX_IsZero(self.x):
return False
return ( zz_p_rep(zz_pX_LeadCoeff(self.x)) == 1 )
def set_x(self):
"""
Set this polynomial to the monomial "x".
EXAMPLES:
sage: f = ntl.zz_pX([],177)
sage: f.set_x()
sage: f
[0, 1]
sage: g = ntl.zz_pX([0,1],177)
sage: f == g
True
Though f and g are equal, they are not the same objects in memory:
sage: f is g
False
"""
self.c.restore_c()
zz_pX_SetX(self.x)
def is_x(self):
"""
True if this is the polynomial "x".
EXAMPLES:
sage: f = ntl.zz_pX([],100)
sage: f.set_x()
sage: f.is_x()
True
sage: f = ntl.zz_pX([0,1],383)
sage: f.is_x()
True
sage: f = ntl.zz_pX([1],38)
sage: f.is_x()
False
"""
self.c.restore_c()
return zz_pX_IsX(self.x)
def clear(self):
"""
Reset this polynomial to 0. Changes this polynomial in place.
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3],17)
sage: f
[1, 2, 3]
sage: f.clear()
sage: f
[]
"""
self.c.restore_c()
zz_pX_clear(self.x)
def preallocate_space(self, long n):
"""
Pre-allocate spaces for n coefficients. The polynomial that f
represents is unchanged. This is useful if you know you'll be
setting coefficients up to n, so memory isn't re-allocated as
the polynomial grows. (You might save a millisecond with this
function.)
EXAMPLES:
sage: f = ntl.zz_pX([1,2,3],17)
sage: f.preallocate_space(20)
sage: f
[1, 2, 3]
sage: f[10]=5 # no new memory is allocated
sage: f
[1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 5]
"""
self.c.restore_c()
sig_on()
self.x.SetMaxLength(n)
sig_off()
return
def make_zz_pX(L, context):
"""
For unpickling.
TESTS:
sage: f = ntl.zz_pX(range(16), 12)
sage: loads(dumps(f)) == f
True
"""
return ntl_zz_pX(L, context)