Skip to content
This repository has been archived by the owner on May 31, 2024. It is now read-only.

Commit

Permalink
feat(interp1d_unc): add cubic bspline interpolation-kind
Browse files Browse the repository at this point in the history
  • Loading branch information
mgrub committed Apr 23, 2021
1 parent 98f3846 commit f0c6d19
Showing 1 changed file with 80 additions and 2 deletions.
82 changes: 80 additions & 2 deletions PyDynamic/uncertainty/interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import Optional, Tuple, Union

import numpy as np
from scipy.interpolate import interp1d
from scipy.interpolate import interp1d, splrep, BSpline

__all__ = ["interp1d_unc", "make_equidistant"]

Expand Down Expand Up @@ -60,7 +60,7 @@ def interp1d_unc(
associated with y.
kind : str, optional
Specifies the kind of interpolation for y as a string ('previous',
'next', 'nearest' or 'linear'). Default is ‘linear’.
'next', 'nearest', 'linear' or 'cubic'). Default is ‘linear’.
copy : bool, optional
If True, the method makes internal copies of t and y. If False,
references to t and y are used. The default is to copy.
Expand Down Expand Up @@ -303,6 +303,84 @@ def interp1d_unc(
(t_new[interp_range] - t_hi) ** 2 * uy_prev_sqr
+ (t_new[interp_range] - t_lo) ** 2 * uy_next_sqr
) / (t_hi - t_lo)
elif kind == "cubic":
# Calculate boolean arrays of indices from t_new which are outside t's bounds...
extrap_range_below = t_new < np.min(t)
extrap_range_above = t_new > np.max(t)
extrap_range = extrap_range_below | extrap_range_above
# .. and inside t's bounds.
interp_range = ~extrap_range

# Initialize the result array for the standard uncertainties.
uy_new = np.empty_like(y_new)

# Initialize the sensitivity matrix of shape (M, N)
C = np.zeros((len(t_new), len(uy)), "float64")

# First extrapolate the according values if required and then
# compute interpolated uncertainties following White, 2017.

# If extrapolation is needed, fill in the values provided via fill_unc.
if np.any(extrap_range):
# At this point fill_unc is either a float (np.nan is a float as well) or
# a 2-tuple of floats. In case we have one float we set uy_new to this value
# inside the extrapolation range.
if isinstance(fill_unc, float):
uy_new[extrap_range] = fill_unc
else:
# Now fill_unc should be a 2-tuple, which we can fill into uy_new.
uy_new[extrap_range_below], uy_new[extrap_range_above] = fill_unc

if returnC:
# In each row of C corresponding to an extrapolation value below the
# original range set the first column to 1 and in each row of C
# corresponding to an extrapolation value above the original range set
# the last column to 1. It is important to do this before
# interpolating, because in general those two columns can contain
# non-zero values in the interpolation range.
C[:, 0], C[:, -1] = extrap_range_below, extrap_range_above

# If interpolation is needed, compute uncertainties following White, 2017.
if np.any(interp_range):
# This following section is taken mainly from scipy.interpolate.interp1d to
# determine the indices of the relevant original timestamps (or frequencies)
# just for the interpolation range.
# --------------------------------------------------------------------------
# 2. Find where in the original data, the values to interpolate
# would be inserted.
# Note: If t_new[n] == t[m], then m is returned by searchsorted.
t_new_indices = np.searchsorted(t, t_new[interp_range])

# 3. Clip x_new_indices so that they are within the range of
# self.x indices and at least 1. Removes mis-interpolation
# of x_new[n] = x[0]
t_new_indices = t_new_indices.clip(1, len(t) - 1).astype(int)

# 4. Calculate the uncertainty
# generate spline of sensitivities, this procedure is described by eq. (19) of White2017
F = []
for i in range(len(t)):
tf = t
xf = np.zeros_like(t)
xf[i] = 1.0
f_tck = splrep(tf, xf, k=3)
Fi = BSpline(*f_tck)
F.append(Fi)

# calculate sensitivity
C[interp_range] = np.array([Fi(t_new[interp_range]) for Fi in F]).T
C2 = np.square(C[interp_range])

# if at some point time-uncertainties are of interest, White2017 already provides the formulas
# ut = np.zeros_like(t)
# ut_new = np.zeros_like(t_new)
# a1 = np.dot(C2, np.square(uy))
# a2 = np.dot(C2, np.squeeze(np.square(interp_y._spline(t, nu=1))) * np.square(ut))
# a3 = np.square(np.squeeze(interp_y._spline(t_new, nu=1))) * np.square(ut_new)
# uy_new[interp_range] = np.sqrt(a1 - a2 + a3)

# without consideration of time-uncertainty
uy_new[interp_range] = np.sqrt(np.dot(C2, np.square(uy)))
else:
raise NotImplementedError(
"%s is unsupported yet. Let us know, that you need it." % kind
Expand Down

0 comments on commit f0c6d19

Please sign in to comment.