Skip to content

Basis systems

fdatools.basis: the seven basis families and their shared interface (evaluation, roughness penalties, Gram matrices, products).

Functional bases: B-spline, Fourier, monomial, exponential, power, polygonal.

Every basis is an immutable dataclass, is callable (b(t, deriv=0) returns the (len(t), n_basis) basis matrix in the caller's array namespace) and knows how to build its own penalty and Gram matrices.

The mathematics is implemented from first principles: the Cox-de Boor recursion for B-splines, the analytic Fourier/monomial/exponential/power definitions, and Gauss-Legendre quadrature of sufficient order for the inner products, following Ramsay & Silverman, Functional Data Analysis (2nd ed.), chapter 3.

Examples:

>>> import numpy as np
>>> from fdatools import BSpline
>>> b = BSpline(domain=(0.0, 1.0), n_basis=6)
>>> b(np.array([0.0, 0.5, 1.0])).shape
(3, 6)
>>> float(np.max(np.abs(b(np.linspace(0, 1, 11)).sum(axis=1) - 1.0))) < 1e-14
True

Basis dataclass

Basis()

Bases: ABC

Abstract base class for every fdatools basis system.

A basis is a frozen dataclass, so it is hashable, comparable and safe to use as a cache key. Subclasses provide the evaluation rule; this class provides the shared machinery for derivatives, penalties, Gram matrices and products.

Attributes:

Name Type Description
domain tuple of float

The interval (a, b) the basis is defined on.

n_basis abstractmethod property

n_basis: int

Number of basis functions.

names abstractmethod property

names: tuple[str, ...]

Human-readable name of each basis function.

__call__

__call__(t: Array, deriv: int | LDO = 0) -> Array

Evaluate the basis (or a derivative of it) at t.

Parameters:

Name Type Description Default
t array_like

Evaluation points, shape (n_t,); a scalar is read as one point.

required
deriv int or LDO

Derivative order, or a linear differential operator to apply.

0

Returns:

Type Description
array

Basis matrix of shape (n_t, n_basis) in the namespace of t.

Raises:

Type Description
ValueError

If t has more than one axis, if deriv is negative, or if t leaves the domain of a piecewise-defined basis (B-spline, polygonal).

Examples:

>>> import numpy as np
>>> from fdatools import Monomial
>>> Monomial(domain=(0.0, 1.0), n_basis=3)(np.array([2.0])).tolist()
[[1.0, 2.0, 4.0]]

penalty

penalty(
    op: int | LDO = 2, *, xp: ModuleType | None = None
) -> Array

Roughness penalty matrix R[i, j] = ∫ (L φ_i)(L φ_j) dt.

Parameters:

Name Type Description Default
op int or LDO

Derivative order (default 2, the curvature penalty) or a full linear differential operator.

2
xp module

Array namespace for the result. Defaults to NumPy; the matrix is a constant of the basis, so it carries no gradient either way.

None

Returns:

Type Description
array

Symmetric positive-semidefinite matrix of shape (n_basis, n_basis).

Examples:

>>> import numpy as np
>>> from fdatools import Fourier
>>> pen = Fourier(domain=(0.0, 1.0), n_basis=3).penalty(1)
>>> np.round(np.diag(pen), 6).tolist()
[0.0, 39.478418, 39.478418]

gram

gram(
    deriv: int = 0, *, xp: ModuleType | None = None
) -> Array

Return the cached Gram matrix G[i, j] = ∫ φ_i^{(deriv)} φ_j^{(deriv)} dt.

Parameters:

Name Type Description Default
deriv int

Derivative order applied to both factors.

0
xp module

Array namespace for the result. Defaults to NumPy.

None

Returns:

Type Description
array

Symmetric matrix of shape (n_basis, n_basis).

Examples:

>>> import numpy as np
>>> from fdatools import Fourier
>>> np.round(Fourier(domain=(0.0, 1.0), n_basis=3).gram(), 12).tolist()
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]

__mul__

__mul__(other: Basis) -> Basis

Return a basis whose span contains every pointwise product φ_i ψ_j.

Parameters:

Name Type Description Default
other Basis

The second factor; must share this basis's domain.

required

Returns:

Type Description
Basis

The product basis. Exact for spline x spline (merged breaks, order o1 + o2 - 1), Fourier x Fourier of equal period, and for the monomial / power / exponential families (summed exponents or rates). Any other combination falls back to a rich B-spline space.

BSpline dataclass

BSpline(
    domain: tuple[float, float] = (0.0, 1.0),
    n_basis: int | None = None,
    order: int = 4,
    breaks: Sequence[float] | None = None,
)

Bases: Basis

B-spline basis of a given order on a set of break points.

Replaces R's create.bspline.basis.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b). Defaults to (0.0, 1.0).

(0.0, 1.0)
n_basis int

Number of basis functions. Ignored when breaks is supplied, where it becomes len(breaks) + order - 2. Defaults to order, i.e. a single polynomial piece.

None
order int

