225 lines
7.6 KiB
Python
225 lines
7.6 KiB
Python
import numpy as np
|
|
from numbers import Number
|
|
import matplotlib.pyplot as plt
|
|
|
|
class Polynomial:
|
|
"""
|
|
Represents a polynomial and supports common mathematical operations.
|
|
|
|
The coefficients are stored in an array in order of increasing power,
|
|
e.g., `[c₀, c₁, c₂]` for the polynomial c₀ + c₁x + c₂x².
|
|
"""
|
|
|
|
def __init__(self, coeff):
|
|
"""Initializes the polynomial with a list, tuple, or numpy array of coefficients."""
|
|
# Trim trailing zeros that don't affect the polynomial's degree
|
|
self.coefficients = np.trim_zeros(np.asarray(coeff, dtype=float), 'b')
|
|
if self.coefficients.size == 0:
|
|
self.coefficients = np.array([0.0])
|
|
|
|
|
|
@property
|
|
def degree(self) -> int:
|
|
"""Returns the degree of the polynomial."""
|
|
# The degree is the number of coefficients minus one.
|
|
# Returns -1 for the zero polynomial P(x) = 0.
|
|
if self.coefficients.size == 1 and self.coefficients[0] == 0:
|
|
return -1
|
|
return self.coefficients.size - 1
|
|
|
|
def evalf(self, x: float) -> float:
|
|
"""
|
|
Evaluates the polynomial at a given value x using Horner's method.
|
|
|
|
For a polynomial P(x) = c₀ + c₁x + ... + cₙxⁿ, this is computed as
|
|
c₀ + x(c₁ + x(c₂ + ...)).
|
|
"""
|
|
# The user's original implementation was correct.
|
|
rval = self.coefficients[-1]
|
|
for c in reversed(self.coefficients[:-1]):
|
|
rval = rval * x + c
|
|
return rval
|
|
|
|
def integrate(self) -> 'Polynomial':
|
|
"""
|
|
Computes the indefinite integral of the polynomial, assuming the
|
|
constant of integration is zero.
|
|
"""
|
|
# Corrected to use np.zeros for safe array initialization.
|
|
integrated_coeffs = np.zeros(self.coefficients.size + 1)
|
|
for i, c in enumerate(self.coefficients):
|
|
integrated_coeffs[i + 1] = c / (i + 1)
|
|
return Polynomial(integrated_coeffs)
|
|
|
|
def integratef(self, a: float, b: float) -> float:
|
|
"""Computes the definite integral of the polynomial from a to b."""
|
|
# Corrected to pass the integration bounds to evalf.
|
|
integral = self.integrate()
|
|
return integral.evalf(b) - integral.evalf(a)
|
|
|
|
# --- Refinements and New Features ---
|
|
|
|
def __str__(self) -> str:
|
|
"""Provides a user-friendly string representation of the polynomial."""
|
|
if self.degree == -1:
|
|
return "0"
|
|
|
|
terms = []
|
|
# Iterate from the highest degree term to the lowest
|
|
for i, c in list(enumerate(self.coefficients)):
|
|
if np.isclose(c, 0) and i != 0:
|
|
continue
|
|
|
|
# Sign of the term
|
|
sign = ""
|
|
if len(terms) > 0:
|
|
sign = " + " if c > 0 else " - "
|
|
elif c < 0:
|
|
sign = "-"
|
|
|
|
c = abs(c)
|
|
|
|
# Coefficient string
|
|
coeff_str = ""
|
|
if not np.isclose(c, 1) or i == 0:
|
|
coeff_str = f"{c:g}"
|
|
|
|
# Variable and power string
|
|
power_str = ""
|
|
if i > 0:
|
|
power_str = "x"
|
|
if i > 1:
|
|
power_str += f"^{i}"
|
|
|
|
# Combine coefficient and power
|
|
term = ""
|
|
if coeff_str and power_str:
|
|
term = coeff_str + "*" + power_str
|
|
else:
|
|
term = coeff_str or power_str
|
|
|
|
terms.append(sign + term)
|
|
|
|
return "".join(terms)
|
|
|
|
def __repr__(self) -> str:
|
|
"""Provides an unambiguous representation of the polynomial."""
|
|
return f"Polynomial({self.coefficients.tolist()})"
|
|
|
|
def __add__(self, other):
|
|
"""Adds two polynomials or a polynomial and a scalar."""
|
|
if isinstance(other, Polynomial):
|
|
n1, n2 = self.coefficients.size, other.coefficients.size
|
|
if n1 > n2:
|
|
new_coeffs = self.coefficients.copy()
|
|
new_coeffs[:n2] += other.coefficients
|
|
else:
|
|
new_coeffs = other.coefficients.copy()
|
|
new_coeffs[:n1] += self.coefficients
|
|
return Polynomial(new_coeffs)
|
|
elif isinstance(other, Number):
|
|
new_coeffs = self.coefficients.copy()
|
|
new_coeffs[0] += other
|
|
return Polynomial(new_coeffs)
|
|
return NotImplemented
|
|
|
|
def __sub__(self, other):
|
|
"""Subtracts two polynomials or a scalar from a polynomial."""
|
|
if isinstance(other, Polynomial):
|
|
n1, n2 = self.coefficients.size, other.coefficients.size
|
|
new_coeffs = np.zeros(max(n1, n2), dtype=float)
|
|
new_coeffs[:n1] = self.coefficients
|
|
new_coeffs[:n2] -= other.coefficients
|
|
return Polynomial(new_coeffs)
|
|
elif isinstance(other, Number):
|
|
new_coeffs = self.coefficients.copy()
|
|
new_coeffs[0] -= other
|
|
return Polynomial(new_coeffs)
|
|
return NotImplemented
|
|
|
|
def __mul__(self, other):
|
|
"""Multiplies two polynomials or a polynomial and a scalar."""
|
|
if isinstance(other, Polynomial):
|
|
# Use convolution to multiply polynomial coefficients
|
|
return Polynomial(np.convolve(self.coefficients, other.coefficients))
|
|
elif isinstance(other, Number):
|
|
return Polynomial(self.coefficients * other)
|
|
return NotImplemented
|
|
|
|
def __pow__(self, n: int):
|
|
"""Raises the polynomial to a non-negative integer power."""
|
|
if not isinstance(n, int) or n < 0:
|
|
raise ValueError("Exponent must be a non-negative integer.")
|
|
|
|
# Use exponentiation by squaring for efficiency (O(log n) multiplications)
|
|
if n == 0:
|
|
return Polynomial([1]) # P^0 = 1
|
|
|
|
result = Polynomial([1])
|
|
base = self
|
|
while n > 0:
|
|
if n % 2 == 1:
|
|
result *= base
|
|
base *= base
|
|
n //= 2
|
|
return result
|
|
|
|
def __radd__(self, other):
|
|
return self.__add__(other)
|
|
|
|
def __rsub__(self, other):
|
|
# other - self = -(self - other)
|
|
res = self.__sub__(other)
|
|
return res * -1
|
|
|
|
def __rmul__(self, other):
|
|
return self.__mul__(other)
|
|
|
|
def __call__(self, t):
|
|
return self.evalf(t)
|
|
|
|
def picardIteration(t0, y0, num_iters = 20):
|
|
phi = [Polynomial([y0])]
|
|
for _ in range(num_iters):
|
|
fty = Polynomial([0, 0, 0, 1]) + phi[-1]**3
|
|
intg = fty.integrate()
|
|
intg_result = intg - intg.evalf(t0)
|
|
phi.append(intg_result + phi[0])
|
|
return phi
|
|
|
|
# (t0, tM, y0, ym, yM)
|
|
lims = [
|
|
(0, 2, 0, -1, 100),
|
|
(0, 1, 1, 0, 100),
|
|
(0, 1, -1, -100, 0)
|
|
]
|
|
num_iter = 6
|
|
|
|
fig, axes = plt.subplots(2, 3)
|
|
for idx, (t0, tM, y0, ym, yM) in enumerate(lims):
|
|
tm = min(t0, tM)
|
|
picard_result = picardIteration(t0, y0, num_iter)
|
|
for idp, poly in enumerate(picard_result):
|
|
np_poly = np.vectorize(poly)
|
|
ts = np.linspace(t0, tM, 1000)
|
|
ys = np_poly(ts)
|
|
axes[0][idx].plot(ts, ys, label=f'#{idp} iteration')
|
|
axes[0][idx].legend()
|
|
axes[0][idx].set_xlim(tm, tM)
|
|
axes[0][idx].set_ylim(ym, yM)
|
|
|
|
#last iter coeffs
|
|
axes[1][idx].scatter(list(range(poly.degree + 1)), poly.coefficients, s=0.5)
|
|
axes[1][idx].set_yscale('log')
|
|
|
|
col_titles = ["y(0) = 0", "y(0) = 1", "y(0) = -1"]
|
|
row_titles = ["plots of solutions", "coefficients for $t^n$"]
|
|
|
|
# Add column titles
|
|
for col_idx, col_title in enumerate(col_titles):
|
|
axes[0][col_idx].set_title(col_title, fontsize=12)
|
|
|
|
for row_idx, row_title in enumerate(row_titles):
|
|
axes[row_idx][0].set_ylabel(row_title, fontsize=12, rotation=90, labelpad=20)
|
|
plt.show()
|