Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1__all__ = ['interp1d', 'interp2d', 'lagrange', 'PPoly', 'BPoly', 'NdPPoly', 

2 'RegularGridInterpolator', 'interpn'] 

3 

4import itertools 

5import warnings 

6import functools 

7import operator 

8 

9import numpy as np 

10from numpy import (array, transpose, searchsorted, atleast_1d, atleast_2d, 

11 ravel, poly1d, asarray, intp) 

12 

13import scipy.special as spec 

14from scipy.special import comb 

15from scipy._lib._util import prod 

16 

17from . import fitpack 

18from . import dfitpack 

19from . import _fitpack 

20from .polyint import _Interpolator1D 

21from . import _ppoly 

22from .fitpack2 import RectBivariateSpline 

23from .interpnd import _ndim_coords_from_arrays 

24from ._bsplines import make_interp_spline, BSpline 

25 

26 

27def lagrange(x, w): 

28 r""" 

29 Return a Lagrange interpolating polynomial. 

30 

31 Given two 1-D arrays `x` and `w,` returns the Lagrange interpolating 

32 polynomial through the points ``(x, w)``. 

33 

34 Warning: This implementation is numerically unstable. Do not expect to 

35 be able to use more than about 20 points even if they are chosen optimally. 

36 

37 Parameters 

38 ---------- 

39 x : array_like 

40 `x` represents the x-coordinates of a set of datapoints. 

41 w : array_like 

42 `w` represents the y-coordinates of a set of datapoints, i.e., f(`x`). 

43 

44 Returns 

45 ------- 

46 lagrange : `numpy.poly1d` instance 

47 The Lagrange interpolating polynomial. 

48 

49 Examples 

50 -------- 

51 Interpolate :math:`f(x) = x^3` by 3 points. 

52 

53 >>> from scipy.interpolate import lagrange 

54 >>> x = np.array([0, 1, 2]) 

55 >>> y = x**3 

56 >>> poly = lagrange(x, y) 

57 

58 Since there are only 3 points, Lagrange polynomial has degree 2. Explicitly, 

59 it is given by 

60 

61 .. math:: 

62 

63 \begin{aligned} 

64 L(x) &= 1\times \frac{x (x - 2)}{-1} + 8\times \frac{x (x-1)}{2} \\ 

65 &= x (-2 + 3x) 

66 \end{aligned} 

67 

68 >>> from numpy.polynomial.polynomial import Polynomial 

69 >>> Polynomial(poly).coef 

70 array([ 3., -2., 0.]) 

71 

72 """ 

73 

74 M = len(x) 

75 p = poly1d(0.0) 

76 for j in range(M): 

77 pt = poly1d(w[j]) 

78 for k in range(M): 

79 if k == j: 

80 continue 

81 fac = x[j]-x[k] 

82 pt *= poly1d([1.0, -x[k]])/fac 

83 p += pt 

84 return p 

85 

86 

87# !! Need to find argument for keeping initialize. If it isn't 

88# !! found, get rid of it! 

89 

90 

91class interp2d(object): 

92 """ 

93 interp2d(x, y, z, kind='linear', copy=True, bounds_error=False, 

94 fill_value=None) 

95 

96 Interpolate over a 2-D grid. 

97 

98 `x`, `y` and `z` are arrays of values used to approximate some function 

99 f: ``z = f(x, y)``. This class returns a function whose call method uses 

100 spline interpolation to find the value of new points. 

101 

102 If `x` and `y` represent a regular grid, consider using 

103 RectBivariateSpline. 

104 

105 Note that calling `interp2d` with NaNs present in input values results in 

106 undefined behaviour. 

107 

108 Methods 

109 ------- 

110 __call__ 

111 

112 Parameters 

113 ---------- 

114 x, y : array_like 

115 Arrays defining the data point coordinates. 

116 

117 If the points lie on a regular grid, `x` can specify the column 

118 coordinates and `y` the row coordinates, for example:: 

119 

120 >>> x = [0,1,2]; y = [0,3]; z = [[1,2,3], [4,5,6]] 

121 

122 Otherwise, `x` and `y` must specify the full coordinates for each 

123 point, for example:: 

124 

125 >>> x = [0,1,2,0,1,2]; y = [0,0,0,3,3,3]; z = [1,2,3,4,5,6] 

126 

127 If `x` and `y` are multidimensional, they are flattened before use. 

128 z : array_like 

129 The values of the function to interpolate at the data points. If 

130 `z` is a multidimensional array, it is flattened before use. The 

131 length of a flattened `z` array is either 

132 len(`x`)*len(`y`) if `x` and `y` specify the column and row coordinates 

133 or ``len(z) == len(x) == len(y)`` if `x` and `y` specify coordinates 

134 for each point. 

135 kind : {'linear', 'cubic', 'quintic'}, optional 

136 The kind of spline interpolation to use. Default is 'linear'. 

137 copy : bool, optional 

138 If True, the class makes internal copies of x, y and z. 

139 If False, references may be used. The default is to copy. 

140 bounds_error : bool, optional 

141 If True, when interpolated values are requested outside of the 

142 domain of the input data (x,y), a ValueError is raised. 

143 If False, then `fill_value` is used. 

144 fill_value : number, optional 

145 If provided, the value to use for points outside of the 

146 interpolation domain. If omitted (None), values outside 

147 the domain are extrapolated via nearest-neighbor extrapolation. 

148 

149 See Also 

150 -------- 

151 RectBivariateSpline : 

152 Much faster 2-D interpolation if your input data is on a grid 

153 bisplrep, bisplev : 

154 Spline interpolation based on FITPACK 

155 BivariateSpline : a more recent wrapper of the FITPACK routines 

156 interp1d : 1-D version of this function 

157 

158 Notes 

159 ----- 

160 The minimum number of data points required along the interpolation 

161 axis is ``(k+1)**2``, with k=1 for linear, k=3 for cubic and k=5 for 

162 quintic interpolation. 

163 

164 The interpolator is constructed by `bisplrep`, with a smoothing factor 

165 of 0. If more control over smoothing is needed, `bisplrep` should be 

166 used directly. 

167 

168 Examples 

169 -------- 

170 Construct a 2-D grid and interpolate on it: 

171 

172 >>> from scipy import interpolate 

173 >>> x = np.arange(-5.01, 5.01, 0.25) 

174 >>> y = np.arange(-5.01, 5.01, 0.25) 

175 >>> xx, yy = np.meshgrid(x, y) 

176 >>> z = np.sin(xx**2+yy**2) 

177 >>> f = interpolate.interp2d(x, y, z, kind='cubic') 

178 

179 Now use the obtained interpolation function and plot the result: 

180 

181 >>> import matplotlib.pyplot as plt 

182 >>> xnew = np.arange(-5.01, 5.01, 1e-2) 

183 >>> ynew = np.arange(-5.01, 5.01, 1e-2) 

184 >>> znew = f(xnew, ynew) 

185 >>> plt.plot(x, z[0, :], 'ro-', xnew, znew[0, :], 'b-') 

186 >>> plt.show() 

187 """ 

188 

189 def __init__(self, x, y, z, kind='linear', copy=True, bounds_error=False, 

190 fill_value=None): 

191 x = ravel(x) 

192 y = ravel(y) 

193 z = asarray(z) 

194 

195 rectangular_grid = (z.size == len(x) * len(y)) 

196 if rectangular_grid: 

197 if z.ndim == 2: 

198 if z.shape != (len(y), len(x)): 

199 raise ValueError("When on a regular grid with x.size = m " 

200 "and y.size = n, if z.ndim == 2, then z " 

201 "must have shape (n, m)") 

202 if not np.all(x[1:] >= x[:-1]): 

203 j = np.argsort(x) 

204 x = x[j] 

205 z = z[:, j] 

206 if not np.all(y[1:] >= y[:-1]): 

207 j = np.argsort(y) 

208 y = y[j] 

209 z = z[j, :] 

210 z = ravel(z.T) 

211 else: 

212 z = ravel(z) 

213 if len(x) != len(y): 

214 raise ValueError( 

215 "x and y must have equal lengths for non rectangular grid") 

216 if len(z) != len(x): 

217 raise ValueError( 

218 "Invalid length for input z for non rectangular grid") 

219 

220 try: 

221 kx = ky = {'linear': 1, 

222 'cubic': 3, 

223 'quintic': 5}[kind] 

224 except KeyError: 

225 raise ValueError("Unsupported interpolation type.") 

226 

227 if not rectangular_grid: 

228 # TODO: surfit is really not meant for interpolation! 

229 self.tck = fitpack.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0) 

230 else: 

231 nx, tx, ny, ty, c, fp, ier = dfitpack.regrid_smth( 

232 x, y, z, None, None, None, None, 

233 kx=kx, ky=ky, s=0.0) 

234 self.tck = (tx[:nx], ty[:ny], c[:(nx - kx - 1) * (ny - ky - 1)], 

235 kx, ky) 

236 

237 self.bounds_error = bounds_error 

238 self.fill_value = fill_value 

239 self.x, self.y, self.z = [array(a, copy=copy) for a in (x, y, z)] 

240 

241 self.x_min, self.x_max = np.amin(x), np.amax(x) 

242 self.y_min, self.y_max = np.amin(y), np.amax(y) 

243 

244 def __call__(self, x, y, dx=0, dy=0, assume_sorted=False): 

245 """Interpolate the function. 

246 

247 Parameters 

248 ---------- 

249 x : 1-D array 

250 x-coordinates of the mesh on which to interpolate. 

251 y : 1-D array 

252 y-coordinates of the mesh on which to interpolate. 

253 dx : int >= 0, < kx 

254 Order of partial derivatives in x. 

255 dy : int >= 0, < ky 

256 Order of partial derivatives in y. 

257 assume_sorted : bool, optional 

258 If False, values of `x` and `y` can be in any order and they are 

259 sorted first. 

260 If True, `x` and `y` have to be arrays of monotonically 

261 increasing values. 

262 

263 Returns 

264 ------- 

265 z : 2-D array with shape (len(y), len(x)) 

266 The interpolated values. 

267 """ 

268 

269 x = atleast_1d(x) 

270 y = atleast_1d(y) 

271 

272 if x.ndim != 1 or y.ndim != 1: 

273 raise ValueError("x and y should both be 1-D arrays") 

274 

275 if not assume_sorted: 

276 x = np.sort(x) 

277 y = np.sort(y) 

278 

279 if self.bounds_error or self.fill_value is not None: 

280 out_of_bounds_x = (x < self.x_min) | (x > self.x_max) 

281 out_of_bounds_y = (y < self.y_min) | (y > self.y_max) 

282 

283 any_out_of_bounds_x = np.any(out_of_bounds_x) 

284 any_out_of_bounds_y = np.any(out_of_bounds_y) 

285 

286 if self.bounds_error and (any_out_of_bounds_x or any_out_of_bounds_y): 

287 raise ValueError("Values out of range; x must be in %r, y in %r" 

288 % ((self.x_min, self.x_max), 

289 (self.y_min, self.y_max))) 

290 

291 z = fitpack.bisplev(x, y, self.tck, dx, dy) 

292 z = atleast_2d(z) 

293 z = transpose(z) 

294 

295 if self.fill_value is not None: 

296 if any_out_of_bounds_x: 

297 z[:, out_of_bounds_x] = self.fill_value 

298 if any_out_of_bounds_y: 

299 z[out_of_bounds_y, :] = self.fill_value 

300 

301 if len(z) == 1: 

302 z = z[0] 

303 return array(z) 

304 

305 

306def _check_broadcast_up_to(arr_from, shape_to, name): 

307 """Helper to check that arr_from broadcasts up to shape_to""" 

308 shape_from = arr_from.shape 

309 if len(shape_to) >= len(shape_from): 

310 for t, f in zip(shape_to[::-1], shape_from[::-1]): 

311 if f != 1 and f != t: 

312 break 

313 else: # all checks pass, do the upcasting that we need later 

314 if arr_from.size != 1 and arr_from.shape != shape_to: 

315 arr_from = np.ones(shape_to, arr_from.dtype) * arr_from 

316 return arr_from.ravel() 

317 # at least one check failed 

318 raise ValueError('%s argument must be able to broadcast up ' 

319 'to shape %s but had shape %s' 

320 % (name, shape_to, shape_from)) 

321 

322 

323def _do_extrapolate(fill_value): 

324 """Helper to check if fill_value == "extrapolate" without warnings""" 

325 return (isinstance(fill_value, str) and 

326 fill_value == 'extrapolate') 

327 

328 

329class interp1d(_Interpolator1D): 

