Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/interpolate/_polyint.py: 15%

207 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1import warnings 

2 

3import numpy as np 

4from scipy.special import factorial 

5from scipy._lib._util import _asarray_validated, float_factorial 

6 

7 

8__all__ = ["KroghInterpolator", "krogh_interpolate", "BarycentricInterpolator", 

9 "barycentric_interpolate", "approximate_taylor_polynomial"] 

10 

11 

12def _isscalar(x): 

13 """Check whether x is if a scalar type, or 0-dim""" 

14 return np.isscalar(x) or hasattr(x, 'shape') and x.shape == () 

15 

16 

17class _Interpolator1D: 

18 """ 

19 Common features in univariate interpolation 

20 

21 Deal with input data type and interpolation axis rolling. The 

22 actual interpolator can assume the y-data is of shape (n, r) where 

23 `n` is the number of x-points, and `r` the number of variables, 

24 and use self.dtype as the y-data type. 

25 

26 Attributes 

27 ---------- 

28 _y_axis 

29 Axis along which the interpolation goes in the original array 

30 _y_extra_shape 

31 Additional trailing shape of the input arrays, excluding 

32 the interpolation axis. 

33 dtype 

34 Dtype of the y-data arrays. Can be set via _set_dtype, which 

35 forces it to be float or complex. 

36 

37 Methods 

38 ------- 

39 __call__ 

40 _prepare_x 

41 _finish_y 

42 _reshape_yi 

43 _set_yi 

44 _set_dtype 

45 _evaluate 

46 

47 """ 

48 

49 __slots__ = ('_y_axis', '_y_extra_shape', 'dtype') 

50 

51 def __init__(self, xi=None, yi=None, axis=None): 

52 self._y_axis = axis 

53 self._y_extra_shape = None 

54 self.dtype = None 

55 if yi is not None: 

56 self._set_yi(yi, xi=xi, axis=axis) 

57 

58 def __call__(self, x): 

59 """ 

60 Evaluate the interpolant 

61 

62 Parameters 

63 ---------- 

64 x : array_like 

65 Points to evaluate the interpolant at. 

66 

67 Returns 

68 ------- 

69 y : array_like 

70 Interpolated values. Shape is determined by replacing 

71 the interpolation axis in the original array with the shape of x. 

72 

73 Notes 

74 ----- 

75 Input values `x` must be convertible to `float` values like `int` 

76 or `float`. 

77 

78 """ 

79 x, x_shape = self._prepare_x(x) 

80 y = self._evaluate(x) 

81 return self._finish_y(y, x_shape) 

82 

83 def _evaluate(self, x): 

84 """ 

85 Actually evaluate the value of the interpolator. 

86 """ 

87 raise NotImplementedError() 

88 

89 def _prepare_x(self, x): 

90 """Reshape input x array to 1-D""" 

91 x = _asarray_validated(x, check_finite=False, as_inexact=True) 

92 x_shape = x.shape 

93 return x.ravel(), x_shape 

94 

95 def _finish_y(self, y, x_shape): 

96 """Reshape interpolated y back to an N-D array similar to initial y""" 

97 y = y.reshape(x_shape + self._y_extra_shape) 

98 if self._y_axis != 0 and x_shape != (): 

99 nx = len(x_shape) 

100 ny = len(self._y_extra_shape) 

101 s = (list(range(nx, nx + self._y_axis)) 

102 + list(range(nx)) + list(range(nx+self._y_axis, nx+ny))) 

103 y = y.transpose(s) 

104 return y 

105 

106 def _reshape_yi(self, yi, check=False): 

107 yi = np.moveaxis(np.asarray(yi), self._y_axis, 0) 

108 if check and yi.shape[1:] != self._y_extra_shape: 

109 ok_shape = "%r + (N,) + %r" % (self._y_extra_shape[-self._y_axis:], 

110 self._y_extra_shape[:-self._y_axis]) 

111 raise ValueError("Data must be of shape %s" % ok_shape) 

112 return yi.reshape((yi.shape[0], -1)) 

113 

114 def _set_yi(self, yi, xi=None, axis=None): 

