nemos.basis.FourierEval#

class nemos.basis.FourierEval(frequencies, ndim=1, bounds=None, frequency_mask='no-intercept', label='FourierEval')[source]#

Bases: EvalBasisMixin, FourierBasis

N-dimensional Fourier basis for feature expansion.

This class generates a set of sine and cosine basis functions defined over an n-dimensional input space. The basis functions are constructed from a Cartesian product of frequencies specified for each input dimension. Each selected frequency combination contributes two basis functions (cosine and sine), except for the all-zero frequency (DC component), which contributes only a cosine term.

The class supports flexible frequency specification (integers, ranges, or arrays per dimension) and optional masking to include or exclude specific frequency combinations.

Parameters:
  • frequencies

    Frequency specification(s).

    Single specification (broadcasted to all dimensions when ndim > 1):

    • int: An integer k with k >= 0.

    • tuple: (low, high), a 2-element tuple of integers with 0 <= low < high.

    • ndarray: 1-D NumPy array of non-negative integers. If not sorted ascending, a UserWarning is issue for non-sorted arrays.

    Per-dimension container:

    • A list of length ndim whose elements are each a valid single specification. For ndim == 1, a length-1 list is also accepted.

  • ndim – Dimensionality of the basis. Default is 1.

  • bounds

    Period bounds for each dimension. Unlike other basis classes where bounds define a valid domain (with out-of-bounds samples filled with NaN), for the Fourier basis the bounds define the period of the basis functions. Samples outside these bounds are still valid and will be evaluated using the periodic nature of the basis.

    • tuple: (low, high) of floats: applies to all dimensions.

    • list of tuple: [(low, high), ...], one tuple per dimension, length must match ndim.

    • None: the period is inferred from the input data (minimum to maximum values).

    In all cases, low must be strictly less than high, and values must be convertible to floats.

  • frequency_mask

    Optional mask specifying which frequency components to include. Can be:

    • Literal: either "no-intercept" - default - which drops the 0-frequency DC term, or "all" which keeps all the frequencies - equivalent to None. The default excludes the intercept because these basis objects are most commonly used to generate design matrices for NeMoS GLMs, which already include an intercept term by default, making an additional intercept in the design matrix redundant.

    • Array-like of integers {0, 1} or booleans: Selects frequencies to keep (1/True) or exclude (0/False). Shape must match the number of available frequencies for each dimension.

    • Callable: A function applied to each frequency index (one index per dimension), returning a single boolean or {0, 1} indicating whether to keep that frequency.

    • None: All frequencies are kept.

    Values must be 0/1 or boolean. Callables must return a single boolean or {0, 1} value for each frequency coordinate.

  • label – Descriptive label for the basis (e.g., to use in plots or summaries).

Notes

  • If frequency_mask is provided, only the selected frequency combinations are used to build the basis.

  • The output of compute_features contains both cosine and sine components for each active frequency combination, except that the all-zero frequency includes only a cosine term.

  • When a tuple is provided as a frequency, it is interpreted as a single range specification. Tuples that are not exactly a 2-element tuple of non-negative integers are invalid.

Examples

>>> import numpy as np
>>> from nemos.basis import FourierEval
>>> rng = np.random.default_rng(0)

1D: basic usage

>>> n_freq = 5
>>> fourier_1d = FourierEval(n_freq)
>>> # cos at 0..4 (5) + sin at 1..4 (4) = 9
>>> fourier_1d.n_basis_funcs
8
>>> x = rng.normal(size=8)
>>> X = fourier_1d.compute_features(x)
>>> X.shape  # (n_samples, n_basis_funcs)
(8, 8)

2D: unmasked grid of frequency pairs

>>> fourier_2d = FourierEval(n_freq, ndim=2)
>>> # (5*5 frequency pairs) * 2 (cos+sin) - 1 (no sine at DC) = 49
>>> fourier_2d.n_basis_funcs
48
>>> x, y = rng.normal(size=(2, 6))
>>> X = fourier_2d.compute_features(x, y)
>>> X.shape
(6, 48)

2D: masking with an array (drop 3 pairs)

>>> mask = np.ones((5, 5))
>>> # drop 3 frequency pairs, including DC term (0,0)
>>> mask[[0, 0, 1], [0, 1, 2]] = 0
>>> fourier_2d_masked = FourierEval(
...     n_freq,
...     ndim=2,
...     frequency_mask=mask
... )
>>> # (5*5-3 frequency pairs) * 2 (cos+sin) = 44
>>> fourier_2d_masked.n_basis_funcs
44

2D: masking with a callable

