Chebyshev Transforms, Calculus & Quadrature¶
Transforms¶
ChebyshevTransform1D
¶
Bases: Module
Forward/inverse 1D Chebyshev transform.
Thin wrapper around :meth:ChebyshevGrid1D.transform that exposes
:meth:to_spectral / :meth:from_spectral methods. Useful when you
want to pass the transform around as a first-class object (e.g. into
a PDE residual, or to a higher-order library).
Attributes:
| Name | Type | Description |
|---|---|---|
grid |
ChebyshevGrid1D
|
Underlying grid carrying the node convention (GL or Gauss). |
Examples:
>>> import jax.numpy as jnp
>>> grid = ChebyshevGrid1D.from_N_L(N=16, L=1.0)
>>> cheb = ChebyshevTransform1D(grid=grid)
>>> u = jnp.sin(jnp.pi * grid.x)
>>> a = cheb.to_spectral(u)
>>> u_roundtrip = cheb.from_spectral(a) # ≈ u
Source code in spectraldiffx/_src/chebyshev/transforms.py
ChebyshevTransform2D
¶
Bases: Module
Forward/inverse 2D Chebyshev transform (tensor product of 1D).
Attributes:
| Name | Type | Description |
|---|---|---|
grid |
ChebyshevGrid2D
|
Underlying 2D grid. |
Examples:
>>> import jax.numpy as jnp
>>> grid = ChebyshevGrid2D.from_N_L(Nx=16, Ny=16, Lx=1.0, Ly=1.0)
>>> cheb = ChebyshevTransform2D(grid=grid)
>>> X, Y = grid.X
>>> u = jnp.sin(jnp.pi * X) * jnp.cos(jnp.pi * Y)
>>> a = cheb.to_spectral(u)
>>> u_roundtrip = cheb.from_spectral(a)
Source code in spectraldiffx/_src/chebyshev/transforms.py
cheb_dealias_product(grid, a, b)
¶
Compute the dealiased pointwise product a·b on a Chebyshev grid.
Implements a 2/3-style truncation in Chebyshev-coefficient space:
1. Forward-transform a and b to coefficient space.
2. Zero modes with index > 2N/3 on both inputs.
3. Inverse-transform, multiply pointwise.
4. Forward-transform the product, zero modes with index > 2N/3,
and inverse-transform once more.
This is the Chebyshev analogue of Orszag's 2/3 rule for Fourier grids. It is exact for products whose highest relevant Chebyshev mode is below the cut-off, and otherwise prevents aliasing into the retained modes at the cost of some high-mode truncation error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
ChebyshevGrid1D or ChebyshevGrid2D
|
Grid providing the transform; must have |
required |
a
|
Num[Array, ...]
|
Nodal fields on the same grid. Shapes |
required |
b
|
Num[Array, ...]
|
Nodal fields on the same grid. Shapes |
required |
Returns:
| Type | Description |
|---|---|
Num[Array, ...]
|
Dealiased pointwise product on the same grid. |
Examples:
>>> import jax.numpy as jnp
>>> grid = ChebyshevGrid1D.from_N_L(N=32, L=1.0, dealias="2/3")
>>> x = grid.x
>>> u = jnp.sin(jnp.pi * x)
>>> v = jnp.cos(jnp.pi * x)
>>> uv = dealias_product(grid, u, v) # ≈ ½ sin(2πx)
Source code in spectraldiffx/_src/chebyshev/transforms.py
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
Coefficient-space calculus¶
chebyshev_derivative_coeffs(a, L=1.0, order=1)
¶
Differentiate a Chebyshev series in coefficient space (last axis).
For u(x) = Σₖ aₖ Tₖ(x/L), the derivative u'(x) = Σₖ a'ₖ Tₖ(x/L) has
cₖ a'ₖ = a'ₖ₊₂ + 2(k+1) aₖ₊₁, a'_N = a'_{N+1} = 0
(c₀ = 2, cₖ = 1 otherwise), scaled by 1/L for the map x = L·ξ. Unrolling the recurrence gives a closed form: a'ₖ is a sum over the modes j > k of opposite parity,
a'ₖ = (2 / (cₖ L)) Σ_{j>k, j+k odd} j·aⱼ
which we evaluate with two reverse cumulative sums (one per parity),
so the whole derivative costs O(N) and parallelises on accelerators —
no sequential scan is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Num[Array, '... Nmodes']
|
Chebyshev coefficients along the last axis (e.g. from
:meth: |
required |
L
|
float
|
Domain half-length. Default 1.0. |
1.0
|
order
|
int
|
Derivative order (≥ 0). |
1
|
Returns:
| Type | Description |
|---|---|
Num[Array, '... Nmodes']
|
Coefficients of the |
Examples:
d/dx T₃(x) = 3 U₂(x) = 3 (T₀ + 2 T₂):
>>> import jax.numpy as jnp
>>> a = jnp.array([0.0, 0.0, 0.0, 1.0, 0.0])
>>> chebyshev_derivative_coeffs(a) # ≈ [3, 0, 6, 0, 0]
Source code in spectraldiffx/_src/chebyshev/transforms.py
chebyshev_antiderivative_coeffs(a, L=1.0)
¶
Indefinite integral of a Chebyshev series, vanishing at x = −L.
Uses ∫T₀ = T₁, ∫T₁ = T₂/4 and, for k ≥ 2,
∫Tₖ dξ = T_{k+1} / (2(k+1)) − T_{k−1} / (2(k−1))
so that the antiderivative U(x) = Σₖ Bₖ Tₖ(x/L) has
Bₖ = L (cₖ₋₁ aₖ₋₁ − aₖ₊₁) / (2k), k ≥ 1 (c₀ = 2, a_{N+1} = 0)
and B₀ is fixed by U(−L) = Σₖ Bₖ (−1)ᵏ = 0.
The exact antiderivative has degree N+1; its top mode B_{N+1} = L a_N / (2(N+1)) is dropped to keep the array shape, which is exact whenever a_N = 0 (e.g. for any dealiased or resolved field).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Num[Array, '... Nmodes']
|
Chebyshev coefficients along the last axis. |
required |
L
|
float
|
Domain half-length. |
1.0
|
Returns:
| Type | Description |
|---|---|
Num[Array, '... Nmodes']
|
Coefficients of U(x) = ∫_{−L}^{x} u(s) ds. |
Examples:
∫_{−1}^{x} 1 ds = x + 1 = T₀ + T₁:
>>> import jax.numpy as jnp
>>> chebyshev_antiderivative_coeffs(jnp.array([1.0, 0.0, 0.0])) # ≈ [1, 1, 0]
Source code in spectraldiffx/_src/chebyshev/transforms.py
chebyshev_integral_coeffs(a, L=1.0)
¶
Definite integral ∫_{−L}^{L} u(x) dx from Chebyshev coefficients.
Since ∫_{−1}^{1} Tₖ(ξ) dξ = 2 / (1 − k²) for even k and 0 for odd k,
∫_{−L}^{L} u dx = L Σ_{k even} 2 aₖ / (1 − k²)
On Gauss–Lobatto nodes this is identical to Clenshaw–Curtis quadrature; on Gauss nodes it is the corresponding Fejér-type rule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Num[Array, '... Nmodes']
|
Chebyshev coefficients along the last axis. |
required |
L
|
float
|
Domain half-length. |
1.0
|
Returns:
| Type | Description |
|---|---|
Num[Array, '...']
|
The integral, reducing the last axis. |
Source code in spectraldiffx/_src/chebyshev/transforms.py
Clenshaw–Curtis quadrature¶
clenshaw_curtis_weights(N, L=1.0)
¶
Clenshaw–Curtis quadrature weights on Gauss–Lobatto nodes of [−L, L].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
N
|
int
|
Chebyshev polynomial degree. The grid has N+1 Gauss–Lobatto nodes. |
required |
L
|
float
|
Domain half-length (default 1). The weights scale linearly with L. |
1.0
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'Npts']
|
Weights |
Examples:
Integrate exp(x) on [−1, 1] (exact value e − 1/e):
>>> import jax.numpy as jnp
>>> w = clenshaw_curtis_weights(N=32, L=1.0)
>>> x = jnp.cos(jnp.pi * jnp.arange(33) / 32)
>>> float(jnp.sum(w * jnp.exp(x)))
Source code in spectraldiffx/_src/chebyshev/quadrature.py
clenshaw_curtis_integrate_1d(grid, f)
¶
Integrate a 1D nodal field over [−L, L] using Clenshaw–Curtis.
The grid must use Gauss–Lobatto nodes (Gauss nodes would require a different quadrature rule — Gauss–Chebyshev — which is not provided here because it is less accurate for smooth non-periodic f).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
ChebyshevGrid1D
|
Must have |
required |
f
|
Num[Array, 'Npts']
|
Nodal values of the integrand. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar approximation to ∫_{−L}^{L} f(x) dx. |
Examples:
>>> import jax.numpy as jnp
>>> grid = ChebyshevGrid1D.from_N_L(N=32, L=1.0)
>>> f = jnp.exp(grid.x)
>>> I = clenshaw_curtis_integrate_1d(grid, f) # ≈ e − 1/e
Source code in spectraldiffx/_src/chebyshev/quadrature.py
clenshaw_curtis_integrate_2d(grid, f)
¶
Integrate a 2D nodal field over [−Lx, Lx] × [−Ly, Ly].
Uses the tensor product of 1D Clenshaw–Curtis rules:
∫∫ f(x, y) dx dy ≈ Σⱼᵢ w_y[j] · w_x[i] · f[j, i]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
ChebyshevGrid2D
|
Must use Gauss–Lobatto nodes in both directions. |
required |
f
|
Num[Array, 'Nypts Nxpts']
|
Nodal values of the integrand on the (Nᵧ+1, Nₓ+1) grid. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar approximation to ∫∫ f dA. |
Examples:
>>> import jax.numpy as jnp
>>> grid = ChebyshevGrid2D.from_N_L(Nx=24, Ny=24, Lx=1.0, Ly=1.0)
>>> X, Y = grid.X
>>> f = jnp.exp(X + Y)
>>> I = clenshaw_curtis_integrate_2d(grid, f) # ≈ (e − 1/e)²