330 """ 

331 Interpolate a 1-D function. 

332 

333 `x` and `y` are arrays of values used to approximate some function f: 

334 ``y = f(x)``. This class returns a function whose call method uses 

335 interpolation to find the value of new points. 

336 

337 Note that calling `interp1d` with NaNs present in input values results in 

338 undefined behaviour. 

339 

340 Parameters 

341 ---------- 

342 x : (N,) array_like 

343 A 1-D array of real values. 

344 y : (...,N,...) array_like 

345 A N-D array of real values. The length of `y` along the interpolation 

346 axis must be equal to the length of `x`. 

347 kind : str or int, optional 

348 Specifies the kind of interpolation as a string 

349 ('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 

350 'previous', 'next', where 'zero', 'slinear', 'quadratic' and 'cubic' 

351 refer to a spline interpolation of zeroth, first, second or third 

352 order; 'previous' and 'next' simply return the previous or next value 

353 of the point) or as an integer specifying the order of the spline 

354 interpolator to use. 

355 Default is 'linear'. 

356 axis : int, optional 

357 Specifies the axis of `y` along which to interpolate. 

358 Interpolation defaults to the last axis of `y`. 

359 copy : bool, optional 

360 If True, the class makes internal copies of x and y. 

361 If False, references to `x` and `y` are used. The default is to copy. 

362 bounds_error : bool, optional 

363 If True, a ValueError is raised any time interpolation is attempted on 

364 a value outside of the range of x (where extrapolation is 

365 necessary). If False, out of bounds values are assigned `fill_value`. 

366 By default, an error is raised unless ``fill_value="extrapolate"``. 

367 fill_value : array-like or (array-like, array_like) or "extrapolate", optional 

368 - if a ndarray (or float), this value will be used to fill in for 

369 requested points outside of the data range. If not provided, then 

370 the default is NaN. The array-like must broadcast properly to the 

371 dimensions of the non-interpolation axes. 

372 - If a two-element tuple, then the first element is used as a 

373 fill value for ``x_new < x[0]`` and the second element is used for 

374 ``x_new > x[-1]``. Anything that is not a 2-element tuple (e.g., 

375 list or ndarray, regardless of shape) is taken to be a single 

376 array-like argument meant to be used for both bounds as 

377 ``below, above = fill_value, fill_value``. 

378 

379 .. versionadded:: 0.17.0 

380 - If "extrapolate", then points outside the data range will be 

381 extrapolated. 

382 

383 .. versionadded:: 0.17.0 

384 assume_sorted : bool, optional 

385 If False, values of `x` can be in any order and they are sorted first. 

386 If True, `x` has to be an array of monotonically increasing values. 

387 

388 Attributes 

389 ---------- 

390 fill_value 

391 

392 Methods 

393 ------- 

394 __call__ 

395 

396 See Also 

397 -------- 

398 splrep, splev 

399 Spline interpolation/smoothing based on FITPACK. 

400 UnivariateSpline : An object-oriented wrapper of the FITPACK routines. 

401 interp2d : 2-D interpolation 

402 

403 Examples 

404 -------- 

405 >>> import matplotlib.pyplot as plt 

406 >>> from scipy import interpolate 

407 >>> x = np.arange(0, 10) 

408 >>> y = np.exp(-x/3.0) 

409 >>> f = interpolate.interp1d(x, y) 

410 

411 >>> xnew = np.arange(0, 9, 0.1) 

412 >>> ynew = f(xnew) # use interpolation function returned by `interp1d` 

413 >>> plt.plot(x, y, 'o', xnew, ynew, '-') 

414 >>> plt.show() 

415 """ 

416 

417 def __init__(self, x, y, kind='linear', axis=-1, 

418 copy=True, bounds_error=None, fill_value=np.nan, 

419 assume_sorted=False): 

420 """ Initialize a 1-D linear interpolation class.""" 

421 _Interpolator1D.__init__(self, x, y, axis=axis) 

422 

423 self.bounds_error = bounds_error # used by fill_value setter 

424 self.copy = copy 

425 

426 if kind in ['zero', 'slinear', 'quadratic', 'cubic']: 

427 order = {'zero': 0, 'slinear': 1, 

428 'quadratic': 2, 'cubic': 3}[kind] 

429 kind = 'spline' 

430 elif isinstance(kind, int): 

431 order = kind 

432 kind = 'spline' 

433 elif kind not in ('linear', 'nearest', 'previous', 'next'): 

434 raise NotImplementedError("%s is unsupported: Use fitpack " 

435 "routines for other types." % kind) 

436 x = array(x, copy=self.copy) 

437 y = array(y, copy=self.copy) 

438 

439 if not assume_sorted: 

440 ind = np.argsort(x) 

441 x = x[ind] 

442 y = np.take(y, ind, axis=axis) 

443 

444 if x.ndim != 1: 

445 raise ValueError("the x array must have exactly one dimension.") 

446 if y.ndim == 0: 

447 raise ValueError("the y array must have at least one dimension.") 

448 

449 # Force-cast y to a floating-point type, if it's not yet one 

450 if not issubclass(y.dtype.type, np.inexact): 

451 y = y.astype(np.float_) 

452 

453 # Backward compatibility 

454 self.axis = axis % y.ndim 

455 

456 # Interpolation goes internally along the first axis 

457 self.y = y 

458 self._y = self._reshape_yi(self.y) 

459 self.x = x 

460 del y, x # clean up namespace to prevent misuse; use attributes 

461 self._kind = kind 

462 self.fill_value = fill_value # calls the setter, can modify bounds_err 

463 

464 # Adjust to interpolation kind; store reference to *unbound* 

465 # interpolation methods, in order to avoid circular references to self 

466 # stored in the bound instance methods, and therefore delayed garbage 

467 # collection. See: https://docs.python.org/reference/datamodel.html 

468 if kind in ('linear', 'nearest', 'previous', 'next'): 

469 # Make a "view" of the y array that is rotated to the interpolation 

470 # axis. 

471 minval = 2 

472 if kind == 'nearest': 

473 # Do division before addition to prevent possible integer 

474 # overflow 

475 self.x_bds = self.x / 2.0 

476 self.x_bds = self.x_bds[1:] + self.x_bds[:-1] 

477 

478 self._call = self.__class__._call_nearest 

479 elif kind == 'previous': 

480 # Side for np.searchsorted and index for clipping 

481 self._side = 'left' 

482 self._ind = 0 

483 # Move x by one floating point value to the left 

484 self._x_shift = np.nextafter(self.x, -np.inf) 

485 self._call = self.__class__._call_previousnext 

486 elif kind == 'next': 

487 self._side = 'right' 

488 self._ind = 1 

489 # Move x by one floating point value to the right 

490 self._x_shift = np.nextafter(self.x, np.inf) 

491 self._call = self.__class__._call_previousnext 

492 else: 

493 # Check if we can delegate to numpy.interp (2x-10x faster). 

494 cond = self.x.dtype == np.float_ and self.y.dtype == np.float_ 

495 cond = cond and self.y.ndim == 1 

496 cond = cond and not _do_extrapolate(fill_value) 

497 

498 if cond: 

499 self._call = self.__class__._call_linear_np 

500 else: 

501 self._call = self.__class__._call_linear 

502 else: 

503 minval = order + 1 

504 

505 rewrite_nan = False 

506 xx, yy = self.x, self._y 

507 if order > 1: 

508 # Quadratic or cubic spline. If input contains even a single 

509 # nan, then the output is all nans. We cannot just feed data 

510 # with nans to make_interp_spline because it calls LAPACK. 

511 # So, we make up a bogus x and y with no nans and use it 

512 # to get the correct shape of the output, which we then fill 

513 # with nans. 

514 # For slinear or zero order spline, we just pass nans through. 

515 mask = np.isnan(self.x) 

516 if mask.any(): 

517 sx = self.x[~mask] 

518 if sx.size == 0: 

519 raise ValueError("`x` array is all-nan") 

520 xx = np.linspace(np.nanmin(self.x), 

521 np.nanmax(self.x), 

522 len(self.x)) 

523 rewrite_nan = True 

524 if np.isnan(self._y).any(): 

525 yy = np.ones_like(self._y) 

526 rewrite_nan = True 

527 

528 self._spline = make_interp_spline(xx, yy, k=order, 

529 check_finite=False) 

530 if rewrite_nan: 

531 self._call = self.__class__._call_nan_spline 

532 else: 

533 self._call = self.__class__._call_spline 

534 

535 if len(self.x) < minval: 

536 raise ValueError("x and y arrays must have at " 

537 "least %d entries" % minval) 

538 

539 @property 

540 def fill_value(self): 

541 """The fill value.""" 

542 # backwards compat: mimic a public attribute 

543 return self._fill_value_orig 

544 

545 @fill_value.setter 

546 def fill_value(self, fill_value): 

547 # extrapolation only works for nearest neighbor and linear methods 

548 if _do_extrapolate(fill_value): 

549 if self.bounds_error: 

550 raise ValueError("Cannot extrapolate and raise " 

551 "at the same time.") 

552 self.bounds_error = False 

553 self._extrapolate = True 

554 else: 

555 broadcast_shape = (self.y.shape[:self.axis] + 

556 self.y.shape[self.axis + 1:]) 

557 if len(broadcast_shape) == 0: 

558 broadcast_shape = (1,) 

559 # it's either a pair (_below_range, _above_range) or a single value 

560 # for both above and below range 

561 if isinstance(fill_value, tuple) and len(fill_value) == 2: 

562 below_above = [np.asarray(fill_value[0]), 

563 np.asarray(fill_value[1])] 

564 names = ('fill_value (below)', 'fill_value (above)') 

565 for ii in range(2): 

566 below_above[ii] = _check_broadcast_up_to( 

567 below_above[ii], broadcast_shape, names[ii]) 

568 else: 

569 fill_value = np.asarray(fill_value) 

570 below_above = [_check_broadcast_up_to( 

571 fill_value, broadcast_shape, 'fill_value')] * 2 

572 self._fill_value_below, self._fill_value_above = below_above 

573 self._extrapolate = False 

574 if self.bounds_error is None: 

575 self.bounds_error = True 

576 # backwards compat: fill_value was a public attr; make it writeable 

577 self._fill_value_orig = fill_value 

578 

579 def _call_linear_np(self, x_new): 

580 # Note that out-of-bounds values are taken care of in self._evaluate 

581 return np.interp(x_new, self.x, self.y) 

582 

583 def _call_linear(self, x_new): 

584 # 2. Find where in the original data, the values to interpolate 

585 # would be inserted. 

586 # Note: If x_new[n] == x[m], then m is returned by searchsorted. 

587 x_new_indices = searchsorted(self.x, x_new) 

588 

589 # 3. Clip x_new_indices so that they are within the range of 

590 # self.x indices and at least 1. Removes mis-interpolation 

591 # of x_new[n] = x[0] 

592 x_new_indices = x_new_indices.clip(1, len(self.x)-1).astype(int) 

593 

594 # 4. Calculate the slope of regions that each x_new value falls in. 

595 lo = x_new_indices - 1 

596 hi = x_new_indices 

597 

598 x_lo = self.x[lo] 

599 x_hi = self.x[hi] 

600 y_lo = self._y[lo] 

601 y_hi = self._y[hi] 

602 

603 # Note that the following two expressions rely on the specifics of the 

604 # broadcasting semantics. 

605 slope = (y_hi - y_lo) / (x_hi - x_lo)[:, None] 

606 

607 # 5. Calculate the actual value for each entry in x_new. 

608 y_new = slope*(x_new - x_lo)[:, None] + y_lo 

609 

610 return y_new 

611 

612 def _call_nearest(self, x_new): 

613 """ Find nearest neighbor interpolated y_new = f(x_new).""" 

614 

615 # 2. Find where in the averaged data the values to interpolate 

616 # would be inserted. 

617 # Note: use side='left' (right) to searchsorted() to define the 

618 # halfway point to be nearest to the left (right) neighbor 

619 x_new_indices = searchsorted(self.x_bds, x_new, side='left') 

620 

621 # 3. Clip x_new_indices so that they are within the range of x indices. 

622 x_new_indices = x_new_indices.clip(0, len(self.x)-1).astype(intp) 

623 

624 # 4. Calculate the actual value for each entry in x_new. 

625 y_new = self._y[x_new_indices] 

626 

627 return y_new 

628 

629 def _call_previousnext(self, x_new): 

630 """Use previous/next neighbor of x_new, y_new = f(x_new).""" 

631 

632 # 1. Get index of left/right value 

633 x_new_indices = searchsorted(self._x_shift, x_new, side=self._side) 

634 

635 # 2. Clip x_new_indices so that they are within the range of x indices. 

636 x_new_indices = x_new_indices.clip(1-self._ind, 

637 len(self.x)-self._ind).astype(intp) 

638 

639 # 3. Calculate the actual value for each entry in x_new. 

640 y_new = self._y[x_new_indices+self._ind-1] 

641 

642 return y_new 

643 

644 def _call_spline(self, x_new): 

645 return self._spline(x_new) 

646 

647 def _call_nan_spline(self, x_new): 

648 out = self._spline(x_new) 

649 out[...] = np.nan 

650 return out 

651 

652 def _evaluate(self, x_new): 

653 # 1. Handle values in x_new that are outside of x. Throw error, 

654 # or return a list of mask array indicating the outofbounds values. 

655 # The behavior is set by the bounds_error variable. 

656 x_new = asarray(x_new) 

657 y_new = self._call(self, x_new) 

658 if not self._extrapolate: 

659 below_bounds, above_bounds = self._check_bounds(x_new) 

660 if len(y_new) > 0: 

661 # Note fill_value must be broadcast up to the proper size 

662 # and flattened to work here 

663 y_new[below_bounds] = self._fill_value_below 

664 y_new[above_bounds] = self._fill_value_above 

665 return y_new 

666 

667 def _check_bounds(self, x_new): 

668 """Check the inputs for being in the bounds of the interpolated data. 

669 

670 Parameters 

671 ---------- 

672 x_new : array 

673 

674 Returns 

675 ------- 

676 out_of_bounds : bool array 

677 The mask on x_new of values that are out of the bounds. 

678 """ 

679 

680 # If self.bounds_error is True, we raise an error if any x_new values 

681 # fall outside the range of x. Otherwise, we return an array indicating 

682 # which values are outside the boundary region. 

683 below_bounds = x_new < self.x[0] 

684 above_bounds = x_new > self.x[-1] 

685 

686 # !! Could provide more information about which values are out of bounds 

