1. Demonstration of the numpy.polynomial package

1.1 And especially a small hand-made pretty printing function for Polynomial objects

For both Python 2 and Python 3, numpy has a very nice module to work with polynomials: numpy.polynomial.

If you are not familiar with it, I highly recommend you to read this tutorial.


Now, let assume you already know everything about Python, and the numpy.polynomial package. So you know that it should be imported like this:

In [1]:
from numpy.polynomial import Polynomial as P

And we can then define the monome $X$ as P([0, 1]), defined by the list of its coefficients in the canonical basis $(X_i)_{i \in \mathbb{N}}$:

In [2]:
X = P([0, 1])
print("We defined the monome X =", X)
X
We defined the monome X = poly([ 0.  1.])
Out[2]:
Polynomial([ 0.,  1.], [-1.,  1.], [-1.,  1.])

The main issue with this output (either poly([ 0. 1.]) or Polynomial([ 0., 1.], [-1, 1], [-1, 1])) is its lack of sexyness: it gives the required information (coefficients, domain, window etc, see here for more details) but it is too far from the mathematical notation.

If we define $Q(X) = 1 + 17 X^3$, we would like Python (or IPython, or in this case, the Jupyter Notebook) to display this polynomial nicely, either as ASCII text: 1 + 17 * X**3 (valid Python code), or as a nice $\LaTeX{}$ code: 1 + 17 X^3.

In [3]:
Q = 1 + 17 * X ** 3
print("Q(X) =", Q)
Q
Q(X) = poly([  1.   0.   0.  17.])
Out[3]:
Polynomial([  1.,   0.,   0.,  17.], [-1.,  1.], [-1.,  1.])

1.2 First goal: pretty print in ASCII text

Our first task will be to implement a small function, or an overload of the numpy.polynomial.Polynomial class to be able to pretty-print a Polynomial object nicely.

Note: more details, along with a documentation, some examples and a fully pylint-compatible code are available on this Bitbucket snippet: bitbucket.org/snippets/lbesson/j6dpz.

The function prettyprintPolynomial defined below implements a natural strategy to print a polynomial: it prints the coefficients, as a_i * X**i, in increasing order.

This function takes care of all the special cases:

  • it removes a trailing .0 if one coefficient is an integer,
  • it displays the coefficient between ( and ) if it is negative,
  • it only displays the non-zero coefficients,
  • if one coefficient is 1, no need to display it.
In [4]:
def prettyprintPolynomial(p):
    """ Small function to print nicely the polynomial p as we write it in maths, in ASCII text."""
    coefs = p.coef  # List of coefficient, sorted by increasing degrees
    res = ""  # The resulting string
    for i, a in enumerate(coefs):
        if int(a) == a:  # Remove the trailing .0
            a = int(a)
        if i == 0:  # First coefficient, no need for X
            if a > 0:
                res += "{a} + ".format(a=a)
            elif a < 0:  # Negative a is printed like (a)
                res += "({a}) + ".format(a=a)
            # a = 0 is not displayed 
        elif i == 1:  # Second coefficient, only X and not X**i
            if a == 1:  # a = 1 does not need to be displayed
                res += "X + "
            elif a > 0:
                res += "{a} * X + ".format(a=a)
            elif a < 0:
                res += "({a}) * X + ".format(a=a)
        else:
            if a == 1:
                res += "X**{i} + ".format(i=i)
            elif a > 0:
                res += "{a} * X**{i} + ".format(a=a, i=i)
            elif a < 0:
                res += "({a}) * X**{i} + ".format(a=a, i=i)
    return res[:-3] if res else ""

We can check its behavior on some small polynomials:

In [5]:
print("X =", prettyprintPolynomial(X))
print("Q(X) =", prettyprintPolynomial(Q))
X = X
Q(X) = 1 + 17 * X**3
In [6]:
Q3 = -1 - 2*X - 17*X**3
print("Q_3(X) =", prettyprintPolynomial(Q3))
print("- Q_3(X) =", prettyprintPolynomial(-Q3))
Q_3(X) = (-1) + (-2) * X + (-17) * X**3
- Q_3(X) = 1 + 2 * X + 17 * X**3

