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.

FitBasis (Fourier + Chebyshev)

Open In Colab

This notebook demonstrates how to fit a set of scalar measurements using FitBasis for all four basis options exposed in the Python wrapper:

  • FourierBasis

  • Chebyshev1Basis

  • Chebyshev2Basis

  • Chebyshev2 (pseudo-spectral, parameters are values at Chebyshev points)

We use a time domain from t = 0 to 1 and fit each basis with fewer parameters than points. For Chebyshev bases, we map time into the Chebyshev interval x = 2t - 1 to match the default [-1, 1] domain. For the Fourier basis we use x = 2pi t to model periodic behavior.


import numpy as np
import gtsam
import plotly.graph_objects as go

np.set_printoptions(precision=3, suppress=True)

# Sample data
rng = np.random.default_rng(42)
num_points = 30
t = np.linspace(0.0, 1.0, num_points)

# A smooth, mildly periodic signal with a trend
y_clean = 0.7 * np.sin(2 * np.pi * t) + 0.3 * np.cos(4 * np.pi * t) + 0.2 * t
y = y_clean + 0.05 * rng.standard_normal(size=t.size)

# Parameter count must be smaller or equal than number of points
N = 10

def plot_fit(t_samples, y_samples, t_dense, y_fit, title, extra=None):
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=t_samples, y=y_samples, mode="markers", name="Samples", marker=dict(color="blue", symbol="diamond")))
    fig.add_trace(go.Scatter(x=t_dense, y=y_fit, mode="lines", name="Fit"))
    if extra is not None:
        fig.add_trace(extra)
    fig.update_layout(
        title=title,
        xaxis_title="time t",
        yaxis_title="value",
        template="plotly_white",
        width=900,
        height=450,
    )
    fig.show()

FourierBasis Fit

This cell:

  1. Builds a {x: y} sample map using the periodic domain x = 2pi t.

  2. Fits Fourier coefficients with FitBasisFourierBasis.

  3. Evaluates the fit on a dense grid via FourierBasis.WeightMatrix.

Copy the core fit + evaluation parts if you want to integrate this into your own pipeline (plotting is in a helper function).

x_fourier = 2.0 * np.pi * t
sequence : dict = {float(xi): float(yi) for xi, yi in zip(x_fourier, y)}
model = gtsam.noiseModel.Isotropic.Sigma(1, 0.05)
fit = gtsam.FitBasisFourierBasis(sequence, model, N)
params = fit.parameters()

# Extend the time domain to show periodic behavior
t_dense = np.linspace(-0.5, 1.5, 600)
x_dense = 2.0 * np.pi * t_dense
W = gtsam.FourierBasis.WeightMatrix(len(params), x_dense)
y_fit = W @ params

plot_fit(t, y, t_dense, y_fit, "FourierBasis Fit (periodic beyond [0, 1])")
Loading...

Chebyshev1Basis Fit

This cell:

  1. Maps time to the Chebyshev interval with x = 2t - 1.

  2. Fits Chebyshev-1 coefficients with FitBasisChebyshev1Basis.

  3. Evaluates the fit using Chebyshev1Basis.WeightMatrix.

Copy the fit + evaluation steps to reuse with your own data or noise model.

x_cheb = 2.0 * t - 1.0
sequence = {float(xi): float(yi) for xi, yi in zip(x_cheb, y)}
model = gtsam.noiseModel.Isotropic.Sigma(1, 0.05)
fit = gtsam.FitBasisChebyshev1Basis(sequence, model, N)
params = fit.parameters()

t_dense = np.linspace(-0.1, 1.1, 600)
x_dense = 2.0 * t_dense - 1.0
W = gtsam.Chebyshev1Basis.WeightMatrix(len(params), x_dense)
y_fit = W @ params

plot_fit(t, y, t_dense, y_fit, "Chebyshev1Basis Fit (extrapolation outside [0, 1])")
Loading...

Chebyshev2Basis Fit

This cell uses the second-kind Chebyshev basis (coefficient form):

  1. Map time to x = 2t - 1.

  2. Fit with FitBasisChebyshev2Basis.

  3. Evaluate with Chebyshev2Basis.WeightMatrix.

These steps are the minimal pieces you need for non-plotting usage.

sequence = {float(xi): float(yi) for xi, yi in zip(x_cheb, y)}
model = gtsam.noiseModel.Isotropic.Sigma(1, 0.05)
fit = gtsam.FitBasisChebyshev2Basis(sequence, model, N)
params = fit.parameters()

t_dense = np.linspace(-0.1, 1.1, 600)
x_dense = 2.0 * t_dense - 1.0
W = gtsam.Chebyshev2Basis.WeightMatrix(len(params), x_dense)
y_fit = W @ params

plot_fit(t, y, t_dense, y_fit, "Chebyshev2Basis Fit (extrapolation outside [0, 1])")
Loading...

Chebyshev2 (Pseudo-Spectral) Fit

This variant treats the parameters as function values at Chebyshev points. The steps here are slightly different conceptually:

  1. Fit values at Chebyshev points with FitBasisChebyshev2.

  2. Evaluate by barycentric interpolation using Chebyshev2.WeightMatrix.

  3. Plot the interpolation nodes alongside the fitted curve.

If you don’t need plots, copy the fit + evaluation and skip the marker trace.

sequence = {float(xi): float(yi) for xi, yi in zip(x_cheb, y)}
model = gtsam.noiseModel.Isotropic.Sigma(1, 0.05)
fit = gtsam.FitBasisChebyshev2(sequence, model, N)
params = fit.parameters()

t_dense = np.linspace(-0.1, 1.1, 600)
x_dense = 2.0 * t_dense - 1.0
W = gtsam.Chebyshev2.WeightMatrix(len(params), x_dense)
y_fit = W @ params

cheb_points = gtsam.Chebyshev2.Points(N)
t_cheb = 0.5 * (cheb_points + 1.0)
y_cheb = params
markers = go.Scatter(x=t_cheb, y=y_cheb, mode="markers", name="Chebyshev2 points", marker=dict(color="red"))

plot_fit(t, y, t_dense, y_fit, "Chebyshev2 Pseudo-Spectral Fit (extrapolation outside [0, 1])", extra=markers)
Loading...

The Chebyshev points above are the parameterization. They values at those points are moved up and down to make the blue points fit the polynomial as closely as possible. Because for every NN points we can exactly fit an n=N1n=N-1 degree polynomial, the values at these Chebyshev points are a parameterization of the polynomial.

Chebyshev points are fixed nodes that cluster near the interval ends; this improves interpolation stability and reduces endpoint oscillations compared to equally spaced nodes (mitigating the Runge phenomenon).