687 if self.bounds_error and below_bounds.any(): 

688 raise ValueError("A value in x_new is below the interpolation " 

689 "range.") 

690 if self.bounds_error and above_bounds.any(): 

691 raise ValueError("A value in x_new is above the interpolation " 

692 "range.") 

693 

694 # !! Should we emit a warning if some values are out of bounds? 

695 # !! matlab does not. 

696 return below_bounds, above_bounds 

697 

698 

699class _PPolyBase(object): 

700 """Base class for piecewise polynomials.""" 

701 __slots__ = ('c', 'x', 'extrapolate', 'axis') 

702 

703 def __init__(self, c, x, extrapolate=None, axis=0): 

704 self.c = np.asarray(c) 

705 self.x = np.ascontiguousarray(x, dtype=np.float64) 

706 

707 if extrapolate is None: 

708 extrapolate = True 

709 elif extrapolate != 'periodic': 

710 extrapolate = bool(extrapolate) 

711 self.extrapolate = extrapolate 

712 

713 if self.c.ndim < 2: 

714 raise ValueError("Coefficients array must be at least " 

715 "2-dimensional.") 

716 

717 if not (0 <= axis < self.c.ndim - 1): 

718 raise ValueError("axis=%s must be between 0 and %s" % 

719 (axis, self.c.ndim-1)) 

720 

721 self.axis = axis 

722 if axis != 0: 

723 # roll the interpolation axis to be the first one in self.c 

724 # More specifically, the target shape for self.c is (k, m, ...), 

725 # and axis !=0 means that we have c.shape (..., k, m, ...) 

726 # ^ 

727 # axis 

728 # So we roll two of them. 

729 self.c = np.rollaxis(self.c, axis+1) 

730 self.c = np.rollaxis(self.c, axis+1) 

731 

732 if self.x.ndim != 1: 

733 raise ValueError("x must be 1-dimensional") 

734 if self.x.size < 2: 

735 raise ValueError("at least 2 breakpoints are needed") 

736 if self.c.ndim < 2: 

737 raise ValueError("c must have at least 2 dimensions") 

738 if self.c.shape[0] == 0: 

739 raise ValueError("polynomial must be at least of order 0") 

740 if self.c.shape[1] != self.x.size-1: 

741 raise ValueError("number of coefficients != len(x)-1") 

742 dx = np.diff(self.x) 

743 if not (np.all(dx >= 0) or np.all(dx <= 0)): 

744 raise ValueError("`x` must be strictly increasing or decreasing.") 

745 

746 dtype = self._get_dtype(self.c.dtype) 

747 self.c = np.ascontiguousarray(self.c, dtype=dtype) 

748 

749 def _get_dtype(self, dtype): 

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

751 or np.issubdtype(self.c.dtype, np.complexfloating): 

752 return np.complex_ 

753 else: 

754 return np.float_ 

755 

756 @classmethod 

757 def construct_fast(cls, c, x, extrapolate=None, axis=0): 

758 """ 

759 Construct the piecewise polynomial without making checks. 

760 

761 Takes the same parameters as the constructor. Input arguments 

762 ``c`` and ``x`` must be arrays of the correct shape and type. The 

763 ``c`` array can only be of dtypes float and complex, and ``x`` 

764 array must have dtype float. 

765 """ 

766 self = object.__new__(cls) 

767 self.c = c 

768 self.x = x 

769 self.axis = axis 

770 if extrapolate is None: 

771 extrapolate = True 

772 self.extrapolate = extrapolate 

773 return self 

774 

775 def _ensure_c_contiguous(self): 

776 """ 

777 c and x may be modified by the user. The Cython code expects 

778 that they are C contiguous. 

779 """ 

780 if not self.x.flags.c_contiguous: 

781 self.x = self.x.copy() 

782 if not self.c.flags.c_contiguous: 

783 self.c = self.c.copy() 

784 

785 def extend(self, c, x, right=None): 

786 """ 

787 Add additional breakpoints and coefficients to the polynomial. 

788 

789 Parameters 

790 ---------- 

791 c : ndarray, size (k, m, ...) 

792 Additional coefficients for polynomials in intervals. Note that 

793 the first additional interval will be formed using one of the 

794 ``self.x`` end points. 

795 x : ndarray, size (m,) 

796 Additional breakpoints. Must be sorted in the same order as 

797 ``self.x`` and either to the right or to the left of the current 

798 breakpoints. 

799 right 

800 Deprecated argument. Has no effect. 

801 

802 .. deprecated:: 0.19 

803 """ 

804 if right is not None: 

805 warnings.warn("`right` is deprecated and will be removed.") 

806 

807 c = np.asarray(c) 

808 x = np.asarray(x) 

809 

810 if c.ndim < 2: 

811 raise ValueError("invalid dimensions for c") 

812 if x.ndim != 1: 

813 raise ValueError("invalid dimensions for x") 

814 if x.shape[0] != c.shape[1]: 

815 raise ValueError("x and c have incompatible sizes") 

816 if c.shape[2:] != self.c.shape[2:] or c.ndim != self.c.ndim: 

817 raise ValueError("c and self.c have incompatible shapes") 

818 

819 if c.size == 0: 

820 return 

821 

822 dx = np.diff(x) 

823 if not (np.all(dx >= 0) or np.all(dx <= 0)): 

824 raise ValueError("`x` is not sorted.") 

825 

826 if self.x[-1] >= self.x[0]: 

827 if not x[-1] >= x[0]: 

828 raise ValueError("`x` is in the different order " 

829 "than `self.x`.") 

830 

831 if x[0] >= self.x[-1]: 

832 action = 'append' 

833 elif x[-1] <= self.x[0]: 

834 action = 'prepend' 

835 else: 

836 raise ValueError("`x` is neither on the left or on the right " 

837 "from `self.x`.") 

838 else: 

839 if not x[-1] <= x[0]: 

840 raise ValueError("`x` is in the different order " 

841 "than `self.x`.") 

842 

843 if x[0] <= self.x[-1]: 

844 action = 'append' 

845 elif x[-1] >= self.x[0]: 

846 action = 'prepend' 

847 else: 

848 raise ValueError("`x` is neither on the left or on the right " 

849 "from `self.x`.") 

850 

851 dtype = self._get_dtype(c.dtype) 

852 

853 k2 = max(c.shape[0], self.c.shape[0]) 

854 c2 = np.zeros((k2, self.c.shape[1] + c.shape[1]) + self.c.shape[2:], 

855 dtype=dtype) 

856 

857 if action == 'append': 

858 c2[k2-self.c.shape[0]:, :self.c.shape[1]] = self.c 

859 c2[k2-c.shape[0]:, self.c.shape[1]:] = c 

860 self.x = np.r_[self.x, x] 

861 elif action == 'prepend': 

862 c2[k2-self.c.shape[0]:, :c.shape[1]] = c 

863 c2[k2-c.shape[0]:, c.shape[1]:] = self.c 

864 self.x = np.r_[x, self.x] 

865 

866 self.c = c2 

867 

868 def __call__(self, x, nu=0, extrapolate=None): 

869 """ 

870 Evaluate the piecewise polynomial or its derivative. 

871 

872 Parameters 

873 ---------- 

874 x : array_like 

875 Points to evaluate the interpolant at. 

876 nu : int, optional 

877 Order of derivative to evaluate. Must be non-negative. 

878 extrapolate : {bool, 'periodic', None}, optional 

879 If bool, determines whether to extrapolate to out-of-bounds points 

880 based on first and last intervals, or to return NaNs. 

881 If 'periodic', periodic extrapolation is used. 

882 If None (default), use `self.extrapolate`. 

883 

884 Returns 

885 ------- 

886 y : array_like 

887 Interpolated values. Shape is determined by replacing 

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

889 

890 Notes 

891 ----- 

892 Derivatives are evaluated piecewise for each polynomial 

893 segment, even if the polynomial is not differentiable at the 

894 breakpoints. The polynomial intervals are considered half-open, 

895 ``[a, b)``, except for the last interval which is closed 

896 ``[a, b]``. 

897 """ 

898 if extrapolate is None: 

899 extrapolate = self.extrapolate 

900 x = np.asarray(x) 

901 x_shape, x_ndim = x.shape, x.ndim 

902 x = np.ascontiguousarray(x.ravel(), dtype=np.float_) 

903 

904 # With periodic extrapolation we map x to the segment 

905 # [self.x[0], self.x[-1]]. 

906 if extrapolate == 'periodic': 

907 x = self.x[0] + (x - self.x[0]) % (self.x[-1] - self.x[0]) 

908 extrapolate = False 

909 

910 out = np.empty((len(x), prod(self.c.shape[2:])), dtype=self.c.dtype) 

911 self._ensure_c_contiguous() 

912 self._evaluate(x, nu, extrapolate, out) 

913 out = out.reshape(x_shape + self.c.shape[2:]) 

914 if self.axis != 0: 

915 # transpose to move the calculated values to the interpolation axis 

916 l = list(range(out.ndim)) 

917 l = l[x_ndim:x_ndim+self.axis] + l[:x_ndim] + l[x_ndim+self.axis:] 

918 out = out.transpose(l) 

919 return out 

920 

921 

922class PPoly(_PPolyBase): 

923 """ 

924 Piecewise polynomial in terms of coefficients and breakpoints 

925 

926 The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the 

927 local power basis:: 

928 

929 S = sum(c[m, i] * (xp - x[i])**(k-m) for m in range(k+1)) 

930 

931 where ``k`` is the degree of the polynomial. 

932 

933 Parameters 

934 ---------- 

935 c : ndarray, shape (k, m, ...) 

936 Polynomial coefficients, order `k` and `m` intervals. 

937 x : ndarray, shape (m+1,) 

938 Polynomial breakpoints. Must be sorted in either increasing or 

939 decreasing order. 

940 extrapolate : bool or 'periodic', optional 

941 If bool, determines whether to extrapolate to out-of-bounds points 

942 based on first and last intervals, or to return NaNs. If 'periodic', 

943 periodic extrapolation is used. Default is True. 

944 axis : int, optional 

945 Interpolation axis. Default is zero. 

946 

947 Attributes 

948 ---------- 

949 x : ndarray 

950 Breakpoints. 

951 c : ndarray 

952 Coefficients of the polynomials. They are reshaped 

953 to a 3-D array with the last dimension representing 

954 the trailing dimensions of the original coefficient array. 

955 axis : int 

956 Interpolation axis. 

957 

958 Methods 

959 ------- 

960 __call__ 

961 derivative 

962 antiderivative 

963 integrate 

964 solve 

965 roots 

966 extend 

967 from_spline 

968 from_bernstein_basis 

969 construct_fast 

970 

971 See also 

972 -------- 

973 BPoly : piecewise polynomials in the Bernstein basis 

974 

975 Notes 

976 ----- 

977 High-order polynomials in the power basis can be numerically 

978 unstable. Precision problems can start to appear for orders 

979 larger than 20-30. 

980 """ 

981 def _evaluate(self, x, nu, extrapolate, out): 

982 _ppoly.evaluate(self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

983 self.x, x, nu, bool(extrapolate), out) 

984 

985 def derivative(self, nu=1): 

986 """ 

987 Construct a new piecewise polynomial representing the derivative. 

988 

989 Parameters 

990 ---------- 

991 nu : int, optional 

992 Order of derivative to evaluate. Default is 1, i.e., compute the 

993 first derivative. If negative, the antiderivative is returned. 

994 

995 Returns 

996 ------- 

997 pp : PPoly 

998 Piecewise polynomial of order k2 = k - n representing the derivative 

999 of this polynomial. 

1000 

1001 Notes 

1002 ----- 

1003 Derivatives are evaluated piecewise for each polynomial 

1004 segment, even if the polynomial is not differentiable at the 

1005 breakpoints. The polynomial intervals are considered half-open, 

1006 ``[a, b)``, except for the last interval which is closed 

1007 ``[a, b]``. 

1008 """ 

1009 if nu < 0: 

1010 return self.antiderivative(-nu) 

1011 

1012 # reduce order 

1013 if nu == 0: 

1014 c2 = self.c.copy() 

1015 else: 

1016 c2 = self.c[:-nu, :].copy() 

1017 

1018 if c2.shape[0] == 0: 

1019 # derivative of order 0 is zero 

1020 c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype) 

1021 

1022 # multiply by the correct rising factorials 

1023 factor = spec.poch(np.arange(c2.shape[0], 0, -1), nu) 

1024 c2 *= factor[(slice(None),) + (None,)*(c2.ndim-1)] 

1025 

1026 # construct a compatible polynomial 

1027 return self.construct_fast(c2, self.x, self.extrapolate, self.axis) 

1028 

1029 def antiderivative(self, nu=1): 

1030 """ 

1031 Construct a new piecewise polynomial representing the antiderivative. 

1032 

1033 Antiderivative is also the indefinite integral of the function, 

1034 and derivative is its inverse operation. 

1035 

1036 Parameters 

1037 ---------- 

1038 nu : int, optional 

1039 Order of antiderivative to evaluate. Default is 1, i.e., compute 

1040 the first integral. If negative, the derivative is returned. 

1041 

1042 Returns 

1043 ------- 

1044 pp : PPoly 

1045 Piecewise polynomial of order k2 = k + n representing 

1046 the antiderivative of this polynomial. 

1047 

1048 Notes 

1049 ----- 

1050 The antiderivative returned by this function is continuous and 

1051 continuously differentiable to order n-1, up to floating point 

1052 rounding error. 

1053 

1054 If antiderivative is computed and ``self.extrapolate='periodic'``, 

1055 it will be set to False for the returned instance. This is done because 

1056 the antiderivative is no longer periodic and its correct evaluation 

1057 outside of the initially given x interval is difficult. 

1058 """ 

