Skip to content

core.interpolation

Generic interpolation utilities for tabulated curves used across machwave.

BoundedCubicSpline

Many parts of machwave consume property curves as small tables — pump head versus volumetric flow, burn rate versus pressure, nozzle area versus axial station. BoundedCubicSpline wraps such a table in a cubic spline that:

  1. Interpolates between knots with a cubic spline. Smooth derivatives matter when the curve is composed with downstream solvers (RK4 step, root-finder on injector–chamber balance, etc.).
  2. Refuses to extrapolate. scipy.interpolate.CubicSpline is constructed with extrapolate=False, and the class additionally raises ValueError for any input outside [x_points[0], x_points[-1]]. A simulation that drifts off the calibrated domain fails loudly rather than producing fabricated values.

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.

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

    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])