We can create a more complicated polynomial $Q_4(X) = (1 + 2 X + 17 X^3) ^ {12}$ and check that it is also nicely printed:

In [7]:
Q4 = (1 + 2*X + 17*X**3) ** 12
print("Q_4(X) =", prettyprintPolynomial(Q4))
Q_4(X) = 1 + 24 * X + 264 * X**2 + 1964 * X**3 + 12408 * X**4 + 70224 * X**5 + 347490 * X**6 + 1559976 * X**7 + 6575976 * X**8 + 25536412 * X**9 + 92228664 * X**10 + 318098112 * X**11 + 1029784111 * X**12 + 3135774576 * X**13 + 9208582032 * X**14 + 25554505944 * X**15 + 67047551472 * X**16 + 171037015776 * X**17 + 411810470236 * X**18 + 939707466192 * X**19 + 2104452578448 * X**20 + 4397274411288 * X**21 + 8746569014832 * X**22 + 17281727001792 * X**23 + 30879457534959 * X**24 + 53623057787640 * X**25 + 93271621727592 * X**26 + 136585330694780 * X**27 + 211783995908760 * X**28 + 313071993952080 * X**29 + 341770260064354 * X**30 + 532222389718536 * X**31 + 532222389718536 * X**32 + 411262755691596 * X**33 + 822525511383192 * X**34 + 582622237229761 * X**36

1.3 Second goal: pretty-print in $\LaTeX{}$ code

We will simply modify the function prettyprintPolynomial to use $\LaTeX{}$ code instead of ASCII text: the string has to be between $, the multiplications are without symbols (e.g., $17 X$ for 17 * X), and the powers are with the ^ symbol instead of ** :

In [8]:
def Polynomial_to_LaTeX(p):
    """ Small function to print nicely the polynomial p as we write it in maths, in LaTeX code."""
    coefs = p.coef  # List of coefficient, sorted by increasing degrees
    res = ""  # The resulting string
    for i, a in enumerate(coefs):
        if int(a) == a:  # Remove the trailing .0
            a = int(a)
        if i == 0:  # First coefficient, no need for X
            if a > 0:
                res += "{a} + ".format(a=a)
            elif a < 0:  # Negative a is printed like (a)
                res += "({a}) + ".format(a=a)
            # a = 0 is not displayed 
        elif i == 1:  # Second coefficient, only X and not X**i
            if a == 1:  # a = 1 does not need to be displayed
                res += "X + "
            elif a > 0:
                res += "{a} \;X + ".format(a=a)
            elif a < 0:
                res += "({a}) \;X + ".format(a=a)
        else:
            if a == 1:
                # A special care needs to be addressed to put the exponent in {..} in LaTeX
                res += "X^{i} + ".format(i="{%d}" % i)
            elif a > 0:
                res += "{a} \;X^{i} + ".format(a=a, i="{%d}" % i)
            elif a < 0:
                res += "({a}) \;X^{i} + ".format(a=a, i="{%d}" % i)
    return "$" + res[:-3] + "$" if res else ""

We can quickly try the same examples as before:

In [9]:
print("X =", Polynomial_to_LaTeX(X))
print("Q(X) =", Polynomial_to_LaTeX(Q))
X = $X$
Q(X) = $1 + 17 \;X^{3}$

But we want the $\LaTeX{}$ code to be pretty-printed by IPython, not just displayed like this. For this, the internal function Latex from the module IPython.display is required:

In [10]:
from IPython.display import Latex
In [11]:
print("X =")
Latex(Polynomial_to_LaTeX(X))
X =
Out[11]:
$X$
In [12]:
print("Q(X) =")
Latex(Polynomial_to_LaTeX(Q))
Q(X) =
Out[12]:
$1 + 17 \;X^{3}$

Allright! It starts to look like what we wanted! Let's try with a bigger polynomial, as we did before:

