1"""
2Functions to operate on polynomials.
3
4"""
5__all__ = ['poly', 'roots', 'polyint', 'polyder', 'polyadd',
6 'polysub', 'polymul', 'polydiv', 'polyval', 'poly1d',
7 'polyfit', 'RankWarning']
8
9import functools
10import re
11import warnings
12import numpy.core.numeric as NX
13
14from numpy.core import (isscalar, abs, finfo, atleast_1d, hstack, dot, array,
15 ones)
16from numpy.core import overrides
17from numpy.core.overrides import set_module
18from numpy.lib.twodim_base import diag, vander
19from numpy.lib.function_base import trim_zeros
20from numpy.lib.type_check import iscomplex, real, imag, mintypecode
21from numpy.linalg import eigvals, lstsq, inv
22
23
24array_function_dispatch = functools.partial(
25 overrides.array_function_dispatch, module='numpy')
26
27
28@set_module('numpy')
29class RankWarning(UserWarning):
30 """
31 Issued by `polyfit` when the Vandermonde matrix is rank deficient.
32
33 For more information, a way to suppress the warning, and an example of
34 `RankWarning` being issued, see `polyfit`.
35
36 """
37 pass
38
39
40def _poly_dispatcher(seq_of_zeros):
41 return seq_of_zeros
42
43
44@array_function_dispatch(_poly_dispatcher)
45def poly(seq_of_zeros):
46 """
47 Find the coefficients of a polynomial with the given sequence of roots.
48
49 .. note::
50 This forms part of the old polynomial API. Since version 1.4, the
51 new polynomial API defined in `numpy.polynomial` is preferred.
52 A summary of the differences can be found in the
53 :doc:`transition guide </reference/routines.polynomials>`.
54
55 Returns the coefficients of the polynomial whose leading coefficient
56 is one for the given sequence of zeros (multiple roots must be included
57 in the sequence as many times as their multiplicity; see Examples).
58 A square matrix (or array, which will be treated as a matrix) can also
59 be given, in which case the coefficients of the characteristic polynomial
60 of the matrix are returned.
61
62 Parameters
63 ----------
64 seq_of_zeros : array_like, shape (N,) or (N, N)
65 A sequence of polynomial roots, or a square array or matrix object.
66
67 Returns
68 -------
69 c : ndarray
70 1D array of polynomial coefficients from highest to lowest degree:
71
72 ``c[0] * x**(N) + c[1] * x**(N-1) + ... + c[N-1] * x + c[N]``
73 where c[0] always equals 1.
74
75 Raises
76 ------
77 ValueError
78 If input is the wrong shape (the input must be a 1-D or square
79 2-D array).
80
81 See Also
82 --------
83 polyval : Compute polynomial values.
84 roots : Return the roots of a polynomial.
85 polyfit : Least squares polynomial fit.
86 poly1d : A one-dimensional polynomial class.
87
88 Notes
89 -----
90 Specifying the roots of a polynomial still leaves one degree of
91 freedom, typically represented by an undetermined leading
92 coefficient. [1]_ In the case of this function, that coefficient -
93 the first one in the returned array - is always taken as one. (If
94 for some reason you have one other point, the only automatic way
95 presently to leverage that information is to use ``polyfit``.)
96
97 The characteristic polynomial, :math:`p_a(t)`, of an `n`-by-`n`
98 matrix **A** is given by
99
100 :math:`p_a(t) = \\mathrm{det}(t\\, \\mathbf{I} - \\mathbf{A})`,
101
102 where **I** is the `n`-by-`n` identity matrix. [2]_
103
104 References
105 ----------
106 .. [1] M. Sullivan and M. Sullivan, III, "Algebra and Trignometry,
107 Enhanced With Graphing Utilities," Prentice-Hall, pg. 318, 1996.
108
109 .. [2] G. Strang, "Linear Algebra and Its Applications, 2nd Edition,"
110 Academic Press, pg. 182, 1980.
111
112 Examples
113 --------
114 Given a sequence of a polynomial's zeros:
115
116 >>> np.poly((0, 0, 0)) # Multiple root example
117 array([1., 0., 0., 0.])
118
119 The line above represents z**3 + 0*z**2 + 0*z + 0.
120
121 >>> np.poly((-1./2, 0, 1./2))
122 array([ 1. , 0. , -0.25, 0. ])
123
124 The line above represents z**3 - z/4
125
126 >>> np.poly((np.random.random(1)[0], 0, np.random.random(1)[0]))
127 array([ 1. , -0.77086955, 0.08618131, 0. ]) # random
128
129 Given a square array object:
130
131 >>> P = np.array([[0, 1./3], [-1./2, 0]])
132 >>> np.poly(P)
133 array([1. , 0. , 0.16666667])
134
135 Note how in all cases the leading coefficient is always 1.
136
137 """
138 seq_of_zeros = atleast_1d(seq_of_zeros)
139 sh = seq_of_zeros.shape
140
141 if len(sh) == 2 and sh[0] == sh[1] and sh[0] != 0:
142 seq_of_zeros = eigvals(seq_of_zeros)
143 elif len(sh) == 1:
144 dt = seq_of_zeros.dtype
145 # Let object arrays slip through, e.g. for arbitrary precision
146 if dt != object:
147 seq_of_zeros = seq_of_zeros.astype(mintypecode(dt.char))
148 else:
149 raise ValueError("input must be 1d or non-empty square 2d array.")
150
151 if len(seq_of_zeros) == 0:
152 return 1.0
153 dt = seq_of_zeros.dtype
154 a = ones((1,), dtype=dt)
155 for zero in seq_of_zeros:
156 a = NX.convolve(a, array([1, -zero], dtype=dt), mode='full')
157
158 if issubclass(a.dtype.type, NX.complexfloating):
159 # if complex roots are all complex conjugates, the roots are real.
160 roots = NX.asarray(seq_of_zeros, complex)
161 if NX.all(NX.sort(roots) == NX.sort(roots.conjugate())):
162 a = a.real.copy()
163
164 return a
165
166
167def _roots_dispatcher(p):
168 return p
169
170
171@array_function_dispatch(_roots_dispatcher)
172def roots(p):
173 """
174 Return the roots of a polynomial with coefficients given in p.
175
176 .. note::
177 This forms part of the old polynomial API. Since version 1.4, the
178 new polynomial API defined in `numpy.polynomial` is preferred.
179 A summary of the differences can be found in the
180 :doc:`transition guide </reference/routines.polynomials>`.
181
182 The values in the rank-1 array `p` are coefficients of a polynomial.
183 If the length of `p` is n+1 then the polynomial is described by::
184
185 p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n]
186
187 Parameters
188 ----------
189 p : array_like
190 Rank-1 array of polynomial coefficients.
191
192 Returns
193 -------
194 out : ndarray
195 An array containing the roots of the polynomial.
196
197 Raises
198 ------
199 ValueError
200 When `p` cannot be converted to a rank-1 array.
201
202 See also
203 --------
204 poly : Find the coefficients of a polynomial with a given sequence
205 of roots.
206 polyval : Compute polynomial values.
207 polyfit : Least squares polynomial fit.
208 poly1d : A one-dimensional polynomial class.
209
210 Notes
211 -----
212 The algorithm relies on computing the eigenvalues of the
213 companion matrix [1]_.
214
215 References
216 ----------
217 .. [1] R. A. Horn & C. R. Johnson, *Matrix Analysis*. Cambridge, UK:
218 Cambridge University Press, 1999, pp. 146-7.
219
220 Examples
221 --------
222 >>> coeff = [3.2, 2, 1]
223 >>> np.roots(coeff)
224 array([-0.3125+0.46351241j, -0.3125-0.46351241j])
225
226 """
227 # If input is scalar, this makes it an array
228 p = atleast_1d(p)
229 if p.ndim != 1:
230 raise ValueError("Input must be a rank-1 array.")
231
232 # find non-zero array entries
233 non_zero = NX.nonzero(NX.ravel(p))[0]
234
235 # Return an empty array if polynomial is all zeros
236 if len(non_zero) == 0:
237 return NX.array([])
238
239 # find the number of trailing zeros -- this is the number of roots at 0.
240 trailing_zeros = len(p) - non_zero[-1] - 1
241
242 # strip leading and trailing zeros
243 p = p[int(non_zero[0]):int(non_zero[-1])+1]
244
245 # casting: if incoming array isn't floating point, make it floating point.
246 if not issubclass(p.dtype.type, (NX.floating, NX.complexfloating)):
247 p = p.astype(float)
248
249 N = len(p)
250 if N > 1:
251 # build companion matrix and find its eigenvalues (the roots)
252 A = diag(NX.ones((N-2,), p.dtype), -1)
253 A[0,:] = -p[1:] / p[0]
254 roots = eigvals(A)
255 else:
256 roots = NX.array([])
257
258 # tack any zeros onto the back of the array
259 roots = hstack((roots, NX.zeros(trailing_zeros, roots.dtype)))
260 return roots
261
262
263def _polyint_dispatcher(p, m=None, k=None):
264 return (p,)
265
266
267@array_function_dispatch(_polyint_dispatcher)
268def polyint(p, m=1, k=None):
269 """
270 Return an antiderivative (indefinite integral) of a polynomial.
271
272 .. note::
273 This forms part of the old polynomial API. Since version 1.4, the
274 new polynomial API defined in `numpy.polynomial` is preferred.
275 A summary of the differences can be found in the
276 :doc:`transition guide </reference/routines.polynomials>`.
277
278 The returned order `m` antiderivative `P` of polynomial `p` satisfies
279 :math:`\\frac{d^m}{dx^m}P(x) = p(x)` and is defined up to `m - 1`
280 integration constants `k`. The constants determine the low-order
281 polynomial part
282
283 .. math:: \\frac{k_{m-1}}{0!} x^0 + \\ldots + \\frac{k_0}{(m-1)!}x^{m-1}
284
285 of `P` so that :math:`P^{(j)}(0) = k_{m-j-1}`.
286
287 Parameters
288 ----------
289 p : array_like or poly1d
290 Polynomial to integrate.
291 A sequence is interpreted as polynomial coefficients, see `poly1d`.
292 m : int, optional
293 Order of the antiderivative. (Default: 1)
294 k : list of `m` scalars or scalar, optional
295 Integration constants. They are given in the order of integration:
296 those corresponding to highest-order terms come first.
297
298 If ``None`` (default), all constants are assumed to be zero.
299 If `m = 1`, a single scalar can be given instead of a list.
300
301 See Also
302 --------
303 polyder : derivative of a polynomial
304 poly1d.integ : equivalent method
305
306 Examples
307 --------
308 The defining property of the antiderivative:
309
310 >>> p = np.poly1d([1,1,1])
311 >>> P = np.polyint(p)
312 >>> P
313 poly1d([ 0.33333333, 0.5 , 1. , 0. ]) # may vary
314 >>> np.polyder(P) == p
315 True
316
317 The integration constants default to zero, but can be specified:
318
319 >>> P = np.polyint(p, 3)
320 >>> P(0)
321 0.0
322 >>> np.polyder(P)(0)
323 0.0
324 >>> np.polyder(P, 2)(0)
325 0.0
326 >>> P = np.polyint(p, 3, k=[6,5,3])
327 >>> P
328 poly1d([ 0.01666667, 0.04166667, 0.16666667, 3. , 5. , 3. ]) # may vary
329
330 Note that 3 = 6 / 2!, and that the constants are given in the order of
331 integrations. Constant of the highest-order polynomial term comes first:
332
333 >>> np.polyder(P, 2)(0)
334 6.0
335 >>> np.polyder(P, 1)(0)
336 5.0
337 >>> P(0)
338 3.0
339
340 """
341 m = int(m)
342 if m < 0:
343 raise ValueError("Order of integral must be positive (see polyder)")
344 if k is None:
345 k = NX.zeros(m, float)
346 k = atleast_1d(k)
347 if len(k) == 1 and m > 1:
348 k = k[0]*NX.ones(m, float)
349 if len(k) < m:
350 raise ValueError(
351 "k must be a scalar or a rank-1 array of length 1 or >m.")
352
353 truepoly = isinstance(p, poly1d)
354 p = NX.asarray(p)
355 if m == 0:
356 if truepoly:
357 return poly1d(p)
358 return p
359 else:
360 # Note: this must work also with object and integer arrays
361 y = NX.concatenate((p.__truediv__(NX.arange(len(p), 0, -1)), [k[0]]))
362 val = polyint(y, m - 1, k=k[1:])
363 if truepoly:
364 return poly1d(val)
365 return val
366
367
368def _polyder_dispatcher(p, m=None):
369 return (p,)
370
371
372@array_function_dispatch(_polyder_dispatcher)
373def polyder(p, m=1):
374 """
375 Return the derivative of the specified order of a polynomial.
376
377 .. note::
378 This forms part of the old polynomial API. Since version 1.4, the
379 new polynomial API defined in `numpy.polynomial` is preferred.
380 A summary of the differences can be found in the
381 :doc:`transition guide </reference/routines.polynomials>`.
382
383 Parameters
384 ----------
385 p : poly1d or sequence
386 Polynomial to differentiate.
387 A sequence is interpreted as polynomial coefficients, see `poly1d`.
388 m : int, optional
389 Order of differentiation (default: 1)
390
391 Returns
392 -------
393 der : poly1d
394 A new polynomial representing the derivative.
395
396 See Also
397 --------
398 polyint : Anti-derivative of a polynomial.
399 poly1d : Class for one-dimensional polynomials.
400
401 Examples
402 --------
403 The derivative of the polynomial :math:`x^3 + x^2 + x^1 + 1` is:
404
405 >>> p = np.poly1d([1,1,1,1])
406 >>> p2 = np.polyder(p)
407 >>> p2
408 poly1d([3, 2, 1])
409
410 which evaluates to:
411
412 >>> p2(2.)
413 17.0
414
415 We can verify this, approximating the derivative with
416 ``(f(x + h) - f(x))/h``:
417
418 >>> (p(2. + 0.001) - p(2.)) / 0.001
419 17.007000999997857
420
421 The fourth-order derivative of a 3rd-order polynomial is zero:
422
423 >>> np.polyder(p, 2)
424 poly1d([6, 2])
425 >>> np.polyder(p, 3)
426 poly1d([6])
427 >>> np.polyder(p, 4)
428 poly1d([0])
429
430 """
431 m = int(m)
432 if m < 0:
433 raise ValueError("Order of derivative must be positive (see polyint)")
434
435 truepoly = isinstance(p, poly1d)
436 p = NX.asarray(p)
437 n = len(p) - 1
438 y = p[:-1] * NX.arange(n, 0, -1)
439 if m == 0:
440 val = p
441 else:
442 val = polyder(y, m - 1)
443 if truepoly:
444 val = poly1d(val)
445 return val
446
447
448def _polyfit_dispatcher(x, y, deg, rcond=None, full=None, w=None, cov=None):
449 return (x, y, w)
450
451
452@array_function_dispatch(_polyfit_dispatcher)
453def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False):
454 """
455 Least squares polynomial fit.
456
457 .. note::
458 This forms part of the old polynomial API. Since version 1.4, the
459 new polynomial API defined in `numpy.polynomial` is preferred.
460 A summary of the differences can be found in the
461 :doc:`transition guide </reference/routines.polynomials>`.
462
463 Fit a polynomial ``p(x) = p[0] * x**deg + ... + p[deg]`` of degree `deg`
464 to points `(x, y)`. Returns a vector of coefficients `p` that minimises
465 the squared error in the order `deg`, `deg-1`, ... `0`.
466
467 The `Polynomial.fit <numpy.polynomial.polynomial.Polynomial.fit>` class
468 method is recommended for new code as it is more stable numerically. See
469 the documentation of the method for more information.
470
471 Parameters
472 ----------
473 x : array_like, shape (M,)
474 x-coordinates of the M sample points ``(x[i], y[i])``.
475 y : array_like, shape (M,) or (M, K)
476 y-coordinates of the sample points. Several data sets of sample
477 points sharing the same x-coordinates can be fitted at once by
478 passing in a 2D-array that contains one dataset per column.
479 deg : int
480 Degree of the fitting polynomial
481 rcond : float, optional
482 Relative condition number of the fit. Singular values smaller than
483 this relative to the largest singular value will be ignored. The
484 default value is len(x)*eps, where eps is the relative precision of
485 the float type, about 2e-16 in most cases.
486 full : bool, optional
487 Switch determining nature of return value. When it is False (the
488 default) just the coefficients are returned, when True diagnostic
489 information from the singular value decomposition is also returned.
490 w : array_like, shape (M,), optional
491 Weights. If not None, the weight ``w[i]`` applies to the unsquared
492 residual ``y[i] - y_hat[i]`` at ``x[i]``. Ideally the weights are
493 chosen so that the errors of the products ``w[i]*y[i]`` all have the
494 same variance. When using inverse-variance weighting, use
495 ``w[i] = 1/sigma(y[i])``. The default value is None.
496 cov : bool or str, optional
497 If given and not `False`, return not just the estimate but also its
498 covariance matrix. By default, the covariance are scaled by
499 chi2/dof, where dof = M - (deg + 1), i.e., the weights are presumed
500 to be unreliable except in a relative sense and everything is scaled
501 such that the reduced chi2 is unity. This scaling is omitted if
502 ``cov='unscaled'``, as is relevant for the case that the weights are
503 w = 1/sigma, with sigma known to be a reliable estimate of the
504 uncertainty.
505
506 Returns
507 -------
508 p : ndarray, shape (deg + 1,) or (deg + 1, K)
509 Polynomial coefficients, highest power first. If `y` was 2-D, the
510 coefficients for `k`-th data set are in ``p[:,k]``.
511
512 residuals, rank, singular_values, rcond
513 These values are only returned if ``full == True``
514
515 - residuals -- sum of squared residuals of the least squares fit
516 - rank -- the effective rank of the scaled Vandermonde
517 coefficient matrix
518 - singular_values -- singular values of the scaled Vandermonde
519 coefficient matrix
520 - rcond -- value of `rcond`.
521
522 For more details, see `numpy.linalg.lstsq`.
523
524 V : ndarray, shape (M,M) or (M,M,K)
525 Present only if ``full == False`` and ``cov == True``. The covariance
526 matrix of the polynomial coefficient estimates. The diagonal of
527 this matrix are the variance estimates for each coefficient. If y
528 is a 2-D array, then the covariance matrix for the `k`-th data set
529 are in ``V[:,:,k]``
530
531
532 Warns
533 -----
534 RankWarning
535 The rank of the coefficient matrix in the least-squares fit is
536 deficient. The warning is only raised if ``full == False``.
537
538 The warnings can be turned off by
539
540 >>> import warnings
541 >>> warnings.simplefilter('ignore', np.RankWarning)
542
543 See Also
544 --------
545 polyval : Compute polynomial values.
546 linalg.lstsq : Computes a least-squares fit.
547 scipy.interpolate.UnivariateSpline : Computes spline fits.
548
549 Notes
550 -----
551 The solution minimizes the squared error
552
553 .. math::
554 E = \\sum_{j=0}^k |p(x_j) - y_j|^2
555
556 in the equations::
557
558 x[0]**n * p[0] + ... + x[0] * p[n-1] + p[n] = y[0]
559 x[1]**n * p[0] + ... + x[1] * p[n-1] + p[n] = y[1]
560 ...
561 x[k]**n * p[0] + ... + x[k] * p[n-1] + p[n] = y[k]
562
563 The coefficient matrix of the coefficients `p` is a Vandermonde matrix.
564
565 `polyfit` issues a `RankWarning` when the least-squares fit is badly
566 conditioned. This implies that the best fit is not well-defined due
567 to numerical error. The results may be improved by lowering the polynomial
568 degree or by replacing `x` by `x` - `x`.mean(). The `rcond` parameter
569 can also be set to a value smaller than its default, but the resulting
570 fit may be spurious: including contributions from the small singular
571 values can add numerical noise to the result.
572
573 Note that fitting polynomial coefficients is inherently badly conditioned
574 when the degree of the polynomial is large or the interval of sample points
575 is badly centered. The quality of the fit should always be checked in these
576 cases. When polynomial fits are not satisfactory, splines may be a good
577 alternative.
578
579 References
580 ----------
581 .. [1] Wikipedia, "Curve fitting",
582 https://en.wikipedia.org/wiki/Curve_fitting
583 .. [2] Wikipedia, "Polynomial interpolation",
584 https://en.wikipedia.org/wiki/Polynomial_interpolation
585
586 Examples
587 --------
588 >>> import warnings
589 >>> x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
590 >>> y = np.array([0.0, 0.8, 0.9, 0.1, -0.8, -1.0])
591 >>> z = np.polyfit(x, y, 3)
592 >>> z
593 array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) # may vary
594
595 It is convenient to use `poly1d` objects for dealing with polynomials:
596
597 >>> p = np.poly1d(z)
598 >>> p(0.5)
599 0.6143849206349179 # may vary
600 >>> p(3.5)
601 -0.34732142857143039 # may vary
602 >>> p(10)
603 22.579365079365115 # may vary
604
605 High-order polynomials may oscillate wildly:
606
607 >>> with warnings.catch_warnings():
608 ... warnings.simplefilter('ignore', np.RankWarning)
609 ... p30 = np.poly1d(np.polyfit(x, y, 30))
610 ...
611 >>> p30(4)
612 -0.80000000000000204 # may vary
613 >>> p30(5)
614 -0.99999999999999445 # may vary
615 >>> p30(4.5)
616 -0.10547061179440398 # may vary
617
618 Illustration:
619
620 >>> import matplotlib.pyplot as plt
621 >>> xp = np.linspace(-2, 6, 100)
622 >>> _ = plt.plot(x, y, '.', xp, p(xp), '-', xp, p30(xp), '--')
623 >>> plt.ylim(-2,2)
624 (-2, 2)
625 >>> plt.show()
626
627 """
628 order = int(deg) + 1
629 x = NX.asarray(x) + 0.0
630 y = NX.asarray(y) + 0.0
631
632 # check arguments.
633 if deg < 0:
634 raise ValueError("expected deg >= 0")
635 if x.ndim != 1:
636 raise TypeError("expected 1D vector for x")
637 if x.size == 0:
638 raise TypeError("expected non-empty vector for x")
639 if y.ndim < 1 or y.ndim > 2:
640 raise TypeError("expected 1D or 2D array for y")
641 if x.shape[0] != y.shape[0]:
642 raise TypeError("expected x and y to have same length")
643
644 # set rcond
645 if rcond is None:
646 rcond = len(x)*finfo(x.dtype).eps
647
648 # set up least squares equation for powers of x
649 lhs = vander(x, order)
650 rhs = y
651
652 # apply weighting
653 if w is not None:
654 w = NX.asarray(w) + 0.0
655 if w.ndim != 1:
656 raise TypeError("expected a 1-d array for weights")
657 if w.shape[0] != y.shape[0]:
658 raise TypeError("expected w and y to have the same length")
659 lhs *= w[:, NX.newaxis]
660 if rhs.ndim == 2:
661 rhs *= w[:, NX.newaxis]
662 else:
663 rhs *= w
664
665 # scale lhs to improve condition number and solve
666 scale = NX.sqrt((lhs*lhs).sum(axis=0))
667 lhs /= scale
668 c, resids, rank, s = lstsq(lhs, rhs, rcond)
669 c = (c.T/scale).T # broadcast scale coefficients
670
671 # warn on rank reduction, which indicates an ill conditioned matrix
672 if rank != order and not full:
673 msg = "Polyfit may be poorly conditioned"
674 warnings.warn(msg, RankWarning, stacklevel=4)
675
676 if full:
677 return c, resids, rank, s, rcond
678 elif cov:
679 Vbase = inv(dot(lhs.T, lhs))
680 Vbase /= NX.outer(scale, scale)
681 if cov == "unscaled":
682 fac = 1
683 else:
684 if len(x) <= order:
685 raise ValueError("the number of data points must exceed order "
686 "to scale the covariance matrix")
687 # note, this used to be: fac = resids / (len(x) - order - 2.0)
688 # it was deciced that the "- 2" (originally justified by "Bayesian
689 # uncertainty analysis") is not what the user expects
690 # (see gh-11196 and gh-11197)
691 fac = resids / (len(x) - order)
692 if y.ndim == 1:
693 return c, Vbase * fac
694 else:
695 return c, Vbase[:,:, NX.newaxis] * fac
696 else:
697 return c
698
699
700def _polyval_dispatcher(p, x):
701 return (p, x)
702
703
704@array_function_dispatch(_polyval_dispatcher)
705def polyval(p, x):
706 """
707 Evaluate a polynomial at specific values.
708
709 .. note::
710 This forms part of the old polynomial API. Since version 1.4, the
711 new polynomial API defined in `numpy.polynomial` is preferred.
712 A summary of the differences can be found in the
713 :doc:`transition guide </reference/routines.polynomials>`.
714
715 If `p` is of length N, this function returns the value:
716
717 ``p[0]*x**(N-1) + p[1]*x**(N-2) + ... + p[N-2]*x + p[N-1]``
718
719 If `x` is a sequence, then ``p(x)`` is returned for each element of ``x``.
720 If `x` is another polynomial then the composite polynomial ``p(x(t))``
721 is returned.
722
723 Parameters
724 ----------
725 p : array_like or poly1d object
726 1D array of polynomial coefficients (including coefficients equal
727 to zero) from highest degree to the constant term, or an
728 instance of poly1d.
729 x : array_like or poly1d object
730 A number, an array of numbers, or an instance of poly1d, at
731 which to evaluate `p`.
732
733 Returns
734 -------
735 values : ndarray or poly1d
736 If `x` is a poly1d instance, the result is the composition of the two
737 polynomials, i.e., `x` is "substituted" in `p` and the simplified
738 result is returned. In addition, the type of `x` - array_like or
739 poly1d - governs the type of the output: `x` array_like => `values`
740 array_like, `x` a poly1d object => `values` is also.
741
742 See Also
743 --------
744 poly1d: A polynomial class.
745
746 Notes
747 -----
748 Horner's scheme [1]_ is used to evaluate the polynomial. Even so,
749 for polynomials of high degree the values may be inaccurate due to
750 rounding errors. Use carefully.
751
752 If `x` is a subtype of `ndarray` the return value will be of the same type.
753
754 References
755 ----------
756 .. [1] I. N. Bronshtein, K. A. Semendyayev, and K. A. Hirsch (Eng.
757 trans. Ed.), *Handbook of Mathematics*, New York, Van Nostrand
758 Reinhold Co., 1985, pg. 720.
759
760 Examples
761 --------
762 >>> np.polyval([3,0,1], 5) # 3 * 5**2 + 0 * 5**1 + 1
763 76
764 >>> np.polyval([3,0,1], np.poly1d(5))
765 poly1d([76])
766 >>> np.polyval(np.poly1d([3,0,1]), 5)
767 76
768 >>> np.polyval(np.poly1d([3,0,1]), np.poly1d(5))
769 poly1d([76])
770
771 """
772 p = NX.asarray(p)
773 if isinstance(x, poly1d):
774 y = 0
775 else:
776 x = NX.asanyarray(x)
777 y = NX.zeros_like(x)
778 for pv in p:
779 y = y * x + pv
780 return y
781
782
783def _binary_op_dispatcher(a1, a2):
784 return (a1, a2)
785
786
787@array_function_dispatch(_binary_op_dispatcher)
788def polyadd(a1, a2):
789 """
790 Find the sum of two polynomials.
791
792 .. note::
793 This forms part of the old polynomial API. Since version 1.4, the
794 new polynomial API defined in `numpy.polynomial` is preferred.
795 A summary of the differences can be found in the
796 :doc:`transition guide </reference/routines.polynomials>`.
797
798 Returns the polynomial resulting from the sum of two input polynomials.
799 Each input must be either a poly1d object or a 1D sequence of polynomial
800 coefficients, from highest to lowest degree.
801
802 Parameters
803 ----------
804 a1, a2 : array_like or poly1d object
805 Input polynomials.
806
807 Returns
808 -------
809 out : ndarray or poly1d object
810 The sum of the inputs. If either input is a poly1d object, then the
811 output is also a poly1d object. Otherwise, it is a 1D array of
812 polynomial coefficients from highest to lowest degree.
813
814 See Also
815 --------
816 poly1d : A one-dimensional polynomial class.
817 poly, polyadd, polyder, polydiv, polyfit, polyint, polysub, polyval
818
819 Examples
820 --------
821 >>> np.polyadd([1, 2], [9, 5, 4])
822 array([9, 6, 6])
823
824 Using poly1d objects:
825
826 >>> p1 = np.poly1d([1, 2])
827 >>> p2 = np.poly1d([9, 5, 4])
828 >>> print(p1)
829 1 x + 2
830 >>> print(p2)
831 2
832 9 x + 5 x + 4
833 >>> print(np.polyadd(p1, p2))
834 2
835 9 x + 6 x + 6
836
837 """
838 truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d))
839 a1 = atleast_1d(a1)
840 a2 = atleast_1d(a2)
841 diff = len(a2) - len(a1)
842 if diff == 0:
843 val = a1 + a2
844 elif diff > 0:
845 zr = NX.zeros(diff, a1.dtype)
846 val = NX.concatenate((zr, a1)) + a2
847 else:
848 zr = NX.zeros(abs(diff), a2.dtype)
849 val = a1 + NX.concatenate((zr, a2))
850 if truepoly:
851 val = poly1d(val)
852 return val
853
854
855@array_function_dispatch(_binary_op_dispatcher)
856def polysub(a1, a2):
857 """
858 Difference (subtraction) of two polynomials.
859
860 .. note::
861 This forms part of the old polynomial API. Since version 1.4, the
862 new polynomial API defined in `numpy.polynomial` is preferred.
863 A summary of the differences can be found in the
864 :doc:`transition guide </reference/routines.polynomials>`.
865
866 Given two polynomials `a1` and `a2`, returns ``a1 - a2``.
867 `a1` and `a2` can be either array_like sequences of the polynomials'
868 coefficients (including coefficients equal to zero), or `poly1d` objects.
869
870 Parameters
871 ----------
872 a1, a2 : array_like or poly1d
873 Minuend and subtrahend polynomials, respectively.
874
875 Returns
876 -------
877 out : ndarray or poly1d
878 Array or `poly1d` object of the difference polynomial's coefficients.
879
880 See Also
881 --------
882 polyval, polydiv, polymul, polyadd
883
884 Examples
885 --------
886 .. math:: (2 x^2 + 10 x - 2) - (3 x^2 + 10 x -4) = (-x^2 + 2)
887
888 >>> np.polysub([2, 10, -2], [3, 10, -4])
889 array([-1, 0, 2])
890
891 """
892 truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d))
893 a1 = atleast_1d(a1)
894 a2 = atleast_1d(a2)
895 diff = len(a2) - len(a1)
896 if diff == 0:
897 val = a1 - a2
898 elif diff > 0:
899 zr = NX.zeros(diff, a1.dtype)
900 val = NX.concatenate((zr, a1)) - a2
901 else:
902 zr = NX.zeros(abs(diff), a2.dtype)
903 val = a1 - NX.concatenate((zr, a2))
904 if truepoly:
905 val = poly1d(val)
906 return val
907
908
909@array_function_dispatch(_binary_op_dispatcher)
910def polymul(a1, a2):
911 """
912 Find the product of two polynomials.
913
914 .. note::
915 This forms part of the old polynomial API. Since version 1.4, the
916 new polynomial API defined in `numpy.polynomial` is preferred.
917 A summary of the differences can be found in the
918 :doc:`transition guide </reference/routines.polynomials>`.
919
920 Finds the polynomial resulting from the multiplication of the two input
921 polynomials. Each input must be either a poly1d object or a 1D sequence
922 of polynomial coefficients, from highest to lowest degree.
923
924 Parameters
925 ----------
926 a1, a2 : array_like or poly1d object
927 Input polynomials.
928
929 Returns
930 -------
931 out : ndarray or poly1d object
932 The polynomial resulting from the multiplication of the inputs. If
933 either inputs is a poly1d object, then the output is also a poly1d
934 object. Otherwise, it is a 1D array of polynomial coefficients from
935 highest to lowest degree.
936
937 See Also
938 --------
939 poly1d : A one-dimensional polynomial class.
940 poly, polyadd, polyder, polydiv, polyfit, polyint, polysub, polyval
941 convolve : Array convolution. Same output as polymul, but has parameter
942 for overlap mode.
943
944 Examples
945 --------
946 >>> np.polymul([1, 2, 3], [9, 5, 1])
947 array([ 9, 23, 38, 17, 3])
948
949 Using poly1d objects:
950
951 >>> p1 = np.poly1d([1, 2, 3])
952 >>> p2 = np.poly1d([9, 5, 1])
953 >>> print(p1)
954 2
955 1 x + 2 x + 3
956 >>> print(p2)
957 2
958 9 x + 5 x + 1
959 >>> print(np.polymul(p1, p2))
960 4 3 2
961 9 x + 23 x + 38 x + 17 x + 3
962
963 """
964 truepoly = (isinstance(a1, poly1d) or isinstance(a2, poly1d))
965 a1, a2 = poly1d(a1), poly1d(a2)
966 val = NX.convolve(a1, a2)
967 if truepoly:
968 val = poly1d(val)
969 return val
970
971
972def _polydiv_dispatcher(u, v):
973 return (u, v)
974
975
976@array_function_dispatch(_polydiv_dispatcher)
977def polydiv(u, v):
978 """
979 Returns the quotient and remainder of polynomial division.
980
981 .. note::
982 This forms part of the old polynomial API. Since version 1.4, the
983 new polynomial API defined in `numpy.polynomial` is preferred.
984 A summary of the differences can be found in the
985 :doc:`transition guide </reference/routines.polynomials>`.
986
987 The input arrays are the coefficients (including any coefficients
988 equal to zero) of the "numerator" (dividend) and "denominator"
989 (divisor) polynomials, respectively.
990
991 Parameters
992 ----------
993 u : array_like or poly1d
994 Dividend polynomial's coefficients.
995
996 v : array_like or poly1d
997 Divisor polynomial's coefficients.
998
999 Returns
1000 -------
1001 q : ndarray
1002 Coefficients, including those equal to zero, of the quotient.
1003 r : ndarray
1004 Coefficients, including those equal to zero, of the remainder.
1005
1006 See Also
1007 --------
1008 poly, polyadd, polyder, polydiv, polyfit, polyint, polymul, polysub
1009 polyval
1010
1011 Notes
1012 -----
1013 Both `u` and `v` must be 0-d or 1-d (ndim = 0 or 1), but `u.ndim` need
1014 not equal `v.ndim`. In other words, all four possible combinations -
1015 ``u.ndim = v.ndim = 0``, ``u.ndim = v.ndim = 1``,
1016 ``u.ndim = 1, v.ndim = 0``, and ``u.ndim = 0, v.ndim = 1`` - work.
1017
1018 Examples
1019 --------
1020 .. math:: \\frac{3x^2 + 5x + 2}{2x + 1} = 1.5x + 1.75, remainder 0.25
1021
1022 >>> x = np.array([3.0, 5.0, 2.0])
1023 >>> y = np.array([2.0, 1.0])
1024 >>> np.polydiv(x, y)
1025 (array([1.5 , 1.75]), array([0.25]))
1026
1027 """
1028 truepoly = (isinstance(u, poly1d) or isinstance(v, poly1d))
1029 u = atleast_1d(u) + 0.0
1030 v = atleast_1d(v) + 0.0
1031 # w has the common type
1032 w = u[0] + v[0]
1033 m = len(u) - 1
1034 n = len(v) - 1
1035 scale = 1. / v[0]
1036 q = NX.zeros((max(m - n + 1, 1),), w.dtype)
1037 r = u.astype(w.dtype)
1038 for k in range(0, m-n+1):
1039 d = scale * r[k]
1040 q[k] = d
1041 r[k:k+n+1] -= d*v
1042 while NX.allclose(r[0], 0, rtol=1e-14) and (r.shape[-1] > 1):
1043 r = r[1:]
1044 if truepoly:
1045 return poly1d(q), poly1d(r)
1046 return q, r
1047
1048_poly_mat = re.compile(r"\*\*([0-9]*)")
1049def _raise_power(astr, wrap=70):
1050 n = 0
1051 line1 = ''
1052 line2 = ''
1053 output = ' '
1054 while True:
1055 mat = _poly_mat.search(astr, n)
1056 if mat is None:
1057 break
1058 span = mat.span()
1059 power = mat.groups()[0]
1060 partstr = astr[n:span[0]]
1061 n = span[1]
1062 toadd2 = partstr + ' '*(len(power)-1)
1063 toadd1 = ' '*(len(partstr)-1) + power
1064 if ((len(line2) + len(toadd2) > wrap) or
1065 (len(line1) + len(toadd1) > wrap)):
1066 output += line1 + "\n" + line2 + "\n "
1067 line1 = toadd1
1068 line2 = toadd2
1069 else:
1070 line2 += partstr + ' '*(len(power)-1)
1071 line1 += ' '*(len(partstr)-1) + power
1072 output += line1 + "\n" + line2
1073 return output + astr[n:]
1074
1075
1076@set_module('numpy')
1077class poly1d:
1078 """
1079 A one-dimensional polynomial class.
1080
1081 .. note::
1082 This forms part of the old polynomial API. Since version 1.4, the
1083 new polynomial API defined in `numpy.polynomial` is preferred.
1084 A summary of the differences can be found in the
1085 :doc:`transition guide </reference/routines.polynomials>`.
1086
1087 A convenience class, used to encapsulate "natural" operations on
1088 polynomials so that said operations may take on their customary
1089 form in code (see Examples).
1090
1091 Parameters
1092 ----------
1093 c_or_r : array_like
1094 The polynomial's coefficients, in decreasing powers, or if
1095 the value of the second parameter is True, the polynomial's
1096 roots (values where the polynomial evaluates to 0). For example,
1097 ``poly1d([1, 2, 3])`` returns an object that represents
1098 :math:`x^2 + 2x + 3`, whereas ``poly1d([1, 2, 3], True)`` returns
1099 one that represents :math:`(x-1)(x-2)(x-3) = x^3 - 6x^2 + 11x -6`.
1100 r : bool, optional
1101 If True, `c_or_r` specifies the polynomial's roots; the default
1102 is False.
1103 variable : str, optional
1104 Changes the variable used when printing `p` from `x` to `variable`
1105 (see Examples).
1106
1107 Examples
1108 --------
1109 Construct the polynomial :math:`x^2 + 2x + 3`:
1110
1111 >>> p = np.poly1d([1, 2, 3])
1112 >>> print(np.poly1d(p))
1113 2
1114 1 x + 2 x + 3
1115
1116 Evaluate the polynomial at :math:`x = 0.5`:
1117
1118 >>> p(0.5)
1119 4.25
1120
1121 Find the roots:
1122
1123 >>> p.r
1124 array([-1.+1.41421356j, -1.-1.41421356j])
1125 >>> p(p.r)
1126 array([ -4.44089210e-16+0.j, -4.44089210e-16+0.j]) # may vary
1127
1128 These numbers in the previous line represent (0, 0) to machine precision
1129
1130 Show the coefficients:
1131
1132 >>> p.c
1133 array([1, 2, 3])
1134
1135 Display the order (the leading zero-coefficients are removed):
1136
1137 >>> p.order
1138 2
1139
1140 Show the coefficient of the k-th power in the polynomial
1141 (which is equivalent to ``p.c[-(i+1)]``):
1142
1143 >>> p[1]
1144 2
1145
1146 Polynomials can be added, subtracted, multiplied, and divided
1147 (returns quotient and remainder):
1148
1149 >>> p * p
1150 poly1d([ 1, 4, 10, 12, 9])
1151
1152 >>> (p**3 + 4) / p
1153 (poly1d([ 1., 4., 10., 12., 9.]), poly1d([4.]))
1154
1155 ``asarray(p)`` gives the coefficient array, so polynomials can be
1156 used in all functions that accept arrays:
1157
1158 >>> p**2 # square of polynomial
1159 poly1d([ 1, 4, 10, 12, 9])
1160
1161 >>> np.square(p) # square of individual coefficients
1162 array([1, 4, 9])
1163
1164 The variable used in the string representation of `p` can be modified,
1165 using the `variable` parameter:
1166
1167 >>> p = np.poly1d([1,2,3], variable='z')
1168 >>> print(p)
1169 2
1170 1 z + 2 z + 3
1171
1172 Construct a polynomial from its roots:
1173
1174 >>> np.poly1d([1, 2], True)
1175 poly1d([ 1., -3., 2.])
1176
1177 This is the same polynomial as obtained by:
1178
1179 >>> np.poly1d([1, -1]) * np.poly1d([1, -2])
1180 poly1d([ 1, -3, 2])
1181
1182 """
1183 __hash__ = None
1184
1185 @property
1186 def coeffs(self):
1187 """ The polynomial coefficients """
1188 return self._coeffs
1189
1190 @coeffs.setter
1191 def coeffs(self, value):
1192 # allowing this makes p.coeffs *= 2 legal
1193 if value is not self._coeffs:
1194 raise AttributeError("Cannot set attribute")
1195
1196 @property
1197 def variable(self):
1198 """ The name of the polynomial variable """
1199 return self._variable
1200
1201 # calculated attributes
1202 @property
1203 def order(self):
1204 """ The order or degree of the polynomial """
1205 return len(self._coeffs) - 1
1206
1207 @property
1208 def roots(self):
1209 """ The roots of the polynomial, where self(x) == 0 """
1210 return roots(self._coeffs)
1211
1212 # our internal _coeffs property need to be backed by __dict__['coeffs'] for
1213 # scipy to work correctly.
1214 @property
1215 def _coeffs(self):
1216 return self.__dict__['coeffs']
1217 @_coeffs.setter
1218 def _coeffs(self, coeffs):
1219 self.__dict__['coeffs'] = coeffs
1220
1221 # alias attributes
1222 r = roots
1223 c = coef = coefficients = coeffs
1224 o = order
1225
1226 def __init__(self, c_or_r, r=False, variable=None):
1227 if isinstance(c_or_r, poly1d):
1228 self._variable = c_or_r._variable
1229 self._coeffs = c_or_r._coeffs
1230
1231 if set(c_or_r.__dict__) - set(self.__dict__):
1232 msg = ("In the future extra properties will not be copied "
1233 "across when constructing one poly1d from another")
1234 warnings.warn(msg, FutureWarning, stacklevel=2)
1235 self.__dict__.update(c_or_r.__dict__)
1236
1237 if variable is not None:
1238 self._variable = variable
1239 return
1240 if r:
1241 c_or_r = poly(c_or_r)
1242 c_or_r = atleast_1d(c_or_r)
1243 if c_or_r.ndim > 1:
1244 raise ValueError("Polynomial must be 1d only.")
1245 c_or_r = trim_zeros(c_or_r, trim='f')
1246 if len(c_or_r) == 0:
1247 c_or_r = NX.array([0], dtype=c_or_r.dtype)
1248 self._coeffs = c_or_r
1249 if variable is None:
1250 variable = 'x'
1251 self._variable = variable
1252
1253 def __array__(self, t=None):
1254 if t:
1255 return NX.asarray(self.coeffs, t)
1256 else:
1257 return NX.asarray(self.coeffs)
1258
1259 def __repr__(self):
1260 vals = repr(self.coeffs)
1261 vals = vals[6:-1]
1262 return "poly1d(%s)" % vals
1263
1264 def __len__(self):
1265 return self.order
1266
1267 def __str__(self):
1268 thestr = "0"
1269 var = self.variable
1270
1271 # Remove leading zeros
1272 coeffs = self.coeffs[NX.logical_or.accumulate(self.coeffs != 0)]
1273 N = len(coeffs)-1
1274
1275 def fmt_float(q):
1276 s = '%.4g' % q
1277 if s.endswith('.0000'):
1278 s = s[:-5]
1279 return s
1280
1281 for k, coeff in enumerate(coeffs):
1282 if not iscomplex(coeff):
1283 coefstr = fmt_float(real(coeff))
1284 elif real(coeff) == 0:
1285 coefstr = '%sj' % fmt_float(imag(coeff))
1286 else:
1287 coefstr = '(%s + %sj)' % (fmt_float(real(coeff)),
1288 fmt_float(imag(coeff)))
1289
1290 power = (N-k)
1291 if power == 0:
1292 if coefstr != '0':
1293 newstr = '%s' % (coefstr,)
1294 else:
1295 if k == 0:
1296 newstr = '0'
1297 else:
1298 newstr = ''
1299 elif power == 1:
1300 if coefstr == '0':
1301 newstr = ''
1302 elif coefstr == 'b':
1303 newstr = var
1304 else:
1305 newstr = '%s %s' % (coefstr, var)
1306 else:
1307 if coefstr == '0':
1308 newstr = ''
1309 elif coefstr == 'b':
1310 newstr = '%s**%d' % (var, power,)
1311 else:
1312 newstr = '%s %s**%d' % (coefstr, var, power)
1313
1314 if k > 0:
1315 if newstr != '':
1316 if newstr.startswith('-'):
1317 thestr = "%s - %s" % (thestr, newstr[1:])
1318 else:
1319 thestr = "%s + %s" % (thestr, newstr)
1320 else:
1321 thestr = newstr
1322 return _raise_power(thestr)
1323
1324 def __call__(self, val):
1325 return polyval(self.coeffs, val)
1326
1327 def __neg__(self):
1328 return poly1d(-self.coeffs)
1329
1330 def __pos__(self):
1331 return self
1332
1333 def __mul__(self, other):
1334 if isscalar(other):
1335 return poly1d(self.coeffs * other)
1336 else:
1337 other = poly1d(other)
1338 return poly1d(polymul(self.coeffs, other.coeffs))
1339
1340 def __rmul__(self, other):
1341 if isscalar(other):
1342 return poly1d(other * self.coeffs)
1343 else:
1344 other = poly1d(other)
1345 return poly1d(polymul(self.coeffs, other.coeffs))
1346
1347 def __add__(self, other):
1348 other = poly1d(other)
1349 return poly1d(polyadd(self.coeffs, other.coeffs))
1350
1351 def __radd__(self, other):
1352 other = poly1d(other)
1353 return poly1d(polyadd(self.coeffs, other.coeffs))
1354
1355 def __pow__(self, val):
1356 if not isscalar(val) or int(val) != val or val < 0:
1357 raise ValueError("Power to non-negative integers only.")
1358 res = [1]
1359 for _ in range(val):
1360 res = polymul(self.coeffs, res)
1361 return poly1d(res)
1362
1363 def __sub__(self, other):
1364 other = poly1d(other)
1365 return poly1d(polysub(self.coeffs, other.coeffs))
1366
1367 def __rsub__(self, other):
1368 other = poly1d(other)
1369 return poly1d(polysub(other.coeffs, self.coeffs))
1370
1371 def __div__(self, other):
1372 if isscalar(other):
1373 return poly1d(self.coeffs/other)
1374 else:
1375 other = poly1d(other)
1376 return polydiv(self, other)
1377
1378 __truediv__ = __div__
1379
1380 def __rdiv__(self, other):
1381 if isscalar(other):
1382 return poly1d(other/self.coeffs)
1383 else:
1384 other = poly1d(other)
1385 return polydiv(other, self)
1386
1387 __rtruediv__ = __rdiv__
1388
1389 def __eq__(self, other):
1390 if not isinstance(other, poly1d):
1391 return NotImplemented
1392 if self.coeffs.shape != other.coeffs.shape:
1393 return False
1394 return (self.coeffs == other.coeffs).all()
1395
1396 def __ne__(self, other):
1397 if not isinstance(other, poly1d):
1398 return NotImplemented
1399 return not self.__eq__(other)
1400
1401
1402 def __getitem__(self, val):
1403 ind = self.order - val
1404 if val > self.order:
1405 return self.coeffs.dtype.type(0)
1406 if val < 0:
1407 return self.coeffs.dtype.type(0)
1408 return self.coeffs[ind]
1409
1410 def __setitem__(self, key, val):
1411 ind = self.order - key
1412 if key < 0:
1413 raise ValueError("Does not support negative powers.")
1414 if key > self.order:
1415 zr = NX.zeros(key-self.order, self.coeffs.dtype)
1416 self._coeffs = NX.concatenate((zr, self.coeffs))
1417 ind = 0
1418 self._coeffs[ind] = val
1419 return
1420
1421 def __iter__(self):
1422 return iter(self.coeffs)
1423
1424 def integ(self, m=1, k=0):
1425 """
1426 Return an antiderivative (indefinite integral) of this polynomial.
1427
1428 Refer to `polyint` for full documentation.
1429
1430 See Also
1431 --------
1432 polyint : equivalent function
1433
1434 """
1435 return poly1d(polyint(self.coeffs, m=m, k=k))
1436
1437 def deriv(self, m=1):
1438 """
1439 Return a derivative of this polynomial.
1440
1441 Refer to `polyder` for full documentation.
1442
1443 See Also
1444 --------
1445 polyder : equivalent function
1446
1447 """
1448 return poly1d(polyder(self.coeffs, m=m))
1449
1450# Stuff to do on module import
1451
1452warnings.simplefilter('always', RankWarning)