_polyint.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. import warnings
  2. import numpy as np
  3. from scipy.special import factorial
  4. from scipy._lib._util import _asarray_validated, float_factorial
  5. __all__ = ["KroghInterpolator", "krogh_interpolate", "BarycentricInterpolator",
  6. "barycentric_interpolate", "approximate_taylor_polynomial"]
  7. def _isscalar(x):
  8. """Check whether x is if a scalar type, or 0-dim"""
  9. return np.isscalar(x) or hasattr(x, 'shape') and x.shape == ()
  10. class _Interpolator1D:
  11. """
  12. Common features in univariate interpolation
  13. Deal with input data type and interpolation axis rolling. The
  14. actual interpolator can assume the y-data is of shape (n, r) where
  15. `n` is the number of x-points, and `r` the number of variables,
  16. and use self.dtype as the y-data type.
  17. Attributes
  18. ----------
  19. _y_axis
  20. Axis along which the interpolation goes in the original array
  21. _y_extra_shape
  22. Additional trailing shape of the input arrays, excluding
  23. the interpolation axis.
  24. dtype
  25. Dtype of the y-data arrays. Can be set via _set_dtype, which
  26. forces it to be float or complex.
  27. Methods
  28. -------
  29. __call__
  30. _prepare_x
  31. _finish_y
  32. _reshape_yi
  33. _set_yi
  34. _set_dtype
  35. _evaluate
  36. """
  37. __slots__ = ('_y_axis', '_y_extra_shape', 'dtype')
  38. def __init__(self, xi=None, yi=None, axis=None):
  39. self._y_axis = axis
  40. self._y_extra_shape = None
  41. self.dtype = None
  42. if yi is not None:
  43. self._set_yi(yi, xi=xi, axis=axis)
  44. def __call__(self, x):
  45. """
  46. Evaluate the interpolant
  47. Parameters
  48. ----------
  49. x : array_like
  50. Points to evaluate the interpolant at.
  51. Returns
  52. -------
  53. y : array_like
  54. Interpolated values. Shape is determined by replacing
  55. the interpolation axis in the original array with the shape of x.
  56. Notes
  57. -----
  58. Input values `x` must be convertible to `float` values like `int`
  59. or `float`.
  60. """
  61. x, x_shape = self._prepare_x(x)
  62. y = self._evaluate(x)
  63. return self._finish_y(y, x_shape)
  64. def _evaluate(self, x):
  65. """
  66. Actually evaluate the value of the interpolator.
  67. """
  68. raise NotImplementedError()
  69. def _prepare_x(self, x):
  70. """Reshape input x array to 1-D"""
  71. x = _asarray_validated(x, check_finite=False, as_inexact=True)
  72. x_shape = x.shape
  73. return x.ravel(), x_shape
  74. def _finish_y(self, y, x_shape):
  75. """Reshape interpolated y back to an N-D array similar to initial y"""
  76. y = y.reshape(x_shape + self._y_extra_shape)
  77. if self._y_axis != 0 and x_shape != ():
  78. nx = len(x_shape)
  79. ny = len(self._y_extra_shape)
  80. s = (list(range(nx, nx + self._y_axis))
  81. + list(range(nx)) + list(range(nx+self._y_axis, nx+ny)))
  82. y = y.transpose(s)
  83. return y
  84. def _reshape_yi(self, yi, check=False):
  85. yi = np.moveaxis(np.asarray(yi), self._y_axis, 0)
  86. if check and yi.shape[1:] != self._y_extra_shape:
  87. ok_shape = "%r + (N,) + %r" % (self._y_extra_shape[-self._y_axis:],
  88. self._y_extra_shape[:-self._y_axis])
  89. raise ValueError("Data must be of shape %s" % ok_shape)
  90. return yi.reshape((yi.shape[0], -1))
  91. def _set_yi(self, yi, xi=None, axis=None):
  92. if axis is None:
  93. axis = self._y_axis
  94. if axis is None:
  95. raise ValueError("no interpolation axis specified")
  96. yi = np.asarray(yi)
  97. shape = yi.shape
  98. if shape == ():
  99. shape = (1,)
  100. if xi is not None and shape[axis] != len(xi):
  101. raise ValueError("x and y arrays must be equal in length along "
  102. "interpolation axis.")
  103. self._y_axis = (axis % yi.ndim)
  104. self._y_extra_shape = yi.shape[:self._y_axis]+yi.shape[self._y_axis+1:]
  105. self.dtype = None
  106. self._set_dtype(yi.dtype)
  107. def _set_dtype(self, dtype, union=False):
  108. if np.issubdtype(dtype, np.complexfloating) \
  109. or np.issubdtype(self.dtype, np.complexfloating):
  110. self.dtype = np.complex_
  111. else:
  112. if not union or self.dtype != np.complex_:
  113. self.dtype = np.float_
  114. class _Interpolator1DWithDerivatives(_Interpolator1D):
  115. def derivatives(self, x, der=None):
  116. """
  117. Evaluate many derivatives of the polynomial at the point x
  118. Produce an array of all derivative values at the point x.
  119. Parameters
  120. ----------
  121. x : array_like
  122. Point or points at which to evaluate the derivatives
  123. der : int or None, optional
  124. How many derivatives to extract; None for all potentially
  125. nonzero derivatives (that is a number equal to the number
  126. of points). This number includes the function value as 0th
  127. derivative.
  128. Returns
  129. -------
  130. d : ndarray
  131. Array with derivatives; d[j] contains the jth derivative.
  132. Shape of d[j] is determined by replacing the interpolation
  133. axis in the original array with the shape of x.
  134. Examples
  135. --------
  136. >>> from scipy.interpolate import KroghInterpolator
  137. >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives(0)
  138. array([1.0,2.0,3.0])
  139. >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives([0,0])
  140. array([[1.0,1.0],
  141. [2.0,2.0],
  142. [3.0,3.0]])
  143. """
  144. x, x_shape = self._prepare_x(x)
  145. y = self._evaluate_derivatives(x, der)
  146. y = y.reshape((y.shape[0],) + x_shape + self._y_extra_shape)
  147. if self._y_axis != 0 and x_shape != ():
  148. nx = len(x_shape)
  149. ny = len(self._y_extra_shape)
  150. s = ([0] + list(range(nx+1, nx + self._y_axis+1))
  151. + list(range(1, nx+1)) +
  152. list(range(nx+1+self._y_axis, nx+ny+1)))
  153. y = y.transpose(s)
  154. return y
  155. def derivative(self, x, der=1):
  156. """
  157. Evaluate one derivative of the polynomial at the point x
  158. Parameters
  159. ----------
  160. x : array_like
  161. Point or points at which to evaluate the derivatives
  162. der : integer, optional
  163. Which derivative to extract. This number includes the
  164. function value as 0th derivative.
  165. Returns
  166. -------
  167. d : ndarray
  168. Derivative interpolated at the x-points. Shape of d is
  169. determined by replacing the interpolation axis in the
  170. original array with the shape of x.
  171. Notes
  172. -----
  173. This is computed by evaluating all derivatives up to the desired
  174. one (using self.derivatives()) and then discarding the rest.
  175. """
  176. x, x_shape = self._prepare_x(x)
  177. y = self._evaluate_derivatives(x, der+1)
  178. return self._finish_y(y[der], x_shape)
  179. class KroghInterpolator(_Interpolator1DWithDerivatives):
  180. """
  181. Interpolating polynomial for a set of points.
  182. The polynomial passes through all the pairs (xi,yi). One may
  183. additionally specify a number of derivatives at each point xi;
  184. this is done by repeating the value xi and specifying the
  185. derivatives as successive yi values.
  186. Allows evaluation of the polynomial and all its derivatives.
  187. For reasons of numerical stability, this function does not compute
  188. the coefficients of the polynomial, although they can be obtained
  189. by evaluating all the derivatives.
  190. Parameters
  191. ----------
  192. xi : array_like, length N
  193. Known x-coordinates. Must be sorted in increasing order.
  194. yi : array_like
  195. Known y-coordinates. When an xi occurs two or more times in
  196. a row, the corresponding yi's represent derivative values.
  197. axis : int, optional
  198. Axis in the yi array corresponding to the x-coordinate values.
  199. Notes
  200. -----
  201. Be aware that the algorithms implemented here are not necessarily
  202. the most numerically stable known. Moreover, even in a world of
  203. exact computation, unless the x coordinates are chosen very
  204. carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice -
  205. polynomial interpolation itself is a very ill-conditioned process
  206. due to the Runge phenomenon. In general, even with well-chosen
  207. x values, degrees higher than about thirty cause problems with
  208. numerical instability in this code.
  209. Based on [1]_.
  210. References
  211. ----------
  212. .. [1] Krogh, "Efficient Algorithms for Polynomial Interpolation
  213. and Numerical Differentiation", 1970.
  214. Examples
  215. --------
  216. To produce a polynomial that is zero at 0 and 1 and has
  217. derivative 2 at 0, call
  218. >>> from scipy.interpolate import KroghInterpolator
  219. >>> KroghInterpolator([0,0,1],[0,2,0])
  220. This constructs the quadratic 2*X**2-2*X. The derivative condition
  221. is indicated by the repeated zero in the xi array; the corresponding
  222. yi values are 0, the function value, and 2, the derivative value.
  223. For another example, given xi, yi, and a derivative ypi for each
  224. point, appropriate arrays can be constructed as:
  225. >>> import numpy as np
  226. >>> rng = np.random.default_rng()
  227. >>> xi = np.linspace(0, 1, 5)
  228. >>> yi, ypi = rng.random((2, 5))
  229. >>> xi_k, yi_k = np.repeat(xi, 2), np.ravel(np.dstack((yi,ypi)))
  230. >>> KroghInterpolator(xi_k, yi_k)
  231. To produce a vector-valued polynomial, supply a higher-dimensional
  232. array for yi:
  233. >>> KroghInterpolator([0,1],[[2,3],[4,5]])
  234. This constructs a linear polynomial giving (2,3) at 0 and (4,5) at 1.
  235. """
  236. def __init__(self, xi, yi, axis=0):
  237. _Interpolator1DWithDerivatives.__init__(self, xi, yi, axis)
  238. self.xi = np.asarray(xi)
  239. self.yi = self._reshape_yi(yi)
  240. self.n, self.r = self.yi.shape
  241. if (deg := self.xi.size) > 30:
  242. warnings.warn(f"{deg} degrees provided, degrees higher than about"
  243. " thirty cause problems with numerical instability "
  244. "with 'KroghInterpolator'", stacklevel=2)
  245. c = np.zeros((self.n+1, self.r), dtype=self.dtype)
  246. c[0] = self.yi[0]
  247. Vk = np.zeros((self.n, self.r), dtype=self.dtype)
  248. for k in range(1, self.n):
  249. s = 0
  250. while s <= k and xi[k-s] == xi[k]:
  251. s += 1
  252. s -= 1
  253. Vk[0] = self.yi[k]/float_factorial(s)
  254. for i in range(k-s):
  255. if xi[i] == xi[k]:
  256. raise ValueError("Elements if `xi` can't be equal.")
  257. if s == 0:
  258. Vk[i+1] = (c[i]-Vk[i])/(xi[i]-xi[k])
  259. else:
  260. Vk[i+1] = (Vk[i+1]-Vk[i])/(xi[i]-xi[k])
  261. c[k] = Vk[k-s]
  262. self.c = c
  263. def _evaluate(self, x):
  264. pi = 1
  265. p = np.zeros((len(x), self.r), dtype=self.dtype)
  266. p += self.c[0,np.newaxis,:]
  267. for k in range(1, self.n):
  268. w = x - self.xi[k-1]
  269. pi = w*pi
  270. p += pi[:,np.newaxis] * self.c[k]
  271. return p
  272. def _evaluate_derivatives(self, x, der=None):
  273. n = self.n
  274. r = self.r
  275. if der is None:
  276. der = self.n
  277. pi = np.zeros((n, len(x)))
  278. w = np.zeros((n, len(x)))
  279. pi[0] = 1
  280. p = np.zeros((len(x), self.r), dtype=self.dtype)
  281. p += self.c[0, np.newaxis, :]
  282. for k in range(1, n):
  283. w[k-1] = x - self.xi[k-1]
  284. pi[k] = w[k-1] * pi[k-1]
  285. p += pi[k, :, np.newaxis] * self.c[k]
  286. cn = np.zeros((max(der, n+1), len(x), r), dtype=self.dtype)
  287. cn[:n+1, :, :] += self.c[:n+1, np.newaxis, :]
  288. cn[0] = p
  289. for k in range(1, n):
  290. for i in range(1, n-k+1):
  291. pi[i] = w[k+i-1]*pi[i-1] + pi[i]
  292. cn[k] = cn[k] + pi[i, :, np.newaxis]*cn[k+i]
  293. cn[k] *= float_factorial(k)
  294. cn[n, :, :] = 0
  295. return cn[:der]
  296. def krogh_interpolate(xi, yi, x, der=0, axis=0):
  297. """
  298. Convenience function for polynomial interpolation.
  299. See `KroghInterpolator` for more details.
  300. Parameters
  301. ----------
  302. xi : array_like
  303. Known x-coordinates.
  304. yi : array_like
  305. Known y-coordinates, of shape ``(xi.size, R)``. Interpreted as
  306. vectors of length R, or scalars if R=1.
  307. x : array_like
  308. Point or points at which to evaluate the derivatives.
  309. der : int or list, optional
  310. How many derivatives to extract; None for all potentially
  311. nonzero derivatives (that is a number equal to the number
  312. of points), or a list of derivatives to extract. This number
  313. includes the function value as 0th derivative.
  314. axis : int, optional
  315. Axis in the yi array corresponding to the x-coordinate values.
  316. Returns
  317. -------
  318. d : ndarray
  319. If the interpolator's values are R-D then the
  320. returned array will be the number of derivatives by N by R.
  321. If `x` is a scalar, the middle dimension will be dropped; if
  322. the `yi` are scalars then the last dimension will be dropped.
  323. See Also
  324. --------
  325. KroghInterpolator : Krogh interpolator
  326. Notes
  327. -----
  328. Construction of the interpolating polynomial is a relatively expensive
  329. process. If you want to evaluate it repeatedly consider using the class
  330. KroghInterpolator (which is what this function uses).
  331. Examples
  332. --------
  333. We can interpolate 2D observed data using krogh interpolation:
  334. >>> import numpy as np
  335. >>> import matplotlib.pyplot as plt
  336. >>> from scipy.interpolate import krogh_interpolate
  337. >>> x_observed = np.linspace(0.0, 10.0, 11)
  338. >>> y_observed = np.sin(x_observed)
  339. >>> x = np.linspace(min(x_observed), max(x_observed), num=100)
  340. >>> y = krogh_interpolate(x_observed, y_observed, x)
  341. >>> plt.plot(x_observed, y_observed, "o", label="observation")
  342. >>> plt.plot(x, y, label="krogh interpolation")
  343. >>> plt.legend()
  344. >>> plt.show()
  345. """
  346. P = KroghInterpolator(xi, yi, axis=axis)
  347. if der == 0:
  348. return P(x)
  349. elif _isscalar(der):
  350. return P.derivative(x,der=der)
  351. else:
  352. return P.derivatives(x,der=np.amax(der)+1)[der]
  353. def approximate_taylor_polynomial(f,x,degree,scale,order=None):
  354. """
  355. Estimate the Taylor polynomial of f at x by polynomial fitting.
  356. Parameters
  357. ----------
  358. f : callable
  359. The function whose Taylor polynomial is sought. Should accept
  360. a vector of `x` values.
  361. x : scalar
  362. The point at which the polynomial is to be evaluated.
  363. degree : int
  364. The degree of the Taylor polynomial
  365. scale : scalar
  366. The width of the interval to use to evaluate the Taylor polynomial.
  367. Function values spread over a range this wide are used to fit the
  368. polynomial. Must be chosen carefully.
  369. order : int or None, optional
  370. The order of the polynomial to be used in the fitting; `f` will be
  371. evaluated ``order+1`` times. If None, use `degree`.
  372. Returns
  373. -------
  374. p : poly1d instance
  375. The Taylor polynomial (translated to the origin, so that
  376. for example p(0)=f(x)).
  377. Notes
  378. -----
  379. The appropriate choice of "scale" is a trade-off; too large and the
  380. function differs from its Taylor polynomial too much to get a good
  381. answer, too small and round-off errors overwhelm the higher-order terms.
  382. The algorithm used becomes numerically unstable around order 30 even
  383. under ideal circumstances.
  384. Choosing order somewhat larger than degree may improve the higher-order
  385. terms.
  386. Examples
  387. --------
  388. We can calculate Taylor approximation polynomials of sin function with
  389. various degrees:
  390. >>> import numpy as np
  391. >>> import matplotlib.pyplot as plt
  392. >>> from scipy.interpolate import approximate_taylor_polynomial
  393. >>> x = np.linspace(-10.0, 10.0, num=100)
  394. >>> plt.plot(x, np.sin(x), label="sin curve")
  395. >>> for degree in np.arange(1, 15, step=2):
  396. ... sin_taylor = approximate_taylor_polynomial(np.sin, 0, degree, 1,
  397. ... order=degree + 2)
  398. ... plt.plot(x, sin_taylor(x), label=f"degree={degree}")
  399. >>> plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left',
  400. ... borderaxespad=0.0, shadow=True)
  401. >>> plt.tight_layout()
  402. >>> plt.axis([-10, 10, -10, 10])
  403. >>> plt.show()
  404. """
  405. if order is None:
  406. order = degree
  407. n = order+1
  408. # Choose n points that cluster near the endpoints of the interval in
  409. # a way that avoids the Runge phenomenon. Ensure, by including the
  410. # endpoint or not as appropriate, that one point always falls at x
  411. # exactly.
  412. xs = scale*np.cos(np.linspace(0,np.pi,n,endpoint=n % 1)) + x
  413. P = KroghInterpolator(xs, f(xs))
  414. d = P.derivatives(x,der=degree+1)
  415. return np.poly1d((d/factorial(np.arange(degree+1)))[::-1])
  416. class BarycentricInterpolator(_Interpolator1D):
  417. """The interpolating polynomial for a set of points
  418. Constructs a polynomial that passes through a given set of points.
  419. Allows evaluation of the polynomial, efficient changing of the y
  420. values to be interpolated, and updating by adding more x values.
  421. For reasons of numerical stability, this function does not compute
  422. the coefficients of the polynomial.
  423. The values yi need to be provided before the function is
  424. evaluated, but none of the preprocessing depends on them, so rapid
  425. updates are possible.
  426. Parameters
  427. ----------
  428. xi : array_like
  429. 1-D array of x coordinates of the points the polynomial
  430. should pass through
  431. yi : array_like, optional
  432. The y coordinates of the points the polynomial should pass through.
  433. If None, the y values will be supplied later via the `set_y` method.
  434. axis : int, optional
  435. Axis in the yi array corresponding to the x-coordinate values.
  436. Notes
  437. -----
  438. This class uses a "barycentric interpolation" method that treats
  439. the problem as a special case of rational function interpolation.
  440. This algorithm is quite stable, numerically, but even in a world of
  441. exact computation, unless the x coordinates are chosen very
  442. carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice -
  443. polynomial interpolation itself is a very ill-conditioned process
  444. due to the Runge phenomenon.
  445. Based on Berrut and Trefethen 2004, "Barycentric Lagrange Interpolation".
  446. """
  447. def __init__(self, xi, yi=None, axis=0):
  448. _Interpolator1D.__init__(self, xi, yi, axis)
  449. self.xi = np.asfarray(xi)
  450. self.set_yi(yi)
  451. self.n = len(self.xi)
  452. # See page 510 of Berrut and Trefethen 2004 for an explanation of the
  453. # capacity scaling and the suggestion of using a random permutation of
  454. # the input factors.
  455. # At the moment, the permutation is not performed for xi that are
  456. # appended later through the add_xi interface. It's not clear to me how
  457. # to implement that and it seems that most situations that require
  458. # these numerical stability improvements will be able to provide all
  459. # the points to the constructor.
  460. self._inv_capacity = 4.0 / (np.max(self.xi) - np.min(self.xi))
  461. permute = np.random.permutation(self.n)
  462. inv_permute = np.zeros(self.n, dtype=np.int32)
  463. inv_permute[permute] = np.arange(self.n)
  464. self.wi = np.zeros(self.n)
  465. for i in range(self.n):
  466. dist = self._inv_capacity * (self.xi[i] - self.xi[permute])
  467. dist[inv_permute[i]] = 1.0
  468. self.wi[i] = 1.0 / np.prod(dist)
  469. def set_yi(self, yi, axis=None):
  470. """
  471. Update the y values to be interpolated
  472. The barycentric interpolation algorithm requires the calculation
  473. of weights, but these depend only on the xi. The yi can be changed
  474. at any time.
  475. Parameters
  476. ----------
  477. yi : array_like
  478. The y coordinates of the points the polynomial should pass through.
  479. If None, the y values will be supplied later.
  480. axis : int, optional
  481. Axis in the yi array corresponding to the x-coordinate values.
  482. """
  483. if yi is None:
  484. self.yi = None
  485. return
  486. self._set_yi(yi, xi=self.xi, axis=axis)
  487. self.yi = self._reshape_yi(yi)
  488. self.n, self.r = self.yi.shape
  489. def add_xi(self, xi, yi=None):
  490. """
  491. Add more x values to the set to be interpolated
  492. The barycentric interpolation algorithm allows easy updating by
  493. adding more points for the polynomial to pass through.
  494. Parameters
  495. ----------
  496. xi : array_like
  497. The x coordinates of the points that the polynomial should pass
  498. through.
  499. yi : array_like, optional
  500. The y coordinates of the points the polynomial should pass through.
  501. Should have shape ``(xi.size, R)``; if R > 1 then the polynomial is
  502. vector-valued.
  503. If `yi` is not given, the y values will be supplied later. `yi`
  504. should be given if and only if the interpolator has y values
  505. specified.
  506. """
  507. if yi is not None:
  508. if self.yi is None:
  509. raise ValueError("No previous yi value to update!")
  510. yi = self._reshape_yi(yi, check=True)
  511. self.yi = np.vstack((self.yi,yi))
  512. else:
  513. if self.yi is not None:
  514. raise ValueError("No update to yi provided!")
  515. old_n = self.n
  516. self.xi = np.concatenate((self.xi,xi))
  517. self.n = len(self.xi)
  518. self.wi **= -1
  519. old_wi = self.wi
  520. self.wi = np.zeros(self.n)
  521. self.wi[:old_n] = old_wi
  522. for j in range(old_n, self.n):
  523. self.wi[:j] *= self._inv_capacity * (self.xi[j]-self.xi[:j])
  524. self.wi[j] = np.multiply.reduce(
  525. self._inv_capacity * (self.xi[:j]-self.xi[j])
  526. )
  527. self.wi **= -1
  528. def __call__(self, x):
  529. """Evaluate the interpolating polynomial at the points x
  530. Parameters
  531. ----------
  532. x : array_like
  533. Points to evaluate the interpolant at.
  534. Returns
  535. -------
  536. y : array_like
  537. Interpolated values. Shape is determined by replacing
  538. the interpolation axis in the original array with the shape of x.
  539. Notes
  540. -----
  541. Currently the code computes an outer product between x and the
  542. weights, that is, it constructs an intermediate array of size
  543. N by len(x), where N is the degree of the polynomial.
  544. """
  545. return _Interpolator1D.__call__(self, x)
  546. def _evaluate(self, x):
  547. if x.size == 0:
  548. p = np.zeros((0, self.r), dtype=self.dtype)
  549. else:
  550. c = x[..., np.newaxis] - self.xi
  551. z = c == 0
  552. c[z] = 1
  553. c = self.wi/c
  554. with np.errstate(divide='ignore'):
  555. p = np.dot(c, self.yi) / np.sum(c, axis=-1)[..., np.newaxis]
  556. # Now fix where x==some xi
  557. r = np.nonzero(z)
  558. if len(r) == 1: # evaluation at a scalar
  559. if len(r[0]) > 0: # equals one of the points
  560. p = self.yi[r[0][0]]
  561. else:
  562. p[r[:-1]] = self.yi[r[-1]]
  563. return p
  564. def barycentric_interpolate(xi, yi, x, axis=0):
  565. """
  566. Convenience function for polynomial interpolation.
  567. Constructs a polynomial that passes through a given set of points,
  568. then evaluates the polynomial. For reasons of numerical stability,
  569. this function does not compute the coefficients of the polynomial.
  570. This function uses a "barycentric interpolation" method that treats
  571. the problem as a special case of rational function interpolation.
  572. This algorithm is quite stable, numerically, but even in a world of
  573. exact computation, unless the `x` coordinates are chosen very
  574. carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice -
  575. polynomial interpolation itself is a very ill-conditioned process
  576. due to the Runge phenomenon.
  577. Parameters
  578. ----------
  579. xi : array_like
  580. 1-D array of x coordinates of the points the polynomial should
  581. pass through
  582. yi : array_like
  583. The y coordinates of the points the polynomial should pass through.
  584. x : scalar or array_like
  585. Points to evaluate the interpolator at.
  586. axis : int, optional
  587. Axis in the yi array corresponding to the x-coordinate values.
  588. Returns
  589. -------
  590. y : scalar or array_like
  591. Interpolated values. Shape is determined by replacing
  592. the interpolation axis in the original array with the shape of x.
  593. See Also
  594. --------
  595. BarycentricInterpolator : Bary centric interpolator
  596. Notes
  597. -----
  598. Construction of the interpolation weights is a relatively slow process.
  599. If you want to call this many times with the same xi (but possibly
  600. varying yi or x) you should use the class `BarycentricInterpolator`.
  601. This is what this function uses internally.
  602. Examples
  603. --------
  604. We can interpolate 2D observed data using barycentric interpolation:
  605. >>> import numpy as np
  606. >>> import matplotlib.pyplot as plt
  607. >>> from scipy.interpolate import barycentric_interpolate
  608. >>> x_observed = np.linspace(0.0, 10.0, 11)
  609. >>> y_observed = np.sin(x_observed)
  610. >>> x = np.linspace(min(x_observed), max(x_observed), num=100)
  611. >>> y = barycentric_interpolate(x_observed, y_observed, x)
  612. >>> plt.plot(x_observed, y_observed, "o", label="observation")
  613. >>> plt.plot(x, y, label="barycentric interpolation")
  614. >>> plt.legend()
  615. >>> plt.show()
  616. """
  617. return BarycentricInterpolator(xi, yi, axis=axis)(x)