115 if axis is None: 

116 axis = self._y_axis 

117 if axis is None: 

118 raise ValueError("no interpolation axis specified") 

119 

120 yi = np.asarray(yi) 

121 

122 shape = yi.shape 

123 if shape == (): 

124 shape = (1,) 

125 if xi is not None and shape[axis] != len(xi): 

126 raise ValueError("x and y arrays must be equal in length along " 

127 "interpolation axis.") 

128 

129 self._y_axis = (axis % yi.ndim) 

130 self._y_extra_shape = yi.shape[:self._y_axis]+yi.shape[self._y_axis+1:] 

131 self.dtype = None 

132 self._set_dtype(yi.dtype) 

133 

134 def _set_dtype(self, dtype, union=False): 

135 if np.issubdtype(dtype, np.complexfloating) \ 

136 or np.issubdtype(self.dtype, np.complexfloating): 

137 self.dtype = np.complex_ 

138 else: 

139 if not union or self.dtype != np.complex_: 

140 self.dtype = np.float_ 

141 

142 

143class _Interpolator1DWithDerivatives(_Interpolator1D): 

144 def derivatives(self, x, der=None): 

145 """ 

146 Evaluate many derivatives of the polynomial at the point x 

147 

148 Produce an array of all derivative values at the point x. 

149 

150 Parameters 

151 ---------- 

152 x : array_like 

153 Point or points at which to evaluate the derivatives 

154 der : int or None, optional 

155 How many derivatives to extract; None for all potentially 

156 nonzero derivatives (that is a number equal to the number 

157 of points). This number includes the function value as 0th 

158 derivative. 

159 

160 Returns 

161 ------- 

162 d : ndarray 

163 Array with derivatives; d[j] contains the jth derivative. 

164 Shape of d[j] is determined by replacing the interpolation 

165 axis in the original array with the shape of x. 

166 

167 Examples 

168 -------- 

169 >>> from scipy.interpolate import KroghInterpolator 

170 >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives(0) 

171 array([1.0,2.0,3.0]) 

172 >>> KroghInterpolator([0,0,0],[1,2,3]).derivatives([0,0]) 

173 array([[1.0,1.0], 

174 [2.0,2.0], 

175 [3.0,3.0]]) 

176 

177 """ 

178 x, x_shape = self._prepare_x(x) 

179 y = self._evaluate_derivatives(x, der) 

180 

181 y = y.reshape((y.shape[0],) + x_shape + self._y_extra_shape) 

182 if self._y_axis != 0 and x_shape != (): 

183 nx = len(x_shape) 

184 ny = len(self._y_extra_shape) 

185 s = ([0] + list(range(nx+1, nx + self._y_axis+1)) 

186 + list(range(1, nx+1)) + 

187 list(range(nx+1+self._y_axis, nx+ny+1))) 

188 y = y.transpose(s) 

189 return y 

190 

191 def derivative(self, x, der=1): 

192 """ 

193 Evaluate one derivative of the polynomial at the point x 

194 

195 Parameters 

196 ---------- 

197 x : array_like 

198 Point or points at which to evaluate the derivatives 

199 

200 der : integer, optional 

201 Which derivative to extract. This number includes the 

202 function value as 0th derivative. 

203 

204 Returns 

205 ------- 

206 d : ndarray 

207 Derivative interpolated at the x-points. Shape of d is 

208 determined by replacing the interpolation axis in the 

209 original array with the shape of x. 

210 

211 Notes 

212 ----- 

213 This is computed by evaluating all derivatives up to the desired 

214 one (using self.derivatives()) and then discarding the rest. 

215 

216 """ 

217 x, x_shape = self._prepare_x(x) 

218 y = self._evaluate_derivatives(x, der+1) 

219 return self._finish_y(y[der], x_shape) 

220 

221 

222class KroghInterpolator(_Interpolator1DWithDerivatives): 

