| __all__ = ['interp1d', 'interp2d', 'lagrange', 'PPoly', 'BPoly', 'NdPPoly'] |
|
|
| from math import prod |
|
|
| import numpy as np |
| from numpy import array, asarray, intp, poly1d, searchsorted |
|
|
| import scipy.special as spec |
| from scipy._lib._util import copy_if_needed |
| from scipy.special import comb |
|
|
| from . import _fitpack_py |
| from ._polyint import _Interpolator1D |
| from . import _ppoly |
| from ._interpnd import _ndim_coords_from_arrays |
| from ._bsplines import make_interp_spline, BSpline |
|
|
|
|
| def lagrange(x, w): |
| r""" |
| Return a Lagrange interpolating polynomial. |
| |
| Given two 1-D arrays `x` and `w,` returns the Lagrange interpolating |
| polynomial through the points ``(x, w)``. |
| |
| Warning: This implementation is numerically unstable. Do not expect to |
| be able to use more than about 20 points even if they are chosen optimally. |
| |
| Parameters |
| ---------- |
| x : array_like |
| `x` represents the x-coordinates of a set of datapoints. |
| w : array_like |
| `w` represents the y-coordinates of a set of datapoints, i.e., f(`x`). |
| |
| Returns |
| ------- |
| lagrange : `numpy.poly1d` instance |
| The Lagrange interpolating polynomial. |
| |
| Examples |
| -------- |
| Interpolate :math:`f(x) = x^3` by 3 points. |
| |
| >>> import numpy as np |
| >>> from scipy.interpolate import lagrange |
| >>> x = np.array([0, 1, 2]) |
| >>> y = x**3 |
| >>> poly = lagrange(x, y) |
| |
| Since there are only 3 points, Lagrange polynomial has degree 2. Explicitly, |
| it is given by |
| |
| .. math:: |
| |
| \begin{aligned} |
| L(x) &= 1\times \frac{x (x - 2)}{-1} + 8\times \frac{x (x-1)}{2} \\ |
| &= x (-2 + 3x) |
| \end{aligned} |
| |
| >>> from numpy.polynomial.polynomial import Polynomial |
| >>> Polynomial(poly.coef[::-1]).coef |
| array([ 0., -2., 3.]) |
| |
| >>> import matplotlib.pyplot as plt |
| >>> x_new = np.arange(0, 2.1, 0.1) |
| >>> plt.scatter(x, y, label='data') |
| >>> plt.plot(x_new, Polynomial(poly.coef[::-1])(x_new), label='Polynomial') |
| >>> plt.plot(x_new, 3*x_new**2 - 2*x_new + 0*x_new, |
| ... label=r"$3 x^2 - 2 x$", linestyle='-.') |
| >>> plt.legend() |
| >>> plt.show() |
| |
| """ |
|
|
| M = len(x) |
| p = poly1d(0.0) |
| for j in range(M): |
| pt = poly1d(w[j]) |
| for k in range(M): |
| if k == j: |
| continue |
| fac = x[j]-x[k] |
| pt *= poly1d([1.0, -x[k]])/fac |
| p += pt |
| return p |
|
|
|
|
| |
| |
|
|
|
|
| err_mesg = """\ |
| `interp2d` has been removed in SciPy 1.14.0. |
| |
| For legacy code, nearly bug-for-bug compatible replacements are |
| `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for |
| scattered 2D data. |
| |
| In new code, for regular grids use `RegularGridInterpolator` instead. |
| For scattered data, prefer `LinearNDInterpolator` or |
| `CloughTocher2DInterpolator`. |
| |
| For more details see |
| https://scipy.github.io/devdocs/tutorial/interpolate/interp_transition_guide.html |
| """ |
|
|
| class interp2d: |
| """ |
| interp2d(x, y, z, kind='linear', copy=True, bounds_error=False, |
| fill_value=None) |
| |
| .. versionremoved:: 1.14.0 |
| |
| `interp2d` has been removed in SciPy 1.14.0. |
| |
| For legacy code, nearly bug-for-bug compatible replacements are |
| `RectBivariateSpline` on regular grids, and `bisplrep`/`bisplev` for |
| scattered 2D data. |
| |
| In new code, for regular grids use `RegularGridInterpolator` instead. |
| For scattered data, prefer `LinearNDInterpolator` or |
| `CloughTocher2DInterpolator`. |
| |
| For more details see :ref:`interp-transition-guide`. |
| """ |
| def __init__(self, x, y, z, kind='linear', copy=True, bounds_error=False, |
| fill_value=None): |
| raise NotImplementedError(err_mesg) |
|
|
|
|
| def _check_broadcast_up_to(arr_from, shape_to, name): |
| """Helper to check that arr_from broadcasts up to shape_to""" |
| shape_from = arr_from.shape |
| if len(shape_to) >= len(shape_from): |
| for t, f in zip(shape_to[::-1], shape_from[::-1]): |
| if f != 1 and f != t: |
| break |
| else: |
| if arr_from.size != 1 and arr_from.shape != shape_to: |
| arr_from = np.ones(shape_to, arr_from.dtype) * arr_from |
| return arr_from.ravel() |
| |
| raise ValueError(f'{name} argument must be able to broadcast up ' |
| f'to shape {shape_to} but had shape {shape_from}') |
|
|
|
|
| def _do_extrapolate(fill_value): |
| """Helper to check if fill_value == "extrapolate" without warnings""" |
| return (isinstance(fill_value, str) and |
| fill_value == 'extrapolate') |
|
|
|
|
| class interp1d(_Interpolator1D): |
| """ |
| Interpolate a 1-D function. |
| |
| .. legacy:: class |
| |
| For a guide to the intended replacements for `interp1d` see |
| :ref:`tutorial-interpolate_1Dsection`. |
| |
| `x` and `y` are arrays of values used to approximate some function f: |
| ``y = f(x)``. This class returns a function whose call method uses |
| interpolation to find the value of new points. |
| |
| Parameters |
| ---------- |
| x : (npoints, ) array_like |
| A 1-D array of real values. |
| y : (..., npoints, ...) array_like |
| A N-D array of real values. The length of `y` along the interpolation |
| axis must be equal to the length of `x`. Use the ``axis`` parameter |
| to select correct axis. Unlike other interpolators, the default |
| interpolation axis is the last axis of `y`. |
| kind : str or int, optional |
| Specifies the kind of interpolation as a string or as an integer |
| specifying the order of the spline interpolator to use. |
| The string has to be one of 'linear', 'nearest', 'nearest-up', 'zero', |
| 'slinear', 'quadratic', 'cubic', 'previous', or 'next'. 'zero', |
| 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of |
| zeroth, first, second or third order; 'previous' and 'next' simply |
| return the previous or next value of the point; 'nearest-up' and |
| 'nearest' differ when interpolating half-integers (e.g. 0.5, 1.5) |
| in that 'nearest-up' rounds up and 'nearest' rounds down. Default |
| is 'linear'. |
| axis : int, optional |
| Axis in the ``y`` array corresponding to the x-coordinate values. Unlike |
| other interpolators, defaults to ``axis=-1``. |
| copy : bool, optional |
| If ``True``, the class makes internal copies of x and y. If ``False``, |
| references to ``x`` and ``y`` are used if possible. The default is to copy. |
| bounds_error : bool, optional |
| If True, a ValueError is raised any time interpolation is attempted on |
| a value outside of the range of x (where extrapolation is |
| necessary). If False, out of bounds values are assigned `fill_value`. |
| By default, an error is raised unless ``fill_value="extrapolate"``. |
| fill_value : array-like or (array-like, array_like) or "extrapolate", optional |
| - if a ndarray (or float), this value will be used to fill in for |
| requested points outside of the data range. If not provided, then |
| the default is NaN. The array-like must broadcast properly to the |
| dimensions of the non-interpolation axes. |
| - If a two-element tuple, then the first element is used as a |
| fill value for ``x_new < x[0]`` and the second element is used for |
| ``x_new > x[-1]``. Anything that is not a 2-element tuple (e.g., |
| list or ndarray, regardless of shape) is taken to be a single |
| array-like argument meant to be used for both bounds as |
| ``below, above = fill_value, fill_value``. Using a two-element tuple |
| or ndarray requires ``bounds_error=False``. |
| |
| .. versionadded:: 0.17.0 |
| - If "extrapolate", then points outside the data range will be |
| extrapolated. |
| |
| .. versionadded:: 0.17.0 |
| assume_sorted : bool, optional |
| If False, values of `x` can be in any order and they are sorted first. |
| If True, `x` has to be an array of monotonically increasing values. |
| |
| Attributes |
| ---------- |
| fill_value |
| |
| Methods |
| ------- |
| __call__ |
| |
| See Also |
| -------- |
| splrep, splev |
| Spline interpolation/smoothing based on FITPACK. |
| UnivariateSpline : An object-oriented wrapper of the FITPACK routines. |
| interp2d : 2-D interpolation |
| |
| Notes |
| ----- |
| Calling `interp1d` with NaNs present in input values results in |
| undefined behaviour. |
| |
| Input values `x` and `y` must be convertible to `float` values like |
| `int` or `float`. |
| |
| If the values in `x` are not unique, the resulting behavior is |
| undefined and specific to the choice of `kind`, i.e., changing |
| `kind` will change the behavior for duplicates. |
| |
| |
| Examples |
| -------- |
| >>> import numpy as np |
| >>> import matplotlib.pyplot as plt |
| >>> from scipy import interpolate |
| >>> x = np.arange(0, 10) |
| >>> y = np.exp(-x/3.0) |
| >>> f = interpolate.interp1d(x, y) |
| |
| >>> xnew = np.arange(0, 9, 0.1) |
| >>> ynew = f(xnew) # use interpolation function returned by `interp1d` |
| >>> plt.plot(x, y, 'o', xnew, ynew, '-') |
| >>> plt.show() |
| """ |
|
|
| def __init__(self, x, y, kind='linear', axis=-1, |
| copy=True, bounds_error=None, fill_value=np.nan, |
| assume_sorted=False): |
| """ Initialize a 1-D linear interpolation class.""" |
| _Interpolator1D.__init__(self, x, y, axis=axis) |
|
|
| self.bounds_error = bounds_error |
|
|
| |
| |
| self.copy = copy |
| if not copy: |
| self.copy = copy_if_needed |
|
|
| if kind in ['zero', 'slinear', 'quadratic', 'cubic']: |
| order = {'zero': 0, 'slinear': 1, |
| 'quadratic': 2, 'cubic': 3}[kind] |
| kind = 'spline' |
| elif isinstance(kind, int): |
| order = kind |
| kind = 'spline' |
| elif kind not in ('linear', 'nearest', 'nearest-up', 'previous', |
| 'next'): |
| raise NotImplementedError(f"{kind} is unsupported: Use fitpack " |
| "routines for other types.") |
| x = array(x, copy=self.copy) |
| y = array(y, copy=self.copy) |
|
|
| if not assume_sorted: |
| ind = np.argsort(x, kind="mergesort") |
| x = x[ind] |
| y = np.take(y, ind, axis=axis) |
|
|
| if x.ndim != 1: |
| raise ValueError("the x array must have exactly one dimension.") |
| if y.ndim == 0: |
| raise ValueError("the y array must have at least one dimension.") |
|
|
| |
| if not issubclass(y.dtype.type, np.inexact): |
| y = y.astype(np.float64) |
|
|
| |
| self.axis = axis % y.ndim |
|
|
| |
| self.y = y |
| self._y = self._reshape_yi(self.y) |
| self.x = x |
| del y, x |
| self._kind = kind |
|
|
| |
| |
| |
| |
| if kind in ('linear', 'nearest', 'nearest-up', 'previous', 'next'): |
| |
| |
| minval = 1 |
| if kind == 'nearest': |
| |
| |
| self._side = 'left' |
| self.x_bds = self.x / 2.0 |
| self.x_bds = self.x_bds[1:] + self.x_bds[:-1] |
|
|
| self._call = self.__class__._call_nearest |
| elif kind == 'nearest-up': |
| |
| |
| self._side = 'right' |
| self.x_bds = self.x / 2.0 |
| self.x_bds = self.x_bds[1:] + self.x_bds[:-1] |
|
|
| self._call = self.__class__._call_nearest |
| elif kind == 'previous': |
| |
| self._side = 'left' |
| self._ind = 0 |
| |
| self._x_shift = np.nextafter(self.x, -np.inf) |
| self._call = self.__class__._call_previousnext |
| if _do_extrapolate(fill_value): |
| self._check_and_update_bounds_error_for_extrapolation() |
| |
| fill_value = (np.nan, np.take(self.y, -1, axis)) |
| elif kind == 'next': |
| self._side = 'right' |
| self._ind = 1 |
| |
| self._x_shift = np.nextafter(self.x, np.inf) |
| self._call = self.__class__._call_previousnext |
| if _do_extrapolate(fill_value): |
| self._check_and_update_bounds_error_for_extrapolation() |
| |
| fill_value = (np.take(self.y, 0, axis), np.nan) |
| else: |
| |
| np_dtypes = (np.dtype(np.float64), np.dtype(int)) |
| cond = self.x.dtype in np_dtypes and self.y.dtype in np_dtypes |
| cond = cond and self.y.ndim == 1 |
| cond = cond and not _do_extrapolate(fill_value) |
|
|
| if cond: |
| self._call = self.__class__._call_linear_np |
| else: |
| self._call = self.__class__._call_linear |
| else: |
| minval = order + 1 |
|
|
| rewrite_nan = False |
| xx, yy = self.x, self._y |
| if order > 1: |
| |
| |
| |
| |
| |
| |
| |
| mask = np.isnan(self.x) |
| if mask.any(): |
| sx = self.x[~mask] |
| if sx.size == 0: |
| raise ValueError("`x` array is all-nan") |
| xx = np.linspace(np.nanmin(self.x), |
| np.nanmax(self.x), |
| len(self.x)) |
| rewrite_nan = True |
| if np.isnan(self._y).any(): |
| yy = np.ones_like(self._y) |
| rewrite_nan = True |
|
|
| self._spline = make_interp_spline(xx, yy, k=order, |
| check_finite=False) |
| if rewrite_nan: |
| self._call = self.__class__._call_nan_spline |
| else: |
| self._call = self.__class__._call_spline |
|
|
| if len(self.x) < minval: |
| raise ValueError("x and y arrays must have at " |
| "least %d entries" % minval) |
|
|
| self.fill_value = fill_value |
|
|
| @property |
| def fill_value(self): |
| """The fill value.""" |
| |
| return self._fill_value_orig |
|
|
| @fill_value.setter |
| def fill_value(self, fill_value): |
| |
| if _do_extrapolate(fill_value): |
| self._check_and_update_bounds_error_for_extrapolation() |
| self._extrapolate = True |
| else: |
| broadcast_shape = (self.y.shape[:self.axis] + |
| self.y.shape[self.axis + 1:]) |
| if len(broadcast_shape) == 0: |
| broadcast_shape = (1,) |
| |
| |
| if isinstance(fill_value, tuple) and len(fill_value) == 2: |
| below_above = [np.asarray(fill_value[0]), |
| np.asarray(fill_value[1])] |
| names = ('fill_value (below)', 'fill_value (above)') |
| for ii in range(2): |
| below_above[ii] = _check_broadcast_up_to( |
| below_above[ii], broadcast_shape, names[ii]) |
| else: |
| fill_value = np.asarray(fill_value) |
| below_above = [_check_broadcast_up_to( |
| fill_value, broadcast_shape, 'fill_value')] * 2 |
| self._fill_value_below, self._fill_value_above = below_above |
| self._extrapolate = False |
| if self.bounds_error is None: |
| self.bounds_error = True |
| |
| self._fill_value_orig = fill_value |
|
|
| def _check_and_update_bounds_error_for_extrapolation(self): |
| if self.bounds_error: |
| raise ValueError("Cannot extrapolate and raise " |
| "at the same time.") |
| self.bounds_error = False |
|
|
| def _call_linear_np(self, x_new): |
| |
| return np.interp(x_new, self.x, self.y) |
|
|
| def _call_linear(self, x_new): |
| |
| |
| |
| x_new_indices = searchsorted(self.x, x_new) |
|
|
| |
| |
| |
| x_new_indices = x_new_indices.clip(1, len(self.x)-1).astype(int) |
|
|
| |
| lo = x_new_indices - 1 |
| hi = x_new_indices |
|
|
| x_lo = self.x[lo] |
| x_hi = self.x[hi] |
| y_lo = self._y[lo] |
| y_hi = self._y[hi] |
|
|
| |
| |
| slope = (y_hi - y_lo) / (x_hi - x_lo)[:, None] |
|
|
| |
| y_new = slope*(x_new - x_lo)[:, None] + y_lo |
|
|
| return y_new |
|
|
| def _call_nearest(self, x_new): |
| """ Find nearest neighbor interpolated y_new = f(x_new).""" |
|
|
| |
| |
| |
| |
| x_new_indices = searchsorted(self.x_bds, x_new, side=self._side) |
|
|
| |
| x_new_indices = x_new_indices.clip(0, len(self.x)-1).astype(intp) |
|
|
| |
| y_new = self._y[x_new_indices] |
|
|
| return y_new |
|
|
| def _call_previousnext(self, x_new): |
| """Use previous/next neighbor of x_new, y_new = f(x_new).""" |
|
|
| |
| x_new_indices = searchsorted(self._x_shift, x_new, side=self._side) |
|
|
| |
| x_new_indices = x_new_indices.clip(1-self._ind, |
| len(self.x)-self._ind).astype(intp) |
|
|
| |
| y_new = self._y[x_new_indices+self._ind-1] |
|
|
| return y_new |
|
|
| def _call_spline(self, x_new): |
| return self._spline(x_new) |
|
|
| def _call_nan_spline(self, x_new): |
| out = self._spline(x_new) |
| out[...] = np.nan |
| return out |
|
|
| def _evaluate(self, x_new): |
| |
| |
| |
| x_new = asarray(x_new) |
| y_new = self._call(self, x_new) |
| if not self._extrapolate: |
| below_bounds, above_bounds = self._check_bounds(x_new) |
| if len(y_new) > 0: |
| |
| |
| y_new[below_bounds] = self._fill_value_below |
| y_new[above_bounds] = self._fill_value_above |
| return y_new |
|
|
| def _check_bounds(self, x_new): |
| """Check the inputs for being in the bounds of the interpolated data. |
| |
| Parameters |
| ---------- |
| x_new : array |
| |
| Returns |
| ------- |
| out_of_bounds : bool array |
| The mask on x_new of values that are out of the bounds. |
| """ |
|
|
| |
| |
| |
| below_bounds = x_new < self.x[0] |
| above_bounds = x_new > self.x[-1] |
|
|
| if self.bounds_error and below_bounds.any(): |
| below_bounds_value = x_new[np.argmax(below_bounds)] |
| raise ValueError(f"A value ({below_bounds_value}) in x_new is below " |
| f"the interpolation range's minimum value ({self.x[0]}).") |
| if self.bounds_error and above_bounds.any(): |
| above_bounds_value = x_new[np.argmax(above_bounds)] |
| raise ValueError(f"A value ({above_bounds_value}) in x_new is above " |
| f"the interpolation range's maximum value ({self.x[-1]}).") |
|
|
| |
| |
| return below_bounds, above_bounds |
|
|
|
|
| class _PPolyBase: |
| """Base class for piecewise polynomials.""" |
| __slots__ = ('c', 'x', 'extrapolate', 'axis') |
|
|
| def __init__(self, c, x, extrapolate=None, axis=0): |
| self.c = np.asarray(c) |
| self.x = np.ascontiguousarray(x, dtype=np.float64) |
|
|
| if extrapolate is None: |
| extrapolate = True |
| elif extrapolate != 'periodic': |
| extrapolate = bool(extrapolate) |
| self.extrapolate = extrapolate |
|
|
| if self.c.ndim < 2: |
| raise ValueError("Coefficients array must be at least " |
| "2-dimensional.") |
|
|
| if not (0 <= axis < self.c.ndim - 1): |
| raise ValueError(f"axis={axis} must be between 0 and {self.c.ndim-1}") |
|
|
| self.axis = axis |
| if axis != 0: |
| |
| |
| |
| |
| |
| |
| self.c = np.moveaxis(self.c, axis+1, 0) |
| self.c = np.moveaxis(self.c, axis+1, 0) |
|
|
| if self.x.ndim != 1: |
| raise ValueError("x must be 1-dimensional") |
| if self.x.size < 2: |
| raise ValueError("at least 2 breakpoints are needed") |
| if self.c.ndim < 2: |
| raise ValueError("c must have at least 2 dimensions") |
| if self.c.shape[0] == 0: |
| raise ValueError("polynomial must be at least of order 0") |
| if self.c.shape[1] != self.x.size-1: |
| raise ValueError("number of coefficients != len(x)-1") |
| dx = np.diff(self.x) |
| if not (np.all(dx >= 0) or np.all(dx <= 0)): |
| raise ValueError("`x` must be strictly increasing or decreasing.") |
|
|
| dtype = self._get_dtype(self.c.dtype) |
| self.c = np.ascontiguousarray(self.c, dtype=dtype) |
|
|
| def _get_dtype(self, dtype): |
| if np.issubdtype(dtype, np.complexfloating) \ |
| or np.issubdtype(self.c.dtype, np.complexfloating): |
| return np.complex128 |
| else: |
| return np.float64 |
|
|
| @classmethod |
| def construct_fast(cls, c, x, extrapolate=None, axis=0): |
| """ |
| Construct the piecewise polynomial without making checks. |
| |
| Takes the same parameters as the constructor. Input arguments |
| ``c`` and ``x`` must be arrays of the correct shape and type. The |
| ``c`` array can only be of dtypes float and complex, and ``x`` |
| array must have dtype float. |
| """ |
| self = object.__new__(cls) |
| self.c = c |
| self.x = x |
| self.axis = axis |
| if extrapolate is None: |
| extrapolate = True |
| self.extrapolate = extrapolate |
| return self |
|
|
| def _ensure_c_contiguous(self): |
| """ |
| c and x may be modified by the user. The Cython code expects |
| that they are C contiguous. |
| """ |
| if not self.x.flags.c_contiguous: |
| self.x = self.x.copy() |
| if not self.c.flags.c_contiguous: |
| self.c = self.c.copy() |
|
|
| def extend(self, c, x): |
| """ |
| Add additional breakpoints and coefficients to the polynomial. |
| |
| Parameters |
| ---------- |
| c : ndarray, size (k, m, ...) |
| Additional coefficients for polynomials in intervals. Note that |
| the first additional interval will be formed using one of the |
| ``self.x`` end points. |
| x : ndarray, size (m,) |
| Additional breakpoints. Must be sorted in the same order as |
| ``self.x`` and either to the right or to the left of the current |
| breakpoints. |
| |
| Notes |
| ----- |
| This method is not thread safe and must not be executed concurrently |
| with other methods available in this class. Doing so may cause |
| unexpected errors or numerical output mismatches. |
| """ |
|
|
| c = np.asarray(c) |
| x = np.asarray(x) |
|
|
| if c.ndim < 2: |
| raise ValueError("invalid dimensions for c") |
| if x.ndim != 1: |
| raise ValueError("invalid dimensions for x") |
| if x.shape[0] != c.shape[1]: |
| raise ValueError(f"Shapes of x {x.shape} and c {c.shape} are incompatible") |
| if c.shape[2:] != self.c.shape[2:] or c.ndim != self.c.ndim: |
| raise ValueError( |
| f"Shapes of c {c.shape} and self.c {self.c.shape} are incompatible" |
| ) |
|
|
| if c.size == 0: |
| return |
|
|
| dx = np.diff(x) |
| if not (np.all(dx >= 0) or np.all(dx <= 0)): |
| raise ValueError("`x` is not sorted.") |
|
|
| if self.x[-1] >= self.x[0]: |
| if not x[-1] >= x[0]: |
| raise ValueError("`x` is in the different order " |
| "than `self.x`.") |
|
|
| if x[0] >= self.x[-1]: |
| action = 'append' |
| elif x[-1] <= self.x[0]: |
| action = 'prepend' |
| else: |
| raise ValueError("`x` is neither on the left or on the right " |
| "from `self.x`.") |
| else: |
| if not x[-1] <= x[0]: |
| raise ValueError("`x` is in the different order " |
| "than `self.x`.") |
|
|
| if x[0] <= self.x[-1]: |
| action = 'append' |
| elif x[-1] >= self.x[0]: |
| action = 'prepend' |
| else: |
| raise ValueError("`x` is neither on the left or on the right " |
| "from `self.x`.") |
|
|
| dtype = self._get_dtype(c.dtype) |
|
|
| k2 = max(c.shape[0], self.c.shape[0]) |
| c2 = np.zeros((k2, self.c.shape[1] + c.shape[1]) + self.c.shape[2:], |
| dtype=dtype) |
|
|
| if action == 'append': |
| c2[k2-self.c.shape[0]:, :self.c.shape[1]] = self.c |
| c2[k2-c.shape[0]:, self.c.shape[1]:] = c |
| self.x = np.r_[self.x, x] |
| elif action == 'prepend': |
| c2[k2-self.c.shape[0]:, :c.shape[1]] = c |
| c2[k2-c.shape[0]:, c.shape[1]:] = self.c |
| self.x = np.r_[x, self.x] |
|
|
| self.c = c2 |
|
|
| def __call__(self, x, nu=0, extrapolate=None): |
| """ |
| Evaluate the piecewise polynomial or its derivative. |
| |
| Parameters |
| ---------- |
| x : array_like |
| Points to evaluate the interpolant at. |
| nu : int, optional |
| Order of derivative to evaluate. Must be non-negative. |
| extrapolate : {bool, 'periodic', None}, optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. |
| If None (default), use `self.extrapolate`. |
| |
| Returns |
| ------- |
| y : array_like |
| Interpolated values. Shape is determined by replacing |
| the interpolation axis in the original array with the shape of x. |
| |
| Notes |
| ----- |
| Derivatives are evaluated piecewise for each polynomial |
| segment, even if the polynomial is not differentiable at the |
| breakpoints. The polynomial intervals are considered half-open, |
| ``[a, b)``, except for the last interval which is closed |
| ``[a, b]``. |
| """ |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
| x = np.asarray(x) |
| x_shape, x_ndim = x.shape, x.ndim |
| x = np.ascontiguousarray(x.ravel(), dtype=np.float64) |
|
|
| |
| |
| if extrapolate == 'periodic': |
| x = self.x[0] + (x - self.x[0]) % (self.x[-1] - self.x[0]) |
| extrapolate = False |
|
|
| out = np.empty((len(x), prod(self.c.shape[2:])), dtype=self.c.dtype) |
| self._ensure_c_contiguous() |
| self._evaluate(x, nu, extrapolate, out) |
| out = out.reshape(x_shape + self.c.shape[2:]) |
| if self.axis != 0: |
| |
| l = list(range(out.ndim)) |
| l = l[x_ndim:x_ndim+self.axis] + l[:x_ndim] + l[x_ndim+self.axis:] |
| out = out.transpose(l) |
| return out |
|
|
|
|
| class PPoly(_PPolyBase): |
| """ |
| Piecewise polynomial in terms of coefficients and breakpoints |
| |
| The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the |
| local power basis:: |
| |
| S = sum(c[m, i] * (xp - x[i])**(k-m) for m in range(k+1)) |
| |
| where ``k`` is the degree of the polynomial. |
| |
| Parameters |
| ---------- |
| c : ndarray, shape (k, m, ...) |
| Polynomial coefficients, order `k` and `m` intervals. |
| x : ndarray, shape (m+1,) |
| Polynomial breakpoints. Must be sorted in either increasing or |
| decreasing order. |
| extrapolate : bool or 'periodic', optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. If 'periodic', |
| periodic extrapolation is used. Default is True. |
| axis : int, optional |
| Interpolation axis. Default is zero. |
| |
| Attributes |
| ---------- |
| x : ndarray |
| Breakpoints. |
| c : ndarray |
| Coefficients of the polynomials. They are reshaped |
| to a 3-D array with the last dimension representing |
| the trailing dimensions of the original coefficient array. |
| axis : int |
| Interpolation axis. |
| |
| Methods |
| ------- |
| __call__ |
| derivative |
| antiderivative |
| integrate |
| solve |
| roots |
| extend |
| from_spline |
| from_bernstein_basis |
| construct_fast |
| |
| See also |
| -------- |
| BPoly : piecewise polynomials in the Bernstein basis |
| |
| Notes |
| ----- |
| High-order polynomials in the power basis can be numerically |
| unstable. Precision problems can start to appear for orders |
| larger than 20-30. |
| """ |
|
|
| def _evaluate(self, x, nu, extrapolate, out): |
| _ppoly.evaluate(self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, x, nu, bool(extrapolate), out) |
|
|
| def derivative(self, nu=1): |
| """ |
| Construct a new piecewise polynomial representing the derivative. |
| |
| Parameters |
| ---------- |
| nu : int, optional |
| Order of derivative to evaluate. Default is 1, i.e., compute the |
| first derivative. If negative, the antiderivative is returned. |
| |
| Returns |
| ------- |
| pp : PPoly |
| Piecewise polynomial of order k2 = k - n representing the derivative |
| of this polynomial. |
| |
| Notes |
| ----- |
| Derivatives are evaluated piecewise for each polynomial |
| segment, even if the polynomial is not differentiable at the |
| breakpoints. The polynomial intervals are considered half-open, |
| ``[a, b)``, except for the last interval which is closed |
| ``[a, b]``. |
| """ |
| if nu < 0: |
| return self.antiderivative(-nu) |
|
|
| |
| if nu == 0: |
| c2 = self.c.copy() |
| else: |
| c2 = self.c[:-nu, :].copy() |
|
|
| if c2.shape[0] == 0: |
| |
| c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype) |
|
|
| |
| factor = spec.poch(np.arange(c2.shape[0], 0, -1), nu) |
| c2 *= factor[(slice(None),) + (None,)*(c2.ndim-1)] |
|
|
| |
| return self.construct_fast(c2, self.x, self.extrapolate, self.axis) |
|
|
| def antiderivative(self, nu=1): |
| """ |
| Construct a new piecewise polynomial representing the antiderivative. |
| |
| Antiderivative is also the indefinite integral of the function, |
| and derivative is its inverse operation. |
| |
| Parameters |
| ---------- |
| nu : int, optional |
| Order of antiderivative to evaluate. Default is 1, i.e., compute |
| the first integral. If negative, the derivative is returned. |
| |
| Returns |
| ------- |
| pp : PPoly |
| Piecewise polynomial of order k2 = k + n representing |
| the antiderivative of this polynomial. |
| |
| Notes |
| ----- |
| The antiderivative returned by this function is continuous and |
| continuously differentiable to order n-1, up to floating point |
| rounding error. |
| |
| If antiderivative is computed and ``self.extrapolate='periodic'``, |
| it will be set to False for the returned instance. This is done because |
| the antiderivative is no longer periodic and its correct evaluation |
| outside of the initially given x interval is difficult. |
| """ |
| if nu <= 0: |
| return self.derivative(-nu) |
|
|
| c = np.zeros((self.c.shape[0] + nu, self.c.shape[1]) + self.c.shape[2:], |
| dtype=self.c.dtype) |
| c[:-nu] = self.c |
|
|
| |
| factor = spec.poch(np.arange(self.c.shape[0], 0, -1), nu) |
| c[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)] |
|
|
| |
| self._ensure_c_contiguous() |
| _ppoly.fix_continuity(c.reshape(c.shape[0], c.shape[1], -1), |
| self.x, nu - 1) |
|
|
| if self.extrapolate == 'periodic': |
| extrapolate = False |
| else: |
| extrapolate = self.extrapolate |
|
|
| |
| return self.construct_fast(c, self.x, extrapolate, self.axis) |
|
|
| def integrate(self, a, b, extrapolate=None): |
| """ |
| Compute a definite integral over a piecewise polynomial. |
| |
| Parameters |
| ---------- |
| a : float |
| Lower integration bound |
| b : float |
| Upper integration bound |
| extrapolate : {bool, 'periodic', None}, optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. |
| If None (default), use `self.extrapolate`. |
| |
| Returns |
| ------- |
| ig : array_like |
| Definite integral of the piecewise polynomial over [a, b] |
| """ |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
|
|
| |
| sign = 1 |
| if b < a: |
| a, b = b, a |
| sign = -1 |
|
|
| range_int = np.empty((prod(self.c.shape[2:]),), dtype=self.c.dtype) |
| self._ensure_c_contiguous() |
|
|
| |
| if extrapolate == 'periodic': |
| |
| |
|
|
| xs, xe = self.x[0], self.x[-1] |
| period = xe - xs |
| interval = b - a |
| n_periods, left = divmod(interval, period) |
|
|
| if n_periods > 0: |
| _ppoly.integrate( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, xs, xe, False, out=range_int) |
| range_int *= n_periods |
| else: |
| range_int.fill(0) |
|
|
| |
| a = xs + (a - xs) % period |
| b = a + left |
|
|
| |
| |
| remainder_int = np.empty_like(range_int) |
| if b <= xe: |
| _ppoly.integrate( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, a, b, False, out=remainder_int) |
| range_int += remainder_int |
| else: |
| _ppoly.integrate( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, a, xe, False, out=remainder_int) |
| range_int += remainder_int |
|
|
| _ppoly.integrate( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, xs, xs + left + a - xe, False, out=remainder_int) |
| range_int += remainder_int |
| else: |
| _ppoly.integrate( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, a, b, bool(extrapolate), out=range_int) |
|
|
| |
| range_int *= sign |
| return range_int.reshape(self.c.shape[2:]) |
|
|
| def solve(self, y=0., discontinuity=True, extrapolate=None): |
| """ |
| Find real solutions of the equation ``pp(x) == y``. |
| |
| Parameters |
| ---------- |
| y : float, optional |
| Right-hand side. Default is zero. |
| discontinuity : bool, optional |
| Whether to report sign changes across discontinuities at |
| breakpoints as roots. |
| extrapolate : {bool, 'periodic', None}, optional |
| If bool, determines whether to return roots from the polynomial |
| extrapolated based on first and last intervals, 'periodic' works |
| the same as False. If None (default), use `self.extrapolate`. |
| |
| Returns |
| ------- |
| roots : ndarray |
| Roots of the polynomial(s). |
| |
| If the PPoly object describes multiple polynomials, the |
| return value is an object array whose each element is an |
| ndarray containing the roots. |
| |
| Notes |
| ----- |
| This routine works only on real-valued polynomials. |
| |
| If the piecewise polynomial contains sections that are |
| identically zero, the root list will contain the start point |
| of the corresponding interval, followed by a ``nan`` value. |
| |
| If the polynomial is discontinuous across a breakpoint, and |
| there is a sign change across the breakpoint, this is reported |
| if the `discont` parameter is True. |
| |
| Examples |
| -------- |
| |
| Finding roots of ``[x**2 - 1, (x - 1)**2]`` defined on intervals |
| ``[-2, 1], [1, 2]``: |
| |
| >>> import numpy as np |
| >>> from scipy.interpolate import PPoly |
| >>> pp = PPoly(np.array([[1, -4, 3], [1, 0, 0]]).T, [-2, 1, 2]) |
| >>> pp.solve() |
| array([-1., 1.]) |
| """ |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
|
|
| self._ensure_c_contiguous() |
|
|
| if np.issubdtype(self.c.dtype, np.complexfloating): |
| raise ValueError("Root finding is only for " |
| "real-valued polynomials") |
|
|
| y = float(y) |
| r = _ppoly.real_roots(self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, y, bool(discontinuity), |
| bool(extrapolate)) |
| if self.c.ndim == 2: |
| return r[0] |
| else: |
| r2 = np.empty(prod(self.c.shape[2:]), dtype=object) |
| |
| |
| for ii, root in enumerate(r): |
| r2[ii] = root |
|
|
| return r2.reshape(self.c.shape[2:]) |
|
|
| def roots(self, discontinuity=True, extrapolate=None): |
| """ |
| Find real roots of the piecewise polynomial. |
| |
| Parameters |
| ---------- |
| discontinuity : bool, optional |
| Whether to report sign changes across discontinuities at |
| breakpoints as roots. |
| extrapolate : {bool, 'periodic', None}, optional |
| If bool, determines whether to return roots from the polynomial |
| extrapolated based on first and last intervals, 'periodic' works |
| the same as False. If None (default), use `self.extrapolate`. |
| |
| Returns |
| ------- |
| roots : ndarray |
| Roots of the polynomial(s). |
| |
| If the PPoly object describes multiple polynomials, the |
| return value is an object array whose each element is an |
| ndarray containing the roots. |
| |
| See Also |
| -------- |
| PPoly.solve |
| """ |
| return self.solve(0, discontinuity, extrapolate) |
|
|
| @classmethod |
| def from_spline(cls, tck, extrapolate=None): |
| """ |
| Construct a piecewise polynomial from a spline |
| |
| Parameters |
| ---------- |
| tck |
| A spline, as returned by `splrep` or a BSpline object. |
| extrapolate : bool or 'periodic', optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. Default is True. |
| |
| Examples |
| -------- |
| Construct an interpolating spline and convert it to a `PPoly` instance |
| |
| >>> import numpy as np |
| >>> from scipy.interpolate import splrep, PPoly |
| >>> x = np.linspace(0, 1, 11) |
| >>> y = np.sin(2*np.pi*x) |
| >>> tck = splrep(x, y, s=0) |
| >>> p = PPoly.from_spline(tck) |
| >>> isinstance(p, PPoly) |
| True |
| |
| Note that this function only supports 1D splines out of the box. |
| |
| If the ``tck`` object represents a parametric spline (e.g. constructed |
| by `splprep` or a `BSpline` with ``c.ndim > 1``), you will need to loop |
| over the dimensions manually. |
| |
| >>> from scipy.interpolate import splprep, splev |
| >>> t = np.linspace(0, 1, 11) |
| >>> x = np.sin(2*np.pi*t) |
| >>> y = np.cos(2*np.pi*t) |
| >>> (t, c, k), u = splprep([x, y], s=0) |
| |
| Note that ``c`` is a list of two arrays of length 11. |
| |
| >>> unew = np.arange(0, 1.01, 0.01) |
| >>> out = splev(unew, (t, c, k)) |
| |
| To convert this spline to the power basis, we convert each |
| component of the list of b-spline coefficients, ``c``, into the |
| corresponding cubic polynomial. |
| |
| >>> polys = [PPoly.from_spline((t, cj, k)) for cj in c] |
| >>> polys[0].c.shape |
| (4, 14) |
| |
| Note that the coefficients of the polynomials `polys` are in the |
| power basis and their dimensions reflect just that: here 4 is the order |
| (degree+1), and 14 is the number of intervals---which is nothing but |
| the length of the knot array of the original `tck` minus one. |
| |
| Optionally, we can stack the components into a single `PPoly` along |
| the third dimension: |
| |
| >>> cc = np.dstack([p.c for p in polys]) # has shape = (4, 14, 2) |
| >>> poly = PPoly(cc, polys[0].x) |
| >>> np.allclose(poly(unew).T, # note the transpose to match `splev` |
| ... out, atol=1e-15) |
| True |
| |
| """ |
| if isinstance(tck, BSpline): |
| t, c, k = tck.tck |
| if extrapolate is None: |
| extrapolate = tck.extrapolate |
| else: |
| t, c, k = tck |
|
|
| cvals = np.empty((k + 1, len(t)-1), dtype=c.dtype) |
| for m in range(k, -1, -1): |
| y = _fitpack_py.splev(t[:-1], tck, der=m) |
| cvals[k - m, :] = y/spec.gamma(m+1) |
|
|
| return cls.construct_fast(cvals, t, extrapolate) |
|
|
| @classmethod |
| def from_bernstein_basis(cls, bp, extrapolate=None): |
| """ |
| Construct a piecewise polynomial in the power basis |
| from a polynomial in Bernstein basis. |
| |
| Parameters |
| ---------- |
| bp : BPoly |
| A Bernstein basis polynomial, as created by BPoly |
| extrapolate : bool or 'periodic', optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. Default is True. |
| """ |
| if not isinstance(bp, BPoly): |
| raise TypeError(f".from_bernstein_basis only accepts BPoly instances. " |
| f"Got {type(bp)} instead.") |
|
|
| dx = np.diff(bp.x) |
| k = bp.c.shape[0] - 1 |
|
|
| rest = (None,)*(bp.c.ndim-2) |
|
|
| c = np.zeros_like(bp.c) |
| for a in range(k+1): |
| factor = (-1)**a * comb(k, a) * bp.c[a] |
| for s in range(a, k+1): |
| val = comb(k-a, s-a) * (-1)**s |
| c[k-s] += factor * val / dx[(slice(None),)+rest]**s |
|
|
| if extrapolate is None: |
| extrapolate = bp.extrapolate |
|
|
| return cls.construct_fast(c, bp.x, extrapolate, bp.axis) |
|
|
|
|
| class BPoly(_PPolyBase): |
| """Piecewise polynomial in terms of coefficients and breakpoints. |
| |
| The polynomial between ``x[i]`` and ``x[i + 1]`` is written in the |
| Bernstein polynomial basis:: |
| |
| S = sum(c[a, i] * b(a, k; x) for a in range(k+1)), |
| |
| where ``k`` is the degree of the polynomial, and:: |
| |
| b(a, k; x) = binom(k, a) * t**a * (1 - t)**(k - a), |
| |
| with ``t = (x - x[i]) / (x[i+1] - x[i])`` and ``binom`` is the binomial |
| coefficient. |
| |
| Parameters |
| ---------- |
| c : ndarray, shape (k, m, ...) |
| Polynomial coefficients, order `k` and `m` intervals |
| x : ndarray, shape (m+1,) |
| Polynomial breakpoints. Must be sorted in either increasing or |
| decreasing order. |
| extrapolate : bool, optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. If 'periodic', |
| periodic extrapolation is used. Default is True. |
| axis : int, optional |
| Interpolation axis. Default is zero. |
| |
| Attributes |
| ---------- |
| x : ndarray |
| Breakpoints. |
| c : ndarray |
| Coefficients of the polynomials. They are reshaped |
| to a 3-D array with the last dimension representing |
| the trailing dimensions of the original coefficient array. |
| axis : int |
| Interpolation axis. |
| |
| Methods |
| ------- |
| __call__ |
| extend |
| derivative |
| antiderivative |
| integrate |
| construct_fast |
| from_power_basis |
| from_derivatives |
| |
| See also |
| -------- |
| PPoly : piecewise polynomials in the power basis |
| |
| Notes |
| ----- |
| Properties of Bernstein polynomials are well documented in the literature, |
| see for example [1]_ [2]_ [3]_. |
| |
| References |
| ---------- |
| .. [1] https://en.wikipedia.org/wiki/Bernstein_polynomial |
| |
| .. [2] Kenneth I. Joy, Bernstein polynomials, |
| http://www.idav.ucdavis.edu/education/CAGDNotes/Bernstein-Polynomials.pdf |
| |
| .. [3] E. H. Doha, A. H. Bhrawy, and M. A. Saker, Boundary Value Problems, |
| vol 2011, article ID 829546, :doi:`10.1155/2011/829543`. |
| |
| Examples |
| -------- |
| >>> from scipy.interpolate import BPoly |
| >>> x = [0, 1] |
| >>> c = [[1], [2], [3]] |
| >>> bp = BPoly(c, x) |
| |
| This creates a 2nd order polynomial |
| |
| .. math:: |
| |
| B(x) = 1 \\times b_{0, 2}(x) + 2 \\times b_{1, 2}(x) + 3 |
| \\times b_{2, 2}(x) \\\\ |
| = 1 \\times (1-x)^2 + 2 \\times 2 x (1 - x) + 3 \\times x^2 |
| |
| """ |
|
|
| def _evaluate(self, x, nu, extrapolate, out): |
| _ppoly.evaluate_bernstein( |
| self.c.reshape(self.c.shape[0], self.c.shape[1], -1), |
| self.x, x, nu, bool(extrapolate), out) |
|
|
| def derivative(self, nu=1): |
| """ |
| Construct a new piecewise polynomial representing the derivative. |
| |
| Parameters |
| ---------- |
| nu : int, optional |
| Order of derivative to evaluate. Default is 1, i.e., compute the |
| first derivative. If negative, the antiderivative is returned. |
| |
| Returns |
| ------- |
| bp : BPoly |
| Piecewise polynomial of order k - nu representing the derivative of |
| this polynomial. |
| |
| """ |
| if nu < 0: |
| return self.antiderivative(-nu) |
|
|
| if nu > 1: |
| bp = self |
| for k in range(nu): |
| bp = bp.derivative() |
| return bp |
|
|
| |
| if nu == 0: |
| c2 = self.c.copy() |
| else: |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| rest = (None,)*(self.c.ndim-2) |
|
|
| k = self.c.shape[0] - 1 |
| dx = np.diff(self.x)[(None, slice(None))+rest] |
| c2 = k * np.diff(self.c, axis=0) / dx |
|
|
| if c2.shape[0] == 0: |
| |
| c2 = np.zeros((1,) + c2.shape[1:], dtype=c2.dtype) |
|
|
| |
| return self.construct_fast(c2, self.x, self.extrapolate, self.axis) |
|
|
| def antiderivative(self, nu=1): |
| """ |
| Construct a new piecewise polynomial representing the antiderivative. |
| |
| Parameters |
| ---------- |
| nu : int, optional |
| Order of antiderivative to evaluate. Default is 1, i.e., compute |
| the first integral. If negative, the derivative is returned. |
| |
| Returns |
| ------- |
| bp : BPoly |
| Piecewise polynomial of order k + nu representing the |
| antiderivative of this polynomial. |
| |
| Notes |
| ----- |
| If antiderivative is computed and ``self.extrapolate='periodic'``, |
| it will be set to False for the returned instance. This is done because |
| the antiderivative is no longer periodic and its correct evaluation |
| outside of the initially given x interval is difficult. |
| """ |
| if nu <= 0: |
| return self.derivative(-nu) |
|
|
| if nu > 1: |
| bp = self |
| for k in range(nu): |
| bp = bp.antiderivative() |
| return bp |
|
|
| |
| c, x = self.c, self.x |
| k = c.shape[0] |
| c2 = np.zeros((k+1,) + c.shape[1:], dtype=c.dtype) |
|
|
| c2[1:, ...] = np.cumsum(c, axis=0) / k |
| delta = x[1:] - x[:-1] |
| c2 *= delta[(None, slice(None)) + (None,)*(c.ndim-2)] |
|
|
| |
| |
| |
| |
| |
| |
| c2[:,1:] += np.cumsum(c2[k, :], axis=0)[:-1] |
|
|
| if self.extrapolate == 'periodic': |
| extrapolate = False |
| else: |
| extrapolate = self.extrapolate |
|
|
| return self.construct_fast(c2, x, extrapolate, axis=self.axis) |
|
|
| def integrate(self, a, b, extrapolate=None): |
| """ |
| Compute a definite integral over a piecewise polynomial. |
| |
| Parameters |
| ---------- |
| a : float |
| Lower integration bound |
| b : float |
| Upper integration bound |
| extrapolate : {bool, 'periodic', None}, optional |
| Whether to extrapolate to out-of-bounds points based on first |
| and last intervals, or to return NaNs. If 'periodic', periodic |
| extrapolation is used. If None (default), use `self.extrapolate`. |
| |
| Returns |
| ------- |
| array_like |
| Definite integral of the piecewise polynomial over [a, b] |
| |
| """ |
| |
| |
| ib = self.antiderivative() |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
|
|
| |
| |
| if extrapolate != 'periodic': |
| ib.extrapolate = extrapolate |
|
|
| if extrapolate == 'periodic': |
| |
| |
|
|
| |
| if a <= b: |
| sign = 1 |
| else: |
| a, b = b, a |
| sign = -1 |
|
|
| xs, xe = self.x[0], self.x[-1] |
| period = xe - xs |
| interval = b - a |
| n_periods, left = divmod(interval, period) |
| res = n_periods * (ib(xe) - ib(xs)) |
|
|
| |
| a = xs + (a - xs) % period |
| b = a + left |
|
|
| |
| |
| if b <= xe: |
| res += ib(b) - ib(a) |
| else: |
| res += ib(xe) - ib(a) + ib(xs + left + a - xe) - ib(xs) |
|
|
| return sign * res |
| else: |
| return ib(b) - ib(a) |
|
|
| def extend(self, c, x): |
| k = max(self.c.shape[0], c.shape[0]) |
| self.c = self._raise_degree(self.c, k - self.c.shape[0]) |
| c = self._raise_degree(c, k - c.shape[0]) |
| return _PPolyBase.extend(self, c, x) |
| extend.__doc__ = _PPolyBase.extend.__doc__ |
|
|
| @classmethod |
| def from_power_basis(cls, pp, extrapolate=None): |
| """ |
| Construct a piecewise polynomial in Bernstein basis |
| from a power basis polynomial. |
| |
| Parameters |
| ---------- |
| pp : PPoly |
| A piecewise polynomial in the power basis |
| extrapolate : bool or 'periodic', optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. Default is True. |
| """ |
| if not isinstance(pp, PPoly): |
| raise TypeError(f".from_power_basis only accepts PPoly instances. " |
| f"Got {type(pp)} instead.") |
|
|
| dx = np.diff(pp.x) |
| k = pp.c.shape[0] - 1 |
|
|
| rest = (None,)*(pp.c.ndim-2) |
|
|
| c = np.zeros_like(pp.c) |
| for a in range(k+1): |
| factor = pp.c[a] / comb(k, k-a) * dx[(slice(None),)+rest]**(k-a) |
| for j in range(k-a, k+1): |
| c[j] += factor * comb(j, k-a) |
|
|
| if extrapolate is None: |
| extrapolate = pp.extrapolate |
|
|
| return cls.construct_fast(c, pp.x, extrapolate, pp.axis) |
|
|
| @classmethod |
| def from_derivatives(cls, xi, yi, orders=None, extrapolate=None): |
| """Construct a piecewise polynomial in the Bernstein basis, |
| compatible with the specified values and derivatives at breakpoints. |
| |
| Parameters |
| ---------- |
| xi : array_like |
| sorted 1-D array of x-coordinates |
| yi : array_like or list of array_likes |
| ``yi[i][j]`` is the ``j``\\ th derivative known at ``xi[i]`` |
| orders : None or int or array_like of ints. Default: None. |
| Specifies the degree of local polynomials. If not None, some |
| derivatives are ignored. |
| extrapolate : bool or 'periodic', optional |
| If bool, determines whether to extrapolate to out-of-bounds points |
| based on first and last intervals, or to return NaNs. |
| If 'periodic', periodic extrapolation is used. Default is True. |
| |
| Notes |
| ----- |
| If ``k`` derivatives are specified at a breakpoint ``x``, the |
| constructed polynomial is exactly ``k`` times continuously |
| differentiable at ``x``, unless the ``order`` is provided explicitly. |
| In the latter case, the smoothness of the polynomial at |
| the breakpoint is controlled by the ``order``. |
| |
| Deduces the number of derivatives to match at each end |
| from ``order`` and the number of derivatives available. If |
| possible it uses the same number of derivatives from |
| each end; if the number is odd it tries to take the |
| extra one from y2. In any case if not enough derivatives |
| are available at one end or another it draws enough to |
| make up the total from the other end. |
| |
| If the order is too high and not enough derivatives are available, |
| an exception is raised. |
| |
| Examples |
| -------- |
| |
| >>> from scipy.interpolate import BPoly |
| >>> BPoly.from_derivatives([0, 1], [[1, 2], [3, 4]]) |
| |
| Creates a polynomial `f(x)` of degree 3, defined on ``[0, 1]`` |
| such that `f(0) = 1, df/dx(0) = 2, f(1) = 3, df/dx(1) = 4` |
| |
| >>> BPoly.from_derivatives([0, 1, 2], [[0, 1], [0], [2]]) |
| |
| Creates a piecewise polynomial `f(x)`, such that |
| `f(0) = f(1) = 0`, `f(2) = 2`, and `df/dx(0) = 1`. |
| Based on the number of derivatives provided, the order of the |
| local polynomials is 2 on ``[0, 1]`` and 1 on ``[1, 2]``. |
| Notice that no restriction is imposed on the derivatives at |
| ``x = 1`` and ``x = 2``. |
| |
| Indeed, the explicit form of the polynomial is:: |
| |
| f(x) = | x * (1 - x), 0 <= x < 1 |
| | 2 * (x - 1), 1 <= x <= 2 |
| |
| So that f'(1-0) = -1 and f'(1+0) = 2 |
| |
| """ |
| xi = np.asarray(xi) |
| if len(xi) != len(yi): |
| raise ValueError("xi and yi need to have the same length") |
| if np.any(xi[1:] - xi[:1] <= 0): |
| raise ValueError("x coordinates are not in increasing order") |
|
|
| |
| m = len(xi) - 1 |
|
|
| |
| try: |
| k = max(len(yi[i]) + len(yi[i+1]) for i in range(m)) |
| except TypeError as e: |
| raise ValueError( |
| "Using a 1-D array for y? Please .reshape(-1, 1)." |
| ) from e |
|
|
| if orders is None: |
| orders = [None] * m |
| else: |
| if isinstance(orders, (int, np.integer)): |
| orders = [orders] * m |
| k = max(k, max(orders)) |
|
|
| if any(o <= 0 for o in orders): |
| raise ValueError("Orders must be positive.") |
|
|
| c = [] |
| for i in range(m): |
| y1, y2 = yi[i], yi[i+1] |
| if orders[i] is None: |
| n1, n2 = len(y1), len(y2) |
| else: |
| n = orders[i]+1 |
| n1 = min(n//2, len(y1)) |
| n2 = min(n - n1, len(y2)) |
| n1 = min(n - n2, len(y2)) |
| if n1+n2 != n: |
| mesg = ("Point %g has %d derivatives, point %g" |
| " has %d derivatives, but order %d requested" % ( |
| xi[i], len(y1), xi[i+1], len(y2), orders[i])) |
| raise ValueError(mesg) |
|
|
| if not (n1 <= len(y1) and n2 <= len(y2)): |
| raise ValueError("`order` input incompatible with" |
| " length y1 or y2.") |
|
|
| b = BPoly._construct_from_derivatives(xi[i], xi[i+1], |
| y1[:n1], y2[:n2]) |
| if len(b) < k: |
| b = BPoly._raise_degree(b, k - len(b)) |
| c.append(b) |
|
|
| c = np.asarray(c) |
| return cls(c.swapaxes(0, 1), xi, extrapolate) |
|
|
| @staticmethod |
| def _construct_from_derivatives(xa, xb, ya, yb): |
| r"""Compute the coefficients of a polynomial in the Bernstein basis |
| given the values and derivatives at the edges. |
| |
| Return the coefficients of a polynomial in the Bernstein basis |
| defined on ``[xa, xb]`` and having the values and derivatives at the |
| endpoints `xa` and `xb` as specified by `ya` and `yb`. |
| The polynomial constructed is of the minimal possible degree, i.e., |
| if the lengths of `ya` and `yb` are `na` and `nb`, the degree |
| of the polynomial is ``na + nb - 1``. |
| |
| Parameters |
| ---------- |
| xa : float |
| Left-hand end point of the interval |
| xb : float |
| Right-hand end point of the interval |
| ya : array_like |
| Derivatives at `xa`. ``ya[0]`` is the value of the function, and |
| ``ya[i]`` for ``i > 0`` is the value of the ``i``\ th derivative. |
| yb : array_like |
| Derivatives at `xb`. |
| |
| Returns |
| ------- |
| array |
| coefficient array of a polynomial having specified derivatives |
| |
| Notes |
| ----- |
| This uses several facts from life of Bernstein basis functions. |
| First of all, |
| |
| .. math:: b'_{a, n} = n (b_{a-1, n-1} - b_{a, n-1}) |
| |
| If B(x) is a linear combination of the form |
| |
| .. math:: B(x) = \sum_{a=0}^{n} c_a b_{a, n}, |
| |
| then :math: B'(x) = n \sum_{a=0}^{n-1} (c_{a+1} - c_{a}) b_{a, n-1}. |
| Iterating the latter one, one finds for the q-th derivative |
| |
| .. math:: B^{q}(x) = n!/(n-q)! \sum_{a=0}^{n-q} Q_a b_{a, n-q}, |
| |
| with |
| |
| .. math:: Q_a = \sum_{j=0}^{q} (-)^{j+q} comb(q, j) c_{j+a} |
| |
| This way, only `a=0` contributes to :math: `B^{q}(x = xa)`, and |
| `c_q` are found one by one by iterating `q = 0, ..., na`. |
| |
| At ``x = xb`` it's the same with ``a = n - q``. |
| |
| """ |
| ya, yb = np.asarray(ya), np.asarray(yb) |
| if ya.shape[1:] != yb.shape[1:]: |
| raise ValueError( |
| f"Shapes of ya {ya.shape} and yb {yb.shape} are incompatible" |
| ) |
|
|
| dta, dtb = ya.dtype, yb.dtype |
| if (np.issubdtype(dta, np.complexfloating) or |
| np.issubdtype(dtb, np.complexfloating)): |
| dt = np.complex128 |
| else: |
| dt = np.float64 |
|
|
| na, nb = len(ya), len(yb) |
| n = na + nb |
|
|
| c = np.empty((na+nb,) + ya.shape[1:], dtype=dt) |
|
|
| |
| |
| for q in range(0, na): |
| c[q] = ya[q] / spec.poch(n - q, q) * (xb - xa)**q |
| for j in range(0, q): |
| c[q] -= (-1)**(j+q) * comb(q, j) * c[j] |
|
|
| |
| for q in range(0, nb): |
| c[-q-1] = yb[q] / spec.poch(n - q, q) * (-1)**q * (xb - xa)**q |
| for j in range(0, q): |
| c[-q-1] -= (-1)**(j+1) * comb(q, j+1) * c[-q+j] |
|
|
| return c |
|
|
| @staticmethod |
| def _raise_degree(c, d): |
| r"""Raise a degree of a polynomial in the Bernstein basis. |
| |
| Given the coefficients of a polynomial degree `k`, return (the |
| coefficients of) the equivalent polynomial of degree `k+d`. |
| |
| Parameters |
| ---------- |
| c : array_like |
| coefficient array, 1-D |
| d : integer |
| |
| Returns |
| ------- |
| array |
| coefficient array, 1-D array of length `c.shape[0] + d` |
| |
| Notes |
| ----- |
| This uses the fact that a Bernstein polynomial `b_{a, k}` can be |
| identically represented as a linear combination of polynomials of |
| a higher degree `k+d`: |
| |
| .. math:: b_{a, k} = comb(k, a) \sum_{j=0}^{d} b_{a+j, k+d} \ |
| comb(d, j) / comb(k+d, a+j) |
| |
| """ |
| if d == 0: |
| return c |
|
|
| k = c.shape[0] - 1 |
| out = np.zeros((c.shape[0] + d,) + c.shape[1:], dtype=c.dtype) |
|
|
| for a in range(c.shape[0]): |
| f = c[a] * comb(k, a) |
| for j in range(d+1): |
| out[a+j] += f * comb(d, j) / comb(k+d, a+j) |
| return out |
|
|
|
|
| class NdPPoly: |
| """ |
| Piecewise tensor product polynomial |
| |
| The value at point ``xp = (x', y', z', ...)`` is evaluated by first |
| computing the interval indices `i` such that:: |
| |
| x[0][i[0]] <= x' < x[0][i[0]+1] |
| x[1][i[1]] <= y' < x[1][i[1]+1] |
| ... |
| |
| and then computing:: |
| |
| S = sum(c[k0-m0-1,...,kn-mn-1,i[0],...,i[n]] |
| * (xp[0] - x[0][i[0]])**m0 |
| * ... |
| * (xp[n] - x[n][i[n]])**mn |
| for m0 in range(k[0]+1) |
| ... |
| for mn in range(k[n]+1)) |
| |
| where ``k[j]`` is the degree of the polynomial in dimension j. This |
| representation is the piecewise multivariate power basis. |
| |
| Parameters |
| ---------- |
| c : ndarray, shape (k0, ..., kn, m0, ..., mn, ...) |
| Polynomial coefficients, with polynomial order `kj` and |
| `mj+1` intervals for each dimension `j`. |
| x : ndim-tuple of ndarrays, shapes (mj+1,) |
| Polynomial breakpoints for each dimension. These must be |
| sorted in increasing order. |
| extrapolate : bool, optional |
| Whether to extrapolate to out-of-bounds points based on first |
| and last intervals, or to return NaNs. Default: True. |
| |
| Attributes |
| ---------- |
| x : tuple of ndarrays |
| Breakpoints. |
| c : ndarray |
| Coefficients of the polynomials. |
| |
| Methods |
| ------- |
| __call__ |
| derivative |
| antiderivative |
| integrate |
| integrate_1d |
| construct_fast |
| |
| See also |
| -------- |
| PPoly : piecewise polynomials in 1D |
| |
| Notes |
| ----- |
| High-order polynomials in the power basis can be numerically |
| unstable. |
| |
| """ |
|
|
| def __init__(self, c, x, extrapolate=None): |
| self.x = tuple(np.ascontiguousarray(v, dtype=np.float64) for v in x) |
| self.c = np.asarray(c) |
| if extrapolate is None: |
| extrapolate = True |
| self.extrapolate = bool(extrapolate) |
|
|
| ndim = len(self.x) |
| if any(v.ndim != 1 for v in self.x): |
| raise ValueError("x arrays must all be 1-dimensional") |
| if any(v.size < 2 for v in self.x): |
| raise ValueError("x arrays must all contain at least 2 points") |
| if c.ndim < 2*ndim: |
| raise ValueError("c must have at least 2*len(x) dimensions") |
| if any(np.any(v[1:] - v[:-1] < 0) for v in self.x): |
| raise ValueError("x-coordinates are not in increasing order") |
| if any(a != b.size - 1 for a, b in zip(c.shape[ndim:2*ndim], self.x)): |
| raise ValueError("x and c do not agree on the number of intervals") |
|
|
| dtype = self._get_dtype(self.c.dtype) |
| self.c = np.ascontiguousarray(self.c, dtype=dtype) |
|
|
| @classmethod |
| def construct_fast(cls, c, x, extrapolate=None): |
| """ |
| Construct the piecewise polynomial without making checks. |
| |
| Takes the same parameters as the constructor. Input arguments |
| ``c`` and ``x`` must be arrays of the correct shape and type. The |
| ``c`` array can only be of dtypes float and complex, and ``x`` |
| array must have dtype float. |
| |
| """ |
| self = object.__new__(cls) |
| self.c = c |
| self.x = x |
| if extrapolate is None: |
| extrapolate = True |
| self.extrapolate = extrapolate |
| return self |
|
|
| def _get_dtype(self, dtype): |
| if np.issubdtype(dtype, np.complexfloating) \ |
| or np.issubdtype(self.c.dtype, np.complexfloating): |
| return np.complex128 |
| else: |
| return np.float64 |
|
|
| def _ensure_c_contiguous(self): |
| if not self.c.flags.c_contiguous: |
| self.c = self.c.copy() |
| if not isinstance(self.x, tuple): |
| self.x = tuple(self.x) |
|
|
| def __call__(self, x, nu=None, extrapolate=None): |
| """ |
| Evaluate the piecewise polynomial or its derivative |
| |
| Parameters |
| ---------- |
| x : array-like |
| Points to evaluate the interpolant at. |
| nu : tuple, optional |
| Orders of derivatives to evaluate. Each must be non-negative. |
| extrapolate : bool, optional |
| Whether to extrapolate to out-of-bounds points based on first |
| and last intervals, or to return NaNs. |
| |
| Returns |
| ------- |
| y : array-like |
| Interpolated values. Shape is determined by replacing |
| the interpolation axis in the original array with the shape of x. |
| |
| Notes |
| ----- |
| Derivatives are evaluated piecewise for each polynomial |
| segment, even if the polynomial is not differentiable at the |
| breakpoints. The polynomial intervals are considered half-open, |
| ``[a, b)``, except for the last interval which is closed |
| ``[a, b]``. |
| |
| """ |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
| else: |
| extrapolate = bool(extrapolate) |
|
|
| ndim = len(self.x) |
|
|
| x = _ndim_coords_from_arrays(x) |
| x_shape = x.shape |
| x = np.ascontiguousarray(x.reshape(-1, x.shape[-1]), dtype=np.float64) |
|
|
| if nu is None: |
| nu = np.zeros((ndim,), dtype=np.intc) |
| else: |
| nu = np.asarray(nu, dtype=np.intc) |
| if nu.ndim != 1 or nu.shape[0] != ndim: |
| raise ValueError("invalid number of derivative orders nu") |
|
|
| dim1 = prod(self.c.shape[:ndim]) |
| dim2 = prod(self.c.shape[ndim:2*ndim]) |
| dim3 = prod(self.c.shape[2*ndim:]) |
| ks = np.array(self.c.shape[:ndim], dtype=np.intc) |
|
|
| out = np.empty((x.shape[0], dim3), dtype=self.c.dtype) |
| self._ensure_c_contiguous() |
|
|
| _ppoly.evaluate_nd(self.c.reshape(dim1, dim2, dim3), |
| self.x, |
| ks, |
| x, |
| nu, |
| bool(extrapolate), |
| out) |
|
|
| return out.reshape(x_shape[:-1] + self.c.shape[2*ndim:]) |
|
|
| def _derivative_inplace(self, nu, axis): |
| """ |
| Compute 1-D derivative along a selected dimension in-place |
| May result to non-contiguous c array. |
| """ |
| if nu < 0: |
| return self._antiderivative_inplace(-nu, axis) |
|
|
| ndim = len(self.x) |
| axis = axis % ndim |
|
|
| |
| if nu == 0: |
| |
| return |
| else: |
| sl = [slice(None)]*ndim |
| sl[axis] = slice(None, -nu, None) |
| c2 = self.c[tuple(sl)] |
|
|
| if c2.shape[axis] == 0: |
| |
| shp = list(c2.shape) |
| shp[axis] = 1 |
| c2 = np.zeros(shp, dtype=c2.dtype) |
|
|
| |
| factor = spec.poch(np.arange(c2.shape[axis], 0, -1), nu) |
| sl = [None]*c2.ndim |
| sl[axis] = slice(None) |
| c2 *= factor[tuple(sl)] |
|
|
| self.c = c2 |
|
|
| def _antiderivative_inplace(self, nu, axis): |
| """ |
| Compute 1-D antiderivative along a selected dimension |
| May result to non-contiguous c array. |
| """ |
| if nu <= 0: |
| return self._derivative_inplace(-nu, axis) |
|
|
| ndim = len(self.x) |
| axis = axis % ndim |
|
|
| perm = list(range(ndim)) |
| perm[0], perm[axis] = perm[axis], perm[0] |
| perm = perm + list(range(ndim, self.c.ndim)) |
|
|
| c = self.c.transpose(perm) |
|
|
| c2 = np.zeros((c.shape[0] + nu,) + c.shape[1:], |
| dtype=c.dtype) |
| c2[:-nu] = c |
|
|
| |
| factor = spec.poch(np.arange(c.shape[0], 0, -1), nu) |
| c2[:-nu] /= factor[(slice(None),) + (None,)*(c.ndim-1)] |
|
|
| |
| perm2 = list(range(c2.ndim)) |
| perm2[1], perm2[ndim+axis] = perm2[ndim+axis], perm2[1] |
|
|
| c2 = c2.transpose(perm2) |
| c2 = c2.copy() |
| _ppoly.fix_continuity(c2.reshape(c2.shape[0], c2.shape[1], -1), |
| self.x[axis], nu-1) |
|
|
| c2 = c2.transpose(perm2) |
| c2 = c2.transpose(perm) |
|
|
| |
| self.c = c2 |
|
|
| def derivative(self, nu): |
| """ |
| Construct a new piecewise polynomial representing the derivative. |
| |
| Parameters |
| ---------- |
| nu : ndim-tuple of int |
| Order of derivatives to evaluate for each dimension. |
| If negative, the antiderivative is returned. |
| |
| Returns |
| ------- |
| pp : NdPPoly |
| Piecewise polynomial of orders (k[0] - nu[0], ..., k[n] - nu[n]) |
| representing the derivative of this polynomial. |
| |
| Notes |
| ----- |
| Derivatives are evaluated piecewise for each polynomial |
| segment, even if the polynomial is not differentiable at the |
| breakpoints. The polynomial intervals in each dimension are |
| considered half-open, ``[a, b)``, except for the last interval |
| which is closed ``[a, b]``. |
| |
| """ |
| p = self.construct_fast(self.c.copy(), self.x, self.extrapolate) |
|
|
| for axis, n in enumerate(nu): |
| p._derivative_inplace(n, axis) |
|
|
| p._ensure_c_contiguous() |
| return p |
|
|
| def antiderivative(self, nu): |
| """ |
| Construct a new piecewise polynomial representing the antiderivative. |
| |
| Antiderivative is also the indefinite integral of the function, |
| and derivative is its inverse operation. |
| |
| Parameters |
| ---------- |
| nu : ndim-tuple of int |
| Order of derivatives to evaluate for each dimension. |
| If negative, the derivative is returned. |
| |
| Returns |
| ------- |
| pp : PPoly |
| Piecewise polynomial of order k2 = k + n representing |
| the antiderivative of this polynomial. |
| |
| Notes |
| ----- |
| The antiderivative returned by this function is continuous and |
| continuously differentiable to order n-1, up to floating point |
| rounding error. |
| |
| """ |
| p = self.construct_fast(self.c.copy(), self.x, self.extrapolate) |
|
|
| for axis, n in enumerate(nu): |
| p._antiderivative_inplace(n, axis) |
|
|
| p._ensure_c_contiguous() |
| return p |
|
|
| def integrate_1d(self, a, b, axis, extrapolate=None): |
| r""" |
| Compute NdPPoly representation for one dimensional definite integral |
| |
| The result is a piecewise polynomial representing the integral: |
| |
| .. math:: |
| |
| p(y, z, ...) = \int_a^b dx\, p(x, y, z, ...) |
| |
| where the dimension integrated over is specified with the |
| `axis` parameter. |
| |
| Parameters |
| ---------- |
| a, b : float |
| Lower and upper bound for integration. |
| axis : int |
| Dimension over which to compute the 1-D integrals |
| extrapolate : bool, optional |
| Whether to extrapolate to out-of-bounds points based on first |
| and last intervals, or to return NaNs. |
| |
| Returns |
| ------- |
| ig : NdPPoly or array-like |
| Definite integral of the piecewise polynomial over [a, b]. |
| If the polynomial was 1D, an array is returned, |
| otherwise, an NdPPoly object. |
| |
| """ |
| if extrapolate is None: |
| extrapolate = self.extrapolate |
| else: |
| extrapolate = bool(extrapolate) |
|
|
| ndim = len(self.x) |
| axis = int(axis) % ndim |
|
|
| |
| c = self.c |
| swap = list(range(c.ndim)) |
| swap.insert(0, swap[axis]) |
| del swap[axis + 1] |
| swap.insert(1, swap[ndim + axis]) |
| del swap[ndim + axis + 1] |
|
|
| c = c.transpose(swap) |
| p = PPoly.construct_fast(c.reshape(c.shape[0], c.shape[1], -1), |
| self.x[axis], |
| extrapolate=extrapolate) |
| out = p.integrate(a, b, extrapolate=extrapolate) |
|
|
| |
| if ndim == 1: |
| return out.reshape(c.shape[2:]) |
| else: |
| c = out.reshape(c.shape[2:]) |
| x = self.x[:axis] + self.x[axis+1:] |
| return self.construct_fast(c, x, extrapolate=extrapolate) |
|
|
| def integrate(self, ranges, extrapolate=None): |
| """ |
| Compute a definite integral over a piecewise polynomial. |
| |
| Parameters |
| ---------- |
| ranges : ndim-tuple of 2-tuples float |
| Sequence of lower and upper bounds for each dimension, |
| ``[(a[0], b[0]), ..., (a[ndim-1], b[ndim-1])]`` |
| extrapolate : bool, optional |
| Whether to extrapolate to out-of-bounds points based on first |
| and last intervals, or to return NaNs. |
| |
| Returns |
| ------- |
| ig : array_like |
| Definite integral of the piecewise polynomial over |
| [a[0], b[0]] x ... x [a[ndim-1], b[ndim-1]] |
| |
| """ |
|
|
| ndim = len(self.x) |
|
|
| if extrapolate is None: |
| extrapolate = self.extrapolate |
| else: |
| extrapolate = bool(extrapolate) |
|
|
| if not hasattr(ranges, '__len__') or len(ranges) != ndim: |
| raise ValueError("Range not a sequence of correct length") |
|
|
| self._ensure_c_contiguous() |
|
|
| |
| c = self.c |
| for n, (a, b) in enumerate(ranges): |
| swap = list(range(c.ndim)) |
| swap.insert(1, swap[ndim - n]) |
| del swap[ndim - n + 1] |
|
|
| c = c.transpose(swap) |
|
|
| p = PPoly.construct_fast(c, self.x[n], extrapolate=extrapolate) |
| out = p.integrate(a, b, extrapolate=extrapolate) |
| c = out.reshape(c.shape[2:]) |
|
|
| return c |
|
|