1059 if nu <= 0: 

1060 return self.derivative(-nu) 

1061 

1062 c = np.zeros((self.c.shape[0] + nu, self.c.shape[1]) + self.c.shape[2:], 

1063 dtype=self.c.dtype) 

1064 c[:-nu] = self.c 

1065 

1066 # divide by the correct rising factorials 

1067 factor = spec.poch(np.arange(self.c.shape[0], 0, -1), nu) 

1068 c[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)] 

1069 

1070 # fix continuity of added degrees of freedom 

1071 self._ensure_c_contiguous() 

1072 _ppoly.fix_continuity(c.reshape(c.shape[0], c.shape[1], -1), 

1073 self.x, nu - 1) 

1074 

1075 if self.extrapolate == 'periodic': 

1076 extrapolate = False 

1077 else: 

1078 extrapolate = self.extrapolate 

1079 

1080 # construct a compatible polynomial 

1081 return self.construct_fast(c, self.x, extrapolate, self.axis) 

1082 

1083 def integrate(self, a, b, extrapolate=None): 

1084 """ 

1085 Compute a definite integral over a piecewise polynomial. 

1086 

1087 Parameters 

1088 ---------- 

1089 a : float 

1090 Lower integration bound 

1091 b : float 

1092 Upper integration bound 

1093 extrapolate : {bool, 'periodic', None}, optional 

1094 If bool, determines whether to extrapolate to out-of-bounds points 

1095 based on first and last intervals, or to return NaNs. 

1096 If 'periodic', periodic extrapolation is used. 

1097 If None (default), use `self.extrapolate`. 

1098 

1099 Returns 

1100 ------- 

1101 ig : array_like 

1102 Definite integral of the piecewise polynomial over [a, b] 

1103 """ 

1104 if extrapolate is None: 

1105 extrapolate = self.extrapolate 

1106 

1107 # Swap integration bounds if needed 

1108 sign = 1 

1109 if b < a: 

1110 a, b = b, a 

1111 sign = -1 

1112 

1113 range_int = np.empty((prod(self.c.shape[2:]),), dtype=self.c.dtype) 

1114 self._ensure_c_contiguous() 

1115 

1116 # Compute the integral. 

1117 if extrapolate == 'periodic': 

1118 # Split the integral into the part over period (can be several 

1119 # of them) and the remaining part. 

1120 

1121 xs, xe = self.x[0], self.x[-1] 

1122 period = xe - xs 

1123 interval = b - a 

1124 n_periods, left = divmod(interval, period) 

1125 

1126 if n_periods > 0: 

1127 _ppoly.integrate( 

1128 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1129 self.x, xs, xe, False, out=range_int) 

1130 range_int *= n_periods 

1131 else: 

1132 range_int.fill(0) 

1133 

1134 # Map a to [xs, xe], b is always a + left. 

1135 a = xs + (a - xs) % period 

1136 b = a + left 

1137 

1138 # If b <= xe then we need to integrate over [a, b], otherwise 

1139 # over [a, xe] and from xs to what is remained. 

1140 remainder_int = np.empty_like(range_int) 

1141 if b <= xe: 

1142 _ppoly.integrate( 

1143 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1144 self.x, a, b, False, out=remainder_int) 

1145 range_int += remainder_int 

1146 else: 

1147 _ppoly.integrate( 

1148 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1149 self.x, a, xe, False, out=remainder_int) 

1150 range_int += remainder_int 

1151 

1152 _ppoly.integrate( 

1153 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1154 self.x, xs, xs + left + a - xe, False, out=remainder_int) 

1155 range_int += remainder_int 

1156 else: 

1157 _ppoly.integrate( 

1158 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1159 self.x, a, b, bool(extrapolate), out=range_int) 

1160 

1161 # Return 

1162 range_int *= sign 

1163 return range_int.reshape(self.c.shape[2:]) 

1164 

1165 def solve(self, y=0., discontinuity=True, extrapolate=None): 

1166 """ 

1167 Find real solutions of the the equation ``pp(x) == y``. 

1168 

1169 Parameters 

1170 ---------- 

1171 y : float, optional 

1172 Right-hand side. Default is zero. 

1173 discontinuity : bool, optional 

1174 Whether to report sign changes across discontinuities at 

1175 breakpoints as roots. 

1176 extrapolate : {bool, 'periodic', None}, optional 

1177 If bool, determines whether to return roots from the polynomial 

1178 extrapolated based on first and last intervals, 'periodic' works 

1179 the same as False. If None (default), use `self.extrapolate`. 

1180 

1181 Returns 

1182 ------- 

1183 roots : ndarray 

1184 Roots of the polynomial(s). 

1185 

1186 If the PPoly object describes multiple polynomials, the 

1187 return value is an object array whose each element is an 

1188 ndarray containing the roots. 

1189 

1190 Notes 

1191 ----- 

1192 This routine works only on real-valued polynomials. 

1193 

1194 If the piecewise polynomial contains sections that are 

1195 identically zero, the root list will contain the start point 

1196 of the corresponding interval, followed by a ``nan`` value. 

1197 

1198 If the polynomial is discontinuous across a breakpoint, and 

1199 there is a sign change across the breakpoint, this is reported 

1200 if the `discont` parameter is True. 

1201 

1202 Examples 

1203 -------- 

1204 

1205 Finding roots of ``[x**2 - 1, (x - 1)**2]`` defined on intervals 

1206 ``[-2, 1], [1, 2]``: 

1207 

1208 >>> from scipy.interpolate import PPoly 

1209 >>> pp = PPoly(np.array([[1, -4, 3], [1, 0, 0]]).T, [-2, 1, 2]) 

1210 >>> pp.solve() 

1211 array([-1., 1.]) 

1212 """ 

1213 if extrapolate is None: 

1214 extrapolate = self.extrapolate 

1215 

1216 self._ensure_c_contiguous() 

1217 

1218 if np.issubdtype(self.c.dtype, np.complexfloating): 

1219 raise ValueError("Root finding is only for " 

1220 "real-valued polynomials") 

1221 

1222 y = float(y) 

1223 r = _ppoly.real_roots(self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1224 self.x, y, bool(discontinuity), 

1225 bool(extrapolate)) 

1226 if self.c.ndim == 2: 

1227 return r[0] 

1228 else: 

1229 r2 = np.empty(prod(self.c.shape[2:]), dtype=object) 

1230 # this for-loop is equivalent to ``r2[...] = r``, but that's broken 

1231 # in NumPy 1.6.0 

1232 for ii, root in enumerate(r): 

1233 r2[ii] = root 

1234 

1235 return r2.reshape(self.c.shape[2:]) 

1236 

1237 def roots(self, discontinuity=True, extrapolate=None): 

1238 """ 

1239 Find real roots of the the piecewise polynomial. 

1240 

1241 Parameters 

1242 ---------- 

1243 discontinuity : bool, optional 

1244 Whether to report sign changes across discontinuities at 

1245 breakpoints as roots. 

1246 extrapolate : {bool, 'periodic', None}, optional 

1247 If bool, determines whether to return roots from the polynomial 

1248 extrapolated based on first and last intervals, 'periodic' works 

1249 the same as False. If None (default), use `self.extrapolate`. 

1250 

1251 Returns 

1252 ------- 

1253 roots : ndarray 

1254 Roots of the polynomial(s). 

1255 

1256 If the PPoly object describes multiple polynomials, the 

1257 return value is an object array whose each element is an 

1258 ndarray containing the roots. 

1259 

1260 See Also 

1261 -------- 

1262 PPoly.solve 

1263 """ 

1264 return self.solve(0, discontinuity, extrapolate) 

1265 

1266 @classmethod 

1267 def from_spline(cls, tck, extrapolate=None): 

1268 """ 

1269 Construct a piecewise polynomial from a spline 

1270 

1271 Parameters 

1272 ---------- 

1273 tck 

1274 A spline, as returned by `splrep` or a BSpline object. 

1275 extrapolate : bool or 'periodic', optional 

1276 If bool, determines whether to extrapolate to out-of-bounds points 

1277 based on first and last intervals, or to return NaNs. 

1278 If 'periodic', periodic extrapolation is used. Default is True. 

1279 """ 

1280 if isinstance(tck, BSpline): 

1281 t, c, k = tck.tck 

1282 if extrapolate is None: 

1283 extrapolate = tck.extrapolate 

1284 else: 

1285 t, c, k = tck 

1286 

1287 cvals = np.empty((k + 1, len(t)-1), dtype=c.dtype) 

1288 for m in range(k, -1, -1): 

1289 y = fitpack.splev(t[:-1], tck, der=m) 

1290 cvals[k - m, :] = y/spec.gamma(m+1) 

1291 

1292 return cls.construct_fast(cvals, t, extrapolate) 

1293 

1294 @classmethod 

1295 def from_bernstein_basis(cls, bp, extrapolate=None): 

1296 """ 

1297 Construct a piecewise polynomial in the power basis 

1298 from a polynomial in Bernstein basis. 

1299 

1300 Parameters 

1301 ---------- 

1302 bp : BPoly 

1303 A Bernstein basis polynomial, as created by BPoly 

1304 extrapolate : bool or 'periodic', optional 

1305 If bool, determines whether to extrapolate to out-of-bounds points 

1306 based on first and last intervals, or to return NaNs. 

1307 If 'periodic', periodic extrapolation is used. Default is True. 

1308 """ 

1309 if not isinstance(bp, BPoly): 

1310 raise TypeError(".from_bernstein_basis only accepts BPoly instances. " 

1311 "Got %s instead." % type(bp)) 

1312 

1313 dx = np.diff(bp.x) 

1314 k = bp.c.shape[0] - 1 # polynomial order 

1315 

1316 rest = (None,)*(bp.c.ndim-2) 

1317 

1318 c = np.zeros_like(bp.c) 

1319 for a in range(k+1): 

1320 factor = (-1)**a * comb(k, a) * bp.c[a] 

1321 for s in range(a, k+1): 

1322 val = comb(k-a, s-a) * (-1)**s 

1323 c[k-s] += factor * val / dx[(slice(None),)+rest]**s 

1324 

1325 if extrapolate is None: 

1326 extrapolate = bp.extrapolate 

1327 

1328 return cls.construct_fast(c, bp.x, extrapolate, bp.axis) 

1329 

1330 

1331class BPoly(_PPolyBase): 

1332 """Piecewise polynomial in terms of coefficients and breakpoints. 

1333 

1334 The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the 

1335 Bernstein polynomial basis:: 

1336 

1337 S = sum(c[a, i] * b(a, k; x) for a in range(k+1)), 

1338 

1339 where ``k`` is the degree of the polynomial, and:: 

1340 

1341 b(a, k; x) = binom(k, a) * t**a * (1 - t)**(k - a), 

1342 

1343 with ``t = (x - x[i]) / (x[i+1] - x[i])`` and ``binom`` is the binomial 

1344 coefficient. 

1345 

1346 Parameters 

1347 ---------- 

1348 c : ndarray, shape (k, m, ...) 

1349 Polynomial coefficients, order `k` and `m` intervals 

1350 x : ndarray, shape (m+1,) 

1351 Polynomial breakpoints. Must be sorted in either increasing or 

1352 decreasing order. 

1353 extrapolate : bool, optional 

1354 If bool, determines whether to extrapolate to out-of-bounds points 

1355 based on first and last intervals, or to return NaNs. If 'periodic', 

1356 periodic extrapolation is used. Default is True. 

1357 axis : int, optional 

1358 Interpolation axis. Default is zero. 

1359 

1360 Attributes 

1361 ---------- 

1362 x : ndarray 

1363 Breakpoints. 

1364 c : ndarray 

1365 Coefficients of the polynomials. They are reshaped 

1366 to a 3-D array with the last dimension representing 

1367 the trailing dimensions of the original coefficient array. 

1368 axis : int 

1369 Interpolation axis. 

1370 

1371 Methods 

1372 ------- 

1373 __call__ 

1374 extend 

1375 derivative 

1376 antiderivative 

1377 integrate 

1378 construct_fast 

1379 from_power_basis 

1380 from_derivatives 

1381 

1382 See also 

1383 -------- 

1384 PPoly : piecewise polynomials in the power basis 

1385 

1386 Notes 

1387 ----- 

1388 Properties of Bernstein polynomials are well documented in the literature, 

1389 see for example [1]_ [2]_ [3]_. 

1390 

1391 References 

1392 ---------- 

1393 .. [1] https://en.wikipedia.org/wiki/Bernstein_polynomial 

1394 

1395 .. [2] Kenneth I. Joy, Bernstein polynomials, 

1396 http://www.idav.ucdavis.edu/education/CAGDNotes/Bernstein-Polynomials.pdf 

1397 

1398 .. [3] E. H. Doha, A. H. Bhrawy, and M. A. Saker, Boundary Value Problems, 

1399 vol 2011, article ID 829546, :doi:`10.1155/2011/829543`. 

1400 

1401 Examples 

1402 -------- 

1403 >>> from scipy.interpolate import BPoly 

1404 >>> x = [0, 1] 

1405 >>> c = [[1], [2], [3]] 

1406 >>> bp = BPoly(c, x) 

1407 

1408 This creates a 2nd order polynomial 

1409 

1410 .. math:: 

1411 

1412 B(x) = 1 \\times b_{0, 2}(x) + 2 \\times b_{1, 2}(x) + 3 \\times b_{2, 2}(x) \\\\ 

1413 = 1 \\times (1-x)^2 + 2 \\times 2 x (1 - x) + 3 \\times x^2 

1414 

1415 """ 

