14.2.1.6 ZZX_class Objects

class ZZX_class
The class ZZX implements polynomials in $ \mathbf{Z}[X]$ , i.e., univariate polynomials with integer coefficients.

Polynomial multiplication is very fast, and is implemented using one of 4 different algorithms:

  1. Classical
  2. Karatsuba
  3. Schoenhage and Strassen -- performs an FFT by working modulo a "Fermat number" of appropriate size... good for polynomials with huge coefficients and moderate degree
  4. CRT/FFT -- performs an FFT by working modulo several small primes. This is good for polynomials with moderate coefficients and huge degree.

The choice of algorithm is somewhat heuristic, and may not always be perfect.

Many thanks to Juergen Gerhard <jngerhar@plato.uni-paderborn.de> for pointing out the deficiency in the NTL-1.0 ZZX arithmetic, and for contributing the Schoenhage/Strassen code.

Extensive use is made of modular algorithms to enhance performance (e.g., the GCD algorithm and many others).

Instances of class ZZX_class have the following methods (in addition to inherited methods and special methods):

charpoly_mod,$  $ clear,$  $ constant_term,$  $ content,$  $ copy,$  $ degree,$  $ derivative,$  $ discriminant,$  $ gcd,$  $ invert_and_truncate,$  $ is_monic,$  $ is_one,$  $ is_x,$  $ is_zero,$  $ leading_coefficient,$  $ left_shift,$  $ minpoly_mod_noproof,$  $ multiply_and_truncate,$  $ multiply_mod,$  $ norm_mod,$  $ preallocate_space,$  $ primitive_part,$  $ pseudo_quo_rem,$  $ quo_rem,$  $ resultant,$  $ reverse,$  $ right_shift,$  $ set_x,$  $ square,$  $ square_and_truncate,$  $ trace_list,$  $ trace_mod,$  $ truncate,$  $ xgcd

Further documentation:

charpoly_mod( )

Return the characteristic polynomial of this polynomial modulo the modulus. The modulus must be monic of degree bigger than self. If proof=False (the default is True), then this function may use a randomized strategy that errors with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([1,2,0,3])
sage: mod = ntl.ZZX([-5,2,0,0,1])
sage: f.charpoly_mod(mod)
[-8846 -594 -60 14 1]

clear( )

Reset this polynomial to 0. Changes this polynomial in place.

sage: f = ntl.ZZX([1,2,3])
sage: f
[1 2 3]
sage: f.clear()
sage: f
[]

constant_term( )

Return the constant coefficient of this polynomial.

sage: f = ntl.ZZX([3,6,9])
sage: f.constant_term()
3
sage: f = ntl.ZZX()
sage: f.constant_term()
0

content( )

Return the content of f, which has sign the same as the leading coefficient of f. Also, our convention is that the content of 0 is 0.

sage: f = ntl.ZZX([2,0,0,2])
sage: f.content()
2
sage: f = ntl.ZZX([2,0,0,-2])
sage: f.content()
-2
sage: f = ntl.ZZX([6,12,3,9])
sage: f.content()
3
sage: f = ntl.ZZX([])
sage: f.content()
0

degree( )

Return the degree of this polynomial. The degree of the 0 polynomial is -1.

sage: f = ntl.ZZX([5,0,1])
sage: f.degree()
2
sage: f = ntl.ZZX(range(100))
sage: f.degree()
99
sage: f = ntl.ZZX()
sage: f.degree()
-1
sage: f = ntl.ZZX([1])
sage: f.degree()
0

derivative( )

Return the derivative of this polynomial.

sage: f = ntl.ZZX([1,7,0,13])
sage: f.derivative()
[7 0 39]

discriminant( )

Return the discriminant of self, which is by definition

$\displaystyle (-1)^{m(m-1)/2} {\mbox{\tt resultant}}(a, a')/lc(a),
$

where m = deg(a), and lc(a) is the leading coefficient of a. If proof is False (the default is True), then this function may use a randomized strategy that errors with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([1,2,0,3])
sage: f.discriminant()
-339
sage: f.discriminant(proof=False)
-339

gcd( )

Return the gcd d = gcd(a, b), where by convention the leading coefficient of d is >= 0. We uses a multi-modular algorithm.

sage: f = ntl.ZZX([1,2,3]) * ntl.ZZX([4,5])**2
sage: g = ntl.ZZX([1,1,1])**3 * ntl.ZZX([1,2,3])
sage: f.gcd(g)
[1 2 3]
sage: g.gcd(f)
[1 2 3]

invert_and_truncate( )

Compute and return the inverse of self modulo $ x^m$ . The constant term of self must be 1 or -1.

sage: f = ntl.ZZX([1,2,3,4,5,6,7])
sage: f.invert_and_truncate(20)
[1 -2 1 0 0 0 0 8 -23 22 -7 0 0 0 64 -240 337 -210 49]
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 -512 1344 -1176 343]

