Report Sessions 3 and 4#

Your goal is to « recreate » a very basic version of polynomials (over \(\mathbb{Q}\)) in Sage

We provide a basic structure for representing dense polynomials in poly.py

You need to implement the different methods following the algorithms seen in class.

from poly import PolyDense

Basic methods#

Start by implementing the __init__ methods which takes an optionnal list of coefficients

P = PolyDense()
P = PolyDense([1])
# This should not work because pi is not rational
P = PolyDense([pi])

Implement all these basic methods of PolyDense. See the expected results in the examples in the documentation of each method

coeff

PolyDense().coeff(0)
P = PolyDense([0, 1, 0, 2/3, 0])
[P.coeff(i) for i in range(10)]

degre

PolyDense().degre()
PolyDense([1]).degre()
PolyDense([0, 1]).degre()
PolyDense([0, 1, 0]).degre()
PolyDense([0, 1, 0, 2/3, 0]).degre() # should be 3, not 4 !

Question: how to deal with 0 values in the coef list? Where should the test be? What should be stored?

your answer

bool

bool(PolyDense())
bool(PolyDense([0]))
bool(PolyDense([1]))
bool(PolyDense([0, 1]))

monomials

PolyDense().monomials()
PolyDense([1]).monomials()
PolyDense([0, 1]).monomials()
PolyDense([0, 1, 0, 2/3]).monomials()

In PolyCommon, we have written a __repr__ method to print polynomials. Now it should work!

PolyDense()
PolyDense([1])
PolyDense([0, 1, 0, 2/3])

Two polynomials are equal if they have the same coefficients. Implement the __eq__ method

PolyDense() == PolyDense()
PolyDense([0, 1]) == PolyDense([0, 1])
PolyDense([0, 1]) == PolyDense()

Creation of polynomial, basic operation#

We have implemented an X method to get the polynomial \(X\) of degree 1

X = PolyDense.X()
X

To be able to easily create a polynomial using a natural mathematical notation, we need to implement basic operations

Implement the set_coeff and __add__ methods

P = PolyDense() 
P.set_coeff(2, 3/2)
P
P = PolyDense([0, 1, 0, 2/3])
P.set_coeff(2, -5/3)
P
P.set_coeff(8, -1/8)
P

If we send a negative degree, this should raise an exception

P.set_coeff(-1,2)
PolyDense([0, 1, 3]) + PolyDense([0, 1, 0, 3/2])
PolyDense([0, 1, 3]) + PolyDense([0, -1, -3])

You should be able to add an element of PolyDense with a element of the base ring

PolyDense([0, 1]) + 2

Be carreful to normalize the result of the addition, this should be 0

PolyDense([0, 1]) + PolyDense([0, -1])

Implement the method naive_mul and then the method __mul__ which delegates to naive_mul

PolyDense([0, 1, 2]).naive_mul(PolyDense([2]))
PolyDense().naive_mul(PolyDense([0, 1, 2]))
PolyDense([1]).naive_mul(PolyDense([0, 1, 2]))
PolyDense([1, -2]).naive_mul(PolyDense([0, 1, 2]))
PolyDense()*PolyDense([0, 1, 2])
PolyDense([1])*PolyDense([0, 1, 2])
PolyDense([1, -2])*PolyDense([0, 1, 2])

You should be able to multiply with an element of the base ring

PolyDense([0, 1, 2])*2

Implement the __pow__ method in PolyCommon, make it so that the complexity is logarithmic.

PolyDense([0,1])^5
PolyDense([1,1])^3
PolyDense([0,1])^100

Now we can create polynomials in a natural way (almost)

X = PolyDense.X()
X^2 + 3
X^5 * (5/2) + X^2 + 3
(X + 1) * (X - 1)

Horner’s algorithm#

Implement the __call__ method (evaluation) using Horner’s algorithm

PolyDense([1, 1])(3/2)
PolyDense([1, -2, 3])(2)
X = PolyDense.X()
P = X^5 + X^4*4 + X^3 - X^2 +5
P(1)
P
P(2)

Karatsuba algorithm#

Implement the method karatsuba_mul to multiply two polynomials of degre > 1 using the Karatsuba algorithm.

Then change the __mul__ method so that the naive algo is used for polynomials of degres <= 1 and Karatsuba for higher degree polynomials.

PolyDense([1, -2]).karatsuba_mul(PolyDense([0, 1, 2]))
X = PolyDense.X()
P = X^3 * 3 - X^2 + 1
Q = X^2 * 2 + X + 5
P*Q # 6X^5 + X^4 + 14X^3 - 3X^2 + X + 5

Who is faster?#

The following function return a random polynomial of a given degree.

Below, you see an example testing the time needed for a naive multiplication.

def random_poly(degree):
    r"""
    Return a polynomial of given degree whose coefficients are random integers between 0 and 10
    """
    return PolyDense([randint(0,10) for i in range(degree)] + [randint(1,10)])
P = random_poly(10)
P
Q = random_poly(10)
Q
time P.naive_mul(Q)

Compute the time needed using Karatsuba, is it faster?

Compare again with polynomials of degree 100, 1000 and 3000

# Write your code here
# Write your code here
# Write your code here
# Write your code here
# Write your code here
# Write your code here
# Write your code here

Optimize the __mul__ method so that it uses the most efficient algorithm depending on the degree of the polynomial. See what time it takes to compute products and how far you can go.

Going further#

You can improve your implementation with:

  • Euclidian division

  • Polynomial interpolation

  • sparse polynomials

If you have done any of those, put examples below:

Tests and code check#

Check that the Sage tests are passing here

!sage -t poly.py
# printing code correction
PolyDense.__call__??
PolyDense.naive_mul??
PolyDense.karatsuba_mul??
PolyDense.__mul__??