1416 

1417 def _evaluate(self, x, nu, extrapolate, out): 

1418 _ppoly.evaluate_bernstein( 

1419 self.c.reshape(self.c.shape[0], self.c.shape[1], -1), 

1420 self.x, x, nu, bool(extrapolate), out) 

1421 

1422 def derivative(self, nu=1): 

1423 """ 

1424 Construct a new piecewise polynomial representing the derivative. 

1425 

1426 Parameters 

1427 ---------- 

1428 nu : int, optional 

1429 Order of derivative to evaluate. Default is 1, i.e., compute the 

1430 first derivative. If negative, the antiderivative is returned. 

1431 

1432 Returns 

1433 ------- 

1434 bp : BPoly 

1435 Piecewise polynomial of order k - nu representing the derivative of 

1436 this polynomial. 

1437 

1438 """ 

1439 if nu < 0: 

1440 return self.antiderivative(-nu) 

1441 

1442 if nu > 1: 

1443 bp = self 

1444 for k in range(nu): 

1445 bp = bp.derivative() 

1446 return bp 

1447 

1448 # reduce order 

1449 if nu == 0: 

1450 c2 = self.c.copy() 

1451 else: 

1452 # For a polynomial 

1453 # B(x) = \sum_{a=0}^{k} c_a b_{a, k}(x), 

1454 # we use the fact that 

1455 # b'_{a, k} = k ( b_{a-1, k-1} - b_{a, k-1} ), 

1456 # which leads to 

1457 # B'(x) = \sum_{a=0}^{k-1} (c_{a+1} - c_a) b_{a, k-1} 

1458 # 

1459 # finally, for an interval [y, y + dy] with dy != 1, 

1460 # we need to correct for an extra power of dy 

1461 

1462 rest = (None,)*(self.c.ndim-2) 

1463 

1464 k = self.c.shape[0] - 1 

1465 dx = np.diff(self.x)[(None, slice(None))+rest] 

1466 c2 = k * np.diff(self.c, axis=0) / dx 

1467 

1468 if c2.shape[0] == 0: 

1469 # derivative of order 0 is zero 

1470 c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype) 

1471 

1472 # construct a compatible polynomial 

1473 return self.construct_fast(c2, self.x, self.extrapolate, self.axis) 

1474 

1475 def antiderivative(self, nu=1): 

1476 """ 

1477 Construct a new piecewise polynomial representing the antiderivative. 

1478 

1479 Parameters 

1480 ---------- 

1481 nu : int, optional 

1482 Order of antiderivative to evaluate. Default is 1, i.e., compute 

1483 the first integral. If negative, the derivative is returned. 

1484 

1485 Returns 

1486 ------- 

1487 bp : BPoly 

1488 Piecewise polynomial of order k + nu representing the 

1489 antiderivative of this polynomial. 

1490 

1491 Notes 

1492 ----- 

1493 If antiderivative is computed and ``self.extrapolate='periodic'``, 

1494 it will be set to False for the returned instance. This is done because 

1495 the antiderivative is no longer periodic and its correct evaluation 

1496 outside of the initially given x interval is difficult. 

1497 """ 

1498 if nu <= 0: 

1499 return self.derivative(-nu) 

1500 

1501 if nu > 1: 

1502 bp = self 

1503 for k in range(nu): 

1504 bp = bp.antiderivative() 

1505 return bp 

1506 

1507 # Construct the indefinite integrals on individual intervals 

1508 c, x = self.c, self.x 

1509 k = c.shape[0] 

1510 c2 = np.zeros((k+1,) + c.shape[1:], dtype=c.dtype) 

1511 

1512 c2[1:, ...] = np.cumsum(c, axis=0) / k 

1513 delta = x[1:] - x[:-1] 

1514 c2 *= delta[(None, slice(None)) + (None,)*(c.ndim-2)] 

1515 

1516 # Now fix continuity: on the very first interval, take the integration 

1517 # constant to be zero; on an interval [x_j, x_{j+1}) with j>0, 

1518 # the integration constant is then equal to the jump of the `bp` at x_j. 

1519 # The latter is given by the coefficient of B_{n+1, n+1} 

1520 # *on the previous interval* (other B. polynomials are zero at the 

1521 # breakpoint). Finally, use the fact that BPs form a partition of unity. 

1522 c2[:,1:] += np.cumsum(c2[k, :], axis=0)[:-1] 

1523 

1524 if self.extrapolate == 'periodic': 

1525 extrapolate = False 

1526 else: 

1527 extrapolate = self.extrapolate 

1528 

1529 return self.construct_fast(c2, x, extrapolate, axis=self.axis) 

1530 

1531 def integrate(self, a, b, extrapolate=None): 

1532 """ 

1533 Compute a definite integral over a piecewise polynomial. 

1534 

1535 Parameters 

1536 ---------- 

1537 a : float 

1538 Lower integration bound 

1539 b : float 

1540 Upper integration bound 

1541 extrapolate : {bool, 'periodic', None}, optional 

1542 Whether to extrapolate to out-of-bounds points based on first 

1543 and last intervals, or to return NaNs. If 'periodic', periodic 

1544 extrapolation is used. If None (default), use `self.extrapolate`. 

1545 

1546 Returns 

1547 ------- 

1548 array_like 

1549 Definite integral of the piecewise polynomial over [a, b] 

1550 

1551 """ 

1552 # XXX: can probably use instead the fact that 

1553 # \int_0^{1} B_{j, n}(x) \dx = 1/(n+1) 

1554 ib = self.antiderivative() 

1555 if extrapolate is None: 

1556 extrapolate = self.extrapolate 

1557 

1558 # ib.extrapolate shouldn't be 'periodic', it is converted to 

1559 # False for 'periodic. in antiderivative() call. 

1560 if extrapolate != 'periodic': 

1561 ib.extrapolate = extrapolate 

1562 

1563 if extrapolate == 'periodic': 

1564 # Split the integral into the part over period (can be several 

1565 # of them) and the remaining part. 

1566 

1567 # For simplicity and clarity convert to a <= b case. 

1568 if a <= b: 

1569 sign = 1 

1570 else: 

1571 a, b = b, a 

1572 sign = -1 

1573 

1574 xs, xe = self.x[0], self.x[-1] 

1575 period = xe - xs 

1576 interval = b - a 

1577 n_periods, left = divmod(interval, period) 

1578 res = n_periods * (ib(xe) - ib(xs)) 

1579 

1580 # Map a and b to [xs, xe]. 

1581 a = xs + (a - xs) % period 

1582 b = a + left 

1583 

1584 # If b <= xe then we need to integrate over [a, b], otherwise 

1585 # over [a, xe] and from xs to what is remained. 

1586 if b <= xe: 

1587 res += ib(b) - ib(a) 

1588 else: 

1589 res += ib(xe) - ib(a) + ib(xs + left + a - xe) - ib(xs) 

1590 

1591 return sign * res 

1592 else: 

1593 return ib(b) - ib(a) 

1594 

1595 def extend(self, c, x, right=None): 

1596 k = max(self.c.shape[0], c.shape[0]) 

1597 self.c = self._raise_degree(self.c, k - self.c.shape[0]) 

1598 c = self._raise_degree(c, k - c.shape[0]) 

1599 return _PPolyBase.extend(self, c, x, right) 

1600 extend.__doc__ = _PPolyBase.extend.__doc__ 

1601 

1602 @classmethod 

1603 def from_power_basis(cls, pp, extrapolate=None): 

1604 """ 

1605 Construct a piecewise polynomial in Bernstein basis 

1606 from a power basis polynomial. 

1607 

1608 Parameters 

1609 ---------- 

1610 pp : PPoly 

1611 A piecewise polynomial in the power basis 

1612 extrapolate : bool or 'periodic', optional 

1613 If bool, determines whether to extrapolate to out-of-bounds points 

1614 based on first and last intervals, or to return NaNs. 

1615 If 'periodic', periodic extrapolation is used. Default is True. 

1616 """ 

1617 if not isinstance(pp, PPoly): 

1618 raise TypeError(".from_power_basis only accepts PPoly instances. " 

1619 "Got %s instead." % type(pp)) 

1620 

1621 dx = np.diff(pp.x) 

1622 k = pp.c.shape[0] - 1 # polynomial order 

1623 

1624 rest = (None,)*(pp.c.ndim-2) 

1625 

1626 c = np.zeros_like(pp.c) 

1627 for a in range(k+1): 

1628 factor = pp.c[a] / comb(k, k-a) * dx[(slice(None),)+rest]**(k-a) 

1629 for j in range(k-a, k+1): 

1630 c[j] += factor * comb(j, k-a) 

1631 

1632 if extrapolate is None: 

1633 extrapolate = pp.extrapolate 

1634 

1635 return cls.construct_fast(c, pp.x, extrapolate, pp.axis) 

1636 

1637 @classmethod 

1638 def from_derivatives(cls, xi, yi, orders=None, extrapolate=None): 

1639 """Construct a piecewise polynomial in the Bernstein basis, 

1640 compatible with the specified values and derivatives at breakpoints. 

1641 

1642 Parameters 

1643 ---------- 

1644 xi : array_like 

1645 sorted 1-D array of x-coordinates 

1646 yi : array_like or list of array_likes 

1647 ``yi[i][j]`` is the ``j``th derivative known at ``xi[i]`` 

1648 orders : None or int or array_like of ints. Default: None. 

1649 Specifies the degree of local polynomials. If not None, some 

1650 derivatives are ignored. 

1651 extrapolate : bool or 'periodic', optional 

1652 If bool, determines whether to extrapolate to out-of-bounds points 

1653 based on first and last intervals, or to return NaNs. 

1654 If 'periodic', periodic extrapolation is used. Default is True. 

1655 

1656 Notes 

1657 ----- 

1658 If ``k`` derivatives are specified at a breakpoint ``x``, the 

1659 constructed polynomial is exactly ``k`` times continuously 

1660 differentiable at ``x``, unless the ``order`` is provided explicitly. 

1661 In the latter case, the smoothness of the polynomial at 

1662 the breakpoint is controlled by the ``order``. 

1663 

1664 Deduces the number of derivatives to match at each end 

1665 from ``order`` and the number of derivatives available. If 

1666 possible it uses the same number of derivatives from 

1667 each end; if the number is odd it tries to take the 

1668 extra one from y2. In any case if not enough derivatives 

1669 are available at one end or another it draws enough to 

1670 make up the total from the other end. 

1671 

1672 If the order is too high and not enough derivatives are available, 

1673 an exception is raised. 

1674 

1675 Examples 

1676 -------- 

1677 

1678 >>> from scipy.interpolate import BPoly 

1679 >>> BPoly.from_derivatives([0, 1], [[1, 2], [3, 4]]) 

1680 

1681 Creates a polynomial `f(x)` of degree 3, defined on `[0, 1]` 

1682 such that `f(0) = 1, df/dx(0) = 2, f(1) = 3, df/dx(1) = 4` 

1683 

1684 >>> BPoly.from_derivatives([0, 1, 2], [[0, 1], [0], [2]]) 

1685 

1686 Creates a piecewise polynomial `f(x)`, such that 

1687 `f(0) = f(1) = 0`, `f(2) = 2`, and `df/dx(0) = 1`. 

1688 Based on the number of derivatives provided, the order of the 

1689 local polynomials is 2 on `[0, 1]` and 1 on `[1, 2]`. 

1690 Notice that no restriction is imposed on the derivatives at 

1691 ``x = 1`` and ``x = 2``. 

1692 

1693 Indeed, the explicit form of the polynomial is:: 

1694 

1695 f(x) = | x * (1 - x), 0 <= x < 1 

1696 | 2 * (x - 1), 1 <= x <= 2 

1697 

1698 So that f'(1-0) = -1 and f'(1+0) = 2 

1699 

1700 """ 

1701 xi = np.asarray(xi) 

1702 if len(xi) != len(yi): 

1703 raise ValueError("xi and yi need to have the same length") 

1704 if np.any(xi[1:] - xi[:1] <= 0): 

1705 raise ValueError("x coordinates are not in increasing order") 

1706 

1707 # number of intervals 

1708 m = len(xi) - 1 

1709 

1710 # global poly order is k-1, local orders are <=k and can vary 

1711 try: 

1712 k = max(len(yi[i]) + len(yi[i+1]) for i in range(m)) 

1713 except TypeError: 

1714 raise ValueError("Using a 1-D array for y? Please .reshape(-1, 1).") 

1715 

1716 if orders is None: 

1717 orders = [None] * m 

1718 else: 

1719 if isinstance(orders, (int, np.integer)): 

1720 orders = [orders] * m 

1721 k = max(k, max(orders)) 

1722 

1723 if any(o <= 0 for o in orders): 

1724 raise ValueError("Orders must be positive.") 

1725 

1726 c = [] 

1727 for i in range(m): 

1728 y1, y2 = yi[i], yi[i+1] 

1729 if orders[i] is None: 

1730 n1, n2 = len(y1), len(y2) 

1731 else: 

1732 n = orders[i]+1 

