mirror of
https://github.com/meteoinfo/MeteoInfo.git
synced 2025-12-08 20:36:05 +00:00
617 lines
17 KiB
Python
617 lines
17 KiB
Python
"""
|
|
Functions to operate on polynomials.
|
|
|
|
"""
|
|
|
|
from org.meteoinfo.math.fitting import FittingUtil
|
|
from org.meteoinfo.ndarray.math import ArrayMath, ArrayUtil
|
|
|
|
from ..core import numeric as NX
|
|
from ..core import NDArray, atleast_1d
|
|
from ..linalg import eigvals
|
|
import warnings
|
|
|
|
__all__ = ['polyfit','polyval','polyder']
|
|
|
|
|
|
def poly(seq_of_zeros):
|
|
"""
|
|
Find the coefficients of a polynomial with the given sequence of roots.
|
|
|
|
Returns the coefficients of the polynomial whose leading coefficient
|
|
is one for the given sequence of zeros (multiple roots must be included
|
|
in the sequence as many times as their multiplicity; see Examples).
|
|
A square matrix (or array, which will be treated as a matrix) can also
|
|
be given, in which case the coefficients of the characteristic polynomial
|
|
of the matrix are returned.
|
|
|
|
Parameters
|
|
----------
|
|
seq_of_zeros : array_like, shape (N,) or (N, N)
|
|
A sequence of polynomial roots, or a square array or matrix object.
|
|
|
|
Returns
|
|
-------
|
|
c : ndarray
|
|
1D array of polynomial coefficients from highest to lowest degree:
|
|
|
|
``c[0] * x**(N) + c[1] * x**(N-1) + ... + c[N-1] * x + c[N]``
|
|
where c[0] always equals 1.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If input is the wrong shape (the input must be a 1-D or square
|
|
2-D array).
|
|
|
|
See Also
|
|
--------
|
|
polyval : Compute polynomial values.
|
|
roots : Return the roots of a polynomial.
|
|
polyfit : Least squares polynomial fit.
|
|
poly1d : A one-dimensional polynomial class.
|
|
|
|
Notes
|
|
-----
|
|
Specifying the roots of a polynomial still leaves one degree of
|
|
freedom, typically represented by an undetermined leading
|
|
coefficient. [1]_ In the case of this function, that coefficient -
|
|
the first one in the returned array - is always taken as one. (If
|
|
for some reason you have one other point, the only automatic way
|
|
presently to leverage that information is to use ``polyfit``.)
|
|
|
|
The characteristic polynomial, :math:`p_a(t)`, of an `n`-by-`n`
|
|
matrix **A** is given by
|
|
|
|
:math:`p_a(t) = \\mathrm{det}(t\\, \\mathbf{I} - \\mathbf{A})`,
|
|
|
|
where **I** is the `n`-by-`n` identity matrix. [2]_
|
|
|
|
References
|
|
----------
|
|
.. [1] M. Sullivan and M. Sullivan, III, "Algebra and Trigonometry,
|
|
Enhanced With Graphing Utilities," Prentice-Hall, pg. 318, 1996.
|
|
|
|
.. [2] G. Strang, "Linear Algebra and Its Applications, 2nd Edition,"
|
|
Academic Press, pg. 182, 1980.
|
|
|
|
Examples
|
|
--------
|
|
Given a sequence of a polynomial's zeros:
|
|
|
|
>>> np.poly((0, 0, 0)) # Multiple root example
|
|
array([1., 0., 0., 0.])
|
|
|
|
The line above represents z**3 + 0*z**2 + 0*z + 0.
|
|
|
|
>>> np.poly((-1./2, 0, 1./2))
|
|
array([ 1. , 0. , -0.25, 0. ])
|
|
|
|
The line above represents z**3 - z/4
|
|
|
|
>>> np.poly((np.random.random(1)[0], 0, np.random.random(1)[0]))
|
|
array([ 1. , -0.77086955, 0.08618131, 0. ]) # random
|
|
|
|
Given a square array object:
|
|
|
|
>>> P = np.array([[0, 1./3], [-1./2, 0]])
|
|
>>> np.poly(P)
|
|
array([1. , 0. , 0.16666667])
|
|
|
|
Note how in all cases the leading coefficient is always 1.
|
|
|
|
"""
|
|
seq_of_zeros = atleast_1d(seq_of_zeros)
|
|
sh = seq_of_zeros.shape
|
|
|
|
if len(sh) == 2 and sh[0] == sh[1] and sh[0] != 0:
|
|
seq_of_zeros = eigvals(seq_of_zeros)
|
|
elif len(sh) == 1:
|
|
dt = seq_of_zeros.dtype
|
|
# Let object arrays slip through, e.g. for arbitrary precision
|
|
if dt != object:
|
|
seq_of_zeros = seq_of_zeros.astype(mintypecode(dt.char))
|
|
else:
|
|
raise ValueError("input must be 1d or non-empty square 2d array.")
|
|
|
|
if len(seq_of_zeros) == 0:
|
|
return 1.0
|
|
dt = seq_of_zeros.dtype
|
|
a = ones((1,), dtype=dt)
|
|
for zero in seq_of_zeros:
|
|
a = NX.convolve(a, array([1, -zero], dtype=dt), mode='full')
|
|
|
|
if issubclass(a.dtype.type, NX.complexfloating):
|
|
# if complex roots are all complex conjugates, the roots are real.
|
|
roots = NX.asarray(seq_of_zeros, complex)
|
|
if NX.all(NX.sort(roots) == NX.sort(roots.conjugate())):
|
|
a = a.real.copy()
|
|
|
|
return a
|
|
|
|
def polyfit(x, y, degree, func=False):
|
|
"""
|
|
Polynomial fitting.
|
|
|
|
:param x: (*array_like*) x data array.
|
|
:param y: (*array_like*) y data array.
|
|
:param degree: (*int*) Degree of the fitting polynomial.
|
|
:param func: (*boolean*) Return fit function (for predict function) or not. Default is ``False``.
|
|
|
|
:returns: Fitting parameters and function (optional).
|
|
"""
|
|
if isinstance(x, list):
|
|
x = NDArray(ArrayUtil.array(x))
|
|
if isinstance(y, list):
|
|
y = NDArray(ArrayUtil.array(y))
|
|
r = FittingUtil.polyFit(x.asarray(), y.asarray(), degree)
|
|
if func:
|
|
return r[0], r[1], r[2]
|
|
else:
|
|
return r[0], r[1]
|
|
|
|
def polyval(p, x):
|
|
"""
|
|
Evaluate a polynomial at specific values.
|
|
|
|
If p is of length N, this function returns the value:
|
|
|
|
p[0]*x**(N-1) + p[1]*x**(N-2) + ... + p[N-2]*x + p[N-1]
|
|
|
|
If x is a sequence, then p(x) is returned for each element of x. If x is another polynomial then the
|
|
composite polynomial p(x(t)) is returned.
|
|
|
|
:param p: (*array_like*) 1D array of polynomial coefficients (including coefficients equal to zero)
|
|
from the highest degree to the constant term.
|
|
:param x: (*array_like*) A number, an array of numbers, or an instance of poly1d, at which to evaluate
|
|
p.
|
|
|
|
:returns: Polynomial value
|
|
"""
|
|
if isinstance(x, list):
|
|
x = NDArray(ArrayUtil.array(x))
|
|
return NDArray(ArrayMath.polyVal(p, x.asarray()))
|
|
|
|
def polyder(p, m=1):
|
|
"""
|
|
Return the derivative of the specified order of a polynomial.
|
|
|
|
.. note::
|
|
This forms part of the old polynomial API. Since version 1.4, the
|
|
new polynomial API defined in `numpy.polynomial` is preferred.
|
|
A summary of the differences can be found in the
|
|
:doc:`transition guide </reference/routines.polynomials>`.
|
|
|
|
Parameters
|
|
----------
|
|
p : poly1d or sequence
|
|
Polynomial to differentiate.
|
|
A sequence is interpreted as polynomial coefficients, see `poly1d`.
|
|
m : int, optional
|
|
Order of differentiation (default: 1)
|
|
|
|
Returns
|
|
-------
|
|
der : poly1d
|
|
A new polynomial representing the derivative.
|
|
|
|
See Also
|
|
--------
|
|
polyint : Anti-derivative of a polynomial.
|
|
poly1d : Class for one-dimensional polynomials.
|
|
|
|
Examples
|
|
--------
|
|
The derivative of the polynomial :math:`x^3 + x^2 + x^1 + 1` is:
|
|
|
|
>>> p = np.poly1d([1,1,1,1])
|
|
>>> p2 = np.polyder(p)
|
|
>>> p2
|
|
poly1d([3, 2, 1])
|
|
|
|
which evaluates to:
|
|
|
|
>>> p2(2.)
|
|
17.0
|
|
|
|
We can verify this, approximating the derivative with
|
|
``(f(x + h) - f(x))/h``:
|
|
|
|
>>> (p(2. + 0.001) - p(2.)) / 0.001
|
|
17.007000999997857
|
|
|
|
The fourth-order derivative of a 3rd-order polynomial is zero:
|
|
|
|
>>> np.polyder(p, 2)
|
|
poly1d([6, 2])
|
|
>>> np.polyder(p, 3)
|
|
poly1d([6])
|
|
>>> np.polyder(p, 4)
|
|
poly1d([0])
|
|
|
|
"""
|
|
m = int(m)
|
|
if m < 0:
|
|
raise ValueError("Order of derivative must be positive (see polyint)")
|
|
|
|
#truepoly = isinstance(p, poly1d)
|
|
p = NX.asarray(p)
|
|
n = len(p) - 1
|
|
y = p[:-1] * NX.arange(n, 0, -1)
|
|
if m == 0:
|
|
val = p
|
|
else:
|
|
val = polyder(y, m - 1)
|
|
|
|
#if truepoly:
|
|
# val = poly1d(val)
|
|
|
|
return val
|
|
|
|
class poly1d:
|
|
"""
|
|
A one-dimensional polynomial class.
|
|
|
|
A convenience class, used to encapsulate "natural" operations on
|
|
polynomials so that said operations may take on their customary
|
|
form in code (see Examples).
|
|
|
|
Parameters
|
|
----------
|
|
c_or_r : array_like
|
|
The polynomial's coefficients, in decreasing powers, or if
|
|
the value of the second parameter is True, the polynomial's
|
|
roots (values where the polynomial evaluates to 0). For example,
|
|
``poly1d([1, 2, 3])`` returns an object that represents
|
|
:math:`x^2 + 2x + 3`, whereas ``poly1d([1, 2, 3], True)`` returns
|
|
one that represents :math:`(x-1)(x-2)(x-3) = x^3 - 6x^2 + 11x -6`.
|
|
r : bool, optional
|
|
If True, `c_or_r` specifies the polynomial's roots; the default
|
|
is False.
|
|
variable : str, optional
|
|
Changes the variable used when printing `p` from `x` to `variable`
|
|
(see Examples).
|
|
|
|
Examples
|
|
--------
|
|
Construct the polynomial :math:`x^2 + 2x + 3`:
|
|
|
|
>>> p = np.poly1d([1, 2, 3])
|
|
>>> print(np.poly1d(p))
|
|
2
|
|
1 x + 2 x + 3
|
|
|
|
Evaluate the polynomial at :math:`x = 0.5`:
|
|
|
|
>>> p(0.5)
|
|
4.25
|
|
|
|
Find the roots:
|
|
|
|
>>> p.r
|
|
array([-1.+1.41421356j, -1.-1.41421356j])
|
|
>>> p(p.r)
|
|
array([ -4.44089210e-16+0.j, -4.44089210e-16+0.j]) # may vary
|
|
|
|
These numbers in the previous line represent (0, 0) to machine precision
|
|
|
|
Show the coefficients:
|
|
|
|
>>> p.c
|
|
array([1, 2, 3])
|
|
|
|
Display the order (the leading zero-coefficients are removed):
|
|
|
|
>>> p.order
|
|
2
|
|
|
|
Show the coefficient of the k-th power in the polynomial
|
|
(which is equivalent to ``p.c[-(i+1)]``):
|
|
|
|
>>> p[1]
|
|
2
|
|
|
|
Polynomials can be added, subtracted, multiplied, and divided
|
|
(returns quotient and remainder):
|
|
|
|
>>> p * p
|
|
poly1d([ 1, 4, 10, 12, 9])
|
|
|
|
>>> (p**3 + 4) / p
|
|
(poly1d([ 1., 4., 10., 12., 9.]), poly1d([4.]))
|
|
|
|
``asarray(p)`` gives the coefficient array, so polynomials can be
|
|
used in all functions that accept arrays:
|
|
|
|
>>> p**2 # square of polynomial
|
|
poly1d([ 1, 4, 10, 12, 9])
|
|
|
|
>>> np.square(p) # square of individual coefficients
|
|
array([1, 4, 9])
|
|
|
|
The variable used in the string representation of `p` can be modified,
|
|
using the `variable` parameter:
|
|
|
|
>>> p = np.poly1d([1,2,3], variable='z')
|
|
>>> print(p)
|
|
2
|
|
1 z + 2 z + 3
|
|
|
|
Construct a polynomial from its roots:
|
|
|
|
>>> np.poly1d([1, 2], True)
|
|
poly1d([ 1., -3., 2.])
|
|
|
|
This is the same polynomial as obtained by:
|
|
|
|
>>> np.poly1d([1, -1]) * np.poly1d([1, -2])
|
|
poly1d([ 1, -3, 2])
|
|
|
|
"""
|
|
__hash__ = None
|
|
|
|
@property
|
|
def coeffs(self):
|
|
""" The polynomial coefficients """
|
|
return self._coeffs
|
|
|
|
@coeffs.setter
|
|
def coeffs(self, value):
|
|
# allowing this makes p.coeffs *= 2 legal
|
|
if value is not self._coeffs:
|
|
raise AttributeError("Cannot set attribute")
|
|
|
|
@property
|
|
def variable(self):
|
|
""" The name of the polynomial variable """
|
|
return self._variable
|
|
|
|
# calculated attributes
|
|
@property
|
|
def order(self):
|
|
""" The order or degree of the polynomial """
|
|
return len(self._coeffs) - 1
|
|
|
|
@property
|
|
def roots(self):
|
|
""" The roots of the polynomial, where self(x) == 0 """
|
|
return roots(self._coeffs)
|
|
|
|
# our internal _coeffs property need to be backed by __dict__['coeffs'] for
|
|
# scipy to work correctly.
|
|
@property
|
|
def _coeffs(self):
|
|
return self.__dict__['coeffs']
|
|
@_coeffs.setter
|
|
def _coeffs(self, coeffs):
|
|
self.__dict__['coeffs'] = coeffs
|
|
|
|
# alias attributes
|
|
r = roots
|
|
c = coef = coefficients = coeffs
|
|
o = order
|
|
|
|
def __init__(self, c_or_r, r=False, variable=None):
|
|
if isinstance(c_or_r, poly1d):
|
|
self._variable = c_or_r._variable
|
|
self._coeffs = c_or_r._coeffs
|
|
|
|
if set(c_or_r.__dict__) - set(self.__dict__):
|
|
msg = ("In the future extra properties will not be copied "
|
|
"across when constructing one poly1d from another")
|
|
warnings.warn(msg, FutureWarning, stacklevel=2)
|
|
self.__dict__.update(c_or_r.__dict__)
|
|
|
|
if variable is not None:
|
|
self._variable = variable
|
|
return
|
|
if r:
|
|
c_or_r = poly(c_or_r)
|
|
c_or_r = atleast_1d(c_or_r)
|
|
if c_or_r.ndim > 1:
|
|
raise ValueError("Polynomial must be 1d only.")
|
|
c_or_r = trim_zeros(c_or_r, trim='f')
|
|
if len(c_or_r) == 0:
|
|
c_or_r = NX.array([0], dtype=c_or_r.dtype)
|
|
self._coeffs = c_or_r
|
|
if variable is None:
|
|
variable = 'x'
|
|
self._variable = variable
|
|
|
|
def __array__(self, t=None):
|
|
if t:
|
|
return NX.asarray(self.coeffs, t)
|
|
else:
|
|
return NX.asarray(self.coeffs)
|
|
|
|
def __repr__(self):
|
|
vals = repr(self.coeffs)
|
|
vals = vals[6:-1]
|
|
return "poly1d(%s)" % vals
|
|
|
|
def __len__(self):
|
|
return self.order
|
|
|
|
def __str__(self):
|
|
thestr = "0"
|
|
var = self.variable
|
|
|
|
# Remove leading zeros
|
|
coeffs = self.coeffs[NX.logical_or.accumulate(self.coeffs != 0)]
|
|
N = len(coeffs)-1
|
|
|
|
def fmt_float(q):
|
|
s = '%.4g' % q
|
|
if s.endswith('.0000'):
|
|
s = s[:-5]
|
|
return s
|
|
|
|
for k, coeff in enumerate(coeffs):
|
|
if not iscomplex(coeff):
|
|
coefstr = fmt_float(real(coeff))
|
|
elif real(coeff) == 0:
|
|
coefstr = '%sj' % fmt_float(imag(coeff))
|
|
else:
|
|
coefstr = '(%s + %sj)' % (fmt_float(real(coeff)),
|
|
fmt_float(imag(coeff)))
|
|
|
|
power = (N-k)
|
|
if power == 0:
|
|
if coefstr != '0':
|
|
newstr = '%s' % (coefstr,)
|
|
else:
|
|
if k == 0:
|
|
newstr = '0'
|
|
else:
|
|
newstr = ''
|
|
elif power == 1:
|
|
if coefstr == '0':
|
|
newstr = ''
|
|
elif coefstr == 'b':
|
|
newstr = var
|
|
else:
|
|
newstr = '%s %s' % (coefstr, var)
|
|
else:
|
|
if coefstr == '0':
|
|
newstr = ''
|
|
elif coefstr == 'b':
|
|
newstr = '%s**%d' % (var, power,)
|
|
else:
|
|
newstr = '%s %s**%d' % (coefstr, var, power)
|
|
|
|
if k > 0:
|
|
if newstr != '':
|
|
if newstr.startswith('-'):
|
|
thestr = "%s - %s" % (thestr, newstr[1:])
|
|
else:
|
|
thestr = "%s + %s" % (thestr, newstr)
|
|
else:
|
|
thestr = newstr
|
|
return _raise_power(thestr)
|
|
|
|
def __call__(self, val):
|
|
return polyval(self.coeffs, val)
|
|
|
|
def __neg__(self):
|
|
return poly1d(-self.coeffs)
|
|
|
|
def __pos__(self):
|
|
return self
|
|
|
|
def __mul__(self, other):
|
|
if isscalar(other):
|
|
return poly1d(self.coeffs * other)
|
|
else:
|
|
other = poly1d(other)
|
|
return poly1d(polymul(self.coeffs, other.coeffs))
|
|
|
|
def __rmul__(self, other):
|
|
if isscalar(other):
|
|
return poly1d(other * self.coeffs)
|
|
else:
|
|
other = poly1d(other)
|
|
return poly1d(polymul(self.coeffs, other.coeffs))
|
|
|
|
def __add__(self, other):
|
|
other = poly1d(other)
|
|
return poly1d(polyadd(self.coeffs, other.coeffs))
|
|
|
|
def __radd__(self, other):
|
|
other = poly1d(other)
|
|
return poly1d(polyadd(self.coeffs, other.coeffs))
|
|
|
|
def __pow__(self, val):
|
|
if not isscalar(val) or int(val) != val or val < 0:
|
|
raise ValueError("Power to non-negative integers only.")
|
|
res = [1]
|
|
for _ in range(val):
|
|
res = polymul(self.coeffs, res)
|
|
return poly1d(res)
|
|
|
|
def __sub__(self, other):
|
|
other = poly1d(other)
|
|
return poly1d(polysub(self.coeffs, other.coeffs))
|
|
|
|
def __rsub__(self, other):
|
|
other = poly1d(other)
|
|
return poly1d(polysub(other.coeffs, self.coeffs))
|
|
|
|
def __div__(self, other):
|
|
if isscalar(other):
|
|
return poly1d(self.coeffs/other)
|
|
else:
|
|
other = poly1d(other)
|
|
return polydiv(self, other)
|
|
|
|
__truediv__ = __div__
|
|
|
|
def __rdiv__(self, other):
|
|
if isscalar(other):
|
|
return poly1d(other/self.coeffs)
|
|
else:
|
|
other = poly1d(other)
|
|
return polydiv(other, self)
|
|
|
|
__rtruediv__ = __rdiv__
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, poly1d):
|
|
return NotImplemented
|
|
if self.coeffs.shape != other.coeffs.shape:
|
|
return False
|
|
return (self.coeffs == other.coeffs).all()
|
|
|
|
def __ne__(self, other):
|
|
if not isinstance(other, poly1d):
|
|
return NotImplemented
|
|
return not self.__eq__(other)
|
|
|
|
|
|
def __getitem__(self, val):
|
|
ind = self.order - val
|
|
if val > self.order:
|
|
return self.coeffs.dtype.type(0)
|
|
if val < 0:
|
|
return self.coeffs.dtype.type(0)
|
|
return self.coeffs[ind]
|
|
|
|
def __setitem__(self, key, val):
|
|
ind = self.order - key
|
|
if key < 0:
|
|
raise ValueError("Does not support negative powers.")
|
|
if key > self.order:
|
|
zr = NX.zeros(key-self.order, self.coeffs.dtype)
|
|
self._coeffs = NX.concatenate((zr, self.coeffs))
|
|
ind = 0
|
|
self._coeffs[ind] = val
|
|
return
|
|
|
|
def __iter__(self):
|
|
return iter(self.coeffs)
|
|
|
|
def integ(self, m=1, k=0):
|
|
"""
|
|
Return an antiderivative (indefinite integral) of this polynomial.
|
|
|
|
Refer to `polyint` for full documentation.
|
|
|
|
See Also
|
|
--------
|
|
polyint : equivalent function
|
|
|
|
"""
|
|
return poly1d(polyint(self.coeffs, m=m, k=k))
|
|
|
|
def deriv(self, m=1):
|
|
"""
|
|
Return a derivative of this polynomial.
|
|
|
|
Refer to `polyder` for full documentation.
|
|
|
|
See Also
|
|
--------
|
|
polyder : equivalent function
|
|
|
|
"""
|
|
return poly1d(polyder(self.coeffs, m=m))
|