223 """ 

224 Interpolating polynomial for a set of points. 

225 

226 The polynomial passes through all the pairs (xi,yi). One may 

227 additionally specify a number of derivatives at each point xi; 

228 this is done by repeating the value xi and specifying the 

229 derivatives as successive yi values. 

230 

231 Allows evaluation of the polynomial and all its derivatives. 

232 For reasons of numerical stability, this function does not compute 

233 the coefficients of the polynomial, although they can be obtained 

234 by evaluating all the derivatives. 

235 

236 Parameters 

237 ---------- 

238 xi : array_like, length N 

239 Known x-coordinates. Must be sorted in increasing order. 

240 yi : array_like 

241 Known y-coordinates. When an xi occurs two or more times in 

242 a row, the corresponding yi's represent derivative values. 

243 axis : int, optional 

244 Axis in the yi array corresponding to the x-coordinate values. 

245 

246 Notes 

247 ----- 

248 Be aware that the algorithms implemented here are not necessarily 

249 the most numerically stable known. Moreover, even in a world of 

250 exact computation, unless the x coordinates are chosen very 

251 carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice - 

252 polynomial interpolation itself is a very ill-conditioned process 

253 due to the Runge phenomenon. In general, even with well-chosen 

254 x values, degrees higher than about thirty cause problems with 

255 numerical instability in this code. 

256 

257 Based on [1]_. 

258 

259 References 

260 ---------- 

261 .. [1] Krogh, "Efficient Algorithms for Polynomial Interpolation 

262 and Numerical Differentiation", 1970. 

263 

264 Examples 

265 -------- 

266 To produce a polynomial that is zero at 0 and 1 and has 

267 derivative 2 at 0, call 

268 

269 >>> from scipy.interpolate import KroghInterpolator 

270 >>> KroghInterpolator([0,0,1],[0,2,0]) 

271 

272 This constructs the quadratic 2*X**2-2*X. The derivative condition 

273 is indicated by the repeated zero in the xi array; the corresponding 

274 yi values are 0, the function value, and 2, the derivative value. 

275 

276 For another example, given xi, yi, and a derivative ypi for each 

277 point, appropriate arrays can be constructed as: 

278 

279 >>> import numpy as np 

280 >>> rng = np.random.default_rng() 

281 >>> xi = np.linspace(0, 1, 5) 

282 >>> yi, ypi = rng.random((2, 5)) 

283 >>> xi_k, yi_k = np.repeat(xi, 2), np.ravel(np.dstack((yi,ypi))) 

284 >>> KroghInterpolator(xi_k, yi_k) 

285 

286 To produce a vector-valued polynomial, supply a higher-dimensional 

287 array for yi: 

288 

289 >>> KroghInterpolator([0,1],[[2,3],[4,5]]) 

290 

291 This constructs a linear polynomial giving (2,3) at 0 and (4,5) at 1. 

292 

293 """ 

294 

295 def __init__(self, xi, yi, axis=0): 

296 _Interpolator1DWithDerivatives.__init__(self, xi, yi, axis) 

297 

298 self.xi = np.asarray(xi) 

299 self.yi = self._reshape_yi(yi) 

300 self.n, self.r = self.yi.shape 

301 

302 if (deg := self.xi.size) > 30: 

303 warnings.warn(f"{deg} degrees provided, degrees higher than about" 

304 " thirty cause problems with numerical instability " 

305 "with 'KroghInterpolator'", stacklevel=2) 

306 

307 c = np.zeros((self.n+1, self.r), dtype=self.dtype) 

308 c[0] = self.yi[0] 

309 Vk = np.zeros((self.n, self.r), dtype=self.dtype) 

310 for k in range(1, self.n): 

311 s = 0 

312 while s <= k and xi[k-s] == xi[k]: 

313 s += 1 

314 s -= 1 

315 Vk[0] = self.yi[k]/float_factorial(s) 

316 for i in range(k-s): 

317 if xi[i] == xi[k]: 

318 raise ValueError("Elements if `xi` can't be equal.") 

319 if s == 0: 

320 Vk[i+1] = (c[i]-Vk[i])/(xi[i]-xi[k]) 

321 else: 

322 Vk[i+1] = (Vk[i+1]-Vk[i])/(xi[i]-xi[k]) 

323 c[k] = Vk[k-s] 