>>> # keep pairs inside a circle of radius 3.5 in frequency space
>>> keep_circle = lambda fx, fy: (fx**2 + fy**2) ** 0.5 < 3.5
>>> fourier_2d_funcmask = FourierEval(
...     n_freq,
...     ndim=2,
...     frequency_mask=keep_circle
... )
>>> fourier_2d_funcmask.n_basis_funcs
25

Explicit frequency specifications

>>> # mix forms per-dimension: an explicit array
>>> # and an inclusive tuple (low, high)
>>> fourier_mixed = FourierEval(
...     frequencies=[np.arange(3), (1, 4)],
...     ndim=2
... )
>>> # (3*3 frequency pairs) * 2 (cos+sin) = 18; no DC term (0, 0)
>>> fourier_mixed.n_basis_funcs
18

Attributes

bounds

Returns bounds, as provided.

frequencies

Frequencies for the basis.

frequency_mask

Get the frequency mask for the Fourier basis.

input_shape

Expected per-sample input shape.

is_complex

Whether the basis is intrinsically complex.

label

Label for the basis.

masked_frequencies

The frequencies after the masking is applied.

n_basis_funcs

Number of basis functions.

n_output_features

Number of features returned by the basis.

ndim

The dimensionality of the basis.

__init__(frequencies, ndim=1, bounds=None, frequency_mask='no-intercept', label='FourierEval')[source]#

Methods

__init__(frequencies[, ndim, bounds, ...])

compute_features(*xi)

Evaluate basis at sample points.

evaluate(*sample_pts)

Evaluate the Fourier basis at the sample points.

evaluate_on_grid(*n_samples)

Evaluate the basis set on a grid of equi-spaced sample points.

get_params([deep])

From scikit-learn, get parameters by inspecting init.

set_input_shape(*xi)

Set the expected input shape for the basis object.

set_params(**params)

Set params handling correctly the frequencies and their mask.

setup_basis(*xi)

Set all basis states.

split_by_feature(x[, axis])

Decompose an array along a specified axis into sub-arrays based on the number of expected inputs.

to_transformer()

Turn the Basis into a TransformerBasis for use with scikit-learn.

__add__(other)#

Add two Basis objects together.

Parameters:

other (BasisMixin) – The other Basis object to add.

Returns:

The resulting Basis object.

Return type:

AdditiveBasis

classmethod __init_subclass__(**kwargs)#

Set the set_{method}_request methods.

This uses PEP-487 [1] to set the set_{method}_request methods. It looks for the information available in the set default values which are set using __metadata_request__* class attributes, or inferred from method signatures.

The __metadata_request__* class attributes are used when a method does not explicitly accept a metadata through its arguments or if the developer would like to specify a request value for those metadata which are different from the default None.

References

__iter__()#

Make basis iterable. Re-implemented for additive.

__len__()#

Return the number of additive basis.

__mul__(other)#

Multiply two Basis objects together.

Parameters:

other (BasisMixin | int) – The other Basis object to multiply.

Return type:

Basis

Returns:

The resulting Basis object.

__pow__(exponent)#

Exponentiation of a Basis object.

Define the power of a basis by repeatedly applying the method __multiply__. The exponent must be a positive integer.

Parameters:

exponent (int) – Positive integer exponent

Return type:

BasisMixin

Returns:

The product of the basis with itself “exponent” times. Equivalent to self * self * ... * self.

Raises:
  • TypeError – If the provided exponent is not an integer.

  • ValueError – If the integer is zero or negative.

__rmul__(other)#

Right multiplication operator for basis.

Parameters:

other (BasisMixin | int)

__sklearn_clone__()#

Clone the basis while preserving attributes related to input shapes.

This method ensures that input shape attributes (e.g., _input_shape_product, _input_shape_) are preserved during cloning. Reinitializing the class as in the regular sklearn clone would drop these attributes, rendering cross-validation unusable.

Return type:

Basis

property bounds: List[Tuple[float, float]] | Tuple[float, float] | None#

Returns bounds, as provided.

compute_features(*xi)[source]#

Evaluate basis at sample points.

The basis is evaluated at the locations specified in the inputs. For example, compute_features(np.array([0, .5])) would return the array:

b_1(0) ... b_n(0)
b_1(.5) ... b_n(.5)

where b_i is the i-th basis.

Parameters:

*xi (ArrayLike) – The input samples over which to apply the basis transformation. The samples can be passed as multiple arguments, each representing a different dimension for multivariate inputs.

Return type:

FeatureMatrix

Returns:

A matrix with the transformed features.

Examples

>>> import numpy as np
>>> from nemos.basis import FourierEval
>>> # Generate data
>>> num_samples = 1000
>>> X = np.random.normal(size=(num_samples, ))  # raw time series
>>> basis = FourierEval(10)
>>> features = basis.compute_features(X)  # basis transformed time series
>>> features.shape
(1000, 18)
evaluate(*sample_pts)[source]#