1733 n1 = min(n//2, len(y1)) 

1734 n2 = min(n - n1, len(y2)) 

1735 n1 = min(n - n2, len(y2)) 

1736 if n1+n2 != n: 

1737 mesg = ("Point %g has %d derivatives, point %g" 

1738 " has %d derivatives, but order %d requested" % ( 

1739 xi[i], len(y1), xi[i+1], len(y2), orders[i])) 

1740 raise ValueError(mesg) 

1741 

1742 if not (n1 <= len(y1) and n2 <= len(y2)): 

1743 raise ValueError("`order` input incompatible with" 

1744 " length y1 or y2.") 

1745 

1746 b = BPoly._construct_from_derivatives(xi[i], xi[i+1], 

1747 y1[:n1], y2[:n2]) 

1748 if len(b) < k: 

1749 b = BPoly._raise_degree(b, k - len(b)) 

1750 c.append(b) 

1751 

1752 c = np.asarray(c) 

1753 return cls(c.swapaxes(0, 1), xi, extrapolate) 

1754 

1755 @staticmethod 

1756 def _construct_from_derivatives(xa, xb, ya, yb): 

1757 r"""Compute the coefficients of a polynomial in the Bernstein basis 

1758 given the values and derivatives at the edges. 

1759 

1760 Return the coefficients of a polynomial in the Bernstein basis 

1761 defined on ``[xa, xb]`` and having the values and derivatives at the 

1762 endpoints `xa` and `xb` as specified by `ya`` and `yb`. 

1763 The polynomial constructed is of the minimal possible degree, i.e., 

1764 if the lengths of `ya` and `yb` are `na` and `nb`, the degree 

1765 of the polynomial is ``na + nb - 1``. 

1766 

1767 Parameters 

1768 ---------- 

1769 xa : float 

1770 Left-hand end point of the interval 

1771 xb : float 

1772 Right-hand end point of the interval 

1773 ya : array_like 

1774 Derivatives at `xa`. `ya[0]` is the value of the function, and 

1775 `ya[i]` for ``i > 0`` is the value of the ``i``th derivative. 

1776 yb : array_like 

1777 Derivatives at `xb`. 

1778 

1779 Returns 

1780 ------- 

1781 array 

1782 coefficient array of a polynomial having specified derivatives 

1783 

1784 Notes 

1785 ----- 

1786 This uses several facts from life of Bernstein basis functions. 

1787 First of all, 

1788 

1789 .. math:: b'_{a, n} = n (b_{a-1, n-1} - b_{a, n-1}) 

1790 

1791 If B(x) is a linear combination of the form 

1792 

1793 .. math:: B(x) = \sum_{a=0}^{n} c_a b_{a, n}, 

1794 

1795 then :math: B'(x) = n \sum_{a=0}^{n-1} (c_{a+1} - c_{a}) b_{a, n-1}. 

1796 Iterating the latter one, one finds for the q-th derivative 

1797 

1798 .. math:: B^{q}(x) = n!/(n-q)! \sum_{a=0}^{n-q} Q_a b_{a, n-q}, 

1799 

1800 with 

1801 

1802 .. math:: Q_a = \sum_{j=0}^{q} (-)^{j+q} comb(q, j) c_{j+a} 

1803 

1804 This way, only `a=0` contributes to :math: `B^{q}(x = xa)`, and 

1805 `c_q` are found one by one by iterating `q = 0, ..., na`. 

1806 

1807 At ``x = xb`` it's the same with ``a = n - q``. 

1808 

1809 """ 

1810 ya, yb = np.asarray(ya), np.asarray(yb) 

1811 if ya.shape[1:] != yb.shape[1:]: 

1812 raise ValueError('ya and yb have incompatible dimensions.') 

1813 

1814 dta, dtb = ya.dtype, yb.dtype 

1815 if (np.issubdtype(dta, np.complexfloating) or 

1816 np.issubdtype(dtb, np.complexfloating)): 

1817 dt = np.complex_ 

1818 else: 

1819 dt = np.float_ 

1820 

1821 na, nb = len(ya), len(yb) 

1822 n = na + nb 

1823 

1824 c = np.empty((na+nb,) + ya.shape[1:], dtype=dt) 

1825 

1826 # compute coefficients of a polynomial degree na+nb-1 

1827 # walk left-to-right 

1828 for q in range(0, na): 

1829 c[q] = ya[q] / spec.poch(n - q, q) * (xb - xa)**q 

1830 for j in range(0, q): 

1831 c[q] -= (-1)**(j+q) * comb(q, j) * c[j] 

1832 

1833 # now walk right-to-left 

1834 for q in range(0, nb): 

1835 c[-q-1] = yb[q] / spec.poch(n - q, q) * (-1)**q * (xb - xa)**q 

1836 for j in range(0, q): 

1837 c[-q-1] -= (-1)**(j+1) * comb(q, j+1) * c[-q+j] 

1838 

1839 return c 

1840 

1841 @staticmethod 

1842 def _raise_degree(c, d): 

1843 r"""Raise a degree of a polynomial in the Bernstein basis. 

1844 

1845 Given the coefficients of a polynomial degree `k`, return (the 

1846 coefficients of) the equivalent polynomial of degree `k+d`. 

1847 

1848 Parameters 

1849 ---------- 

1850 c : array_like 

1851 coefficient array, 1-D 

1852 d : integer 

1853 

1854 Returns 

1855 ------- 

1856 array 

1857 coefficient array, 1-D array of length `c.shape[0] + d` 

1858 

1859 Notes 

1860 ----- 

1861 This uses the fact that a Bernstein polynomial `b_{a, k}` can be 

1862 identically represented as a linear combination of polynomials of 

1863 a higher degree `k+d`: 

1864 

1865 .. math:: b_{a, k} = comb(k, a) \sum_{j=0}^{d} b_{a+j, k+d} \ 

1866 comb(d, j) / comb(k+d, a+j) 

1867 

1868 """ 

1869 if d == 0: 

1870 return c 

1871 

1872 k = c.shape[0] - 1 

1873 out = np.zeros((c.shape[0] + d,) + c.shape[1:], dtype=c.dtype) 

1874 

1875 for a in range(c.shape[0]): 

1876 f = c[a] * comb(k, a) 

1877 for j in range(d+1): 

1878 out[a+j] += f * comb(d, j) / comb(k+d, a+j) 

1879 return out 

1880 

1881 

1882class NdPPoly(object): 

1883 """ 

1884 Piecewise tensor product polynomial 

1885 

1886 The value at point ``xp = (x', y', z', ...)`` is evaluated by first 

1887 computing the interval indices `i` such that:: 

1888 

1889 x[0][i[0]] <= x' < x[0][i[0]+1] 

1890 x[1][i[1]] <= y' < x[1][i[1]+1] 

1891 ... 

1892 

1893 and then computing:: 

1894 

1895 S = sum(c[k0-m0-1,...,kn-mn-1,i[0],...,i[n]] 

1896 * (xp[0] - x[0][i[0]])**m0 

1897 * ... 

1898 * (xp[n] - x[n][i[n]])**mn 

1899 for m0 in range(k[0]+1) 

1900 ... 

1901 for mn in range(k[n]+1)) 

1902 

1903 where ``k[j]`` is the degree of the polynomial in dimension j. This 

1904 representation is the piecewise multivariate power basis. 

1905 

1906 Parameters 

1907 ---------- 

1908 c : ndarray, shape (k0, ..., kn, m0, ..., mn, ...) 

1909 Polynomial coefficients, with polynomial order `kj` and 

1910 `mj+1` intervals for each dimension `j`. 

1911 x : ndim-tuple of ndarrays, shapes (mj+1,) 

1912 Polynomial breakpoints for each dimension. These must be 

1913 sorted in increasing order. 

1914 extrapolate : bool, optional 

1915 Whether to extrapolate to out-of-bounds points based on first 

1916 and last intervals, or to return NaNs. Default: True. 

1917 

1918 Attributes 

1919 ---------- 

1920 x : tuple of ndarrays 

1921 Breakpoints. 

1922 c : ndarray 

1923 Coefficients of the polynomials. 

1924 

1925 Methods 

1926 ------- 

1927 __call__ 

1928 construct_fast 

1929 

1930 See also 

1931 -------- 

1932 PPoly : piecewise polynomials in 1D 

1933 

1934 Notes 

1935 ----- 

1936 High-order polynomials in the power basis can be numerically 

1937 unstable. 

1938 

1939 """ 

1940 

1941 def __init__(self, c, x, extrapolate=None): 

1942 self.x = tuple(np.ascontiguousarray(v, dtype=np.float64) for v in x) 

1943 self.c = np.asarray(c) 

1944 if extrapolate is None: 

1945 extrapolate = True 

1946 self.extrapolate = bool(extrapolate) 

1947 

1948 ndim = len(self.x) 

1949 if any(v.ndim != 1 for v in self.x): 

1950 raise ValueError("x arrays must all be 1-dimensional") 

1951 if any(v.size < 2 for v in self.x): 

1952 raise ValueError("x arrays must all contain at least 2 points") 

1953 if c.ndim < 2*ndim: 

1954 raise ValueError("c must have at least 2*len(x) dimensions") 

1955 if any(np.any(v[1:] - v[:-1] < 0) for v in self.x): 

1956 raise ValueError("x-coordinates are not in increasing order") 

1957 if any(a != b.size - 1 for a, b in zip(c.shape[ndim:2*ndim], self.x)): 

1958 raise ValueError("x and c do not agree on the number of intervals") 

1959 

1960 dtype = self._get_dtype(self.c.dtype) 

1961 self.c = np.ascontiguousarray(self.c, dtype=dtype) 

1962 

1963 @classmethod 

1964 def construct_fast(cls, c, x, extrapolate=None): 

1965 """ 

1966 Construct the piecewise polynomial without making checks. 

1967 

1968 Takes the same parameters as the constructor. Input arguments 

1969 ``c`` and ``x`` must be arrays of the correct shape and type. The 

1970 ``c`` array can only be of dtypes float and complex, and ``x`` 

1971 array must have dtype float. 

1972 

1973 """ 

1974 self = object.__new__(cls) 

1975 self.c = c 

1976 self.x = x 

1977 if extrapolate is None: 

1978 extrapolate = True 

1979 self.extrapolate = extrapolate 

1980 return self 

1981 

1982 def _get_dtype(self, dtype): 

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

1984 or np.issubdtype(self.c.dtype, np.complexfloating): 

1985 return np.complex_ 

1986 else: 

1987 return np.float_ 

1988 

1989 def _ensure_c_contiguous(self): 

1990 if not self.c.flags.c_contiguous: 

1991 self.c = self.c.copy() 

1992 if not isinstance(self.x, tuple): 

1993 self.x = tuple(self.x) 

1994 

1995 def __call__(self, x, nu=None, extrapolate=None): 

1996 """ 

1997 Evaluate the piecewise polynomial or its derivative 

1998 

1999 Parameters 

2000 ---------- 

2001 x : array-like 

2002 Points to evaluate the interpolant at. 

2003 nu : tuple, optional 

2004 Orders of derivatives to evaluate. Each must be non-negative. 

2005 extrapolate : bool, optional 

2006 Whether to extrapolate to out-of-bounds points based on first 

2007 and last intervals, or to return NaNs. 

2008 

2009 Returns 

2010 ------- 

2011 y : array-like 

2012 Interpolated values. Shape is determined by replacing 

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

2014 

2015 Notes 

2016 ----- 

2017 Derivatives are evaluated piecewise for each polynomial 

2018 segment, even if the polynomial is not differentiable at the 

2019 breakpoints. The polynomial intervals are considered half-open, 

2020 ``[a, b)``, except for the last interval which is closed 

2021 ``[a, b]``. 

2022 

2023 """ 

2024 if extrapolate is None: 

2025 extrapolate = self.extrapolate 

2026 else: 

2027 extrapolate = bool(extrapolate) 

2028 

2029 ndim = len(self.x) 

2030 

2031 x = _ndim_coords_from_arrays(x) 

2032 x_shape = x.shape 

2033 x = np.ascontiguousarray(x.reshape(-1, x.shape[-1]), dtype=np.float_) 

2034 

2035 if nu is None: 

2036 nu = np.zeros((ndim,), dtype=np.intc) 

2037 else: 

2038 nu = np.asarray(nu, dtype=np.intc) 

2039 if nu.ndim != 1 or nu.shape[0] != ndim: 

2040 raise ValueError("invalid number of derivative orders nu") 

2041 

2042 dim1 = prod(self.c.shape[:ndim]) 

2043 dim2 = prod(self.c.shape[ndim:2*ndim]) 

2044 dim3 = prod(self.c.shape[2*ndim:]) 

2045 ks = np.array(self.c.shape[:ndim], dtype=np.intc) 

2046 

2047 out = np.empty((x.shape[0], dim3), dtype=self.c.dtype) 

2048 self._ensure_c_contiguous() 

2049 

2050 _ppoly.evaluate_nd(self.c.reshape(dim1, dim2, dim3), 

2051 self.x, 

2052 ks, 

2053 x, 

2054 nu, 

2055 bool(extrapolate), 

2056 out) 

2057 

2058 return out.reshape(x_shape[:-1] + self.c.shape[2*ndim:]) 

2059 

2060 def _derivative_inplace(self, nu, axis): 

2061 """ 

2062 Compute 1-D derivative along a selected dimension in-place 

2063 May result to non-contiguous c array. 

2064 """ 

2065 if nu < 0: 

2066 return self._antiderivative_inplace(-nu, axis) 

2067 

2068 ndim = len(self.x) 

2069 axis = axis % ndim 

2070 

2071 # reduce order 

2072 if nu == 0: 

2073 # noop 

2074 return 

2075 else: 

2076 sl = [slice(None)]*ndim 

2077 sl[axis] = slice(None, -nu, None) 

2078 c2 = self.c[tuple(sl)] 

2079 

2080 if c2.shape[axis] == 0: 

2081 # derivative of order 0 is zero 

2082 shp = list(c2.shape) 

2083 shp[axis] = 1 

2084 c2 = np.zeros(shp, dtype=c2.dtype) 

2085 

2086 # multiply by the correct rising factorials 

2087 factor = spec.poch(np.arange(c2.shape[axis], 0, -1), nu) 

2088 sl = [None]*c2.ndim 

2089 sl[axis] = slice(None) 

2090 c2 *= factor[tuple(sl)] 

2091 

2092 self.c = c2 

2093 

2094 def _antiderivative_inplace(self, nu, axis): 

2095 """ 

2096 Compute 1-D antiderivative along a selected dimension 

2097 May result to non-contiguous c array. 

2098 """ 

2099 if nu <= 0: 

2100 return self._derivative_inplace(-nu, axis) 

2101 

2102 ndim = len(self.x) 

2103 axis = axis % ndim 

2104 

2105 perm = list(range(ndim)) 

2106 perm[0], perm[axis] = perm[axis], perm[0] 

2107 perm = perm + list(range(ndim, self.c.ndim)) 

2108 

2109 c = self.c.transpose(perm) 

2110 

2111 c2 = np.zeros((c.shape[0] + nu,) + c.shape[1:], 

2112 dtype=c.dtype) 

2113 c2[:-nu] = c 

2114 

2115 # divide by the correct rising factorials 

2116 factor = spec.poch(np.arange(c.shape[0], 0, -1), nu) 

2117 c2[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)] 