324 self.c = c 

325 

326 def _evaluate(self, x): 

327 pi = 1 

328 p = np.zeros((len(x), self.r), dtype=self.dtype) 

329 p += self.c[0,np.newaxis,:] 

330 for k in range(1, self.n): 

331 w = x - self.xi[k-1] 

332 pi = w*pi 

333 p += pi[:,np.newaxis] * self.c[k] 

334 return p 

335 

336 def _evaluate_derivatives(self, x, der=None): 

337 n = self.n 

338 r = self.r 

339 

340 if der is None: 

341 der = self.n 

342 pi = np.zeros((n, len(x))) 

343 w = np.zeros((n, len(x))) 

344 pi[0] = 1 

345 p = np.zeros((len(x), self.r), dtype=self.dtype) 

346 p += self.c[0, np.newaxis, :] 

347 

348 for k in range(1, n): 

349 w[k-1] = x - self.xi[k-1] 

350 pi[k] = w[k-1] * pi[k-1] 

351 p += pi[k, :, np.newaxis] * self.c[k] 

352 

353 cn = np.zeros((max(der, n+1), len(x), r), dtype=self.dtype) 

354 cn[:n+1, :, :] += self.c[:n+1, np.newaxis, :] 

355 cn[0] = p 

356 for k in range(1, n): 

357 for i in range(1, n-k+1): 

358 pi[i] = w[k+i-1]*pi[i-1] + pi[i] 

359 cn[k] = cn[k] + pi[i, :, np.newaxis]*cn[k+i] 

360 cn[k] *= float_factorial(k) 

361 

362 cn[n, :, :] = 0 

363 return cn[:der] 

364 

365 

366def krogh_interpolate(xi, yi, x, der=0, axis=0): 

367 """ 

368 Convenience function for polynomial interpolation. 

369 

370 See `KroghInterpolator` for more details. 

371 

372 Parameters 

373 ---------- 

374 xi : array_like 

375 Known x-coordinates. 

376 yi : array_like 

377 Known y-coordinates, of shape ``(xi.size, R)``. Interpreted as 

378 vectors of length R, or scalars if R=1. 

379 x : array_like 

380 Point or points at which to evaluate the derivatives. 

381 der : int or list, optional 

382 How many derivatives to extract; None for all potentially 

383 nonzero derivatives (that is a number equal to the number 

384 of points), or a list of derivatives to extract. This number 

385 includes the function value as 0th derivative. 

386 axis : int, optional 

387 Axis in the yi array corresponding to the x-coordinate values. 

388 

389 Returns 

390 ------- 

391 d : ndarray 

392 If the interpolator's values are R-D then the 

393 returned array will be the number of derivatives by N by R. 

394 If `x` is a scalar, the middle dimension will be dropped; if 

395 the `yi` are scalars then the last dimension will be dropped. 

396 

397 See Also 

398 -------- 

399 KroghInterpolator : Krogh interpolator 

400 

401 Notes 

402 ----- 

403 Construction of the interpolating polynomial is a relatively expensive 

404 process. If you want to evaluate it repeatedly consider using the class 

405 KroghInterpolator (which is what this function uses). 

406 

407 Examples 

408 -------- 

409 We can interpolate 2D observed data using krogh interpolation: 

410 

411 >>> import numpy as np 

412 >>> import matplotlib.pyplot as plt 

413 >>> from scipy.interpolate import krogh_interpolate 

414 >>> x_observed = np.linspace(0.0, 10.0, 11) 

415 >>> y_observed = np.sin(x_observed) 

416 >>> x = np.linspace(min(x_observed), max(x_observed), num=100) 

417 >>> y = krogh_interpolate(x_observed, y_observed, x) 

418 >>> plt.plot(x_observed, y_observed, "o", label="observation") 

419 >>> plt.plot(x, y, label="krogh interpolation") 

420 >>> plt.legend() 

421 >>> plt.show() 

422 

423 """ 

424 P = KroghInterpolator(xi, yi, axis=axis) 

425 if der == 0: 

426 return P(x) 

427 elif _isscalar(der): 

428 return P.derivative(x,der=der) 

