285-codes/picard-poly.py

222 lines
7.3 KiB
Python
Raw Normal View History

2025-06-01 19:40:54 +08:00
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
lims = [
(0, 2, 0, 100),
(0, 1, 1, 100),
(0, 1, -1, 100)
]
num_iter = 6
t0, tM, y0, yM = lims[1]
tm, ym = min(t0, tM), min(y0, yM)
picard_result = picardIteration(t0, y0, num_iter)
'''
for idx, poly in enumerate(picard_result):
np_poly = np.vectorize(poly)
ts = np.linspace(t0, tM, 1000)
ys = np_poly(ts)
plt.plot(ts, ys)
print(idx, poly.degree, poly)
'''
poly = picard_result[-1]
#np_poly = np.vectorize(poly)
#ts = np.linspace(t0, tM, 1000)
#ys = np_poly(ts)
#plt.plot(ts, ys)
#print(poly.degree, poly)
print(poly.degree)
plt.scatter(list(range(poly.degree + 1)), poly.coefficients, s=0.5)
plt.yscale('log')
#plt.xlim(t0, tM)
#plt.ylim(ym, yM)
plt.title(f"coefficients of $\\sum c_n t^n$ at y(0) = {y0} of iterations {num_iter}")
plt.ylabel("$c_n$")
plt.show()