is_monic( )

Return True exactly if this polynomial is monic.

sage: f = ntl.ZZX([2,0,0,1])
sage: f.is_monic()
True
sage: g = f.reverse()
sage: g.is_monic()
False
sage: g
[1 0 0 2]

is_one( )

Return True exactly if this polynomial is 1.

sage: f = ntl.ZZX([1,1])
sage: f.is_one()
False
sage: f = ntl.ZZX([1])
sage: f.is_one()
True

is_x( )

True if this is the polynomial "x".

sage: f = ntl.ZZX()
sage: f.set_x()
sage: f.is_x()
True
sage: f = ntl.ZZX([0,1])
sage: f.is_x()
True
sage: f = ntl.ZZX([1])
sage: f.is_x()
False

is_zero( )

Return True exactly if this polynomial is 0.

sage: f = ntl.ZZX([0,0,0,0])
sage: f.is_zero()
True
sage: f = ntl.ZZX([0,0,1])
sage: f
[0 0 1]
sage: f.is_zero()
False

leading_coefficient( )

Return the leading coefficient of this polynomial.

sage: f = ntl.ZZX([3,6,9])
sage: f.leading_coefficient()
9
sage: f = ntl.ZZX()
sage: f.leading_coefficient()
0

left_shift( )

Return the polynomial obtained by shifting all coefficients of this polynomial to the left n positions.

sage: f = ntl.ZZX([2,0,0,1])
sage: f
[2 0 0 1]
sage: f.left_shift(2)
[0 0 2 0 0 1]
sage: f.left_shift(5)
[0 0 0 0 0 2 0 0 1]

A negative left shift is a right shift.

sage: f.left_shift(-2)
[0 1]

minpoly_mod_noproof( )

Return the minimal polynomial of this polynomial modulo the modulus. The modulus must be monic of degree bigger than self. In all cases, this function may use a randomized strategy that errors with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([0,0,1])
sage: g = f*f
sage: f.charpoly_mod(g)
[0 0 0 0 1]

However, since $ f^2 = 0$ moduluo $ g$ , its minimal polynomial is of degree $ 2$ .

sage: f.minpoly_mod_noproof(g)
[0 0 1]

multiply_and_truncate( )

Return self*other but with terms of degree >= m removed.

sage: f = ntl.ZZX([1,2,3,4,5])
sage: g = ntl.ZZX([10])
sage: f.multiply_and_truncate(g, 2)
[10 20]
sage: g.multiply_and_truncate(f, 2)
[10 20]

multiply_mod( )

Return self*other deg(modulus) > 0, and self and other must have smaller degree.

sage: modulus = ntl.ZZX([1,2,0,1])    # must be monic
sage: g = ntl.ZZX([-1,0,1])
sage: h = ntl.ZZX([3,7,13])
sage: h.multiply_mod(g, modulus)
[-10 -34 -36]

norm_mod( )

Return the norm of this polynomial modulo the modulus. The modulus must be monic, and of positive degree strictly greater than the degree of self. If proof=False (the default is proof=True) then it may use a randomized strategy that errors with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([1,2,0,3])
sage: mod = ntl.ZZX([-5,2,0,0,1])
sage: f.norm_mod(mod)
-8846

The norm is the constant term of the characteristic polynomial.

sage: f.charpoly_mod(mod)
[-8846 -594 -60 14 1]

preallocate_space( )

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.)

sage: f = ntl.ZZX([1,2,3])
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]

primitive_part( )

Return the primitive part of f. Our convention is that the leading coefficient of the primitive part is nonnegegative, and the primitive part of 0 is 0.

sage: f = ntl.ZZX([6,12,3,9])
sage: f.primitive_part()
[2 4 1 3]
sage: f
[6 12 3 9]
sage: f = ntl.ZZX([6,12,3,-9])
sage: f
[6 12 3 -9]
sage: f.primitive_part()
[-2 -4 -1 3]
sage: f = ntl.ZZX()
sage: f.primitive_part()
[]

pseudo_quo_rem( )

Performs pseudo-division: computes q and r with deg(r) < deg(b), and LeadCoeff(b)(deg(a)-deg(b)+1) a = b q + r. Only the classical algorithm is used.

sage: f = ntl.ZZX([0,1])
sage: g = ntl.ZZX([1,2,3])
sage: g.pseudo_quo_rem(f)
([1], [2 3])
sage: f = ntl.ZZX([1,1])
sage: g.pseudo_quo_rem(f)
([2], [-1 3])