2118 

2119 # fix continuity of added degrees of freedom 

2120 perm2 = list(range(c2.ndim)) 

2121 perm2[1], perm2[ndim+axis] = perm2[ndim+axis], perm2[1] 

2122 

2123 c2 = c2.transpose(perm2) 

2124 c2 = c2.copy() 

2125 _ppoly.fix_continuity(c2.reshape(c2.shape[0], c2.shape[1], -1), 

2126 self.x[axis], nu-1) 

2127 

2128 c2 = c2.transpose(perm2) 

2129 c2 = c2.transpose(perm) 

2130 

2131 # Done 

2132 self.c = c2 

2133 

2134 def derivative(self, nu): 

2135 """ 

2136 Construct a new piecewise polynomial representing the derivative. 

2137 

2138 Parameters 

2139 ---------- 

2140 nu : ndim-tuple of int 

2141 Order of derivatives to evaluate for each dimension. 

2142 If negative, the antiderivative is returned. 

2143 

2144 Returns 

2145 ------- 

2146 pp : NdPPoly 

2147 Piecewise polynomial of orders (k[0] - nu[0], ..., k[n] - nu[n]) 

2148 representing the derivative of this polynomial. 

2149 

2150 Notes 

2151 ----- 

2152 Derivatives are evaluated piecewise for each polynomial 

2153 segment, even if the polynomial is not differentiable at the 

2154 breakpoints. The polynomial intervals in each dimension are 

2155 considered half-open, ``[a, b)``, except for the last interval 

2156 which is closed ``[a, b]``. 

2157 

2158 """ 

2159 p = self.construct_fast(self.c.copy(), self.x, self.extrapolate) 

2160 

2161 for axis, n in enumerate(nu): 

2162 p._derivative_inplace(n, axis) 

2163 

2164 p._ensure_c_contiguous() 

2165 return p 

2166 

2167 def antiderivative(self, nu): 

2168 """ 

2169 Construct a new piecewise polynomial representing the antiderivative. 

2170 

2171 Antiderivative is also the indefinite integral of the function, 

2172 and derivative is its inverse operation. 

2173 

2174 Parameters 

2175 ---------- 

2176 nu : ndim-tuple of int 

2177 Order of derivatives to evaluate for each dimension. 

2178 If negative, the derivative is returned. 

2179 

2180 Returns 

2181 ------- 

2182 pp : PPoly 

2183 Piecewise polynomial of order k2 = k + n representing 

2184 the antiderivative of this polynomial. 

2185 

2186 Notes 

2187 ----- 

2188 The antiderivative returned by this function is continuous and 

2189 continuously differentiable to order n-1, up to floating point 

2190 rounding error. 

2191 

2192 """ 

2193 p = self.construct_fast(self.c.copy(), self.x, self.extrapolate) 

2194 

2195 for axis, n in enumerate(nu): 

2196 p._antiderivative_inplace(n, axis) 

2197 

2198 p._ensure_c_contiguous() 

2199 return p 

2200 

2201 def integrate_1d(self, a, b, axis, extrapolate=None): 

2202 r""" 

2203 Compute NdPPoly representation for one dimensional definite integral 

2204 

2205 The result is a piecewise polynomial representing the integral: 

2206 

2207 .. math:: 

2208 

2209 p(y, z, ...) = \int_a^b dx\, p(x, y, z, ...) 

2210 

2211 where the dimension integrated over is specified with the 

2212 `axis` parameter. 

2213 

2214 Parameters 

2215 ---------- 

2216 a, b : float 

2217 Lower and upper bound for integration. 

2218 axis : int 

2219 Dimension over which to compute the 1-D integrals 

2220 extrapolate : bool, optional 

2221 Whether to extrapolate to out-of-bounds points based on first 

2222 and last intervals, or to return NaNs. 

2223 

2224 Returns 

2225 ------- 

2226 ig : NdPPoly or array-like 

2227 Definite integral of the piecewise polynomial over [a, b]. 

2228 If the polynomial was 1D, an array is returned, 

2229 otherwise, an NdPPoly object. 

2230 

2231 """ 

2232 if extrapolate is None: 

2233 extrapolate = self.extrapolate 

2234 else: 

2235 extrapolate = bool(extrapolate) 

2236 

2237 ndim = len(self.x) 

2238 axis = int(axis) % ndim 

2239 

2240 # reuse 1-D integration routines 

2241 c = self.c 

2242 swap = list(range(c.ndim)) 

2243 swap.insert(0, swap[axis]) 

2244 del swap[axis + 1] 

2245 swap.insert(1, swap[ndim + axis]) 

2246 del swap[ndim + axis + 1] 

2247 

2248 c = c.transpose(swap) 

2249 p = PPoly.construct_fast(c.reshape(c.shape[0], c.shape[1], -1), 

2250 self.x[axis], 

2251 extrapolate=extrapolate) 

2252 out = p.integrate(a, b, extrapolate=extrapolate) 

2253 

2254 # Construct result 

2255 if ndim == 1: 

2256 return out.reshape(c.shape[2:]) 

2257 else: 

2258 c = out.reshape(c.shape[2:]) 

2259 x = self.x[:axis] + self.x[axis+1:] 

2260 return self.construct_fast(c, x, extrapolate=extrapolate) 

2261 

2262 def integrate(self, ranges, extrapolate=None): 

2263 """ 

2264 Compute a definite integral over a piecewise polynomial. 

2265 

2266 Parameters 

2267 ---------- 

2268 ranges : ndim-tuple of 2-tuples float 

2269 Sequence of lower and upper bounds for each dimension, 

2270 ``[(a[0], b[0]), ..., (a[ndim-1], b[ndim-1])]`` 

2271 extrapolate : bool, optional 

2272 Whether to extrapolate to out-of-bounds points based on first 

2273 and last intervals, or to return NaNs. 

2274 

2275 Returns 

2276 ------- 

2277 ig : array_like 

2278 Definite integral of the piecewise polynomial over 

2279 [a[0], b[0]] x ... x [a[ndim-1], b[ndim-1]] 

2280 

2281 """ 

2282 

2283 ndim = len(self.x) 

2284 

2285 if extrapolate is None: 

2286 extrapolate = self.extrapolate 

2287 else: 

2288 extrapolate = bool(extrapolate) 

2289 

2290 if not hasattr(ranges, '__len__') or len(ranges) != ndim: 

2291 raise ValueError("Range not a sequence of correct length") 

2292 

2293 self._ensure_c_contiguous() 

2294 

2295 # Reuse 1D integration routine 

2296 c = self.c 

2297 for n, (a, b) in enumerate(ranges): 

2298 swap = list(range(c.ndim)) 

2299 swap.insert(1, swap[ndim - n]) 

2300 del swap[ndim - n + 1] 

2301 

2302 c = c.transpose(swap) 

2303 

2304 p = PPoly.construct_fast(c, self.x[n], extrapolate=extrapolate) 

2305 out = p.integrate(a, b, extrapolate=extrapolate) 

2306 c = out.reshape(c.shape[2:]) 

2307 

2308 return c 

2309 

2310 

2311class RegularGridInterpolator(object): 

2312 """ 

2313 Interpolation on a regular grid in arbitrary dimensions 

2314 

2315 The data must be defined on a regular grid; the grid spacing however may be 

2316 uneven. Linear and nearest-neighbor interpolation are supported. After 

2317 setting up the interpolator object, the interpolation method (*linear* or 

2318 *nearest*) may be chosen at each evaluation. 

2319 

2320 Parameters 

2321 ---------- 

2322 points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, ) 

2323 The points defining the regular grid in n dimensions. 

2324 

2325 values : array_like, shape (m1, ..., mn, ...) 

2326 The data on the regular grid in n dimensions. 

2327 

2328 method : str, optional 

2329 The method of interpolation to perform. Supported are "linear" and 

2330 "nearest". This parameter will become the default for the object's 

2331 ``__call__`` method. Default is "linear". 

2332 

2333 bounds_error : bool, optional 

2334 If True, when interpolated values are requested outside of the 

2335 domain of the input data, a ValueError is raised. 

2336 If False, then `fill_value` is used. 

2337 

2338 fill_value : number, optional 

2339 If provided, the value to use for points outside of the 

2340 interpolation domain. If None, values outside 

2341 the domain are extrapolated. 

2342 

2343 Methods 

2344 ------- 

2345 __call__ 

2346 

2347 Notes 

2348 ----- 

2349 Contrary to LinearNDInterpolator and NearestNDInterpolator, this class 

2350 avoids expensive triangulation of the input data by taking advantage of the 

2351 regular grid structure. 

2352 

2353 If any of `points` have a dimension of size 1, linear interpolation will 

2354 return an array of `nan` values. Nearest-neighbor interpolation will work 

2355 as usual in this case. 

2356 

2357 .. versionadded:: 0.14 

2358 

2359 Examples 

2360 -------- 

2361 Evaluate a simple example function on the points of a 3-D grid: 

2362 

2363 >>> from scipy.interpolate import RegularGridInterpolator 

2364 >>> def f(x, y, z): 

2365 ... return 2 * x**3 + 3 * y**2 - z 

2366 >>> x = np.linspace(1, 4, 11) 

2367 >>> y = np.linspace(4, 7, 22) 

2368 >>> z = np.linspace(7, 9, 33) 

2369 >>> data = f(*np.meshgrid(x, y, z, indexing='ij', sparse=True)) 

2370 

2371 ``data`` is now a 3-D array with ``data[i,j,k] = f(x[i], y[j], z[k])``. 

2372 Next, define an interpolating function from this data: 

2373 

2374 >>> my_interpolating_function = RegularGridInterpolator((x, y, z), data) 

2375 

2376 Evaluate the interpolating function at the two points 

2377 ``(x,y,z) = (2.1, 6.2, 8.3)`` and ``(3.3, 5.2, 7.1)``: 

2378 

2379 >>> pts = np.array([[2.1, 6.2, 8.3], [3.3, 5.2, 7.1]]) 

2380 >>> my_interpolating_function(pts) 

2381 array([ 125.80469388, 146.30069388]) 

2382 

2383 which is indeed a close approximation to 

2384 ``[f(2.1, 6.2, 8.3), f(3.3, 5.2, 7.1)]``. 

2385 

2386 See also 

2387 -------- 

2388 NearestNDInterpolator : Nearest neighbor interpolation on unstructured 

2389 data in N dimensions 

2390 

2391 LinearNDInterpolator : Piecewise linear interpolant on unstructured data 

2392 in N dimensions 

2393 

2394 References 

2395 ---------- 

2396 .. [1] Python package *regulargrid* by Johannes Buchner, see 

2397 https://pypi.python.org/pypi/regulargrid/ 

2398 .. [2] Wikipedia, "Trilinear interpolation", 

2399 https://en.wikipedia.org/wiki/Trilinear_interpolation 

2400 .. [3] Weiser, Alan, and Sergio E. Zarantonello. "A note on piecewise linear 

2401 and multilinear table interpolation in many dimensions." MATH. 

2402 COMPUT. 50.181 (1988): 189-196. 

2403 https://www.ams.org/journals/mcom/1988-50-181/S0025-5718-1988-0917826-0/S0025-5718-1988-0917826-0.pdf 

2404 

2405 """ 

2406 # this class is based on code originally programmed by Johannes Buchner, 

2407 # see https://github.com/JohannesBuchner/regulargrid 

2408 

2409 def __init__(self, points, values, method="linear", bounds_error=True, 

2410 fill_value=np.nan): 

2411 if method not in ["linear", "nearest"]: 

2412 raise ValueError("Method '%s' is not defined" % method) 

2413 self.method = method 

2414 self.bounds_error = bounds_error 

2415 

2416 if not hasattr(values, 'ndim'): 

2417 # allow reasonable duck-typed values 

2418 values = np.asarray(values) 

2419 

2420 if len(points) > values.ndim: 

2421 raise ValueError("There are %d point arrays, but values has %d " 

2422 "dimensions" % (len(points), values.ndim)) 