429 else: 

430 return P.derivatives(x,der=np.amax(der)+1)[der] 

431 

432 

433def approximate_taylor_polynomial(f,x,degree,scale,order=None): 

434 """ 

435 Estimate the Taylor polynomial of f at x by polynomial fitting. 

436 

437 Parameters 

438 ---------- 

439 f : callable 

440 The function whose Taylor polynomial is sought. Should accept 

441 a vector of `x` values. 

442 x : scalar 

443 The point at which the polynomial is to be evaluated. 

444 degree : int 

445 The degree of the Taylor polynomial 

446 scale : scalar 

447 The width of the interval to use to evaluate the Taylor polynomial. 

448 Function values spread over a range this wide are used to fit the 

449 polynomial. Must be chosen carefully. 

450 order : int or None, optional 

451 The order of the polynomial to be used in the fitting; `f` will be 

452 evaluated ``order+1`` times. If None, use `degree`. 

453 

454 Returns 

455 ------- 

456 p : poly1d instance 

457 The Taylor polynomial (translated to the origin, so that 

458 for example p(0)=f(x)). 

459 

460 Notes 

461 ----- 

462 The appropriate choice of "scale" is a trade-off; too large and the 

463 function differs from its Taylor polynomial too much to get a good 

464 answer, too small and round-off errors overwhelm the higher-order terms. 

465 The algorithm used becomes numerically unstable around order 30 even 

466 under ideal circumstances. 

467 

468 Choosing order somewhat larger than degree may improve the higher-order 

469 terms. 

470 

471 Examples 

472 -------- 

473 We can calculate Taylor approximation polynomials of sin function with 

474 various degrees: 

475 

476 >>> import numpy as np 

477 >>> import matplotlib.pyplot as plt 

478 >>> from scipy.interpolate import approximate_taylor_polynomial 

479 >>> x = np.linspace(-10.0, 10.0, num=100) 

480 >>> plt.plot(x, np.sin(x), label="sin curve") 

481 >>> for degree in np.arange(1, 15, step=2): 

482 ... sin_taylor = approximate_taylor_polynomial(np.sin, 0, degree, 1, 

483 ... order=degree + 2) 

484 ... plt.plot(x, sin_taylor(x), label=f"degree={degree}") 

485 >>> plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', 

486 ... borderaxespad=0.0, shadow=True) 

487 >>> plt.tight_layout() 

488 >>> plt.axis([-10, 10, -10, 10]) 

489 >>> plt.show() 

490 

491 """ 

492 if order is None: 

493 order = degree 

494 

495 n = order+1 

496 # Choose n points that cluster near the endpoints of the interval in 

497 # a way that avoids the Runge phenomenon. Ensure, by including the 

498 # endpoint or not as appropriate, that one point always falls at x 

499 # exactly. 

500 xs = scale*np.cos(np.linspace(0,np.pi,n,endpoint=n % 1)) + x 

501 

502 P = KroghInterpolator(xs, f(xs)) 

503 d = P.derivatives(x,der=degree+1) 

504 

505 return np.poly1d((d/factorial(np.arange(degree+1)))[::-1]) 

506 

507 

508class BarycentricInterpolator(_Interpolator1D): 

509 """The interpolating polynomial for a set of points 

510 

511 Constructs a polynomial that passes through a given set of points. 

512 Allows evaluation of the polynomial, efficient changing of the y 

513 values to be interpolated, and updating by adding more x values. 

514 For reasons of numerical stability, this function does not compute 

515 the coefficients of the polynomial. 

516 

517 The values yi need to be provided before the function is 

518 evaluated, but none of the preprocessing depends on them, so rapid 

519 updates are possible. 

520 

521 Parameters 

522 ---------- 

523 xi : array_like 

524 1-D array of x coordinates of the points the polynomial 

525 should pass through 

526 yi : array_like, optional 

527 The y coordinates of the points the polynomial should pass through. 

528 If None, the y values will be supplied later via the `set_y` method. 

529 axis : int, optional 

530 Axis in the yi array corresponding to the x-coordinate values. 

531 

532 Notes 

533 ----- 

534 This class uses a "barycentric interpolation" method that treats 

535 the problem as a special case of rational function interpolation. 

536 This algorithm is quite stable, numerically, but even in a world of 

537 exact computation, unless the x coordinates are chosen very 

538 carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice - 

539 polynomial interpolation itself is a very ill-conditioned process 

540 due to the Runge phenomenon. 

541 

542 Based on Berrut and Trefethen 2004, "Barycentric Lagrange Interpolation". 

543 

544 """ 

