|
""" |
|
Functions to operate on polynomials. |
|
|
|
""" |
|
from __future__ import division, absolute_import, print_function |
|
|
|
__all__ = ['poly', 'roots', 'polyint', 'polyder', 'polyadd', |
|
'polysub', 'polymul', 'polydiv', 'polyval', 'poly1d', |
|
'polyfit', 'RankWarning'] |
|
|
|
import re |
|
import warnings |
|
import numpy.core.numeric as NX |
|
|
|
from numpy.core import isscalar, abs, finfo, atleast_1d, hstack, dot |
|
from numpy.lib.twodim_base import diag, vander |
|
from numpy.lib.function_base import trim_zeros, sort_complex |
|
from numpy.lib.type_check import iscomplex, real, imag |
|
from numpy.linalg import eigvals, lstsq, inv |
|
|
|
class RankWarning(UserWarning): |
|
""" |
|
Issued by `polyfit` when the Vandermonde matrix is rank deficient. |
|
|
|
For more information, a way to suppress the warning, and an example of |
|
`RankWarning` being issued, see `polyfit`. |
|
|
|
""" |
|
pass |
|
|
|
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 : Evaluate a polynomial at a point. |
|
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 Trignometry, |
|
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]) |
|
|
|
Or a square matrix object: |
|
|
|
>>> np.poly(np.matrix(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: |
|
pass |
|
else: |
|
raise ValueError("input must be 1d or non-empty square 2d array.") |
|
|
|
if len(seq_of_zeros) == 0: |
|
return 1.0 |
|
|
|
a = [1] |
|
for k in range(len(seq_of_zeros)): |
|
a = NX.convolve(a, [1, -seq_of_zeros[k]], mode='full') |
|
|
|
if issubclass(a.dtype.type, NX.complexfloating): |
|
|
|
roots = NX.asarray(seq_of_zeros, complex) |
|
pos_roots = sort_complex(NX.compress(roots.imag > 0, roots)) |
|
neg_roots = NX.conjugate(sort_complex( |
|
NX.compress(roots.imag < 0, roots))) |
|
if (len(pos_roots) == len(neg_roots) and |
|
NX.alltrue(neg_roots == pos_roots)): |
|
a = a.real.copy() |
|
|
|
return a |
|
|
|
def roots(p): |
|
""" |
|
Return the roots of a polynomial with coefficients given in p. |
|
|
|
The values in the rank-1 array `p` are coefficients of a polynomial. |
|
If the length of `p` is n+1 then the polynomial is described by:: |
|
|
|
p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n] |
|
|
|
Parameters |
|
---------- |
|
p : array_like |
|
Rank-1 array of polynomial coefficients. |
|
|
|
Returns |
|
------- |
|
out : ndarray |
|
An array containing the complex roots of the polynomial. |
|
|
|
Raises |
|
------ |
|
ValueError |
|
When `p` cannot be converted to a rank-1 array. |
|
|
|
See also |
|
-------- |
|
poly : Find the coefficients of a polynomial with a given sequence |
|
of roots. |
|
polyval : Evaluate a polynomial at a point. |
|
polyfit : Least squares polynomial fit. |
|
poly1d : A one-dimensional polynomial class. |
|
|
|
Notes |
|
----- |
|
The algorithm relies on computing the eigenvalues of the |
|
companion matrix [1]_. |
|
|
|
References |
|
---------- |
|
.. [1] R. A. Horn & C. R. Johnson, *Matrix Analysis*. Cambridge, UK: |
|
Cambridge University Press, 1999, pp. 146-7. |
|
|
|
Examples |
|
-------- |
|
>>> coeff = [3.2, 2, 1] |
|
>>> np.roots(coeff) |
|
array([-0.3125+0.46351241j, -0.3125-0.46351241j]) |
|
|
|
""" |
|
|
|
p = atleast_1d(p) |
|
if len(p.shape) != 1: |
|
raise ValueError("Input must be a rank-1 array.") |
|
|
|
|
|
non_zero = NX.nonzero(NX.ravel(p))[0] |
|
|
|
|
|
if len(non_zero) == 0: |
|
return NX.array([]) |
|
|
|
|
|
trailing_zeros = len(p) - non_zero[-1] - 1 |
|
|
|
|
|
p = p[int(non_zero[0]):int(non_zero[-1])+1] |
|
|
|
|
|
if not issubclass(p.dtype.type, (NX.floating, NX.complexfloating)): |
|
p = p.astype(float) |
|
|
|
N = len(p) |
|
if N > 1: |
|
|
|
A = diag(NX.ones((N-2,), p.dtype), -1) |
|
A[0,:] = -p[1:] / p[0] |
|
roots = eigvals(A) |
|
else: |
|
roots = NX.array([]) |
|
|
|
|
|
roots = hstack((roots, NX.zeros(trailing_zeros, roots.dtype))) |
|
return roots |
|
|
|
def polyint(p, m=1, k=None): |
|
""" |
|
Return an antiderivative (indefinite integral) of a polynomial. |
|
|
|
The returned order `m` antiderivative `P` of polynomial `p` satisfies |
|
:math:`\\frac{d^m}{dx^m}P(x) = p(x)` and is defined up to `m - 1` |
|
integration constants `k`. The constants determine the low-order |
|
polynomial part |
|
|
|
.. math:: \\frac{k_{m-1}}{0!} x^0 + \\ldots + \\frac{k_0}{(m-1)!}x^{m-1} |
|
|
|
of `P` so that :math:`P^{(j)}(0) = k_{m-j-1}`. |
|
|
|
Parameters |
|
---------- |
|
p : {array_like, poly1d} |
|
Polynomial to differentiate. |
|
A sequence is interpreted as polynomial coefficients, see `poly1d`. |
|
m : int, optional |
|
Order of the antiderivative. (Default: 1) |
|
k : {None, list of `m` scalars, scalar}, optional |
|
Integration constants. They are given in the order of integration: |
|
those corresponding to highest-order terms come first. |
|
|
|
If ``None`` (default), all constants are assumed to be zero. |
|
If `m = 1`, a single scalar can be given instead of a list. |
|
|
|
See Also |
|
-------- |
|
polyder : derivative of a polynomial |
|
poly1d.integ : equivalent method |
|
|
|
Examples |
|
-------- |
|
The defining property of the antiderivative: |
|
|
|
>>> p = np.poly1d([1,1,1]) |
|
>>> P = np.polyint(p) |
|
>>> P |
|
poly1d([ 0.33333333, 0.5 , 1. , 0. ]) |
|
>>> np.polyder(P) == p |
|
True |
|
|
|
The integration constants default to zero, but can be specified: |
|
|
|
>>> P = np.polyint(p, 3) |
|
>>> P(0) |
|
0.0 |
|
>>> np.polyder(P)(0) |
|
0.0 |
|
>>> np.polyder(P, 2)(0) |
|
0.0 |
|
>>> P = np.polyint(p, 3, k=[6,5,3]) |
|
>>> P |
|
poly1d([ 0.01666667, 0.04166667, 0.16666667, 3. , 5. , 3. ]) |
|
|
|
Note that 3 = 6 / 2!, and that the constants are given in the order of |
|
integrations. Constant of the highest-order polynomial term comes first: |
|
|
|
>>> np.polyder(P, 2)(0) |
|
6.0 |
|
>>> np.polyder(P, 1)(0) |
|
5.0 |
|
>>> P(0) |
|
3.0 |
|
|
|
""" |
|
m = int(m) |
|
if m < 0: |
|
raise ValueError("Order of integral must be positive (see polyder)") |
|
if k is None: |
|
k = NX.zeros(m, float) |
|
k = atleast_1d(k) |
|
if len(k) == 1 and m > 1: |
|
k = k[0]*NX.ones(m, float) |
|
if len(k) < m: |
|
raise ValueError( |
|
"k must be a scalar or a rank-1 array of length 1 or >m.") |
|
|
|
truepoly = isinstance(p, poly1d) |
|
p = NX.asarray(p) |
|
if m == 0: |
|
if truepoly: |
|
return poly1d(p) |
|
return p |
|
else: |
|
|
|
y = NX.concatenate((p.__truediv__(NX.arange(len(p), 0, -1)), [k[0]])) |
|
val = polyint(y, m - 1, k=k[1:]) |
|
if truepoly: |
|
return poly1d(val) |
|
return val |
|
|
|
def polyder(p, m=1): |
|
""" |
|
Return the derivative of the specified order of a polynomial. |
|
|
|
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 |
|
|
|
def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False): |
|
""" |
|
Least squares polynomial fit. |
|
|
|
Fit a polynomial ``p(x) = p[0] * x**deg + ... + p[deg]`` of degree `deg` |
|
to points `(x, y)`. Returns a vector of coefficients `p` that minimises |
|
the squared error. |
|
|
|
Parameters |
|
---------- |
|
x : array_like, shape (M,) |
|
x-coordinates of the M sample points ``(x[i], y[i])``. |
|
y : array_like, shape (M,) or (M, K) |
|
y-coordinates of the sample points. Several data sets of sample |
|
points sharing the same x-coordinates can be fitted at once by |
|
passing in a 2D-array that contains one dataset per column. |
|
deg : int |
|
Degree of the fitting polynomial |
|
rcond : float, optional |
|
Relative condition number of the fit. Singular values smaller than |
|
this relative to the largest singular value will be ignored. The |
|
default value is len(x)*eps, where eps is the relative precision of |
|
the float type, about 2e-16 in most cases. |
|
full : bool, optional |
|
Switch determining nature of return value. When it is False (the |
|
default) just the coefficients are returned, when True diagnostic |
|
information from the singular value decomposition is also returned. |
|
w : array_like, shape (M,), optional |
|
weights to apply to the y-coordinates of the sample points. |
|
cov : bool, optional |
|
Return the estimate and the covariance matrix of the estimate |
|
If full is True, then cov is not returned. |
|
|
|
Returns |
|
------- |
|
p : ndarray, shape (M,) or (M, K) |
|
Polynomial coefficients, highest power first. If `y` was 2-D, the |
|
coefficients for `k`-th data set are in ``p[:,k]``. |
|
|
|
residuals, rank, singular_values, rcond : |
|
Present only if `full` = True. Residuals of the least-squares fit, |
|
the effective rank of the scaled Vandermonde coefficient matrix, |
|
its singular values, and the specified value of `rcond`. For more |
|
details, see `linalg.lstsq`. |
|
|
|
V : ndarray, shape (M,M) or (M,M,K) |
|
Present only if `full` = False and `cov`=True. The covariance |
|
matrix of the polynomial coefficient estimates. The diagonal of |
|
this matrix are the variance estimates for each coefficient. If y |
|
is a 2-D array, then the covariance matrix for the `k`-th data set |
|
are in ``V[:,:,k]`` |
|
|
|
|
|
Warns |
|
----- |
|
RankWarning |
|
The rank of the coefficient matrix in the least-squares fit is |
|
deficient. The warning is only raised if `full` = False. |
|
|
|
The warnings can be turned off by |
|
|
|
>>> import warnings |
|
>>> warnings.simplefilter('ignore', np.RankWarning) |
|
|
|
See Also |
|
-------- |
|
polyval : Computes polynomial values. |
|
linalg.lstsq : Computes a least-squares fit. |
|
scipy.interpolate.UnivariateSpline : Computes spline fits. |
|
|
|
Notes |
|
----- |
|
The solution minimizes the squared error |
|
|
|
.. math :: |
|
E = \\sum_{j=0}^k |p(x_j) - y_j|^2 |
|
|
|
in the equations:: |
|
|
|
x[0]**n * p[0] + ... + x[0] * p[n-1] + p[n] = y[0] |
|
x[1]**n * p[0] + ... + x[1] * p[n-1] + p[n] = y[1] |
|
... |
|
x[k]**n * p[0] + ... + x[k] * p[n-1] + p[n] = y[k] |
|
|
|
The coefficient matrix of the coefficients `p` is a Vandermonde matrix. |
|
|
|
`polyfit` issues a `RankWarning` when the least-squares fit is badly |
|
conditioned. This implies that the best fit is not well-defined due |
|
to numerical error. The results may be improved by lowering the polynomial |
|
degree or by replacing `x` by `x` - `x`.mean(). The `rcond` parameter |
|
can also be set to a value smaller than its default, but the resulting |
|
fit may be spurious: including contributions from the small singular |
|
values can add numerical noise to the result. |
|
|
|
Note that fitting polynomial coefficients is inherently badly conditioned |
|
when the degree of the polynomial is large or the interval of sample points |
|
is badly centered. The quality of the fit should always be checked in these |
|
cases. When polynomial fits are not satisfactory, splines may be a good |
|
alternative. |
|
|
|
References |
|
---------- |
|
.. [1] Wikipedia, "Curve fitting", |
|
http://en.wikipedia.org/wiki/Curve_fitting |
|
.. [2] Wikipedia, "Polynomial interpolation", |
|
http://en.wikipedia.org/wiki/Polynomial_interpolation |
|
|
|
Examples |
|
-------- |
|
>>> x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) |
|
>>> y = np.array([0.0, 0.8, 0.9, 0.1, -0.8, -1.0]) |
|
>>> z = np.polyfit(x, y, 3) |
|
>>> z |
|
array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) |
|
|
|
It is convenient to use `poly1d` objects for dealing with polynomials: |
|
|
|
>>> p = np.poly1d(z) |
|
>>> p(0.5) |
|
0.6143849206349179 |
|
>>> p(3.5) |
|
-0.34732142857143039 |
|
>>> p(10) |
|
22.579365079365115 |
|
|
|
High-order polynomials may oscillate wildly: |
|
|
|
>>> p30 = np.poly1d(np.polyfit(x, y, 30)) |
|
/... RankWarning: Polyfit may be poorly conditioned... |
|
>>> p30(4) |
|
-0.80000000000000204 |
|
>>> p30(5) |
|
-0.99999999999999445 |
|
>>> p30(4.5) |
|
-0.10547061179440398 |
|
|
|
Illustration: |
|
|
|
>>> import matplotlib.pyplot as plt |
|
>>> xp = np.linspace(-2, 6, 100) |
|
>>> _ = plt.plot(x, y, '.', xp, p(xp), '-', xp, p30(xp), '--') |
|
>>> plt.ylim(-2,2) |
|
(-2, 2) |
|
>>> plt.show() |
|
|
|
""" |
|
order = int(deg) + 1 |
|
x = NX.asarray(x) + 0.0 |
|
y = NX.asarray(y) + 0.0 |
|
|
|
|
|
if deg < 0: |
|
raise ValueError("expected deg >= 0") |
|
if x.ndim != 1: |
|
raise TypeError("expected 1D vector for x") |
|
if x.size == 0: |
|
raise TypeError("expected non-empty vector for x") |
|
if y.ndim < 1 or y.ndim > 2: |
|
raise TypeError("expected 1D or 2D array for y") |
|
if x.shape[0] != y.shape[0]: |
|
raise TypeError("expected x and y to have same length") |
|
|
|
|
|
if rcond is None: |
|
rcond = len(x)*finfo(x.dtype).eps |
|
|
|
|
|
lhs = vander(x, order) |
|
rhs = y |
|
|
|
|
|
if w is not None: |
|
w = NX.asarray(w) + 0.0 |
|
if w.ndim != 1: |
|
raise TypeError("expected a 1-d array for weights") |
|
if w.shape[0] != y.shape[0]: |
|
raise TypeError("expected w and y to have the same length") |
|
lhs *= w[:, NX.newaxis] |
|
if rhs.ndim == 2: |
|
rhs *= w[:, NX.newaxis] |
|
else: |
|
rhs *= w |
|
|
|
|
|
scale = NX.sqrt((lhs*lhs).sum(axis=0)) |
|
lhs /= scale |
|
c, resids, rank, s = lstsq(lhs, rhs, rcond) |
|
c = (c.T/scale).T |
|
|
|
|
|
if rank != order and not full: |
|
msg = "Polyfit may be poorly conditioned" |
|
warnings.warn(msg, RankWarning) |
|
|
|
if full: |
|
return c, resids, rank, s, rcond |
|
elif cov: |
|
Vbase = inv(dot(lhs.T, lhs)) |
|
Vbase /= NX.outer(scale, scale) |
|
|
|
|
|
|
|
|
|
fac = resids / (len(x) - order - 2.0) |
|
if y.ndim == 1: |
|
return c, Vbase * fac |
|
else: |
|
return c, Vbase[:,:, NX.newaxis] * fac |
|
else: |
|
return c |
|
|
|
|
|
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. |
|
|
|
Parameters |
|
---------- |
|
p : array_like or poly1d object |
|
1D array of polynomial coefficients (including coefficients equal |
|
to zero) from highest degree to the constant term, or an |
|
instance of poly1d. |
|
x : array_like or poly1d object |
|
A number, a 1D array of numbers, or an instance of poly1d, "at" |
|
which to evaluate `p`. |
|
|
|
Returns |
|
------- |
|
values : ndarray or poly1d |
|
If `x` is a poly1d instance, the result is the composition of the two |
|
polynomials, i.e., `x` is "substituted" in `p` and the simplified |
|
result is returned. In addition, the type of `x` - array_like or |
|
poly1d - governs the type of the output: `x` array_like => `values` |
|
array_like, `x` a poly1d object => `values` is also. |
|
|
|
See Also |
|
-------- |
|
poly1d: A polynomial class. |
|
|
|
Notes |
|
----- |
|
Horner's scheme [1]_ is used to evaluate the polynomial. Even so, |
|
for polynomials of high degree the values may be inaccurate due to |
|
rounding errors. Use carefully. |
|
|
|
References |
|
---------- |
|
.. [1] I. N. Bronshtein, K. A. Semendyayev, and K. A. Hirsch (Eng. |
|
trans. Ed.), *Handbook of Mathematics*, New York, Van Nostrand |
|
Reinhold Co., 1985, pg. 720. |
|
|
|
Examples |
|
-------- |
|
>>> np.polyval([3,0,1], 5) # 3 * 5**2 + 0 * 5**1 + 1 |
|
76 |
|
>>> np.polyval([3,0,1], np.poly1d(5)) |
|
poly1d([ 76.]) |
|
>>> np.polyval(np.poly1d([3,0,1]), 5) |
|
76 |
|
>>> np.polyval(np.poly1d([3,0,1]), np.poly1d(5)) |
|
poly1d([ 76.]) |
|
|
|
""" |
|
p = NX.asarray(p) |
|
if isinstance(x, poly1d): |
|
y = 0 |
|
else: |
|
x = NX.asarray(x) |
|
y = NX.zeros_like(x) |
|
for i in range(len(p)): |
|
y = x * y + p[i] |
|
return y |
|
|
|
def polyadd(a1, a2): |
|
""" |
|
Find the sum of two polynomials. |
|
|
|
Returns the polynomial resulting from the sum of two input polynomials. |
|
Each input must be either a poly1d object or a 1D sequence of polynomial |
|
coefficients, from highest to lowest degree. |
|
|
|
Parameters |
|
---------- |
|
a1, a2 : array_like or poly1d object |
|
Input polynomials. |
|
|
|
Returns |
|
------- |
|
out : ndarray or poly1d object |
|
The sum of the inputs. If either input is a poly1d object, then the |
|
output is also a poly1d object. Otherwise, it is a 1D array of |
|
polynomial coefficients from highest to lowest degree. |
|
|
|
See Also |
|
-------- |
|
poly1d : A one-dimensional polynomial class. |
|
poly, polyadd, polyder, polydiv, polyfit, polyint, polysub, polyval |
|
|
|
Examples |
|
-------- |
|
>>> np.polyadd([1, 2], [9, 5, 4]) |
|
array([9, 6, 6]) |
|
|
|
Using poly1d objects: |
|
|
|
>>> p1 = np.poly1d([1, 2]) |
|
>>> p2 = np.poly1d([9, 5, 4]) |
|
>>> print p1 |
|
1 x + 2 |
|
>>> print p2 |
|
2 |
|
9 x + 5 x + 4 |
|
>>> print np.polyadd(p1, p2) |
|
2 |
|
9 x + 6 x + 6 |
|
|
|
""" |
|
truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d)) |
|
a1 = atleast_1d(a1) |
|
a2 = atleast_1d(a2) |
|
diff = len(a2) - len(a1) |
|
if diff == 0: |
|
val = a1 + a2 |
|
elif diff > 0: |
|
zr = NX.zeros(diff, a1.dtype) |
|
val = NX.concatenate((zr, a1)) + a2 |
|
else: |
|
zr = NX.zeros(abs(diff), a2.dtype) |
|
val = a1 + NX.concatenate((zr, a2)) |
|
if truepoly: |
|
val = poly1d(val) |
|
return val |
|
|
|
def polysub(a1, a2): |
|
""" |
|
Difference (subtraction) of two polynomials. |
|
|
|
Given two polynomials `a1` and `a2`, returns ``a1 - a2``. |
|
`a1` and `a2` can be either array_like sequences of the polynomials' |
|
coefficients (including coefficients equal to zero), or `poly1d` objects. |
|
|
|
Parameters |
|
---------- |
|
a1, a2 : array_like or poly1d |
|
Minuend and subtrahend polynomials, respectively. |
|
|
|
Returns |
|
------- |
|
out : ndarray or poly1d |
|
Array or `poly1d` object of the difference polynomial's coefficients. |
|
|
|
See Also |
|
-------- |
|
polyval, polydiv, polymul, polyadd |
|
|
|
Examples |
|
-------- |
|
.. math:: (2 x^2 + 10 x - 2) - (3 x^2 + 10 x -4) = (-x^2 + 2) |
|
|
|
>>> np.polysub([2, 10, -2], [3, 10, -4]) |
|
array([-1, 0, 2]) |
|
|
|
""" |
|
truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d)) |
|
a1 = atleast_1d(a1) |
|
a2 = atleast_1d(a2) |
|
diff = len(a2) - len(a1) |
|
if diff == 0: |
|
val = a1 - a2 |
|
elif diff > 0: |
|
zr = NX.zeros(diff, a1.dtype) |
|
val = NX.concatenate((zr, a1)) - a2 |
|
else: |
|
zr = NX.zeros(abs(diff), a2.dtype) |
|
val = a1 - NX.concatenate((zr, a2)) |
|
if truepoly: |
|
val = poly1d(val) |
|
return val |
|
|
|
|
|
def polymul(a1, a2): |
|
""" |
|
Find the product of two polynomials. |
|
|
|
Finds the polynomial resulting from the multiplication of the two input |
|
polynomials. Each input must be either a poly1d object or a 1D sequence |
|
of polynomial coefficients, from highest to lowest degree. |
|
|
|
Parameters |
|
---------- |
|
a1, a2 : array_like or poly1d object |
|
Input polynomials. |
|
|
|
Returns |
|
------- |
|
out : ndarray or poly1d object |
|
The polynomial resulting from the multiplication of the inputs. If |
|
either inputs is a poly1d object, then the output is also a poly1d |
|
object. Otherwise, it is a 1D array of polynomial coefficients from |
|
highest to lowest degree. |
|
|
|
See Also |
|
-------- |
|
poly1d : A one-dimensional polynomial class. |
|
poly, polyadd, polyder, polydiv, polyfit, polyint, polysub, |
|
polyval |
|
convolve : Array convolution. Same output as polymul, but has parameter |
|
for overlap mode. |
|
|
|
Examples |
|
-------- |
|
>>> np.polymul([1, 2, 3], [9, 5, 1]) |
|
array([ 9, 23, 38, 17, 3]) |
|
|
|
Using poly1d objects: |
|
|
|
>>> p1 = np.poly1d([1, 2, 3]) |
|
>>> p2 = np.poly1d([9, 5, 1]) |
|
>>> print p1 |
|
2 |
|
1 x + 2 x + 3 |
|
>>> print p2 |
|
2 |
|
9 x + 5 x + 1 |
|
>>> print np.polymul(p1, p2) |
|
4 3 2 |
|
9 x + 23 x + 38 x + 17 x + 3 |
|
|
|
""" |
|
truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d)) |
|
a1, a2 = poly1d(a1), poly1d(a2) |
|
val = NX.convolve(a1, a2) |
|
if truepoly: |
|
val = poly1d(val) |
|
return val |
|
|
|
def polydiv(u, v): |
|
""" |
|
Returns the quotient and remainder of polynomial division. |
|
|
|
The input arrays are the coefficients (including any coefficients |
|
equal to zero) of the "numerator" (dividend) and "denominator" |
|
(divisor) polynomials, respectively. |
|
|
|
Parameters |
|
---------- |
|
u : array_like or poly1d |
|
Dividend polynomial's coefficients. |
|
|
|
v : array_like or poly1d |
|
Divisor polynomial's coefficients. |
|
|
|
Returns |
|
------- |
|
q : ndarray |
|
Coefficients, including those equal to zero, of the quotient. |
|
r : ndarray |
|
Coefficients, including those equal to zero, of the remainder. |
|
|
|
See Also |
|
-------- |
|
poly, polyadd, polyder, polydiv, polyfit, polyint, polymul, polysub, |
|
polyval |
|
|
|
Notes |
|
----- |
|
Both `u` and `v` must be 0-d or 1-d (ndim = 0 or 1), but `u.ndim` need |
|
not equal `v.ndim`. In other words, all four possible combinations - |
|
``u.ndim = v.ndim = 0``, ``u.ndim = v.ndim = 1``, |
|
``u.ndim = 1, v.ndim = 0``, and ``u.ndim = 0, v.ndim = 1`` - work. |
|
|
|
Examples |
|
-------- |
|
.. math:: \\frac{3x^2 + 5x + 2}{2x + 1} = 1.5x + 1.75, remainder 0.25 |
|
|
|
>>> x = np.array([3.0, 5.0, 2.0]) |
|
>>> y = np.array([2.0, 1.0]) |
|
>>> np.polydiv(x, y) |
|
(array([ 1.5 , 1.75]), array([ 0.25])) |
|
|
|
""" |
|
truepoly = (isinstance(u, poly1d) or isinstance(u, poly1d)) |
|
u = atleast_1d(u) + 0.0 |
|
v = atleast_1d(v) + 0.0 |
|
|
|
w = u[0] + v[0] |
|
m = len(u) - 1 |
|
n = len(v) - 1 |
|
scale = 1. / v[0] |
|
q = NX.zeros((max(m - n + 1, 1),), w.dtype) |
|
r = u.copy() |
|
for k in range(0, m-n+1): |
|
d = scale * r[k] |
|
q[k] = d |
|
r[k:k+n+1] -= d*v |
|
while NX.allclose(r[0], 0, rtol=1e-14) and (r.shape[-1] > 1): |
|
r = r[1:] |
|
if truepoly: |
|
return poly1d(q), poly1d(r) |
|
return q, r |
|
|
|
_poly_mat = re.compile(r"[*][*]([0-9]*)") |
|
def _raise_power(astr, wrap=70): |
|
n = 0 |
|
line1 = '' |
|
line2 = '' |
|
output = ' ' |
|
while True: |
|
mat = _poly_mat.search(astr, n) |
|
if mat is None: |
|
break |
|
span = mat.span() |
|
power = mat.groups()[0] |
|
partstr = astr[n:span[0]] |
|
n = span[1] |
|
toadd2 = partstr + ' '*(len(power)-1) |
|
toadd1 = ' '*(len(partstr)-1) + power |
|
if ((len(line2) + len(toadd2) > wrap) or |
|
(len(line1) + len(toadd1) > wrap)): |
|
output += line1 + "\n" + line2 + "\n " |
|
line1 = toadd1 |
|
line2 = toadd2 |
|
else: |
|
line2 += partstr + ' '*(len(power)-1) |
|
line1 += ' '*(len(partstr)-1) + power |
|
output += line1 + "\n" + line2 |
|
return output + astr[n:] |
|
|
|
|
|
class poly1d(object): |
|
""" |
|
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]) |
|
|
|
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]) |
|
|
|
""" |
|
coeffs = None |
|
order = None |
|
variable = None |
|
__hash__ = None |
|
|
|
def __init__(self, c_or_r, r=0, variable=None): |
|
if isinstance(c_or_r, poly1d): |
|
for key in c_or_r.__dict__.keys(): |
|
self.__dict__[key] = c_or_r.__dict__[key] |
|
if variable is not None: |
|
self.__dict__['variable'] = variable |
|
return |
|
if r: |
|
c_or_r = poly(c_or_r) |
|
c_or_r = atleast_1d(c_or_r) |
|
if len(c_or_r.shape) > 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.]) |
|
self.__dict__['coeffs'] = c_or_r |
|
self.__dict__['order'] = len(c_or_r) - 1 |
|
if variable is None: |
|
variable = 'x' |
|
self.__dict__['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 |
|
|
|
|
|
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 in range(len(coeffs)): |
|
if not iscomplex(coeffs[k]): |
|
coefstr = fmt_float(real(coeffs[k])) |
|
elif real(coeffs[k]) == 0: |
|
coefstr = '%sj' % fmt_float(imag(coeffs[k])) |
|
else: |
|
coefstr = '(%s + %sj)' % (fmt_float(real(coeffs[k])), |
|
fmt_float(imag(coeffs[k]))) |
|
|
|
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 self.coeffs.shape != other.coeffs.shape: |
|
return False |
|
return (self.coeffs == other.coeffs).all() |
|
|
|
def __ne__(self, other): |
|
return not self.__eq__(other) |
|
|
|
def __setattr__(self, key, val): |
|
raise ValueError("Attributes cannot be changed this way.") |
|
|
|
def __getattr__(self, key): |
|
if key in ['r', 'roots']: |
|
return roots(self.coeffs) |
|
elif key in ['c', 'coef', 'coefficients']: |
|
return self.coeffs |
|
elif key in ['o']: |
|
return self.order |
|
else: |
|
try: |
|
return self.__dict__[key] |
|
except KeyError: |
|
raise AttributeError( |
|
"'%s' has no attribute '%s'" % (self.__class__, key)) |
|
|
|
def __getitem__(self, val): |
|
ind = self.order - val |
|
if val > self.order: |
|
return 0 |
|
if val < 0: |
|
return 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.__dict__['coeffs'] = NX.concatenate((zr, self.coeffs)) |
|
self.__dict__['order'] = key |
|
ind = 0 |
|
self.__dict__['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)) |
|
|
|
|
|
|
|
warnings.simplefilter('always', RankWarning) |
|
|