2423 

2424 if hasattr(values, 'dtype') and hasattr(values, 'astype'): 

2425 if not np.issubdtype(values.dtype, np.inexact): 

2426 values = values.astype(float) 

2427 

2428 self.fill_value = fill_value 

2429 if fill_value is not None: 

2430 fill_value_dtype = np.asarray(fill_value).dtype 

2431 if (hasattr(values, 'dtype') and not 

2432 np.can_cast(fill_value_dtype, values.dtype, 

2433 casting='same_kind')): 

2434 raise ValueError("fill_value must be either 'None' or " 

2435 "of a type compatible with values") 

2436 

2437 for i, p in enumerate(points): 

2438 if not np.all(np.diff(p) > 0.): 

2439 raise ValueError("The points in dimension %d must be strictly " 

2440 "ascending" % i) 

2441 if not np.asarray(p).ndim == 1: 

2442 raise ValueError("The points in dimension %d must be " 

2443 "1-dimensional" % i) 

2444 if not values.shape[i] == len(p): 

2445 raise ValueError("There are %d points and %d values in " 

2446 "dimension %d" % (len(p), values.shape[i], i)) 

2447 self.grid = tuple([np.asarray(p) for p in points]) 

2448 self.values = values 

2449 

2450 def __call__(self, xi, method=None): 

2451 """ 

2452 Interpolation at coordinates 

2453 

2454 Parameters 

2455 ---------- 

2456 xi : ndarray of shape (..., ndim) 

2457 The coordinates to sample the gridded data at 

2458 

2459 method : str 

2460 The method of interpolation to perform. Supported are "linear" and 

2461 "nearest". 

2462 

2463 """ 

2464 method = self.method if method is None else method 

2465 if method not in ["linear", "nearest"]: 

2466 raise ValueError("Method '%s' is not defined" % method) 

2467 

2468 ndim = len(self.grid) 

2469 xi = _ndim_coords_from_arrays(xi, ndim=ndim) 

2470 if xi.shape[-1] != len(self.grid): 

2471 raise ValueError("The requested sample points xi have dimension " 

2472 "%d, but this RegularGridInterpolator has " 

2473 "dimension %d" % (xi.shape[1], ndim)) 

2474 

2475 xi_shape = xi.shape 

2476 xi = xi.reshape(-1, xi_shape[-1]) 

2477 

2478 if self.bounds_error: 

2479 for i, p in enumerate(xi.T): 

2480 if not np.logical_and(np.all(self.grid[i][0] <= p), 

2481 np.all(p <= self.grid[i][-1])): 

2482 raise ValueError("One of the requested xi is out of bounds " 

2483 "in dimension %d" % i) 

2484 

2485 indices, norm_distances, out_of_bounds = self._find_indices(xi.T) 

2486 if method == "linear": 

2487 result = self._evaluate_linear(indices, 

2488 norm_distances, 

2489 out_of_bounds) 

2490 elif method == "nearest": 

2491 result = self._evaluate_nearest(indices, 

2492 norm_distances, 

2493 out_of_bounds) 

2494 if not self.bounds_error and self.fill_value is not None: 

2495 result[out_of_bounds] = self.fill_value 

2496 

2497 return result.reshape(xi_shape[:-1] + self.values.shape[ndim:]) 

2498 

2499 def _evaluate_linear(self, indices, norm_distances, out_of_bounds): 

2500 # slice for broadcasting over trailing dimensions in self.values 

2501 vslice = (slice(None),) + (None,)*(self.values.ndim - len(indices)) 

2502 

2503 # find relevant values 

2504 # each i and i+1 represents a edge 

2505 edges = itertools.product(*[[i, i + 1] for i in indices]) 

2506 values = 0. 

2507 for edge_indices in edges: 

2508 weight = 1. 

2509 for ei, i, yi in zip(edge_indices, indices, norm_distances): 

2510 weight *= np.where(ei == i, 1 - yi, yi) 

2511 values += np.asarray(self.values[edge_indices]) * weight[vslice] 

2512 return values 

2513 

2514 def _evaluate_nearest(self, indices, norm_distances, out_of_bounds): 

2515 idx_res = [np.where(yi <= .5, i, i + 1) 

2516 for i, yi in zip(indices, norm_distances)] 

2517 return self.values[tuple(idx_res)] 

2518 

2519 def _find_indices(self, xi): 

2520 # find relevant edges between which xi are situated 

2521 indices = [] 

2522 # compute distance to lower edge in unity units 

2523 norm_distances = [] 

2524 # check for out of bounds xi 

2525 out_of_bounds = np.zeros((xi.shape[1]), dtype=bool) 

2526 # iterate through dimensions 

2527 for x, grid in zip(xi, self.grid): 

2528 i = np.searchsorted(grid, x) - 1 

2529 i[i < 0] = 0 

2530 i[i > grid.size - 2] = grid.size - 2 

2531 indices.append(i) 

2532 norm_distances.append((x - grid[i]) / 

2533 (grid[i + 1] - grid[i])) 

2534 if not self.bounds_error: 

2535 out_of_bounds += x < grid[0] 

2536 out_of_bounds += x > grid[-1] 

2537 return indices, norm_distances, out_of_bounds 

2538 

2539 

2540def interpn(points, values, xi, method="linear", bounds_error=True, 

2541 fill_value=np.nan): 

2542 """ 

2543 Multidimensional interpolation on regular grids. 

2544 

2545 Parameters 

2546 ---------- 

2547 points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, ) 

2548 The points defining the regular grid in n dimensions. 

2549 

2550 values : array_like, shape (m1, ..., mn, ...) 

2551 The data on the regular grid in n dimensions. 

2552 

2553 xi : ndarray of shape (..., ndim) 

2554 The coordinates to sample the gridded data at 

2555 

2556 method : str, optional 

2557 The method of interpolation to perform. Supported are "linear" and 

2558 "nearest", and "splinef2d". "splinef2d" is only supported for 

2559 2-dimensional data. 

2560 

2561 bounds_error : bool, optional 

2562 If True, when interpolated values are requested outside of the 

2563 domain of the input data, a ValueError is raised. 

2564 If False, then `fill_value` is used. 

2565 

2566 fill_value : number, optional 

2567 If provided, the value to use for points outside of the 

2568 interpolation domain. If None, values outside 

2569 the domain are extrapolated. Extrapolation is not supported by method 

2570 "splinef2d". 

2571 

2572 Returns 

2573 ------- 

2574 values_x : ndarray, shape xi.shape[:-1] + values.shape[ndim:] 

2575 Interpolated values at input coordinates. 

2576 

2577 Notes 

2578 ----- 

2579 

2580 .. versionadded:: 0.14 

2581 

2582 Examples 

2583 -------- 

2584 Evaluate a simple example function on the points of a regular 3-D grid: 

2585 

2586 >>> from scipy.interpolate import interpn 

2587 >>> def value_func_3d(x, y, z): 

2588 ... return 2 * x + 3 * y - z 

2589 >>> x = np.linspace(0, 5) 

2590 >>> y = np.linspace(0, 5) 

2591 >>> z = np.linspace(0, 5) 

2592 >>> points = (x, y, z) 

2593 >>> values = value_func_3d(*np.meshgrid(*points)) 

2594 

2595 Evaluate the interpolating function at a point 

2596 

2597 >>> point = np.array([2.21, 3.12, 1.15]) 

2598 >>> print(interpn(points, values, point)) 

2599 [11.72] 

2600 

2601 See also 

2602 -------- 

2603 NearestNDInterpolator : Nearest neighbor interpolation on unstructured 

2604 data in N dimensions 

2605 

2606 LinearNDInterpolator : Piecewise linear interpolant on unstructured data 

2607 in N dimensions 

2608 

2609 RegularGridInterpolator : Linear and nearest-neighbor Interpolation on a 

2610 regular grid in arbitrary dimensions 

2611 

2612 RectBivariateSpline : Bivariate spline approximation over a rectangular mesh 

2613 

2614 """ 

2615 # sanity check 'method' kwarg 

2616 if method not in ["linear", "nearest", "splinef2d"]: 

2617 raise ValueError("interpn only understands the methods 'linear', " 

2618 "'nearest', and 'splinef2d'. You provided %s." % 

2619 method) 

2620 

2621 if not hasattr(values, 'ndim'): 

2622 values = np.asarray(values) 

2623 

2624 ndim = values.ndim 

2625 if ndim > 2 and method == "splinef2d": 

2626 raise ValueError("The method splinef2d can only be used for " 

2627 "2-dimensional input data") 

2628 if not bounds_error and fill_value is None and method == "splinef2d": 

2629 raise ValueError("The method splinef2d does not support extrapolation.") 

2630 

2631 # sanity check consistency of input dimensions 

2632 if len(points) > ndim: 

2633 raise ValueError("There are %d point arrays, but values has %d " 

2634 "dimensions" % (len(points), ndim)) 

2635 if len(points) != ndim and method == 'splinef2d': 

2636 raise ValueError("The method splinef2d can only be used for " 

2637 "scalar data with one point per coordinate") 

2638 

2639 # sanity check input grid 

2640 for i, p in enumerate(points): 

2641 if not np.all(np.diff(p) > 0.): 

2642 raise ValueError("The points in dimension %d must be strictly " 

2643 "ascending" % i) 

2644 if not np.asarray(p).ndim == 1: 

2645 raise ValueError("The points in dimension %d must be " 

2646 "1-dimensional" % i) 

2647 if not values.shape[i] == len(p): 

2648 raise ValueError("There are %d points and %d values in " 

2649 "dimension %d" % (len(p), values.shape[i], i)) 

2650 grid = tuple([np.asarray(p) for p in points]) 

2651 

2652 # sanity check requested xi 

2653 xi = _ndim_coords_from_arrays(xi, ndim=len(grid)) 

2654 if xi.shape[-1] != len(grid): 

2655 raise ValueError("The requested sample points xi have dimension " 

2656 "%d, but this RegularGridInterpolator has " 

2657 "dimension %d" % (xi.shape[1], len(grid))) 

2658 

2659 for i, p in enumerate(xi.T): 

2660 if bounds_error and not np.logical_and(np.all(grid[i][0] <= p), 

2661 np.all(p <= grid[i][-1])): 

2662 raise ValueError("One of the requested xi is out of bounds " 

2663 "in dimension %d" % i) 

2664 

2665 # perform interpolation 

2666 if method == "linear": 

2667 interp = RegularGridInterpolator(points, values, method="linear", 

2668 bounds_error=bounds_error, 

2669 fill_value=fill_value) 

2670 return interp(xi) 

2671 elif method == "nearest": 

2672 interp = RegularGridInterpolator(points, values, method="nearest", 

2673 bounds_error=bounds_error, 

2674 fill_value=fill_value) 

2675 return interp(xi) 

2676 elif method == "splinef2d": 

2677 xi_shape = xi.shape 

2678 xi = xi.reshape(-1, xi.shape[-1]) 

2679 

2680 # RectBivariateSpline doesn't support fill_value; we need to wrap here 

2681 idx_valid = np.all((grid[0][0] <= xi[:, 0], xi[:, 0] <= grid[0][-1], 

2682 grid[1][0] <= xi[:, 1], xi[:, 1] <= grid[1][-1]), 

2683 axis=0) 

2684 result = np.empty_like(xi[:, 0]) 

2685 

2686 # make a copy of values for RectBivariateSpline 

2687 interp = RectBivariateSpline(points[0], points[1], values[:]) 

2688 result[idx_valid] = interp.ev(xi[idx_valid, 0], xi[idx_valid, 1]) 

2689 result[np.logical_not(idx_valid)] = fill_value 

2690 

2691 return result.reshape(xi_shape[:-1]) 

2692 

2693 

2694# backward compatibility wrapper 

2695class _ppform(PPoly): 

2696 """ 

2697 Deprecated piecewise polynomial class. 

2698 

2699 New code should use the `PPoly` class instead. 

2700 

2701 """ 

2702 

2703 def __init__(self, coeffs, breaks, fill=0.0, sort=False): 

2704 warnings.warn("_ppform is deprecated -- use PPoly instead", 

2705 category=DeprecationWarning) 

2706 

2707 if sort: 

2708 breaks = np.sort(breaks) 

2709 else: 

2710 breaks = np.asarray(breaks) 

2711 

2712 PPoly.__init__(self, coeffs, breaks) 

2713 

2714 self.coeffs = self.c 

2715 self.breaks = self.x 

2716 self.K = self.coeffs.shape[0] 

2717 self.fill = fill 

2718 self.a = self.breaks[0] 

2719 self.b = self.breaks[-1] 

2720 

2721 def __call__(self, x): 

2722 return PPoly.__call__(self, x, 0, False) 

2723 

2724 def _evaluate(self, x, nu, extrapolate, out): 

2725 PPoly._evaluate(self, x, nu, extrapolate, out) 

2726 out[~((x >= self.a) & (x <= self.b))] = self.fill 

2727 return out 

2728 

2729 @classmethod 

2730 def fromspline(cls, xk, cvals, order, fill=0.0): 

2731 # Note: this spline representation is incompatible with FITPACK 

2732 N = len(xk)-1 

2733 sivals = np.empty((order+1, N), dtype=float) 

2734 for m in range(order, -1, -1): 

2735 fact = spec.gamma(m+1) 

2736 res = _fitpack._bspleval(xk[:-1], xk, cvals, order, m) 

2737 res /= fact 

2738 sivals[order-m, :] = res 

2739 return cls(sivals, xk, fill=fill)