1"""

2 Functions to operate on polynomials.

3

4"""

5__all__ = ['poly', 'roots', 'polyint', 'polyder', 'polyadd',

6 'polysub', 'polymul', 'polydiv', 'polyval', 'poly1d',

7 'polyfit', 'RankWarning']

8

9 import functools

10 import re

11 import warnings

12 import numpy.core.numeric as NX

13

14 from numpy.core import (isscalar, abs, finfo, atleast_1d, hstack, dot, array,

15 ones)

16 from numpy.core import overrides

17 from numpy.core.overrides import set_module

18 from numpy.lib.twodim_base import diag, vander

19 from numpy.lib.function_base import trim_zeros

20 from numpy.lib.type_check import iscomplex, real, imag, mintypecode

21 from numpy.linalg import eigvals, lstsq, inv

22

23

24 array_function_dispatch = functools.partial(

25 overrides.array_function_dispatch, module='numpy')

26

27

28@set_module('numpy')

29 class 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

40 def _poly_dispatcher(seq_of_zeros):

41 return seq_of_zeros

42

43

44@array_function_dispatch(_poly_dispatcher)

45 def 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

167 def _roots_dispatcher(p):

168 return p

169

170

171@array_function_dispatch(_roots_dispatcher)

172 def 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

263 def _polyint_dispatcher(p, m=None, k=None):

264 return (p,)

265

266

267@array_function_dispatch(_polyint_dispatcher)

268 def 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

368 def _polyder_dispatcher(p, m=None):

369 return (p,)

370

371

372@array_function_dispatch(_polyder_dispatcher)

373 def 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

448 def _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)

453 def 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

700 def _polyval_dispatcher(p, x):

701 return (p, x)

702

703

704@array_function_dispatch(_polyval_dispatcher)

705 def 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

783 def _binary_op_dispatcher(a1, a2):

784 return (a1, a2)

785

786

787@array_function_dispatch(_binary_op_dispatcher)

788 def 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)

856 def 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)

910 def 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

972 def _polydiv_dispatcher(u, v):

973 return (u, v)

974

975

976@array_function_dispatch(_polydiv_dispatcher)

977 def 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]*)")

1049 def _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')

1077 class 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

1452 warnings.simplefilter('always', RankWarning)