Spline order = polynomial degree + 1. Defaults to 4 (cubic).

4
breaks sequence of float

Strictly increasing break points including both endpoints. Defaults to n_basis - order + 2 equally spaced points.

None

Attributes:

Name Type Description
order int

Spline order.

breaks tuple of float

The break points actually used.

Raises:

Type Description
ValueError

If the arguments are inconsistent, or n_basis < order.

Examples:

>>> import numpy as np
>>> from fdatools import BSpline
>>> b = BSpline(domain=(0.0, 1.0), n_basis=5, order=3)
>>> b.n_basis
5
>>> b.breaks
(0.0, 0.3333333333333333, 0.6666666666666666, 1.0)
>>> float(b(np.array([1.0]))[0, -1])
1.0

n_basis property

n_basis: int

Number of B-spline basis functions, len(breaks) + order - 2.

Returns:

Type Description
int

The basis dimension.

Examples:

>>> from fdatools import BSpline
>>> BSpline(breaks=[0.0, 0.5, 1.0], order=4).n_basis
5

names property

names: tuple[str, ...]

Names bspl<order>.<i> following R's fda convention.

Returns:

Type Description
tuple of str

One name per basis function.

Examples:

>>> from fdatools import BSpline
>>> BSpline(n_basis=4).names
('bspl4.1', 'bspl4.2', 'bspl4.3', 'bspl4.4')

knots property

knots: tuple[float, ...]

The open knot vector, endpoints repeated order times.

Returns:

Type Description
tuple of float

Knot sequence of length n_basis + order.

Examples:

>>> from fdatools import BSpline
>>> BSpline(breaks=[0.0, 1.0], order=2).knots
(0.0, 0.0, 1.0, 1.0)

Fourier dataclass

Fourier(
    domain: tuple[float, float] = (0.0, 1.0),
    n_basis: int = 3,
    period: float | None = None,
)

Bases: Basis

Fourier basis, orthonormal over one period.

The functions are 1/sqrt(P), then sqrt(2/P) sin(h ω t) and sqrt(2/P) cos(h ω t) for h = 1 .. H with ω = 2π / P. Replaces R's create.fourier.basis.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b). Defaults to (0.0, 1.0).

(0.0, 1.0)
n_basis int

Number of basis functions. An even value is raised by one so that the sine/cosine pairs are complete, matching R; read back the effective size from :attr:n_basis.

3
period float

Period P. Defaults to the width of the domain.

None

Attributes:

Name Type Description
n_harmonics int

Number of sine/cosine pairs H.

period float

The period.

Raises:

Type Description
ValueError

If n_basis < 1 or period <= 0.

Examples:

>>> from fdatools import Fourier
>>> Fourier(domain=(0.0, 365.0), n_basis=4).n_basis
5
>>> Fourier(domain=(0.0, 365.0), n_basis=5).period
365.0

n_basis property

n_basis: int

Number of basis functions, always odd: 2 * n_harmonics + 1.

Returns:

Type Description
int

The basis dimension.

Examples:

>>> from fdatools import Fourier
>>> Fourier(n_basis=6).n_basis
7

names property

names: tuple[str, ...]

Names const, sin1, cos1, ... following R's convention.

Returns:

Type Description
tuple of str

One name per basis function.

Examples:

>>> from fdatools import Fourier
>>> Fourier(n_basis=3).names
('const', 'sin1', 'cos1')

Monomial dataclass

Monomial(
    domain: tuple[float, float] = (0.0, 1.0),
    n_basis: int = 2,
    exponents: Sequence[int] | None = None,
)

Bases: Basis

Monomial basis t^e for non-negative integer exponents.

Replaces R's create.monomial.basis.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b). Defaults to (0.0, 1.0).

(0.0, 1.0)
n_basis int

Number of basis functions; ignored when exponents is given. Defaults to 2 (constant and linear).

2
exponents sequence of int

Exponents to use. Defaults to 0 .. n_basis - 1.

None

Attributes:

Name Type Description
exponents tuple of int

The exponents.

Raises:

Type Description
ValueError

If exponents are negative or repeated, or conflict with n_basis.

Examples:

>>> import numpy as np
>>> from fdatools import Monomial
>>> Monomial(n_basis=3)(np.array([3.0]), deriv=1).tolist()
[[0.0, 1.0, 6.0]]

n_basis property

n_basis: int

Number of monomials.

Returns:

Type Description
int

len(self.exponents).

Examples:

>>> from fdatools import Monomial
>>> Monomial(exponents=[0, 2, 4]).n_basis
3

names property

names: tuple[str, ...]

Names monomial<e>.

Returns:

Type Description
tuple of str

One name per basis function.

Examples:

>>> from fdatools import Monomial
>>> Monomial(n_basis=2).names
('monomial0', 'monomial1')

Power dataclass

Power(
    domain: tuple[float, float] = (1.0, 2.0),
    exponents: Sequence[float] = (0.0, 1.0),
)

Bases: Basis

Power basis t^e for arbitrary real exponents.

