jaxsr.basis#
Basis Function Library for JAXSR.
Provides a flexible, extensible system for defining candidate basis functions for symbolic regression.
- class jaxsr.basis.BasisFunction(name: str, func: ~collections.abc.Callable[[~jax.jaxlib._jax.Array], ~jax.jaxlib._jax.Array], complexity: int = 1, feature_indices: tuple[int, ...] = (), func_type: str = 'custom', func_config: dict[str, ~typing.Any] = <factory>, block: str | None = None)#
Bases:
objectA single basis function with metadata.
- Parameters:
name (str) – Human-readable name for the basis function.
func (Callable) – Function that takes X of shape (n_samples, n_features) and returns array of shape (n_samples,).
complexity (int) – Complexity score for Pareto optimization (higher = more complex).
feature_indices (tuple) – Indices of features used by this basis function.
func_type (str) – Type of function (for serialization): “constant”, “linear”, “polynomial”, “interaction”, “transcendental”, “ratio”, “block”, “custom”.
func_config (dict) – Configuration for reconstructing the function (for serialization).
block (str or None) – Label of the structured block this function belongs to, set by
BasisLibrary.add_block().Nonefor unlabelled functions.
- name: str#
- func: Callable[[Array], Array]#
- complexity: int = 1#
- feature_indices: tuple[int, ...] = ()#
- func_type: str = 'custom'#
- func_config: dict[str, Any]#
- block: str | None = None#
- evaluate(X: Array) Array#
Evaluate the basis function on input data.
- to_dict() dict[str, Any]#
Serialize to dictionary (excluding func).
- class jaxsr.basis.ParametricBasisInfo(basis_index: int, name: str, func: Callable, param_bounds: dict[str, tuple[float, float]], initial_params: dict[str, float], log_scale: bool = False, resolved_params: dict[str, float] | None = None)#
Bases:
objectMetadata for a parametric basis function with free nonlinear parameters.
- Parameters:
basis_index (int) – Index of the corresponding BasisFunction in the library.
name (str) – Name template with parameter symbols, e.g.
"exp(-a*x)".func (Callable) – Function
(X, **params) -> arrayof shape(n_samples,).param_bounds (dict) –
{param_name: (lower, upper)}bounds for optimisation.initial_params (dict) –
{param_name: initial_value}midpoint (or geometric mean) guesses.log_scale (bool) – If True, optimise in log-space (useful when the parameter spans orders of magnitude).
resolved_params (dict or None) – Optimised parameter values, set after fitting.
- basis_index: int#
- name: str#
- func: Callable#
- param_bounds: dict[str, tuple[float, float]]#
- initial_params: dict[str, float]#
- log_scale: bool = False#
- resolved_params: dict[str, float] | None = None#
- class jaxsr.basis.BasisLibrary(n_features: int, feature_names: list[str] | None = None, feature_bounds: list[tuple[float, float]] | None = None, feature_types: list[str] | None = None, categories: dict[int, list] | None = None)#
Bases:
objectLibrary of candidate basis functions for symbolic regression.
Generates basis functions up to specified complexity from input features. Supports method chaining for convenient library construction.
- Parameters:
n_features (int) – Number of input features.
feature_names (list of str, optional) – Names for each feature. Defaults to [“x0”, “x1”, …].
feature_bounds (list of tuple, optional) – Bounds (lower, upper) for each feature. Used for constraint-aware basis generation and adaptive sampling.
Examples
>>> library = (BasisLibrary(n_features=2, feature_names=["x", "y"]) ... .add_constant() ... .add_linear() ... .add_polynomials(max_degree=3) ... .add_interactions(max_order=2) ... .add_transcendental(["log", "exp"]) ... ) >>> Phi = library.evaluate(X)
- property continuous_indices: list[int]#
Indices of continuous features.
- property categorical_indices: list[int]#
Indices of categorical features.
- add_constant() BasisLibrary#
Add constant (intercept) term.
- add_linear() BasisLibrary#
Add linear terms: x_i for each continuous feature.
- add_polynomials(max_degree: int = 2) BasisLibrary#
Add polynomial terms: x_i^d for d in 2..max_degree.
- Parameters:
max_degree (int) – Maximum polynomial degree (default 2).
- add_interactions(max_order: int = 2) BasisLibrary#
Add interaction terms: products of distinct features.
- Parameters:
max_order (int) – Maximum interaction order (default 2 for pairwise).
- add_transcendental(funcs: list[str] | None = None, safe: bool = True) BasisLibrary#
Add transcendental terms: log, exp, sqrt, inv, sin, cos.
- Parameters:
funcs (list of str, optional) – Which functions to include. Defaults to [“log”, “exp”, “sqrt”, “inv”].
safe (bool) – If True, use safe versions that return NaN for invalid inputs.
- add_ratios(safe: bool = True) BasisLibrary#
Add ratio terms: x_i / x_j for all distinct pairs.
- Parameters:
safe (bool) – If True, use safe division that returns NaN for zero denominator.
- add_custom(name: str, func: Callable[[Array], Array], complexity: int = 3, feature_indices: tuple[int, ...] | None = None) BasisLibrary#
Add a custom basis function.
- Parameters:
name (str) – Human-readable name.
func (callable) – Function that takes X of shape (n_samples, n_features) and returns array of shape (n_samples,).
complexity (int) – Complexity score for Pareto optimization.
feature_indices (tuple of int, optional) – Indices of features used by this function.
Notes
Custom functions cannot be serialized automatically. The library can still be saved, but the custom function will need to be re-added after loading.
- add_block(library: BasisLibrary, multiply_by: str | int | None = None, block_name: str | None = None, complexity_offset: int = 0, feature_map: dict[str, str] | None = None) BasisLibrary#
Add every function of another library, optionally times a data column.
This builds design-matrix blocks of the form \(\Theta(a) \odot b\), where
\Thetais a basis over one (or a few) variables andbis another column of the data – typically a measured or estimated derivative. A coefficient selected in such a block is then literally a term of the unknown coefficient function multiplyingb.- Parameters:
library (BasisLibrary) – Source library. Its features are matched to this library’s features by name (see
feature_map); its basis functions are copied, not shared, so the source stays reusable across blocks.multiply_by (str or int, optional) – Feature of this library (name or index) to multiply every function of the block by. If None, the block is added unmultiplied.
block_name (str, optional) – Label recorded on every function of the block. Blocks are reported by
blocksand can be selected withfilter_by_block()or dropped withwithout_blocks().complexity_offset (int) – Added to every inherited complexity score. Multiplying by a column already costs 1 on its own.
feature_map (dict, optional) –
{source_feature_name: target_feature_name}for source features whose names differ from this library’s. Unlisted features are matched by name.
- Returns:
self – For method chaining.
- Return type:
- Raises:
ValueError – If the source library is empty, a source feature has no counterpart in this library, or
multiply_bynames an unknown feature.TypeError – If
libraryis not a BasisLibrary, ormultiply_byis neither a feature name nor an index.
Notes
Names are generated as
"<basis>*<column>", with the constant term collapsing to just"<column>". Parametric basis functions are carried over as parametric: their bounds, log-scale flag and name template pass through unchanged, so profile-likelihood optimisation still applies inside the block.Like custom functions, block functions cannot be deserialized – the library config saves, but the block must be re-added after loading.
Examples
>>> theta = (BasisLibrary(n_features=1, feature_names=["q"]) ... .add_constant().add_linear().add_polynomials(max_degree=2)) >>> library = (BasisLibrary(n_features=2, feature_names=["q", "y_x"]) ... .add_block(theta, multiply_by="y_x", block_name="horizontal") ... .add_block(theta, block_name="vertical")) >>> library.names[:4] ['y_x', 'q*y_x', 'q^2*y_x', '1'] >>> sorted(library.blocks) ['horizontal', 'vertical']
- property blocks: dict[str, list[int]]#
Mapping from block label to the indices of that block’s functions.
- filter_by_block(include: str | list[str] | None = None, exclude: str | list[str] | None = None) list[int]#
Get indices of basis functions by block membership.
- Parameters:
include (str or list of str, optional) – Only keep functions in these blocks. Unlabelled functions are dropped when this is given.
exclude (str or list of str, optional) – Drop functions in these blocks.
- Returns:
indices – Indices of basis functions meeting the criteria.
- Return type:
list of int
- Raises:
ValueError – If a named block is not present in the library.
- without_blocks(*block_names: str) BasisLibrary#
Return a copy of this library with whole blocks removed.
Dropping a block and refitting is the first diagnostic for a structured library: it answers whether the block earned its place at all.
- Parameters:
*block_names (str) – Labels of the blocks to drop.
- Returns:
library – New library holding copies of the remaining basis functions, with parametric bookkeeping re-indexed. The original is unchanged.
- Return type:
- Raises:
ValueError – If a named block is not present in the library.
Examples
>>> reduced = library.without_blocks("vertical")
- add_categorical_indicators(features: list[int] | None = None) BasisLibrary#
Add indicator (dummy variable) basis functions for categorical features.
For each categorical feature with K categories, adds K-1 indicator functions using reference encoding (first category dropped) to avoid multicollinearity with the intercept.
- Parameters:
features (list of int, optional) – Indices of categorical features to encode. Defaults to all categorical features.
- Returns:
self – For method chaining.
- Return type:
- Raises:
ValueError – If a specified feature is not categorical.
- add_categorical_interactions(cat_features: list[int] | None = None, cont_features: list[int] | None = None) BasisLibrary#
Add interactions between categorical indicators and continuous features.
For each (categorical feature, continuous feature) pair, creates indicator * continuous terms. This allows the model to learn different slopes per category.
- Parameters:
cat_features (list of int, optional) – Categorical feature indices. Defaults to all categorical.
cont_features (list of int, optional) – Continuous feature indices. Defaults to all continuous.
- Returns:
self – For method chaining.
- Return type:
- property has_parametric: bool#
Whether the library contains parametric basis functions.
- add_parametric(name: str, func: Callable, param_bounds: dict[str, tuple[float, float]], complexity: int = 3, feature_indices: tuple[int, ...] | None = None, log_scale: bool = False) BasisLibrary#
Add a parametric basis function with free nonlinear parameters.
The nonlinear parameter(s) are optimised via profile likelihood during model selection: for each candidate value the linear coefficients are solved exactly via OLS.
- Parameters:
name (str) – Name with parameter symbols, e.g.
"exp(-a*x)".func (callable) –
func(X, **params) -> arrayof shape(n_samples,).param_bounds (dict) –
{param_name: (lower, upper)}search bounds for each parameter.complexity (int) – Complexity score for Pareto optimisation.
feature_indices (tuple of int, optional) – Indices of input features used by this function.
log_scale (bool) – If True, search in log-space (useful for parameters that span orders of magnitude).
- Returns:
self – For method chaining.
- Return type:
Examples
>>> library.add_parametric( ... name="exp(-a*x)", ... func=lambda X, a: jnp.exp(-a * X[:, 0]), ... param_bounds={"a": (0.01, 10.0)}, ... feature_indices=(0,), ... )
- add_polynomial_interactions(max_total_degree: int = 3, max_individual_degree: int = 2) BasisLibrary#
Add polynomial terms with mixed powers: x_i^a * x_j^b * …
- Parameters:
max_total_degree (int) – Maximum sum of all exponents (default 3).
max_individual_degree (int) – Maximum exponent for any single variable (default 2).
- build_default(max_poly_degree: int = 3, include_transcendental: bool = True, include_ratios: bool = False) BasisLibrary#
Build a default library similar to ALAMO’s standard set.
- Parameters:
max_poly_degree (int) – Maximum polynomial degree.
include_transcendental (bool) – Whether to include transcendental functions.
include_ratios (bool) – Whether to include ratio terms.
- add_compositions(outer_funcs: list[str] | None = None, inner_forms: list[str] | None = None) BasisLibrary#
Add compositions: outer_func(inner_form).
Creates functions like log(x*y), exp(x/y), sqrt(x+y), etc.
- Parameters:
outer_funcs (list of str, optional) – Outer functions to apply. Defaults to [“log”, “exp”, “sqrt”].
inner_forms (list of str, optional) – Inner forms: “product”, “ratio”, “sum”, “diff”. Defaults to [“product”, “ratio”].
Examples
>>> library.add_compositions(["log", "exp"], ["product", "ratio"]) # Adds: log(x*y), log(x/y), exp(x*y), exp(x/y), etc.
- add_rational_forms(numerator_degree: int = 1, denominator_degree: int = 1) BasisLibrary#
Add rational function templates: (a + b*x) / (1 + c*y).
Common in chemical kinetics (Langmuir-Hinshelwood, Michaelis-Menten).
- Parameters:
numerator_degree (int) – Max polynomial degree in numerator.
denominator_degree (int) – Max polynomial degree in denominator.
Examples
>>> library.add_rational_forms() # Adds: x/(1+y), x*y/(1+x), x/(1+x+y), etc.
- add_power_laws(exponents: list[float] | None = None) BasisLibrary#
Add power law terms with fractional exponents.
Common in empirical correlations (Nu = Re^0.8 * Pr^0.33).
- Parameters:
exponents (list of float, optional) – Exponents to use. Defaults to [0.25, 0.33, 0.5, 0.67, 0.75, 1.5, 2.0].
- expand_sisso_style(operations: list[str] | None = None, max_depth: int = 2) BasisLibrary#
SISSO-style recursive feature expansion.
Iteratively applies operations to existing features to create new composite features.
- Parameters:
operations (list of str, optional) – Operations to apply: “add”, “sub”, “mul”, “div”, “exp”, “log”, “sqrt”, “sq”, “inv”. Defaults to [“mul”, “div”, “sq”, “sqrt”].
max_depth (int) – Maximum recursion depth.
Notes
This can create a very large library. Use with caution.
- evaluate(X: Array) Array#
Evaluate all basis functions on input data.
- Parameters:
X (jnp.ndarray) – Input array of shape (n_samples, n_features).
- Returns:
Phi – Design matrix of shape (n_samples, n_basis).
- Return type:
jnp.ndarray
- evaluate_subset(X: Array, indices: list[int] | Array) Array#
Evaluate a subset of basis functions.
- Parameters:
X (jnp.ndarray) – Input array of shape (n_samples, n_features).
indices (array-like) – Indices of basis functions to evaluate.
- Returns:
Phi – Design matrix of shape (n_samples, len(indices)).
- Return type:
jnp.ndarray
- property names: list[str]#
List of basis function names.
- property canonical_names: list[str]#
Basis function names with parametric parameters left as symbols.
Identical to
namesexcept for parametric basis functions, which keep the template they were registered with ("exp(-a*x)") instead of the fitted rendering ("exp(-0.4913*x)").- Returns:
names – One name per basis function, in library order.
- Return type:
list of str
- canonical_name(index: int) str#
Name identifying a basis function independently of any fitted values.
Fitting a parametric basis rewrites its name to embed the optimised parameter value, so
namesis not a stable identity across refits:"exp(-a*x)"becomes"exp(-0.4913*x)", and a different value on the next fit. Anything that aggregates across refits (bootstrap stability, selection frequencies) must key on this name instead.- Parameters:
index (int) – Index of the basis function in the library.
- Returns:
name – The registered template name for a parametric basis function, otherwise the ordinary basis function name.
- Return type:
str
- Raises:
IndexError – If
indexis out of range for the library.
- copy() BasisLibrary#
Return an independent copy of this library.
Basis function callables are shared (they are stateless), but every piece of mutable metadata is duplicated. Fitting a model on a parametric library rewrites basis names and rebinds evaluation closures in place, so a repeated-refit procedure (bootstrap, cross-validation) must fit on a copy or it will leave the caller’s library — and therefore the caller’s fitted model — pinned to the last refit’s parameter values.
- Returns:
library – A new library holding the same basis functions in the same order.
- Return type:
- property complexities: Array#
Array of complexity scores.
- summary() str#
Return a summary of the library contents.
- to_dict() dict[str, Any]#
Serialize library configuration to dictionary.
Notes
Custom functions are not fully serializable. The library config is saved, but custom functions will need to be re-added manually after loading.
- classmethod from_dict(config: dict[str, Any]) BasisLibrary#
Reconstruct library from configuration dictionary.
- Parameters:
config (dict) – Configuration from to_dict().
- Returns:
library – Reconstructed library. Custom functions will raise an error and need to be re-added manually.
- Return type:
- save(filepath: str) None#
Save library configuration to JSON file.
- Parameters:
filepath (str) – Path to save the configuration.
- classmethod load(filepath: str) BasisLibrary#
Load library from JSON file.
- Parameters:
filepath (str) – Path to the configuration file.
- filter_by_complexity(max_complexity: int | None = None, min_complexity: int | None = None) list[int]#
Get indices of basis functions within complexity bounds.
- Parameters:
max_complexity (int, optional) – Maximum allowed complexity.
min_complexity (int, optional) – Minimum required complexity.
- Returns:
indices – Indices of basis functions meeting the criteria.
- Return type:
list of int
- filter_by_features(required_features: list[int] | None = None, excluded_features: list[int] | None = None) list[int]#
Get indices of basis functions using specified features.
- Parameters:
required_features (list of int, optional) – Only include basis functions using these features.
excluded_features (list of int, optional) – Exclude basis functions using these features.
- Returns:
indices – Indices of basis functions meeting the criteria.
- Return type:
list of int