Overview¶
Chebyshev2 implements a pseudo-spectral parameterization on Chebyshev points of the second kind. Instead of coefficients, the parameters are function values at Chebyshev points, and evaluation uses barycentric interpolation. The class also provides differentiation matrices and exact rectangular integration matrices for spectral calculus.
Spectral versus pseudo-spectral¶
Both parameterizations represent the same space of polynomials of degree at most ; the difference is which coordinates describe a polynomial. In GTSAM:
Spectral coefficient form (Chebyshev2Basis) | Pseudo-spectral nodal form (Chebyshev2) | |
|---|---|---|
| Parameters | Coefficients of the second-kind polynomials | Values at Chebyshev-Gauss-Lobatto nodes |
| Evaluation | Weighted sum of the basis polynomials | Barycentric Lagrange interpolation of the values |
| Interpretation | Global polynomial modes | Function values at specific points |
| Calculus | Operations on polynomial coefficients | Dense differentiation and integration matrices acting on node values |
In the broad numerical-analysis sense, both are spectral methods. The word pseudo-spectral indicates that Chebyshev2 stores nodal values and evaluates or constrains the polynomial at collocation points, rather than storing modal coefficients. With nodes, the unique interpolating polynomial has degree at most .
The nodes x_j = -cos(j pi / (N-1)) cluster near the interval endpoints. This is substantially more stable than polynomial interpolation at equally spaced nodes and mitigates the Runge phenomenon. Changing one node value still changes the global interpolating polynomial, so the differentiation and integration operators are generally dense.
The nodal form is especially natural in a factor graph: the unknown columns are state or signal values at the Chebyshev nodes, while a measurement at any sample parameter contributes a factor whose Jacobian is one row of barycentric interpolation weights.
Key Functionality / API¶
Point(N, j[, a, b])andPoints(N[, a, b])return Chebyshev points.CalculateWeights(N, x[, a, b])returns barycentric interpolation weights.DerivativeWeights(N, x[, a, b])returns derivative weights.DifferentiationMatrix(N[, a, b])differentiates values at N nodes.IntegrationMatrix(N[, a, b])maps N derivative values to N+1 exact antiderivative values with F(a)=0.IntegrationWeights(N[, a, b])andDoubleIntegrationWeights(N[, a, b])provide quadrature weights.
Usage Example¶
This example inspects the Chebyshev points, interpolation weights, differentiation matrix, and exact integration matrix. It then treats a set of 2D points at the Chebyshev points as the parameters and interpolates a planar curve through them.
import numpy as np
import gtsam
np.set_printoptions(precision=3, suppress=True)
N = 8
START_TIME, END_TIME = 0.0, 10.0
node_times = gtsam.Chebyshev2.Points(N, START_TIME, END_TIME)
print("Chebyshev times:", np.asarray(node_times).ravel())
time = 4.25
weights = gtsam.Chebyshev2.CalculateWeights(N, time, START_TIME, END_TIME)
print("Interpolation weights at t=4.25:", np.asarray(weights).ravel())
D = gtsam.Chebyshev2.DifferentiationMatrix(N, START_TIME, END_TIME)
print("D shape:", np.asarray(D).shape)
print("First row of D:", np.asarray(D)[0])
P = gtsam.Chebyshev2.IntegrationMatrix(N, START_TIME, END_TIME)
integration_weights = gtsam.Chebyshev2.IntegrationWeights(N, START_TIME, END_TIME)
print("P shape:", np.asarray(P).shape)
print("P final row equals weights:", np.allclose(np.asarray(P)[-1], integration_weights))
Chebyshev times: [ 0. 0.495 1.883 3.887 6.113 8.117 9.505 10. ]
Interpolation weights at t=4.25: [-0.041 0.093 -0.147 0.962 0.187 -0.09 0.066 -0.03 ]
D shape: (8, 8)
First row of D: [-3.3 4.039 -1.062 0.514 -0.327 0.246 -0.21 0.1 ]
P shape: (9, 8)
P final row equals weights: True
Interpolating 2D values at the nodes¶
The matrix node_matrix contains one planar point per column and is the pseudo-spectral parameter matrix. WeightMatrix stacks the barycentric weights for all query parameters, so W @ node_matrix.T interpolates both coordinates at once. Evaluating at the nodes themselves recovers every column of node_matrix exactly.
import plotly.graph_objects as go
node_matrix = np.vstack((node_times + 0.4 * np.sin(1.2 * node_times), 2.0 * np.sin(0.7 * node_times) + 0.15 * node_times)) # 2 x N
query_times = np.linspace(START_TIME, END_TIME, 301)
W = gtsam.Chebyshev2.WeightMatrix(N, query_times, START_TIME, END_TIME)
interpolated_points = W @ node_matrix.T
points_at_nodes = gtsam.Chebyshev2.WeightMatrix(N, node_times, START_TIME, END_TIME) @ node_matrix.T
print("Interpolant recovers every 2D node:", np.allclose(points_at_nodes, node_matrix.T))
fig = go.Figure()
fig.add_trace(go.Scatter(x=interpolated_points[:, 0], y=interpolated_points[:, 1], customdata=query_times, mode="lines", name="Chebyshev interpolant", hovertemplate="t=%{customdata:.2f}<br>x=%{x:.2f}<br>y=%{y:.2f}<extra></extra>"))
fig.add_trace(go.Scatter(x=node_matrix[0], y=node_matrix[1], customdata=node_times, mode="markers", name="2D node values", marker=dict(size=9), hovertemplate="t=%{customdata:.2f}<br>x=%{x:.2f}<br>y=%{y:.2f}<extra></extra>"))
fig.update_layout(title="Planar Chebyshev interpolation over t in [0, 10]", xaxis_title="x", yaxis_title="y", template=None, plot_bgcolor="white", paper_bgcolor="white")
fig.update_xaxes(showgrid=True, gridcolor="#e5e7eb", zeroline=False)
fig.update_yaxes(showgrid=True, gridcolor="#e5e7eb", zeroline=False, scaleanchor="x", scaleratio=1)
fig.show()
Interpolant recovers every 2D node: True
See Also¶
Source: Chebyshev2.h
Trajectory-fitting example: PseudoSpectralChebyshevExample
.ipynb.