In [13]:
Q4 = (1 + 2*X + 17*X**3) ** 12
print("Q_4(X) =")
Latex(Polynomial_to_LaTeX(Q4))
Q_4(X) =
Out[13]:
$1 + 24 \;X + 264 \;X^{2} + 1964 \;X^{3} + 12408 \;X^{4} + 70224 \;X^{5} + 347490 \;X^{6} + 1559976 \;X^{7} + 6575976 \;X^{8} + 25536412 \;X^{9} + 92228664 \;X^{10} + 318098112 \;X^{11} + 1029784111 \;X^{12} + 3135774576 \;X^{13} + 9208582032 \;X^{14} + 25554505944 \;X^{15} + 67047551472 \;X^{16} + 171037015776 \;X^{17} + 411810470236 \;X^{18} + 939707466192 \;X^{19} + 2104452578448 \;X^{20} + 4397274411288 \;X^{21} + 8746569014832 \;X^{22} + 17281727001792 \;X^{23} + 30879457534959 \;X^{24} + 53623057787640 \;X^{25} + 93271621727592 \;X^{26} + 136585330694780 \;X^{27} + 211783995908760 \;X^{28} + 313071993952080 \;X^{29} + 341770260064354 \;X^{30} + 532222389718536 \;X^{31} + 532222389718536 \;X^{32} + 411262755691596 \;X^{33} + 822525511383192 \;X^{34} + 582622237229761 \;X^{36}$

Way nicer! Yay!


1.4 A bonus for the end: add this pretty-printer as the default one in IPython:

This manipulation is showed in IPython's documentation.

But we can configure IPython to do this automatically for us as follows. We hook into the IPython display system and instruct it to use Polynomial_to_LaTeX for the latex mimetype, when encountering objects of the Polynomial type defined in the numpy.polynomial.polynomial module:

In [14]:
ip = get_ipython()
latex_formatter = ip.display_formatter.formatters['text/latex']
latex_formatter.for_type_by_name('numpy.polynomial.polynomial',
                                 'Polynomial', Polynomial_to_LaTeX)

Once our special printer has been loaded, all polynomials will be represented by their mathematical form instead (as $\LaTeX{}$ code displayed with MathJax):

In [15]:
X
Out[15]:
$X$

One a last example:

In [23]:
P = ((1 + X**2)**16)**16 % (1 - X**16)
print("str(P) =", str(P))
print("repr(P) =", repr(P))
P
str(P) = poly([  1.44740112e+76   0.00000000e+00   1.44740112e+76   0.00000000e+00
   1.44740112e+76   0.00000000e+00   1.44740111e+76   0.00000000e+00
   1.44740111e+76   0.00000000e+00   1.44740111e+76   0.00000000e+00
   1.44740112e+76   0.00000000e+00   1.44740112e+76])
repr(P) = Polynomial([  1.44740112e+76,   0.00000000e+00,   1.44740112e+76,
         0.00000000e+00,   1.44740112e+76,   0.00000000e+00,
         1.44740111e+76,   0.00000000e+00,   1.44740111e+76,
         0.00000000e+00,   1.44740111e+76,   0.00000000e+00,
         1.44740112e+76,   0.00000000e+00,   1.44740112e+76], [-1.,  1.], [-1.,  1.])
Out[23]:
$14474011200283702161601714015574787453529301631345998719884624998391009509376 + 14474011186922156948012193183568171432300845296008107824850143024215245193216 \;X^{2} + 14474011154664527641822461644066539565582932765530275549338185566574811807744 \;X^{4} + 14474011122406890300942508809613529989054558529239430662811259194970201915392 \;X^{6} + 14474011109045346694291032236597189509788194535064142289979771003587272900608 \;X^{8} + 14474011122406895121756641586584356614940835552727238229420240543348707819520 \;X^{10} + 14474011154664527641822461644066539565582932765530275549338185566574811807744 \;X^{12} + 14474011186922156948012193183568171432300845296008107824850143024215245193216 \;X^{14}$

That's all for today, folks!