Evaluate the Fourier basis at the sample points.

Parameters:

sample_pts (NDArray) – Spacing for basis functions, holding elements on interval [0, 1]. sample_pts is a n-dimensional (n >= 1) array with first axis being the samples, i.e. sample_pts.shape[0] == n_samples.

Raises:

ValueError – If the sample provided do not lie in [0,1].

Return type:

NDArray

Examples

>>> import numpy as np
>>> from nemos.basis import FourierEval
>>> basis = FourierEval(4)
>>> out = basis.evaluate(np.random.randn(100, 5, 2))
>>> out.shape
(100, 5, 2, 6)
evaluate_on_grid(*n_samples)[source]#

Evaluate the basis set on a grid of equi-spaced sample points.

Parameters:

n_samples (int) – The number of points in the uniformly spaced grid. A higher number of samples will result in a more detailed visualization of the basis functions.

Return type:

Tuple[NDArray, NDArray]

Returns:

  • X – Array of shape (n_samples,) containing the equi-spaced sample points where we’ve evaluated the basis.

  • basis_funcs – Fourier basis functions, shape (n_samples, n_basis_funcs)

Examples

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from nemos.basis import FourierEval
>>> n_frequencies = 5
>>> fourier_basis = FourierEval(n_frequencies)
>>> sample_points, basis_values = fourier_basis.evaluate_on_grid(100)
>>> plt.plot(sample_points, basis_values)
[<matplotlib.lines.Line2D object at ...
>>> plt.show()

(Source code)

../../../_images/nemos-basis-FourierEval-1.png

Fig. 21 FourierEval#

property frequencies: List[Array]#

Frequencies for the basis.

Returns:

A tuple of arrays with the fourier frequencies, one per dimension of the basis.

property frequency_mask: Callable | Array | Literal['all', 'no-intercept']#

Get the frequency mask for the Fourier basis.

The frequency mask can be either:

  • a boolean array (or array-like of 0s and 1s) whose shape matches the number of frequencies along each input dimension, or

  • a callable with signature frequency_mask(*freqs) -> bool (or 0/1) applied to each frequency tuple, or

  • a string, either "all", if all possible frequency combinations are included, or "no-intercept", if the intercept term is dropped (DC component).

Returns:

The callable used to build the mask, the boolean JAX array mask, or None if no mask is applied.

get_metadata_routing()#

Get metadata routing of this object.

Please check User Guide on how the routing mechanism works.

Returns:

routing – A MetadataRequest encapsulating routing information.

Return type:

MetadataRequest

get_params(deep=True)#

From scikit-learn, get parameters by inspecting init.

Parameters:

deep – If True, will return the parameters for this estimator and contained subobjects that are estimators.

Return type:

dict

Returns:

A dictionary containing the parameters. Key is the parameter name, value is the parameter value.

property input_shape#

Expected per-sample input shape.

Returns:

If inputs are shaped (n_samples, *shape), returns shape.

property is_complex#

Whether the basis is intrinsically complex.

Returns:

True if the basis is complex; False otherwise.

Notes

compute_features() always returns a real-valued design matrix. For complex bases (e.g., FourierEval), the real and imaginary parts are returned as separate columns.

property label: str#

Label for the basis.

property masked_frequencies: Array#

The frequencies after the masking is applied.

Returns:

  • The masked frequencies, shape (ndim, n_frequency_combinations).

  • masked_frequencies[:, i] is the frequency combination for the

  • i-th basis function.

property n_basis_funcs: int | None#

Number of basis functions.

property n_output_features: int | None#

Number of features returned by the basis.

Notes

The number of output features can be determined only when the number of inputs provided to the basis is known. Therefore, before the first call to compute_features, this property will return None. After that call, or after setting the input shape with set_input_shape, n_output_features will be available.

property ndim#

The dimensionality of the basis.

set_input_shape(*xi)[source]#

Set the expected input shape for the basis object.

This method configures the shape of the input data that the basis object expects. xi can be specified as an integer, a tuple of integers, or derived from an array. The method also calculates the total number of input features and output features based on the number of basis functions.

Parameters:

xi (Union[int, tuple[int, ...], NDArray]) –

The input shape specification. - An integer: Represents the dimensionality of the input. A value of 1 is treated as scalar input. - A tuple: Represents the exact input shape excluding the first axis (sample axis).

All elements must be integers.

  • An array: The shape is extracted, excluding the first axis (assumed to be the sample axis).

Raises:

ValueError – If a tuple is provided and it contains non-integer elements.

Returns:

Returns the instance itself to allow method chaining.

Return type:

self

Notes

All state attributes that depends on the input must be set in this method in order for the API of basis to work correctly. In particular, this method is called by setup_basis, which is equivalent to fit for a transformer. If any input dependent state is not set in this method, then compute_features (equivalent to fit_transform) will break.

Examples

>>> import nemos as nmo
>>> import numpy as np
>>> basis = nmo.basis.FourierEval(5)
>>> # Configure with an integer input:
>>> _ = basis.set_input_shape(3)
>>> basis.n_output_features
24
>>> # Configure with a tuple:
>>> _ = basis.set_input_shape((4, 5))
>>> basis.n_output_features
160
>>> # Configure with an array:
>>> x = np.ones((10, 4, 5))
>>> _ = basis.set_input_shape(x)
>>> basis.n_output_features
160
set_params(**params)#

Set params handling correctly the frequencies and their mask.

Parameters:

params (Any)

setup_basis(*xi)#

Set all basis states.

This method corresponds sklearn transformer fit. As fit, it must receive the input and it must set all basis states, i.e. kernel_ and all the states relative to the input shape. The difference between this method and the transformer fit is in the expected input structure, where the transformer fit method requires the inputs to be concatenated in a 2D array, while here each input is provided as a separate time series for each basis element.

Parameters:

xi (NDArray) – Input arrays.

Return type:

Basis

Returns:

The basis with ready for evaluation.

split_by_feature(x, axis=1)[source]#

Decompose an array along a specified axis into sub-arrays based on the number of expected inputs.

This function takes an array (e.g., a design matrix or model coefficients) and splits it along a designated axis.

How it works:

  • If the basis expects an input shape (n_samples, n_inputs), then the feature axis length will be total_n_features = n_inputs * n_basis_funcs. This axis is reshaped into dimensions (n_inputs, n_basis_funcs).

  • If the basis expects an input of shape (n_samples,), then the feature axis length will be total_n_features = n_basis_funcs. This axis is reshaped into (1, n_basis_funcs).

For example, if the input array x has shape (1, 2, total_n_features, 4, 5), then after applying this method, it will be reshaped into (1, 2, n_inputs, n_basis_funcs, 4, 5).

The specified axis (axis) determines where the split occurs, and all other dimensions remain unchanged. See the example section below for the most common use cases.

Parameters:
  • x (NDArray) –

    The input array to be split, representing concatenated features, coefficients, or other data. The shape of x along the specified axis must match the total number of features generated by the basis, i.e., self.n_output_features.

    Examples:

    • For a design matrix: (n_samples, total_n_features)

    • For model coefficients: (total_n_features,) or (total_n_features, n_neurons).

  • axis (int) – The axis along which to split the features. Defaults to 1. Use axis=1 for design matrices (features along columns) and axis=0 for coefficient arrays (features along rows). All other dimensions are preserved.

Raises:

ValueError – If the shape of x along the specified axis does not match self.n_output_features.

Returns:

A dictionary where:

  • Key: Label of the basis.

  • Value: the array reshaped to: (..., n_inputs, n_basis_funcs, ...)

Return type:

dict

Examples

>>> import numpy as np
>>> from nemos.basis import FourierEval
>>> from nemos.glm import GLM
>>> basis = FourierEval(6, label="one_input")
>>> X = basis.compute_features(np.random.randn(20,))
>>> split_features_multi = basis.split_by_feature(X, axis=1)
>>> for feature, sub_dict in split_features_multi.items():
...        print(f"{feature}, shape {sub_dict.shape}")
one_input, shape (20, 10)
to_transformer()#

Turn the Basis into a TransformerBasis for use with scikit-learn.

Return type:

TransformerBasis

Examples

Jointly cross-validating basis and GLM parameters with scikit-learn.

>>> import nemos as nmo
>>> from sklearn.pipeline import Pipeline
>>> from sklearn.model_selection import GridSearchCV
>>> # load some data
>>> X, y = np.random.normal(size=(30, 1)), np.random.poisson(size=30)
>>> basis = nmo.basis.RaisedCosineLinearEval(10).set_input_shape(1).to_transformer()
>>> glm = nmo.glm.GLM(regularizer="Ridge", regularizer_strength=1.)
>>> pipeline = Pipeline([("basis", basis), ("glm", glm)])
>>> param_grid = dict(
...     glm__regularizer_strength=(0.1, 0.01, 0.001, 1e-6),
...     basis__n_basis_funcs=(3, 5, 10, 20, 100),
... )
>>> gridsearch = GridSearchCV(
...     pipeline,
...     param_grid=param_grid,
...     cv=5,
... )
>>> gridsearch = gridsearch.fit(X, y)