CyclicPSpline

Cyclic P-Spline (periodic B-spline basis with circular difference penalty).

Usage

Source

CyclicPSpline(
    k=10,
    degree=3,
    m=2,
)

A cyclic P-spline is the periodic counterpart of PSpline: a B-spline basis whose knots and coefficients wrap around a circle, combined with a circular finite-difference penalty that also wraps around, so smoothness is enforced across the boundary rather than just within it. Equivalent to mgcv’s bs="cp" basis. The B-spline basis is constructed to be periodic over the training data range, so f(x_min) = f(x_max) and, because the penalty ties coefficients across the wrap point, the fit avoids any artificial kink at the seam. Values outside the training range are mapped into the periodic domain via modular arithmetic. Prefer CyclicPSpline over CyclicCRS for the same reasons PSpline is often preferred over CRS: cheaper construction, equally-spaced (rather than quantile) knots, and a sparse banded penalty that scales well to larger k; prefer CyclicCRS when knot-based interpretability or an integrated-derivative penalty is wanted.

The circular difference penalty penalizes the m-th differences of adjacent coefficients with wrap-around at the boundaries. The penalty null space is 1-dimensional (constant functions only).

Parameters

k: int = 10

Number of periodic B-spline basis functions. Must satisfy k >= degree + 1. Larger k gives more local flexibility around the cycle; overall smoothness is governed by the penalty and lambda. The default is 10.

degree: int = 3

Polynomial degree of each B-spline piece. degree=3 (cubic) is conventional; lower degrees give coarser, more locally-supported bases. The default is 3 (cubic).

m: int = 2
Order of the circular difference penalty applied to adjacent coefficients, with wrap-around so that the coefficients at the end of the cycle are treated as adjacent to those at the start. m=2 (the default) penalizes circular second differences, the periodic analogue of curvature. The default is 2.

Notes

_periodic_bspline_knots() builds a periodic knot vector by taking k equally-spaced knots spanning one period [x_min, x_max) and extending them periodically by degree knots on each side, so that the B-spline basis functions near the boundary have support that wraps smoothly around the cycle. basis_matrix() evaluates the (non-periodic) B-spline design on this extended knot vector and then folds the trailing degree “wrapped” columns back onto the leading degree columns (B[:, :degree] += B_full[:, k:]), so the returned matrix has exactly k periodic basis functions.

The penalty replaces the ordinary finite-difference matrix used by PSpline with a circular difference matrix \mathbf{D}_m (built by _cyclic_diff_matrix() via repeated left-multiplication by the circular first-difference operator D_1[i, i] = -1, D_1[i, (i+1) \bmod k] = 1), giving a square k \times k penalty

\mathbf{S} = \mathbf{D}_m^\top \mathbf{D}_m,

positive semi-definite with rank k - 1. Its one-dimensional null space is the constant coefficient sequence: because the difference operator wraps around, no nonconstant polynomial sequence in the coefficient index can have zero circular difference (unlike the non-cyclic PSpline, where degree-< m polynomial sequences are all unpenalized). As with PSpline, the penalty is banded/sparse (with corner entries from the wrap-around) and cheap to factorize even for large k.

Examples

import numpy as np
from whittaker.smooths import CyclicPSpline

x = np.linspace(0, 2 * np.pi, 100)
basis = CyclicPSpline(k=10).fit(x)
B = basis.basis_matrix(x)
S = basis.penalty_matrix()
B.shape, S.shape
((100, 10), (10, 10))

Attributes

Name Description
n_basis Total number of periodic basis functions.

n_basis

Total number of periodic basis functions.

n_basis: int

Equal to k: unlike CyclicCRS, the periodic B-spline construction folds the extra “wrapped” columns back into the leading degree columns during basis construction, so no coefficients are dropped and the basis dimension is exactly k.

Methods

Name Description
basis_matrix() Evaluate the periodic B-spline basis at x.
fit() Fit the cyclic P-spline to training data x.
identifiability_constraints() Return the sum-to-zero constraint row for the intercept.
null_space_dimension() Return the dimension of the penalty null space.
penalty_matrix() Return the k x k circular penalty matrix S = D_m' D_m.

basis_matrix()

Evaluate the periodic B-spline basis at x.

Usage

Source

basis_matrix(x)

Any x outside the training range is first mapped into the periodic domain [x_min, x_max) via modular arithmetic. The trailing degree columns of the raw B-spline design (from the periodic knot extension) are folded back onto the leading degree columns so the result has exactly k periodic basis functions.

Parameters

x: NDArray
1-D evaluation points, shape (n,) or (n, 1).

Returns

NDArray
Design matrix of shape (n, k).

fit()

Fit the cyclic P-spline to training data x.

Usage

Source

fit(x)

Determines the period from the data range, builds the periodic knot vector, and pre-computes the circular difference penalty matrix.

Parameters

x: NDArray
1-D training covariate, shape (n,) or (n, 1). The period is inferred as x.max() - x.min().

Returns

CyclicPSpline
Returns self for method chaining.

Raises

ValueError
If n < k, if all values of x are identical, or if x is not 1-D / (n, 1).

identifiability_constraints()

Return the sum-to-zero constraint row for the intercept.

Usage

Source

identifiability_constraints()

Evaluates the basis on a fine equally-spaced grid spanning one period and averages each column, giving a (1, k) row C such that C @ beta == 0 constrains the smooth to have mean zero over the training range.

Returns

NDArray
Shape (1, k) constraint matrix.

null_space_dimension()

Return the dimension of the penalty null space.

Usage

Source

null_space_dimension()

Always 1 for a cyclic P-spline: only the constant coefficient sequence has zero circular difference.

Returns

int
Always 1.

penalty_matrix()

Return the k x k circular penalty matrix S = D_m' D_m.

Usage

Source

penalty_matrix()

S is positive semi-definite with rank k - 1. Its one-dimensional null space is spanned by the constant coefficient sequence; unlike the non-cyclic PSpline, no nonconstant polynomial sequence in the coefficient index is unpenalized because the difference operator wraps around the boundary.

Returns

NDArray
Shape (k, k).