quo_rem( )

Returns the unique integral q and r such that self = q*other + r, if they exist. Otherwise raises an Exception.

sage: f = ntl.ZZX(range(10)); g = ntl.ZZX([-1,0,1])
sage: q, r = f.quo_rem(g)
sage: q, r
([20 24 18 21 14 16 8 9], [20 25])
sage: q*g + r == f
True

resultant( )

Return the resultant of self and other. If proof = False (the default is proof=True), then this function may use a randomized strategy that errors with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([17,0,1,1])
sage: g = ntl.ZZX([34,-17,18,2])
sage: f.resultant(g)
1345873
sage: f.resultant(g, proof=False)
1345873

reverse( )

Return the polynomial obtained by reversing the coefficients of this polynomial. If hi is set then this function behaves as if this polynomial has degree hi.

sage: f = ntl.ZZX([1,2,3,4,5])
sage: f.reverse()
[5 4 3 2 1]
sage: f.reverse(hi=10)
[0 0 0 0 0 0 5 4 3 2 1]
sage: f.reverse(hi=2)
[3 2 1]
sage: f.reverse(hi=-2)
[]

right_shift( )

Return the polynomial obtained by shifting all coefficients of this polynomial to the right n positions.

sage: f = ntl.ZZX([2,0,0,1])
sage: f
[2 0 0 1]
sage: f.right_shift(2)
[0 1]
sage: f.right_shift(5)
[]
sage: f.right_shift(-2)
[0 0 2 0 0 1]

set_x( )

Set this polynomial to the monomial "x".

sage: f = ntl.ZZX()
sage: f.set_x()
sage: f
[0 1]
sage: g = ntl.ZZX([0,1])
sage: f == g
True

Though f and g are equal, they are not the same objects in memory:

sage: f is g
False

square( )

Return f*f.

sage: f = ntl.ZZX([-1,0,1])
sage: f*f
[1 0 -2 0 1]

square_and_truncate( )

Return self*self but with terms of degree >= m removed.

sage: f = ntl.ZZX([1,2,3,4,5])
sage: f.square_and_truncate(4)
[1 4 10 20]
sage: (f*f).truncate(4)
[1 4 10 20]

trace_list( )

Return the list of traces of the powers $ x^i$ of the monomial x modulo this polynomial for i = 0, ..., deg(f)-1. This polynomial must be monic.

sage: f = ntl.ZZX([1,2,0,3,0,1])
sage: f.trace_list()
[5, 0, -6, 0, 10]

The input polynomial must be monic or a ValueError is raised:

sage: f = ntl.ZZX([1,2,0,3,0,2])
sage: f.trace_list()
Traceback (most recent call last):
...
ValueError: polynomial must be monic.

trace_mod( )

Return the trace of this polynomial modulus the modulus. The modulus must be monic, and of positive degree degree bigger than the the degree of self.

sage: f = ntl.ZZX([1,2,0,3])
sage: mod = ntl.ZZX([5,3,-1,1,1])
sage: f.trace_mod(mod)
-37

truncate( )

Return the truncation of this polynomial obtained by removing all terms of degree >= m.

sage: f = ntl.ZZX([1,2,3,4,5])
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)
[]

xgcd( )

Returns r,s,t such that r = s*self + t*other.

Here r is the resultant of a and b; if r != 0, then this function computes s and t such that: a*s + b*t = r; otherwise s and t are both 0. If proof = False (*not* the default), then resultant computation may use a randomized strategy that errs with probability no more than $ 2^{-80}$ .

sage: f = ntl.ZZX([1,2,3]) * ntl.ZZX([4,5])**2
sage: g = ntl.ZZX([1,1,1])**3 * ntl.ZZX([1,2,3])
sage: f.xgcd(g)   # nothing since they are not coprime
(0, [], [])

In this example the input quadratic polynomials have a common root modulo 13.

sage: f = ntl.ZZX([5,0,1])
sage: g = ntl.ZZX([18,0,1])
sage: f.xgcd(g)
(169, [-13], [13])

Instances of class ZZX_class also have the following special methods:

__add__,$  $ __cmp__,$  $ __delitem__,$  $ __div__,$  $ __getitem__,$  $ __mod__,$  $ __mul__,$  $ __neg__,$  $ __pow__,$  $ __radd__,$  $ __rdiv__,$  $ __repr__,$  $ __rmod__,$  $ __rmul__,$  $ __rpow__,$  $ __rsub__,$  $ __setitem__,$  $ __sub__

See About this document... for information on suggesting changes.