Coverage for install/scipp/scipy/interpolate/__init__.py: 100%
38 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-04-28 01:28 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-04-28 01:28 +0000
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3# @author Simon Heybrock
4"""Sub-package for objects used in interpolation.
6This subpackage provides wrappers for a subset of functions from
7:py:mod:`scipy.interpolate`.
8"""
10from typing import Any, Callable, Literal, Union
12import numpy as np
14from ...compat.wrapping import wrap1d
15from ...core import (
16 DataArray,
17 DimensionError,
18 DType,
19 UnitError,
20 Variable,
21 empty,
22 epoch,
23 irreducible_mask,
24)
27def _as_interpolation_type(x):
28 if isinstance(x, np.ndarray):
29 if x.dtype.kind == 'M':
30 return x.astype('int64', copy=False)
31 else:
32 if x.dtype == DType.datetime64:
33 return x - epoch(unit=x.unit)
34 return x
37def _midpoints(var, dim):
38 a = var[dim, :-1]
39 b = var[dim, 1:]
40 return _as_interpolation_type(a) + 0.5 * (b - a)
43def _drop_masked(da, dim):
44 if (mask := irreducible_mask(da.masks, dim)) is not None:
45 return da[~mask]
46 return da
49@wrap1d(is_partial=True, accept_masks=True)
50def interp1d(
51 da: DataArray,
52 dim: str,
53 *,
54 kind: Union[
55 int,
56 Literal[
57 'linear',
58 'nearest',
59 'nearest-up',
60 'zero',
61 'slinear',
62 'quadratic',
63 'cubic',
64 'previous',
65 'next',
66 ],
67 ] = 'linear',
68 fill_value: Any = np.nan,
69 **kwargs,
70) -> Callable:
71 """Interpolate a 1-D function.
73 A data array is used to approximate some function f: y = f(x), where y is given by
74 the array values and x is is given by the coordinate for the given dimension. This
75 class returns a function whose call method uses interpolation to find the value of
76 new points.
78 The function is a wrapper for scipy.interpolate.interp1d. The differences are:
80 - Instead of x and y, a data array defining these is used as input.
81 - Instead of an axis, a dimension label defines the interpolation dimension.
82 - The returned function does not just return the values of f(x) but a new
83 data array with values defined as f(x) and x as a coordinate for the
84 interpolation dimension.
85 - The returned function accepts an extra argument ``midpoints``. When setting
86 ``midpoints=True`` the interpolation uses the midpoints of the new points
87 instead of the points itself. The returned data array is then a histogram, i.e.,
88 the new coordinate is a bin-edge coordinate.
90 If the input data array contains masks that depend on the interpolation dimension
91 the masked points are treated as missing, i.e., they are ignored for the definition
92 of the interpolation function. If such a mask also depends on additional dimensions
93 :py:class:`scipp.DimensionError` is raised since interpolation requires points to
94 be 1-D.
96 For structured input data dtypes such as vectors, rotations, or linear
97 transformations interpolation is structure-element-wise. While this is appropriate
98 for vectors, such a naive interpolation for, e.g., rotations does typically not
99 yield a rotation so this should be used with care, unless the 'kind' parameter is
100 set to, e.g., 'previous', 'next', or 'nearest'.
102 Parameters not described above are forwarded to scipy.interpolate.interp1d. The
103 most relevant ones are (see :py:class:`scipy.interpolate.interp1d` for details):
105 Parameters
106 ----------
107 da:
108 Input data. Defines both dependent and independent variables for interpolation.
109 dim:
110 Dimension of the interpolation.
111 kind:
113 - **integer**: order of the spline interpolator
114 - **string**:
116 - 'zero', 'slinear', 'quadratic', 'cubic':
117 spline interpolation of zeroth, first, second or third order
118 - 'previous' and 'next':
119 simply return the previous or next value of the point
120 - 'nearest-up' and 'nearest'
121 differ when interpolating half-integers (e.g. 0.5, 1.5) in that
122 'nearest-up' rounds up and 'nearest' rounds down
123 fill_value:
124 Set to 'extrapolate' to allow for extrapolation of points
125 outside the range.
127 Returns
128 -------
129 :
130 A callable ``f(x)`` that returns interpolated values of ``da`` at ``x``.
132 Examples
133 --------
135 .. plot:: :context: close-figs
137 >>> x = sc.linspace(dim='x', start=0.1, stop=1.4, num=4, unit='rad')
138 >>> da = sc.DataArray(sc.sin(x), coords={'x': x})
140 >>> from scipp.scipy.interpolate import interp1d
141 >>> f = interp1d(da, 'x')
143 >>> xnew = sc.linspace(dim='x', start=0.1, stop=1.4, num=12, unit='rad')
144 >>> f(xnew) # use interpolation function returned by `interp1d`
145 <scipp.DataArray>
146 Dimensions: Sizes[x:12, ]
147 Coordinates:
148 * x float64 [rad] (x) [0.1, 0.218182, ..., 1.28182, 1.4]
149 Data:
150 float64 [dimensionless] (x) [0.0998334, 0.211262, ..., 0.941144, 0.98545]
152 >>> f(xnew, midpoints=True)
153 <scipp.DataArray>
154 Dimensions: Sizes[x:11, ]
155 Coordinates:
156 * x float64 [rad] (x [bin-edge]) [0.1, 0.218182, ..., 1.28182, 1.4]
157 Data:
158 float64 [dimensionless] (x) [0.155548, 0.266977, ..., 0.918992, 0.963297]
160 .. plot:: :context: close-figs
162 >>> sc.plot({'original':da,
163 ... 'interp1d':f(xnew),
164 ... 'interp1d-midpoints':f(xnew, midpoints=True)})
165 """ # noqa: E501
166 import scipy.interpolate as inter
168 da = _drop_masked(da, dim)
170 def func(xnew: Variable, *, midpoints=False) -> DataArray:
171 """Compute interpolation function defined by ``interp1d``
172 at interpolation points.
174 :param xnew: Interpolation points.
175 :param midpoints: Interpolate at midpoints of given points. The result will be
176 a histogram. Default is ``False``.
177 :return: Interpolated data array with new coord given by interpolation points
178 and data given by interpolation function evaluated at the
179 interpolation points (or evaluated at the midpoints of the given
180 points).
181 """
182 if xnew.unit != da.coords[dim].unit:
183 raise UnitError(
184 f"Unit of interpolation points '{xnew.unit}' does not match unit "
185 f"'{da.coords[dim].unit}' of points defining the interpolation "
186 "function along dimension '{dim}'."
187 )
188 if xnew.dim != dim:
189 raise DimensionError(
190 f"Dimension of interpolation points '{xnew.dim}' does not match "
191 f"interpolation dimension '{dim}'"
192 )
193 f = inter.interp1d(
194 x=_as_interpolation_type(da.coords[dim].values),
195 y=da.values,
196 kind=kind,
197 fill_value=fill_value,
198 **kwargs,
199 )
200 x_ = _as_interpolation_type(_midpoints(xnew, dim) if midpoints else xnew)
201 sizes = da.sizes
202 sizes[dim] = x_.sizes[dim]
203 # ynew is created in this manner to allow for creation of structured dtypes,
204 # which is not possible using scipp.array
205 ynew = empty(sizes=sizes, unit=da.unit, dtype=da.dtype)
206 ynew.values = f(x_.values)
207 return DataArray(data=ynew, coords={dim: xnew})
209 return func
212__all__ = ['interp1d']