Source code for lpspline.spline.cyclic_spline


import numpy as np
import cvxpy as cp
from typing import List, Optional
from .base import Spline

[docs] class CyclicSpline(Spline): """ Periodic Cyclic Spline defined by Fourier series expansions. """ def __init__(self, term: str, order: int, period: float = None, tag: Optional[str] = 'cyclicspline', by: Optional[str] = None): """ Initialize the Cyclic Spline. Parameters ---------- term : str The column name denoting the periodic feature. order : int The number of sine/cosine pairs to generate. period : float, default=None The periodicity interval. If None, it is inferred from data. tag : Optional[str], default='cyclicspline' A tag for identification. by : Optional[str], default=None The column name denoting group classes if interaction modeling is required. """ super().__init__(term=term, tag=tag) self._period = period self._order = order self._by = by self._variables = [] @property def period(self) -> float: """ Returns the determined period of the cyclic spline. Returns ------- float The numeric period length. """ return self._period @property def order(self) -> int: """ Returns the number of generated Fourier term pairs. Returns ------- int The cyclic order describing polynomial flexibility. """ return self._order
[docs] def init_spline(self, x: np.ndarray, by: np.ndarray = None): """ Initializes the periodic boundary conditions based on data. If `period` is not specified directly during Initialization, it calculates the period automatically across the maximum observed range in `x`. Parameters ---------- x : np.ndarray The 1D input array of training periodic measurements. by : np.ndarray, default=None The array of grouping values. """ super().init_spline(x, by) if self._period is None: self._period = np.max(x) - np.min(x)
def _build_basis(self, x: np.ndarray, **kwargs) -> np.ndarray: """ Builds the basis matrix for the cyclic spline using sine/cosine decompositions. Generates expansions evaluating sequentially as: $1, \\sin(2\\pi x / P), \\cos(2\\pi x / P), \\sin(4\\pi x / P), \\dots$ Parameters ---------- x : np.ndarray Input 1D array of cyclic measurements. **kwargs : dict Additional format arguments. Returns ------- np.ndarray A 2D mathematical basis matrix of shape `(n_samples, 1 + 2 * order)`. """ x = np.array(x).flatten() n = len(x) basis_list = [np.ones_like(x)] for k in range(1, self.order + 1): omega = 2 * np.pi * k / self.period basis_list.append(np.sin(omega * x)) basis_list.append(np.cos(omega * x)) base_basis = np.vstack(basis_list).T return base_basis def _build_variables(self) -> cp.Variable: """ Create CVXPY variables representing the Fourier coefficients. Returns ------- cp.Variable A CVXPY Variable of shape `(1 + 2 * order, len(by_classes) if by else 1)`. """ if isinstance(self._variables, list) and not self._variables: dim_base = 1 + 2 * self.order if self._by is not None: self._variables = cp.Variable(shape=(dim_base, len(self._by_classes)), name=f"{self.term}_cyclic") else: self._variables = cp.Variable(shape=(dim_base,), name=f"{self.term}_cyclic") return self._variables def __repr__(self): return f"CyclicSpline(term='{self.term}', period={self.period}, order={self.order}, by={self._by})"