545 

546 def __init__(self, xi, yi=None, axis=0): 

547 _Interpolator1D.__init__(self, xi, yi, axis) 

548 

549 self.xi = np.asfarray(xi) 

550 self.set_yi(yi) 

551 self.n = len(self.xi) 

552 

553 # See page 510 of Berrut and Trefethen 2004 for an explanation of the 

554 # capacity scaling and the suggestion of using a random permutation of 

555 # the input factors. 

556 # At the moment, the permutation is not performed for xi that are 

557 # appended later through the add_xi interface. It's not clear to me how 

558 # to implement that and it seems that most situations that require 

559 # these numerical stability improvements will be able to provide all 

560 # the points to the constructor. 

561 self._inv_capacity = 4.0 / (np.max(self.xi) - np.min(self.xi)) 

562 permute = np.random.permutation(self.n) 

563 inv_permute = np.zeros(self.n, dtype=np.int32) 

564 inv_permute[permute] = np.arange(self.n) 

565 

566 self.wi = np.zeros(self.n) 

567 for i in range(self.n): 

568 dist = self._inv_capacity * (self.xi[i] - self.xi[permute]) 

569 dist[inv_permute[i]] = 1.0 

570 self.wi[i] = 1.0 / np.prod(dist) 

571 

572 def set_yi(self, yi, axis=None): 

573 """ 

574 Update the y values to be interpolated 

575 

576 The barycentric interpolation algorithm requires the calculation 

577 of weights, but these depend only on the xi. The yi can be changed 

578 at any time. 

579 

580 Parameters 

581 ---------- 

582 yi : array_like 

583 The y coordinates of the points the polynomial should pass through. 

584 If None, the y values will be supplied later. 

585 axis : int, optional 

586 Axis in the yi array corresponding to the x-coordinate values. 

587 

588 """ 

589 if yi is None: 

590 self.yi = None 

591 return 

592 self._set_yi(yi, xi=self.xi, axis=axis) 

593 self.yi = self._reshape_yi(yi) 

594 self.n, self.r = self.yi.shape 

595 

596 def add_xi(self, xi, yi=None): 

597 """ 

598 Add more x values to the set to be interpolated 

599 

600 The barycentric interpolation algorithm allows easy updating by 

601 adding more points for the polynomial to pass through. 

602 

603 Parameters 

604 ---------- 

605 xi : array_like 

606 The x coordinates of the points that the polynomial should pass 

607 through. 

608 yi : array_like, optional 

609 The y coordinates of the points the polynomial should pass through. 

610 Should have shape ``(xi.size, R)``; if R > 1 then the polynomial is 

611 vector-valued. 

612 If `yi` is not given, the y values will be supplied later. `yi` 

613 should be given if and only if the interpolator has y values 

614 specified. 

615 

616 """ 

617 if yi is not None: 

618 if self.yi is None: 

619 raise ValueError("No previous yi value to update!") 

620 yi = self._reshape_yi(yi, check=True) 

621 self.yi = np.vstack((self.yi,yi)) 

622 else: 

623 if self.yi is not None: 

624 raise ValueError("No update to yi provided!") 

625 old_n = self.n 

626 self.xi = np.concatenate((self.xi,xi)) 

627 self.n = len(self.xi) 

628 self.wi **= -1 

629 old_wi = self.wi 

630 self.wi = np.zeros(self.n) 

631 self.wi[:old_n] = old_wi 

632 for j in range(old_n, self.n): 

633 self.wi[:j] *= self._inv_capacity * (self.xi[j]-self.xi[:j]) 

