-
-
Notifications
You must be signed in to change notification settings - Fork 12.2k
ENH: Formatting Polynomials #8893
Description
TL;DR Add a way of turning numpy.polynomial.polynomial.Polynomial instances into a human readable and/or machine parseable string, such as
2x³ + x² - 1(utf8)2x^3 + x^2 - 1(ascii)2x^{3} + x^{2} - 1(latex)2x<sup>3</sup> + x<sup>2</sup> - 1(html)2*x**3 + x**2 - 1(python)
Disclaimer: I'm relatively new to numpy so if this is just completely out of the question please bear with me and let me know. If this issue finds approval I'd be happy to implement it myself. :)
Motivation
Numpy is used for scientific work all around. In literature polynomials are typically written in the form 2x³ + x² - 1 which I will refer to as "human readable". It is characterized by descending exponents and omission of unnecessary parts involving coefficients and exponents ±1 and 0. Yet neither str nor format return a version close to the above, instead displaying the "machine oriented" internal representation as an array of coefficients sorted by ascending index. This representation can be confusing for people who only know the human readable form and in some cases completely impractical (e.g. for sparse polynomials of high degree, like x¹²³). It would be very useful for authors who employ numpy to be able to generate output directly in a human readable format or in a common markup language (such as LaTeX and HTML). This allows automation and prevents common slipups when manually translating between the formats.
Possible approaches
I can think of several ways to incorporate a formatting option into numpy. Personally I prefer option 1) as it is the most pythonic and I can not see any significant downsides assuming the default is chosen to avoid backwards compatibility issues at least for upcoming minor versions. Some of these options may also coexist.
1) Implement Polynomial.__format__ with a reasonable default. In this case "reasonable" could mean either defaulting to str if no style option is provided (in the interest of backward compatibility), or defaulting to one of the versions in the TL;DR example.
As a result the following code:
p = Polynomial([-1, 0, 1, 2])
print(format(p)) # default to str(p)
print("{:ascii}".format(p))
print(f"{p :utf8}") # Python 3.6+… could output:
poly([-1. 0. 1. 2.])
2x^3 + x^2 - 1
2x³ + x² - 1
2) Add an explicit method like Polynomial.human_readable that takes some optional parameters (see below).
3) Add a standalone function such as numpy.polynomials.polyutils.format_polynomial which isn't very OOP but at least it exists and it's out of the way.
4) Extend numpy.set_printoptions to allow specifying a formatter for Polynomials. This means of course that the actual implementation of the human readable formatting isn't part of numpy but that it is at least easy to add without monkey patching the Polynomial class.
Demo implementation
I've written a brief sample implementation that can be used to demonstrate the suggested behavior of 1). The monkey-patched Polynomial.__format__ only supports the exponent style option described above, while format_polynomial() demonstrates some additional options. You can run the code as is in Python 3. The UTF-8 literals break Python 2 but substituting a compatible implementation should not be an issue.
exp_styles = {
'utf8': lambda n: "".join("⁰¹²³⁴⁵⁶⁷⁸⁹"[int(k)] for k in str(n)),
'ascii': "^{:d}".format,
'latex': "^{{{:d}}}".format,
'html': "<sup>{:d}</sup>".format,
'python': "**{:d}".format,
}
def format_polynomial(polynomial, variable="x", mul="", exponents=exp_styles['utf8']):
"""
Format a polynomial given as iterable in a human readable string.
Parameters:
polynomial : array_like
variable : string, optional
To be used instead of "x" in the formatted output.
mul : string, optional
To be used as a multiplication symbol in the output
exponents : function (str -> str), optional
Render the exponent from a given integer > 0.
Examples:
Format in a Python parseable format:
>>> s = format_polynomial([-1, 0, 1, 2], "5", "*", "**{}".format)
>>> print(s)
2*5**3 + 5**2 - 1
>>> eval(s)
274
"""
if len(polynomial) == 1:
return format(polynomial[0], "g")
string = sign = ""
exps = ("", variable,
*(variable + exponents(n) for n in range(2, len(polynomial))))
for n, (coef, exp) in enumerate(zip(polynomial, exps)): # right to left
if coef:
string = exp + sign + string
if abs(coef) != 1 or n == 0:
string = format(abs(coef), "g") + (mul if n else "") + string
sign = " - " if coef < 0 else " + "
return "-" + string if sign == " - " else string
def _Polynomial__format__(self, style):
"""Monkey patch for Polynomial.__format__"""
if not style:
return str(self)
return format_polynomial(self, exponents=exp_styles[style])
from numpy.polynomial.polynomial import Polynomial
Polynomial.__format__ = _Polynomial__format__
p = Polynomial([-1, 0, 1, 2])
print(format(p)) # default to str(p)
print("{:ascii}".format(p))
# print(f"{p :utf8}") # Python 3.6+That's it. Please let me know what you think. 👍