Replaces R's create.power.basis. The domain must not contain negative values, and must exclude zero whenever a negative power appears.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b) with a >= 0. Defaults to (1.0, 2.0).

(1.0, 2.0)
exponents sequence of float

The exponents. Defaults to (0.0, 1.0).

(0.0, 1.0)

Attributes:

Name Type Description
exponents tuple of float

The exponents.

Raises:

Type Description
ValueError

If the domain is negative, the exponents repeat, or a negative exponent is combined with a domain touching zero.

Examples:

>>> import numpy as np
>>> from fdatools import Power
>>> Power(domain=(1.0, 4.0), exponents=[0.5])(np.array([4.0])).tolist()
[[2.0]]

n_basis property

n_basis: int

Number of power functions.

Returns:

Type Description
int

len(self.exponents).

Examples:

>>> from fdatools import Power
>>> Power(exponents=[0.0, 0.5, 1.0]).n_basis
3

names property

names: tuple[str, ...]

Names power<e>.

Returns:

Type Description
tuple of str

One name per basis function.

Examples:

>>> from fdatools import Power
>>> Power(exponents=[0.0, 0.5]).names
('power0.0', 'power0.5')

Exponential dataclass

Exponential(
    domain: tuple[float, float] = (0.0, 1.0),
    rates: Sequence[float] = (0.0, 1.0),
)

Bases: Basis

Exponential basis exp(r t) for a set of rates.

Replaces R's create.exponential.basis.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b). Defaults to (0.0, 1.0).

(0.0, 1.0)
rates sequence of float

The rates r. Defaults to (0.0, 1.0).

(0.0, 1.0)

Attributes:

Name Type Description
rates tuple of float

The rates.

Raises:

Type Description
ValueError

If the rates are empty or repeated.

Examples:

>>> import numpy as np
>>> from fdatools import Exponential
>>> Exponential(rates=[0.0, 1.0])(np.array([0.0])).tolist()
[[1.0, 1.0]]

n_basis property

n_basis: int

Number of exponentials.

Returns:

Type Description
int

len(self.rates).

Examples:

>>> from fdatools import Exponential
>>> Exponential(rates=[0.0, 1.0, 2.0]).n_basis
3

names property

names: tuple[str, ...]

Names exp<rate>.

Returns:

Type Description
tuple of str

One name per basis function.

Examples:

>>> from fdatools import Exponential
>>> Exponential(rates=[0.0, 1.0]).names
('exp0.0', 'exp1.0')

Constant dataclass

Constant(domain: tuple[float, float] = (0.0, 1.0))

Bases: Basis

The one-function basis φ(t) = 1.

Replaces R's create.constant.basis.

Parameters:

Name Type Description Default
domain tuple of float

Interval (a, b). Defaults to (0.0, 1.0).

(0.0, 1.0)

Examples:

>>> import numpy as np
>>> from fdatools import Constant
>>> Constant(domain=(0.0, 2.0)).gram().tolist()
[[2.0]]

n_basis property

n_basis: int

Always one.

Returns:

Type Description
int

1.

Examples:

>>> from fdatools import Constant
>>> Constant().n_basis
1

names property

names: tuple[str, ...]

The single name const.

Returns:

Type Description
tuple of str

("const",).

Examples:

>>> from fdatools import Constant
>>> Constant().names
('const',)

Polygonal dataclass

Polygonal(argvals: Sequence[float] = (0.0, 0.5, 1.0))

Bases: Basis

Piecewise-linear ("hat function") basis on a set of arguments.

Replaces R's create.polygonal.basis. Mathematically this is the order-2 B-spline basis with breaks at argvals, and it is evaluated with the same Cox-de Boor kernel.

Parameters:

Name Type Description Default
argvals sequence of float

Strictly increasing vertex locations; the domain is (argvals[0], argvals[-1]). Defaults to (0.0, 0.5, 1.0).

(0.0, 0.5, 1.0)

Attributes:

Name Type Description
argvals tuple of float

The vertices.

Raises:

Type Description
ValueError

If fewer than two arguments are given, or they are not increasing.

Examples:

>>> import numpy as np
>>> from fdatools import Polygonal
>>> Polygonal([0.0, 1.0, 2.0])(np.array([0.5])).tolist()
[[0.5, 0.5, 0.0]]

n_basis property

n_basis: int

One basis function per vertex.

Returns:

Type Description
int

len(self.argvals).

Examples:

>>> from fdatools import Polygonal
>>> Polygonal([0.0, 1.0, 2.0]).n_basis
3

names property

names: tuple[str, ...]

Names polyg<i>.

Returns:

Type Description
tuple of str

One name per vertex.

Examples:

>>> from fdatools import Polygonal
>>> Polygonal([0.0, 1.0]).names
('polyg1', 'polyg2')

order property

order: int

The equivalent B-spline order, always 2.

Returns:

Type Description
int

2.

Examples:

>>> from fdatools import Polygonal
>>> Polygonal([0.0, 1.0]).order
2