Skip to content

core.interpolation

Generic interpolation utilities for tabulated curves used across machwave.

BoundedCubicSpline

Wraps a table of knots in a cubic spline built on scipy.interpolate.CubicSpline with extrapolate=False, raising ValueError for any input outside [x_points[0], x_points[-1]].

The constructor rejects malformed tables eagerly: the two sequences must be one-dimensional, the same length, contain at least two knots, and x_points must be strictly increasing. The inclusive domain bounds are exposed via the domain property.

Example

from machwave.core.interpolation import BoundedCubicSpline

efficiency_curve = BoundedCubicSpline(
    x_points=[0.5, 1.0, 1.5, 2.0],
    y_points=[0.55, 0.65, 0.62, 0.50],
)

efficiency_curve.domain   # → (0.5, 2.0)
efficiency_curve(1.2)     # → smooth spline value
efficiency_curve(3.0)     # raises ValueError — outside [0.5, 2.0]

machwave.core.interpolation

BoundedCubicSpline

Cubic spline interpolant that does not extrapolate outside its domain.

The cubic fit keeps derivatives smooth for the solvers that consume these curves. Inputs outside the knot range raise instead of returning fabricated values.

Source code in machwave/core/interpolation.py
class BoundedCubicSpline:
    """
    Cubic spline interpolant that does not extrapolate outside its domain.

    The cubic fit keeps derivatives smooth for the solvers that consume these
    curves. Inputs outside the knot range raise instead of returning fabricated
    values.
    """

    def __init__(
        self,
        x_points: Sequence[float],
        y_points: Sequence[float],
    ) -> None:
        """
        Construct the spline from a table of knots.

        Args:
            x_points: Strictly increasing knot locations along the independent
                axis. Must contain at least two entries.
            y_points: Dependent values at each knot. Must have the same length
                as `x_points`.

        Raises:
            ValueError: If the inputs are not one-dimensional sequences of
                equal length, if fewer than two knots are supplied, or if
                `x_points` is not strictly increasing.
        """
        x_array = np.asarray(x_points, dtype=float)
        y_array = np.asarray(y_points, dtype=float)

        if x_array.ndim != 1 or y_array.ndim != 1 or x_array.shape != y_array.shape:
            raise ValueError(
                "x_points and y_points must be one-dimensional sequences of equal length"
            )
        if x_array.size < 2:
            raise ValueError("BoundedCubicSpline needs at least two knots")
        if not np.all(np.diff(x_array) > 0):
            raise ValueError("x_points must be strictly increasing")

        self._spline = CubicSpline(x_array, y_array, extrapolate=False)
        self._domain_minimum = float(x_array[0])
        self._domain_maximum = float(x_array[-1])

    @property
    def domain(self) -> tuple[float, float]:
        """Return the inclusive `(minimum, maximum)` bounds of the spline."""
        return (self._domain_minimum, self._domain_maximum)

    def __call__(self, value: float) -> float:
        """
        Return the interpolated value at `value`.

        Args:
            value: Independent-axis input at which to evaluate the spline.

        Returns:
            Interpolated dependent value.

        Raises:
            ValueError: If `value` is outside the inclusive `domain`.
        """
        if value < self._domain_minimum or value > self._domain_maximum:
            raise ValueError(
                f"input {value} is outside "
                f"[{self._domain_minimum}, {self._domain_maximum}]"
            )
        return float(self._spline(value))

domain property

Return the inclusive (minimum, maximum) bounds of the spline.

__call__(value)

Return the interpolated value at value.

Parameters:

Name Type Description Default
value float

Independent-axis input at which to evaluate the spline.

required

Returns:

Type Description
float

Interpolated dependent value.

Raises:

Type Description
ValueError

If value is outside the inclusive domain.

Source code in machwave/core/interpolation.py
def __call__(self, value: float) -> float:
    """
    Return the interpolated value at `value`.

    Args:
        value: Independent-axis input at which to evaluate the spline.

    Returns:
        Interpolated dependent value.

    Raises:
        ValueError: If `value` is outside the inclusive `domain`.
    """
    if value < self._domain_minimum or value > self._domain_maximum:
        raise ValueError(
            f"input {value} is outside "
            f"[{self._domain_minimum}, {self._domain_maximum}]"
        )
    return float(self._spline(value))

__init__(x_points, y_points)

Construct the spline from a table of knots.

Parameters:

Name Type Description Default
x_points Sequence[float]

Strictly increasing knot locations along the independent axis. Must contain at least two entries.

required
y_points Sequence[float]

Dependent values at each knot. Must have the same length as x_points.

required

Raises:

Type Description
ValueError

If the inputs are not one-dimensional sequences of equal length, if fewer than two knots are supplied, or if x_points is not strictly increasing.

Source code in machwave/core/interpolation.py
def __init__(
    self,
    x_points: Sequence[float],
    y_points: Sequence[float],
) -> None:
    """
    Construct the spline from a table of knots.

    Args:
        x_points: Strictly increasing knot locations along the independent
            axis. Must contain at least two entries.
        y_points: Dependent values at each knot. Must have the same length
            as `x_points`.

    Raises:
        ValueError: If the inputs are not one-dimensional sequences of
            equal length, if fewer than two knots are supplied, or if
            `x_points` is not strictly increasing.
    """
    x_array = np.asarray(x_points, dtype=float)
    y_array = np.asarray(y_points, dtype=float)

    if x_array.ndim != 1 or y_array.ndim != 1 or x_array.shape != y_array.shape:
        raise ValueError(
            "x_points and y_points must be one-dimensional sequences of equal length"
        )
    if x_array.size < 2:
        raise ValueError("BoundedCubicSpline needs at least two knots")
    if not np.all(np.diff(x_array) > 0):
        raise ValueError("x_points must be strictly increasing")

    self._spline = CubicSpline(x_array, y_array, extrapolate=False)
    self._domain_minimum = float(x_array[0])
    self._domain_maximum = float(x_array[-1])