634 self.wi[j] = np.multiply.reduce( 

635 self._inv_capacity * (self.xi[:j]-self.xi[j]) 

636 ) 

637 self.wi **= -1 

638 

639 def __call__(self, x): 

640 """Evaluate the interpolating polynomial at the points x 

641 

642 Parameters 

643 ---------- 

644 x : array_like 

645 Points to evaluate the interpolant at. 

646 

647 Returns 

648 ------- 

649 y : array_like 

650 Interpolated values. Shape is determined by replacing 

651 the interpolation axis in the original array with the shape of x. 

652 

653 Notes 

654 ----- 

655 Currently the code computes an outer product between x and the 

656 weights, that is, it constructs an intermediate array of size 

657 N by len(x), where N is the degree of the polynomial. 

658 """ 

659 return _Interpolator1D.__call__(self, x) 

660 

661 def _evaluate(self, x): 

662 if x.size == 0: 

663 p = np.zeros((0, self.r), dtype=self.dtype) 

664 else: 

665 c = x[..., np.newaxis] - self.xi 

666 z = c == 0 

667 c[z] = 1 

668 c = self.wi/c 

669 with np.errstate(divide='ignore'): 

670 p = np.dot(c, self.yi) / np.sum(c, axis=-1)[..., np.newaxis] 

671 # Now fix where x==some xi 

672 r = np.nonzero(z) 

673 if len(r) == 1: # evaluation at a scalar 

674 if len(r[0]) > 0: # equals one of the points 

675 p = self.yi[r[0][0]] 

676 else: 

677 p[r[:-1]] = self.yi[r[-1]] 

678 return p 

679 

680 

681def barycentric_interpolate(xi, yi, x, axis=0): 

682 """ 

683 Convenience function for polynomial interpolation. 

684 

685 Constructs a polynomial that passes through a given set of points, 

686 then evaluates the polynomial. For reasons of numerical stability, 

687 this function does not compute the coefficients of the polynomial. 

688 

689 This function uses a "barycentric interpolation" method that treats 

690 the problem as a special case of rational function interpolation. 

691 This algorithm is quite stable, numerically, but even in a world of 

692 exact computation, unless the `x` coordinates are chosen very 

693 carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice - 

694 polynomial interpolation itself is a very ill-conditioned process 

695 due to the Runge phenomenon. 

696 

697 Parameters 

698 ---------- 

699 xi : array_like 

700 1-D array of x coordinates of the points the polynomial should 

701 pass through 

702 yi : array_like 

703 The y coordinates of the points the polynomial should pass through. 

704 x : scalar or array_like 

705 Points to evaluate the interpolator at. 

706 axis : int, optional 

707 Axis in the yi array corresponding to the x-coordinate values. 

708 

709 Returns 

710 ------- 

711 y : scalar or array_like 

712 Interpolated values. Shape is determined by replacing 

713 the interpolation axis in the original array with the shape of x. 

714 

715 See Also 

716 -------- 

717 BarycentricInterpolator : Bary centric interpolator 

718 

719 Notes 

720 ----- 

721 Construction of the interpolation weights is a relatively slow process. 

722 If you want to call this many times with the same xi (but possibly 

723 varying yi or x) you should use the class `BarycentricInterpolator`. 

724 This is what this function uses internally. 

725 

726 Examples 

727 -------- 

728 We can interpolate 2D observed data using barycentric interpolation: 

729 

730 >>> import numpy as np 

731 >>> import matplotlib.pyplot as plt 

732 >>> from scipy.interpolate import barycentric_interpolate 

733 >>> x_observed = np.linspace(0.0, 10.0, 11) 

734 >>> y_observed = np.sin(x_observed) 

735 >>> x = np.linspace(min(x_observed), max(x_observed), num=100) 

736 >>> y = barycentric_interpolate(x_observed, y_observed, x) 

737 >>> plt.plot(x_observed, y_observed, "o", label="observation") 

738 >>> plt.plot(x, y, label="barycentric interpolation") 

739 >>> plt.legend() 

740 >>> plt.show() 

741 

742 """ 

743 return BarycentricInterpolator(xi, yi, axis=axis)(x)