def _coerce_seq(c):
if hasattr(c, "tolist"):
c = c.tolist()
return list(c)
def _trim(coef, tol=0.0):
out = list(coef)
while len(out) > 1 and abs(out[-1]) <= tol:
out.pop()
return out
def polyval(coef, x):
coef = _coerce_seq(coef)
if not coef:
return 0
acc = coef[-1]
for c in reversed(coef[:-1]):
acc = acc * x + c
return acc
def polyadd(c1, c2):
a = _coerce_seq(c1)
b = _coerce_seq(c2)
n = max(len(a), len(b))
a += [0] * (n - len(a))
b += [0] * (n - len(b))
return [a[i] + b[i] for i in range(n)]
def polysub(c1, c2):
a = _coerce_seq(c1)
b = _coerce_seq(c2)
n = max(len(a), len(b))
a += [0] * (n - len(a))
b += [0] * (n - len(b))
return [a[i] - b[i] for i in range(n)]
def polymul(c1, c2):
a = _coerce_seq(c1)
b = _coerce_seq(c2)
out = [0] * (len(a) + len(b) - 1)
for i, ai in enumerate(a):
for j, bj in enumerate(b):
out[i + j] += ai * bj
return out
def polyder(coef, m=1):
out = _coerce_seq(coef)
for _ in range(m):
if len(out) <= 1:
return [0]
out = [(k + 1) * out[k + 1] for k in range(len(out) - 1)]
return out
def polyint(coef, m=1, k=0):
out = _coerce_seq(coef)
if isinstance(k, (int, float, complex)):
ks = [k] * m
else:
ks = list(k)
if len(ks) != m:
raise ValueError("polyint: k must have length m")
for step in range(m):
out = [ks[step]] + [out[i] / (i + 1) for i in range(len(out))]
return out
def polyroots(coef):
c = _trim(_coerce_seq(coef))
n = len(c) - 1
if n <= 0:
return []
lead = c[-1]
if lead == 0:
return []
monic = [ci / lead for ci in c]
roots = [
complex(
0.4 * _cos(2 * _pi * k / n + 0.4),
0.9 * _sin(2 * _pi * k / n + 0.4),
)
for k in range(n)
]
for _ in range(200):
new = []
max_step = 0.0
for i, r in enumerate(roots):
denom = complex(1.0)
for j, s in enumerate(roots):
if i != j:
denom *= r - s
if denom == 0:
new.append(r)
continue
r2 = r - polyval(monic, r) / denom
new.append(r2)
d = abs(r2 - r)
if d > max_step:
max_step = d
roots = new
if max_step < 1e-14:
break
return roots
def polyfit(x, y, deg, rcond=None, full=False):
_ = rcond _ = full
xs = _coerce_seq(x)
ys = _coerce_seq(y)
if len(xs) != len(ys):
raise ValueError("polyfit: x and y must have the same length")
n = deg + 1
sums = [sum(xv ** k for xv in xs) for k in range(2 * n - 1)]
rhs = [sum(yv * xv ** k for xv, yv in zip(xs, ys)) for k in range(n)]
mat = [[sums[i + j] for j in range(n)] for i in range(n)]
return _gauss_solve(mat, rhs)
def _gauss_solve(a, b):
n = len(a)
a = [row[:] for row in a]
b = b[:]
for k in range(n):
piv = k
for i in range(k + 1, n):
if abs(a[i][k]) > abs(a[piv][k]):
piv = i
if piv != k:
a[k], a[piv] = a[piv], a[k]
b[k], b[piv] = b[piv], b[k]
if a[k][k] == 0:
raise ValueError("polyfit: singular normal-equations matrix")
for i in range(k + 1, n):
f = a[i][k] / a[k][k]
for j in range(k, n):
a[i][j] -= f * a[k][j]
b[i] -= f * b[k]
x = [0.0] * n
for i in range(n - 1, -1, -1):
s = b[i]
for j in range(i + 1, n):
s -= a[i][j] * x[j]
x[i] = s / a[i][i]
return x
class Polynomial:
def __init__(self, coef):
self.coef = _coerce_seq(coef)
if not self.coef:
self.coef = [0]
def __call__(self, x):
if isinstance(x, (list, tuple)):
return [polyval(self.coef, v) for v in x]
return polyval(self.coef, x)
def __add__(self, other):
if isinstance(other, Polynomial):
return Polynomial(polyadd(self.coef, other.coef))
return Polynomial(polyadd(self.coef, [other]))
__radd__ = __add__
def __sub__(self, other):
if isinstance(other, Polynomial):
return Polynomial(polysub(self.coef, other.coef))
return Polynomial(polysub(self.coef, [other]))
def __rsub__(self, other):
return Polynomial(polysub([other], self.coef))
def __mul__(self, other):
if isinstance(other, Polynomial):
return Polynomial(polymul(self.coef, other.coef))
return Polynomial([c * other for c in self.coef])
__rmul__ = __mul__
def __neg__(self):
return Polynomial([-c for c in self.coef])
def __eq__(self, other):
if isinstance(other, Polynomial):
return _trim(self.coef) == _trim(other.coef)
return NotImplemented
def __repr__(self):
terms = ", ".join(repr(c) for c in self.coef)
return f"Polynomial([{terms}])"
@property
def degree(self):
c = _trim(self.coef)
return max(0, len(c) - 1)
def deriv(self, m=1):
return Polynomial(polyder(self.coef, m))
def integ(self, m=1, k=0):
return Polynomial(polyint(self.coef, m, k))
def roots(self):
return polyroots(self.coef)
@classmethod
def fit(cls, x, y, deg):
return cls(polyfit(x, y, deg))
def _basis_to_power(family, n):
if n < 0:
return []
b = [family["zero"][:], family["one"][:]] if n >= 1 else [family["zero"][:]]
for k in range(2, n + 1):
prev = b[k - 1]
prev2 = b[k - 2]
a_k, b_k, c_k = family["recur"](k)
shifted = [0] + list(prev)
if len(shifted) < len(prev2):
shifted += [0] * (len(prev2) - len(shifted))
if len(prev) < len(shifted):
prev = list(prev) + [0] * (len(shifted) - len(prev))
if len(prev2) < len(shifted):
prev2 = list(prev2) + [0] * (len(shifted) - len(prev2))
nxt = [
a_k * shifted[i] + b_k * prev[i] - c_k * prev2[i]
for i in range(len(shifted))
]
b.append(nxt)
return b
def _basis_coefs_to_power(family, coefs):
if not coefs:
return [0]
n = len(coefs) - 1
table = _basis_to_power(family, n)
out = [0.0] * (n + 1)
for k, ck in enumerate(coefs):
row = table[k]
if len(row) > len(out):
out += [0.0] * (len(row) - len(out))
for i, v in enumerate(row):
out[i] += ck * v
return out
def _clenshaw(coefs, x, family):
n = len(coefs)
if n == 0:
return 0
if n == 1:
return coefs[0] * family["one_val"](x)
b1 = coefs[-1]
b2 = 0
for k in range(n - 2, 0, -1):
a_k, bb_k, c_kp1 = family["recur"](k + 1)
b0 = coefs[k] + b1 * (a_k * x + bb_k) - b2 * c_kp1
b2, b1 = b1, b0
a_1, b_1, c_2 = family["recur"](1)
return coefs[0] * family["one_val"](x) + b1 * family["basis1_val"](x) - b2 * c_2 * family["one_val"](x)
_CHEB = {
"zero": [1.0],
"one": [0.0, 1.0],
"recur": lambda k: (2.0, 0.0, 1.0),
"one_val": lambda x: 1.0,
"basis1_val": lambda x: x,
}
_HERMITE = {
"zero": [1.0],
"one": [0.0, 2.0],
"recur": lambda k: (2.0, 0.0, 2.0 * (k - 1)),
"one_val": lambda x: 1.0,
"basis1_val": lambda x: 2.0 * x,
}
_HERMITE_E = {
"zero": [1.0],
"one": [0.0, 1.0],
"recur": lambda k: (1.0, 0.0, k - 1),
"one_val": lambda x: 1.0,
"basis1_val": lambda x: x,
}
_LAGUERRE = {
"zero": [1.0],
"one": [1.0, -1.0],
"recur": lambda k: (-1.0 / k, (2 * k - 1) / k, (k - 1) / k),
"one_val": lambda x: 1.0,
"basis1_val": lambda x: 1 - x,
}
_LEGENDRE = {
"zero": [1.0],
"one": [0.0, 1.0],
"recur": lambda k: ((2 * k - 1) / k, 0.0, (k - 1) / k),
"one_val": lambda x: 1.0,
"basis1_val": lambda x: x,
}
def _family_val(family, coef, x):
if isinstance(x, (list, tuple)):
return [_clenshaw(_coerce_seq(coef), v, family) for v in x]
return _clenshaw(_coerce_seq(coef), x, family)
def _family_roots(family, coef):
return polyroots(_basis_coefs_to_power(family, _coerce_seq(coef)))
def _family_fit(family, x, y, deg):
xs = _coerce_seq(x)
ys = _coerce_seq(y)
n = deg + 1
table = _basis_to_power(family, deg)
cols = [[polyval(table[k], xv) for xv in xs] for k in range(n)]
mat = [
[sum(cols[i][r] * cols[j][r] for r in range(len(xs))) for j in range(n)]
for i in range(n)
]
rhs = [sum(cols[i][r] * ys[r] for r in range(len(xs))) for i in range(n)]
return _gauss_solve(mat, rhs)
def _family_add(c1, c2):
return polyadd(c1, c2)
def _family_sub(c1, c2):
return polysub(c1, c2)
def _family_der(family, coef, m=1):
p = _basis_coefs_to_power(family, _coerce_seq(coef))
return polyder(p, m)
def _family_int(family, coef, m=1, k=0):
p = _basis_coefs_to_power(family, _coerce_seq(coef))
return polyint(p, m, k)
def chebval(x, c): return _family_val(_CHEB, c, x)
def hermval(x, c): return _family_val(_HERMITE, c, x)
def hermeval(x, c): return _family_val(_HERMITE_E, c, x)
def lagval(x, c): return _family_val(_LAGUERRE, c, x)
def legval(x, c): return _family_val(_LEGENDRE, c, x)
def chebroots(c): return _family_roots(_CHEB, c)
def hermroots(c): return _family_roots(_HERMITE, c)
def hermeroots(c): return _family_roots(_HERMITE_E, c)
def lagroots(c): return _family_roots(_LAGUERRE, c)
def legroots(c): return _family_roots(_LEGENDRE, c)
def chebfit(x, y, deg): return _family_fit(_CHEB, x, y, deg)
def hermfit(x, y, deg): return _family_fit(_HERMITE, x, y, deg)
def hermefit(x, y, deg): return _family_fit(_HERMITE_E, x, y, deg)
def lagfit(x, y, deg): return _family_fit(_LAGUERRE, x, y, deg)
def legfit(x, y, deg): return _family_fit(_LEGENDRE, x, y, deg)
class _SeriesBase:
_family = None
def __init__(self, coef, domain=None, window=None):
self.coef = _coerce_seq(coef)
if not self.coef:
self.coef = [0]
self.domain = list(domain) if domain is not None else self._default_domain()
self.window = list(window) if window is not None else list(self._default_domain())
def _default_domain(self):
return [-1, 1]
def __call__(self, x):
return _family_val(self._family, self.coef, x)
def __add__(self, other):
if isinstance(other, _SeriesBase):
return type(self)(_family_add(self.coef, other.coef), self.domain, self.window)
return type(self)(_family_add(self.coef, [other]), self.domain, self.window)
__radd__ = __add__
def __sub__(self, other):
if isinstance(other, _SeriesBase):
return type(self)(_family_sub(self.coef, other.coef), self.domain, self.window)
return type(self)(_family_sub(self.coef, [other]), self.domain, self.window)
def __rsub__(self, other):
return type(self)(_family_sub([other], self.coef), self.domain, self.window)
def __mul__(self, other):
if isinstance(other, _SeriesBase):
p1 = _basis_coefs_to_power(self._family, self.coef)
p2 = _basis_coefs_to_power(self._family, other.coef)
prod = polymul(p1, p2)
return type(self)._from_power(prod, self.domain, self.window)
return type(self)([c * other for c in self.coef], self.domain, self.window)
__rmul__ = __mul__
def __neg__(self):
return type(self)([-c for c in self.coef], self.domain, self.window)
def __repr__(self):
return f"{type(self).__name__}({list(self.coef)!r})"
@property
def degree(self):
c = _trim(self.coef)
return max(0, len(c) - 1)
def deriv(self, m=1):
p = _family_der(self._family, self.coef, m)
return type(self)._from_power(p, self.domain, self.window)
def integ(self, m=1, k=0):
p = _family_int(self._family, self.coef, m, k)
return type(self)._from_power(p, self.domain, self.window)
def roots(self):
return _family_roots(self._family, self.coef)
@classmethod
def fit(cls, x, y, deg, domain=None, window=None):
c = _family_fit(cls._family, x, y, deg)
return cls(c, domain, window)
@classmethod
def _from_power(cls, power_coefs, domain=None, window=None):
return cls(power_coefs, domain, window)
class Chebyshev(_SeriesBase):
_family = _CHEB
class Hermite(_SeriesBase):
_family = _HERMITE
class HermiteE(_SeriesBase):
_family = _HERMITE_E
class Laguerre(_SeriesBase):
_family = _LAGUERRE
def _default_domain(self): return [0, 1]
class Legendre(_SeriesBase):
_family = _LEGENDRE
class _Namespace:
def __init__(self, **kw):
for k, v in kw.items():
setattr(self, k, v)
chebyshev = _Namespace(
Chebyshev=Chebyshev,
chebval=chebval,
chebroots=chebroots,
chebfit=chebfit,
)
hermite = _Namespace(
Hermite=Hermite,
hermval=hermval,
hermroots=hermroots,
hermfit=hermfit,
)
hermite_e = _Namespace(
HermiteE=HermiteE,
hermeval=hermeval,
hermeroots=hermeroots,
hermefit=hermefit,
)
laguerre = _Namespace(
Laguerre=Laguerre,
lagval=lagval,
lagroots=lagroots,
lagfit=lagfit,
)
legendre = _Namespace(
Legendre=Legendre,
legval=legval,
legroots=legroots,
legfit=legfit,
)
polynomial = _Namespace(
Polynomial=Polynomial,
polyval=polyval,
polyadd=polyadd,
polysub=polysub,
polymul=polymul,
polyder=polyder,
polyint=polyint,
polyroots=polyroots,
polyfit=polyfit,
)
def trimcoef(c, tol=0):
return _trim(_coerce_seq(c), tol=tol)
def getdomain(x):
xs = _coerce_seq(x)
if not xs:
return [0, 1]
return [min(xs), max(xs)]
def mapparms(old, new):
o0, o1 = old
n0, n1 = new
scale = (n1 - n0) / (o1 - o0) if (o1 - o0) != 0 else 0
offset = n0 - scale * o0
return [offset, scale]
def mapdomain(x, old, new):
off, scl = mapparms(old, new)
if isinstance(x, (list, tuple)):
return [off + scl * v for v in x]
return off + scl * x
polyutils = _Namespace(
trimcoef=trimcoef,
getdomain=getdomain,
mapparms=mapparms,
mapdomain=mapdomain,
)
_print_style = "unicode"
def set_default_printstyle(style):
global _print_style
if style not in ("ascii", "unicode"):
raise ValueError("set_default_printstyle: style must be 'ascii' or 'unicode'")
_print_style = style
def test(*args, **kwargs):
_ = (args, kwargs)
return True
__all__ = [
"Polynomial",
"polyval",
"polyadd",
"polysub",
"polymul",
"polyder",
"polyint",
"polyroots",
"polyfit",
"Chebyshev",
"Hermite",
"HermiteE",
"Laguerre",
"Legendre",
"chebyshev",
"hermite",
"hermite_e",
"laguerre",
"legendre",
"polynomial",
"polyutils",
"set_default_printstyle",
"test",
]