Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

CardinalSplineBasis: Cubic weights for linear control values

Open In Colab

Note: AI was used in the creation of this example.

GTSAM provides gtsam::CardinalSplineBasis in gtsam/basis/CardinalSplineBasis.h for cubic cardinal-spline interpolation of scalar or vector coefficients. This example inspects the dense weights, evaluates a scalar curve, and differentiates it.

Use this class when your control values live in a vector space and the sample coordinate is known. Do not use these dense weights to blend poses or rotations. For Pose2, Pose3, Rot2, or Rot3, use the separate CumulativeSplineTrajectory example, which works with Lie-group increments.

Primary contributor: Brett Downing.

import gtsam
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

What we are building

We start with six ordinary scalar coefficients. At each coordinate tt, CalculateWeights returns one weight per coefficient. Their dot product is the spline value:

f(t)=i=0N1Bi(t)pi.(1)f(t)=\sum_{i=0}^{N-1} B_i(t)p_i.\tag{1}

This linear combination is exactly what makes CardinalSplineBasis appropriate for scalars and vectors. A pose cannot be substituted for pip_i because scalar multiplication and addition are not defined for poses.

control_values = np.array([0.0, 1.2, -0.4, 1.6, 0.5, 1.0])
sample_times = np.linspace(0.0, len(control_values) + 2.0, 501)

1. Inspect the basis weights

CardinalSplineBasis.CalculateWeights returns weights with local support that change smoothly and sum to one. That partition-of-unity property preserves a constant set of coefficients. Near the ends, the first and last weights absorb the padded tails.

basis_weights = np.vstack([
    gtsam.CardinalSplineBasis.CalculateWeights(len(control_values), float(time))
    for time in sample_times
])
np.testing.assert_allclose(basis_weights.sum(axis=1), 1.0, atol=1e-12)
Source
weights_figure = go.Figure()
for index in range(len(control_values)):
    weights_figure.add_scatter(
        x=sample_times,
        y=basis_weights[:, index],
        name=f"B{index}(t)",
    )
weights_figure.update_layout(
    title="Cubic cardinal-spline basis weights from C++",
    xaxis_title="coordinate t",
    yaxis_title="basis weight",
    template="plotly_white",
    hovermode="x unified",
)
weights_figure.show()
Loading...

2. Evaluate and differentiate a scalar spline

The curve is a matrix-vector product between the sampled basis weights and the coefficients. DerivativeWeights supplies the analytic derivative of the same C++ basis, so no finite differencing or Python spline implementation is needed.

derivative_weights = np.vstack([
    gtsam.CardinalSplineBasis.DerivativeWeights(
        len(control_values), float(time)
    )
    for time in sample_times
])
spline_values = basis_weights @ control_values
spline_derivatives = derivative_weights @ control_values
Source
curve_figure = make_subplots(
    rows=2,
    cols=1,
    shared_xaxes=True,
    subplot_titles=("scalar spline", "analytic derivative"),
)
curve_figure.add_scatter(
    x=sample_times, y=spline_values, name="f(t)", row=1, col=1
)
curve_figure.add_scatter(
    x=sample_times,
    y=spline_derivatives,
    name="df/dt",
    row=2,
    col=1,
)
curve_figure.update_xaxes(title_text="coordinate t", row=2, col=1)
curve_figure.update_yaxes(title_text="value", row=1, col=1)
curve_figure.update_yaxes(title_text="value / coordinate", row=2, col=1)
curve_figure.update_layout(
    title="A scalar curve evaluated with CardinalSplineBasis",
    template="plotly_white",
    hovermode="x unified",
    height=650,
)
curve_figure.show()
Loading...

Which spline API should I use?

Use CardinalSplineBasis when the coefficients are scalars or vectors and the sample coordinate is known. Its dense weights integrate with GTSAM’s Basis functors and basis factors.

Use CumulativeSplineTrajectory<T> when the controls are poses, rotations, or another Lie group; when the sample time is an expression; or when a bounded time window should keep the expression graph sparse. It weights tangent-space increments rather than applying dense weights directly to group elements.

Both constructions use the cubic IrwinHallCDF2 kernel internally, but they are not interchangeable APIs for Lie-group data. Continue with the CardinalSplineBasis user guide for the mathematical relationship and C++ usage, or the separate CumulativeSplineTrajectory guide for Lie-group trajectories.