Polynomial Class¶
The numpy.polynomial.Polynomial class wraps a coefficient array together with domain and window metadata into a single object. This design enables numerically stable fitting, evaluation, and arithmetic without manually managing coordinate transformations. While the sibling page on the np.polynomial module introduces the basics, this page explores the class mechanics in depth — domain and window mapping, weighted fitting, truncation, conversion, and serialization.
Mental Model
A Polynomial object bundles three things: coefficients, a domain (your data range), and a window (the numerical range used internally). When you evaluate or fit, the class silently maps between domain and window, keeping computations stable. Think of it as a polynomial that knows where it lives on the number line.
python
import numpy as np
from numpy.polynomial import Polynomial
When You Need This
You need the Polynomial class (beyond basic Polynomial.fit) when:
- High-degree fits — domain/window control prevents overflow
- Large x-ranges — automatic mapping to \([-1, 1]\) keeps computations stable
- Basis conversion — switching between power, Chebyshev, Legendre representations
- Precision control — truncation, trimming, and weighted fitting
For simple low-degree fits with moderate x-values, Polynomial.fit(x, y, deg) alone is sufficient.
Construction¶
A Polynomial object is created from an array of coefficients in ascending power order. Optional domain and window parameters control the coordinate mapping used during fitting and evaluation.
```python
2 + 3x + x² (ascending order: constant, x, x², ...)¶
p = Polynomial([2, 3, 1]) print(p) # 2.0 + 3.0·x + 1.0·x² print(p.coef) # [2. 3. 1.] print(p.degree()) # 2 ```
From Roots¶
```python
Create polynomial with roots at x = 1 and x = 3¶
(x - 1)(x - 3) = x² - 4x + 3 → coefficients [3, -4, 1]¶
p = Polynomial.fromroots([1, 3]) print(p.coef) # [3. -4. 1.] print(p.roots()) # [1. 3.] ```
Identity and Basis Polynomials¶
```python
Identity polynomial: p(x) = x¶
p_x = Polynomial.identity() print(p_x.coef) # [0. 1.]
Basis polynomial of degree k: p(x) = x^k¶
p_x3 = Polynomial.basis(3) print(p_x3.coef) # [0. 0. 0. 1.] ```
Domain and Window¶
Every Polynomial object carries two intervals: domain and window. These control the affine mapping applied during evaluation.
- domain: the interval in the original data space (e.g., \([0, 100]\) for data spanning 0 to 100)
- window: the interval in the computational space (default \([-1, 1]\))
When calling p(x), the class maps \(x\) from the domain to the window before evaluating the polynomial. This mapping is the key to numerical stability for high-degree fits.
The Mapping Formula¶
The affine mapping from domain \([a, b]\) to window \([c, d]\) is
For the default window \([-1, 1]\) and domain \([a, b]\), this simplifies to
```python
Polynomial with custom domain¶
p = Polynomial([1, 2, 3], domain=[0, 10], window=[-1, 1])
Evaluation maps x=0 → u=-1, x=10 → u=1, x=5 → u=0¶
print(p(0)) # p evaluated at u = -1: 1 - 2 + 3 = 2.0 print(p(10)) # p evaluated at u = 1: 1 + 2 + 3 = 6.0 print(p(5)) # p evaluated at u = 0: 1 + 0 + 0 = 1.0 ```
Why Domain Mapping Matters¶
Without mapping, fitting a degree-10 polynomial to data in \([0, 1000]\) requires computing \(1000^{10} = 10^{30}\), which causes floating-point overflow. Mapping to \([-1, 1]\) keeps all powers bounded by 1.
```python
High-degree fit with large x values — stable with Polynomial.fit¶
x = np.linspace(0, 1000, 100) y = np.sin(x / 100)
p_fit = Polynomial.fit(x, y, deg=10) print(p_fit.domain) # [ 0. 1000.] print(np.max(np.abs(p_fit(x) - y))) # Small residual ```
Fitting with Polynomial.fit¶
Polynomial.fit is a class method that performs least-squares polynomial fitting with automatic domain mapping.
```python x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) y = np.array([1.0, 2.7, 5.8, 11.2, 18.1, 26.5])
Fit degree-2 polynomial¶
p = Polynomial.fit(x, y, deg=2) print(p(x)) # Fitted values close to y ```
Weighted Fitting¶
Assign weights to data points to give some observations more influence than others. Points with higher weight contribute more to the fit.
```python x = np.array([0, 1, 2, 3, 4]) y = np.array([1.0, 2.1, 3.9, 8.5, 16.2])
Uniform weights (default)¶
p_uniform = Polynomial.fit(x, y, deg=2)
High weight on the first three points¶
w = np.array([10, 10, 10, 1, 1]) p_weighted = Polynomial.fit(x, y, deg=2, w=w)
The weighted fit follows the first three points more closely¶
print(p_uniform(x)) print(p_weighted(x)) ```
Specifying the Fitting Domain¶
By default, Polynomial.fit sets the domain to \([\min(x), \max(x)]\). You can override this.
```python
Force domain to [0, 10] even though data only covers [0, 5]¶
p = Polynomial.fit(x, y, deg=2, domain=[0, 10]) print(p.domain) # [ 0. 10.] ```
Conversion and Truncation¶
Converting to Standard Coefficients¶
A fitted Polynomial stores coefficients relative to its domain mapping. Use convert to obtain coefficients in a different domain or the standard power basis.
```python x = np.array([0, 1, 2, 3, 4]) y = np.array([1, 3, 7, 13, 21])
p_fit = Polynomial.fit(x, y, deg=2)
Convert to standard domain [-1, 1] → gives "raw" power-basis coefficients¶
p_standard = p_fit.convert() print(p_standard.coef) # [1. 1. 1.] → 1 + x + x²
Convert to a specific domain¶
p_custom = p_fit.convert(domain=[0, 10]) print(p_custom.domain) # [ 0. 10.] ```
Truncation¶
Remove high-degree terms by truncating to a lower degree.
python
p = Polynomial([1, 2, 3, 4, 5]) # degree 4
p_trunc = p.truncate(3) # keep only up to degree 2
print(p_trunc.coef) # [1. 2. 3.]
Trimming¶
Remove trailing near-zero coefficients.
python
p = Polynomial([1, 2, 3, 1e-16, 2e-17])
p_trimmed = p.trim()
print(p_trimmed.coef) # [1. 2. 3.]
Serialization and Interoperability¶
Converting Between Bases¶
The convert method also enables switching between different polynomial basis classes.
```python from numpy.polynomial import Chebyshev
Start with a power-basis polynomial¶
p = Polynomial([1, 2, 3])
Convert to Chebyshev basis¶
p_cheb = p.convert(kind=Chebyshev)
print(type(p_cheb)) #
Convert back¶
p_back = p_cheb.convert(kind=Polynomial) print(np.allclose(p.coef, p_back.coef)) # True ```
String Representation¶
python
p = Polynomial([1, -2, 3])
print(repr(p)) # Polynomial([1., -2., 3.], domain=[-1, 1], window=[-1, 1])
print(str(p)) # 1.0 - 2.0·x + 3.0·x²
Summary¶
| Operation | Method / Syntax |
|---|---|
| Create from coefficients | Polynomial([c0, c1, c2]) |
| Create from roots | Polynomial.fromroots([r1, r2]) |
| Fit data | Polynomial.fit(x, y, deg) |
| Weighted fit | Polynomial.fit(x, y, deg, w=weights) |
| Evaluate | p(x) |
| Get coefficients | p.coef |
| Get degree | p.degree() |
| Derivative | p.deriv(n) |
| Integral | p.integ(k=0) |
| Roots | p.roots() |
| Convert domain/basis | p.convert(domain=..., kind=...) |
| Truncate degree | p.truncate(n) |
| Trim near-zero | p.trim() |
Key Takeaways:
- The
Polynomialclass bundles coefficients with domain and window metadata - Domain-to-window mapping is the mechanism behind numerical stability in fitting
Polynomial.fithandles domain mapping automatically; useconvertto extract standard coefficients- Weighted fitting adjusts influence of individual data points via the
wparameter convert(kind=...)enables basis conversion between Polynomial, Chebyshev, Legendre, and other classestruncateandtrimsimplify polynomials by removing high-degree or near-zero terms
Exercises¶
Exercise 1.
Create a Polynomial from roots at x = -1, 2, 5 using Polynomial.fromroots. Verify the roots by calling .roots() on the resulting polynomial. Then evaluate the polynomial at x = 0 and confirm it equals the product (-1) * (2) * (5) = -10 (with appropriate sign from the leading coefficient).
Solution to Exercise 1
import numpy as np
from numpy.polynomial import Polynomial
p = Polynomial.fromroots([-1, 2, 5])
print(f"Coefficients: {p.coef}")
print(f"Roots: {p.roots()}")
val_at_0 = p(0)
# (x+1)(x-2)(x-5) at x=0 = (1)(-2)(-5) = 10
# But leading coeff matters: coef gives ascending order
print(f"p(0) = {val_at_0}")
Exercise 2.
Fit a degree-4 polynomial to x = np.linspace(0, 100, 50) and y = np.log(1 + x) using Polynomial.fit. Print the domain of the fitted polynomial. Convert to standard form with .convert() and compare the condition number improvement by printing np.linalg.cond(V) for both the raw and centered-scaled Vandermonde matrices.
Solution to Exercise 2
import numpy as np
from numpy.polynomial import Polynomial
x = np.linspace(0, 100, 50)
y = np.log(1 + x)
p = Polynomial.fit(x, y, deg=4)
print(f"Domain: {p.domain}")
# Raw Vandermonde
V_raw = np.vander(x, N=5, increasing=True)
cond_raw = np.linalg.cond(V_raw)
# Centered/scaled Vandermonde
x_norm = (x - x.mean()) / x.std()
V_norm = np.vander(x_norm, N=5, increasing=True)
cond_norm = np.linalg.cond(V_norm)
print(f"Raw condition number: {cond_raw:.2e}")
print(f"Scaled condition number: {cond_norm:.2e}")
print(f"Improvement: {cond_raw / cond_norm:.0f}x")
Exercise 3.
Create a degree-6 polynomial with Polynomial([1, 0, -3, 0, 2, 0, -0.5]), then use .truncate(5) to keep only terms up to degree 4 and .trim() to remove trailing near-zero coefficients from Polynomial([1, 2, 3, 1e-16, 2e-17]). Print the results and verify the degree of each trimmed/truncated polynomial.
Solution to Exercise 3
import numpy as np
from numpy.polynomial import Polynomial
p = Polynomial([1, 0, -3, 0, 2, 0, -0.5])
print(f"Original degree: {p.degree()}")
p_trunc = p.truncate(5) # keep up to degree 4
print(f"Truncated coeffs: {p_trunc.coef}, degree: {p_trunc.degree()}")
p_noisy = Polynomial([1, 2, 3, 1e-16, 2e-17])
p_trimmed = p_noisy.trim()
print(f"Trimmed coeffs: {p_trimmed.coef}, degree: {p_trimmed.degree()}")