JAXSR: JAX-based Symbolic Regression
JAXSR is a fully open-source symbolic regression library built on JAX that discovers interpretable algebraic expressions from data. It uses sparse optimization techniques with JAX for automatic differentiation, JIT compilation, and GPU acceleration.
Features
- Flexible Basis Library: Easily define candidate basis functions including polynomials, interactions, transcendentals, ratios, and custom functions
- Multiple Selection Strategies: Greedy forward/backward selection, exhaustive search, LASSO path screening
- Uncertainty Quantification: Classical OLS intervals, Bayesian Model Averaging, conformal prediction, bootstrap methods, and Pareto ensemble predictions
- Physical Constraints: Enforce monotonicity, bounds, convexity, and linear constraints
- Adaptive Sampling: Intelligently suggest new data points to improve model quality
- JAX-Accelerated: JIT compilation and GPU support for fast computation
- Symbolic Classification: Discover interpretable logistic models for binary and multiclass problems via IRLS + sparse selection
- Additive Symbolic Regression: Boosting-style ensembles of small symbolic expressions — fit residuals stagewise for many simple, interpretable terms (
jaxsr.additive) - Scikit-learn Compatible: Full estimator protocol (
get_params/set_params/clone) — works withcross_val_score,GridSearchCV,Pipeline - Symbolic Export: Export to SymPy, LaTeX, or pure Python/NumPy functions
Installation
pip install jaxsr
Or install from source:
git clone https://github.com/jkitchin/jaxsr.git
cd jaxsr
pip install -e ".[dev]"
Quick Start
import jax.numpy as jnp
import numpy as np
from jaxsr import BasisLibrary, SymbolicRegressor
# Generate synthetic data: y = 2.5*x0 + 1.2*x0*x1 - 0.8*x1^2 + noise
np.random.seed(42)
n_samples = 200
X = np.random.randn(n_samples, 2) * 2
y = 2.5 * X[:, 0] + 1.2 * X[:, 0] * X[:, 1] - 0.8 * X[:, 1]**2 + np.random.randn(n_samples) * 0.1
X_jax = jnp.array(X)
y_jax = jnp.array(y)
# Build basis library
library = (BasisLibrary(n_features=2, feature_names=["x0", "x1"])
.add_constant()
.add_linear()
.add_polynomials(max_degree=3)
.add_interactions(max_order=2)
)
# Fit model
model = SymbolicRegressor(
basis_library=library,
max_terms=5,
strategy="greedy_forward",
information_criterion="bic",
)
model.fit(X_jax, y_jax)
# Results
print(f"Discovered: {model.expression_}")
print(f"R² = {model.metrics_['r2']:.4f}")
# Predict
y_pred = model.predict(X_jax)
Basis Functions
JAXSR provides a flexible system for defining candidate basis functions:
import jax.numpy as jnp
from jaxsr import BasisLibrary
library = (BasisLibrary(n_features=3, feature_names=["T", "P", "C"])
.add_constant() # Intercept term
.add_linear() # T, P, C
.add_polynomials(max_degree=3) # T^2, T^3, P^2, ...
.add_interactions(max_order=2) # T*P, T*C, P*C
.add_transcendental(["log", "exp", "sqrt", "inv"]) # log(T), exp(T), ...
.add_ratios() # T/P, T/C, P/T, ...
.add_custom( # Custom functions
name="Arrhenius",
func=lambda X: jnp.exp(-X[:, 0] / X[:, 1]),
complexity=3
)
)
Selection Strategies
| Strategy | Description | Use Case |
|---|---|---|
greedy_forward |
Forward stepwise selection | Default, fast for large libraries |
greedy_backward |
Backward elimination | When starting with many terms |
exhaustive |
All combinations | Small libraries (<20 terms) |
lasso_path |
LASSO regularization path | Fast screening |
from jaxsr import SymbolicRegressor
model = SymbolicRegressor(
basis_library=library,
max_terms=5,
strategy="greedy_forward", # Selection strategy
information_criterion="bic", # or "aic", "aicc"
)
Physical Constraints
Incorporate domain knowledge through constraints:
from jaxsr import Constraints
constraints = (Constraints()
.add_bounds("y", lower=0) # Non-negative output
.add_monotonic("T", direction="increasing") # y increases with T
.add_convex("P") # Convex in P
.add_sign_constraint("T", sign="positive") # Positive coefficient
)
model = SymbolicRegressor(
basis_library=library,
constraints=constraints,
)
Adaptive Sampling
Request new data points to improve model quality:
from jaxsr import AdaptiveSampler
sampler = AdaptiveSampler(
model=model,
bounds=[(300, 500), (1, 10), (0.1, 1.0)],
strategy="uncertainty", # or "error", "leverage", "gradient"
)
# Get suggested points
result = sampler.suggest(n_points=5)
X_next = result.points # shape (5, n_features)
scores = result.scores # acquisition function values
Export Options
# Human-readable expression
print(model.expression_) # "y = 2.5*T + 1.2*T*P - 0.8*P^2"
# SymPy expression for symbolic manipulation
sympy_expr = model.to_sympy()
# LaTeX for papers
latex_str = model.to_latex()
# Pure Python/NumPy function (no JAX dependency)
predict_func = model.to_callable()
y_pred = predict_func(X_numpy)
# Save/load models
model.save("model.json")
loaded_model = SymbolicRegressor.load("model.json")
Uncertainty Quantification
JAXSR provides comprehensive UQ capabilities for linear-in-parameters models:
# Prediction intervals (classical OLS)
y_pred, lower, upper = model.predict_interval(X_new, alpha=0.05)
# Confidence band on the mean response
y_pred, conf_lo, conf_hi = model.confidence_band(X_new, alpha=0.05)
# Coefficient confidence intervals
intervals = model.coefficient_intervals(alpha=0.05)
for name, (est, lo, hi, se) in intervals.items():
print(f" {name}: {est:.4f} [{lo:.4f}, {hi:.4f}]")
# Noise standard deviation and coefficient covariance
print(f"sigma = {model.sigma_:.4f}")
cov = model.covariance_matrix_
# Bayesian Model Averaging across Pareto-front models
y_pred, lower, upper = model.predict_bma(X_new, criterion="bic")
# Distribution-free conformal prediction (jackknife+ or split)
y_pred, lower, upper = model.predict_conformal(X_new, method="jackknife+")
# Pareto front ensemble predictions
result = model.predict_ensemble(X_new)
print(f"Ensemble std: {result['y_std']}")
# Residual bootstrap (no Gaussian assumption needed)
from jaxsr import bootstrap_predict
result = bootstrap_predict(model, X_new, n_bootstrap=1000)
Symbolic Classification
JAXSR also supports interpretable classification — discover sparse logistic models that explain class boundaries:
import jax.numpy as jnp
import numpy as np
from jaxsr import BasisLibrary, SymbolicClassifier
# Generate binary classification data
np.random.seed(42)
X = np.random.randn(200, 2)
y = (X[:, 0] + 0.5 * X[:, 1] ** 2 > 0).astype(float)
# Build basis library and fit classifier
library = (BasisLibrary(n_features=2, feature_names=["x0", "x1"])
.add_constant()
.add_linear()
.add_polynomials(max_degree=3)
.add_interactions(max_order=2)
)
clf = SymbolicClassifier(basis_library=library, max_terms=4, strategy="greedy_forward")
clf.fit(jnp.array(X), jnp.array(y))
# Results
print(f"Expression: {clf.expression_}")
print(f"Accuracy: {clf.score(jnp.array(X), jnp.array(y)):.4f}")
# Probabilities and class predictions
proba = clf.predict_proba(jnp.array(X))
y_pred = clf.predict(jnp.array(X))
Multiclass problems are handled automatically via one-vs-rest (OVR), giving each class its own interpretable expression. The classifier also supports coefficient intervals, conformal prediction sets, SymPy/LaTeX export, and save/load.
Additive Symbolic Regression
jaxsr.additive fits a model as a sum of small symbolic expressions,
f(x) = c + Σ_k η_k · g_k(x) — analogous to gradient boosting, but each weak
learner is an interpretable symbolic expression rather than a decision tree.
StagewiseSymbolicRegressor repeatedly fits a small expression to the current
residual and (optionally) refits all linear coefficients by least squares:
Scope: JAXSR is a linear method over a fixed basis library — it selects a sparse combination of basis functions you supply. It excels at interpretable, robust, uncertainty-aware modeling when the right building blocks are on the menu, but it does not discover unknown compositional forms like
exp(x0*x1)orsin(2*x0)(the limit is discovery, not representation). For free-composition equation discovery, use a genetic-programming or neural symbolic-regression tool (PySR, Operon, AI-Feynman).
import numpy as np
from jaxsr.additive import StagewiseSymbolicRegressor
# Additive target: y = 2*x0 + 0.5*x1^2 + noise
rng = np.random.default_rng(0)
X = rng.uniform(-2, 2, size=(200, 2))
y = 2.0 * X[:, 0] + 0.5 * X[:, 1] ** 2 + 0.1 * rng.normal(size=200)
model = StagewiseSymbolicRegressor(
n_terms=5,
learning_rate=0.2,
max_complexity=6,
refit_coefficients=True,
early_stopping=False,
)
model.fit(X, y)
print(model) # pretty structural summary of the ensemble
print(model.expressions_) # per-term expression strings
print(model.intercept_, model.coefficients_)
y_pred = model.predict(X)
combined = model.to_expression() # single combined SymPy expression
model.save("additive_model.json") # JSON round-trip (models are not picklable)
Prefer this over a single large expression when the signal is a sum of several
simple effects: keep max_complexity small and let the ensemble accumulate many
interpretable terms. Early stopping on a validation split guards against
overfitting on noisy data.
Because each term is fit by gradient boosting (fitting the negative gradient), you can also target losses ordinary least-squares selection cannot — robust and quantile symbolic regression:
from jaxsr.additive import StagewiseSymbolicRegressor, QuantileLoss
# Robust to outliers (Huber); use refit_coefficients=False for non-squared losses
robust = StagewiseSymbolicRegressor(loss="huber", learning_rate=0.5,
refit_coefficients=False).fit(X, y)
# 90th-percentile regression (fit several quantiles to build intervals)
q90 = StagewiseSymbolicRegressor(loss=QuantileLoss(0.9), learning_rate=0.5,
refit_coefficients=False).fit(X, y)
An experimental RecursiveSymbolicRegressor grows the basis library along the
residual (composing unary functions, products, and ratios of discovered terms),
partially lifting the fixed-library ceiling — it can recover compositional
targets like exp(x0*x1) that a flat library misses.
Available losses: "squared_error" (default), "absolute_error", "huber",
"quantile". A BackfittingSymbolicRegressor (GAM-style, where a fixed set of
terms is revised rather than frozen) is also available for squared error. It is
never worse than stagewise+refit on training and genuinely helps when per-term
complexity is small and features are collinear (where greedy selection gets
stuck); it is also the foundation for a future Bayesian (BART/iBART-style)
variant.
bootstrap_additive quantifies structural uncertainty — how often each
basis function is selected across bootstrap resamples (a proxy for posterior
inclusion probability), plus a predictive ensemble whose intervals reflect
structural variability, not just coefficient noise:
from jaxsr.additive import bootstrap_additive, bootstrap_predict_additive
res = bootstrap_additive(model, X, y, n_bootstrap=100, random_state=0)
res["inclusion_probabilities"] # {basis: fraction selected}
pi = bootstrap_predict_additive(res["models"], X_new) # mean / lower / upper
See docs/guides/additive-symbolic-regression.md.
Visualization
from jaxsr.plotting import (
plot_pareto_front,
plot_parity,
plot_residuals,
plot_coefficient_path,
plot_prediction_intervals,
plot_coefficient_intervals,
plot_bma_weights,
)
# Pareto front: complexity vs accuracy
plot_pareto_front(model.pareto_front_, highlight_best=True)
# Parity plot
plot_parity(y_true, y_pred)
# Residual diagnostics
plot_residuals(model, X, y)
# Prediction intervals fan chart
plot_prediction_intervals(model, X, y)
# Coefficient confidence intervals (forest plot)
plot_coefficient_intervals(model)
# BMA model weights
plot_bma_weights(model)
Claude Code Skills
JAXSR ships with Claude Code skill files
that let an AI assistant guide you through symbolic regression workflows interactively.
The skill files live in .claude/skills/jaxsr/ (and are mirrored in src/jaxsr/skill/
for packaging).
What's included:
| Resource | Description |
|---|---|
SKILL.md |
Main skill definition — activation triggers, assistant-mode decision trees, quick-reference API and CLI cheat sheets |
guides/basis-library.md |
Choosing and building basis function libraries |
guides/model-fitting.md |
Selection strategies and information criteria |
guides/uncertainty.md |
UQ methods: OLS intervals, BMA, conformal, bootstrap |
guides/constraints.md |
Adding physical constraints (monotonicity, bounds, convexity) |
guides/doe-workflow.md |
End-to-end Design of Experiments lifecycle |
guides/active-learning.md |
Acquisition functions and adaptive sampling |
guides/rsm.md |
Response Surface Methodology designs and analysis |
guides/known-model-fitting.md |
Fitting known model forms (Langmuir, Arrhenius, etc.) |
guides/sklearn-integration.md |
Using JAXSR with sklearn (cross_val_score, GridSearchCV, Pipeline) |
guides/cli.md |
CLI reference for code-free DOE workflows |
Templates (ready-to-run starter scripts in templates/):
| Template | Use Case |
|---|---|
basic-regression.py |
Discover an equation from X, y data |
constrained-model.py |
Add physical constraints to a model |
doe-study.py |
Full DOE workflow from design to report |
uncertainty-analysis.py |
Compare all UQ methods |
active-learning-loop.py |
Iterative experiment-model loop |
langmuir-isotherm.py |
Known-model parameter estimation |
notebook-starter.py |
Jupyter notebook cell structure |
When Claude Code is available, it uses these files to provide context-aware help — recommending basis libraries, selection strategies, UQ methods, and constraint setups based on your specific problem. See the Claude Code Skills guide in the documentation for more details.
Examples
See the docs/examples/ directory for complete worked examples:
basic_usage.py: Simple polynomial fittinguncertainty_quantification.py: Prediction intervals, BMA, conformal, bootstrapchemical_kinetics.py: Discovering rate laws from kinetic dataheat_transfer.py: Heat transfer correlations
The examples/ directory also has a standalone script,
additive_symbolic_regression.py, for boosting-style additive models.
API Reference
See the documentation for full API details.
Citation
If you use JAXSR in your research, please cite:
@software{jaxsr2024,
title = {JAXSR: JAX-based Symbolic Regression},
author = {Kitchin, John},
year = {2024},
url = {https://github.com/jkitchin/jaxsr}
}
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please see our contributing guidelines and open an issue or pull request.
Metadata
Release files for jaxsr 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| jaxsr-0.3.0.tar.gz | 5.7 MB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jaxsr-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 5.9 MB
Release files / jaxsr-0.3.0.tar.gz
| Download URL | jaxsr-0.3.0.tar.gz |
|---|---|
| Size | 5.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c4854e96cc71faf95c6815ac48f1a3a018aeb23615141df318069f3469ef284d
|
|
BLAKE2b-256 checksum How to use checksums |
0ce247952142f209aa2a5246117f7d5353d117994049b9b219e4afa1f15c54c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 3, 2026.
Transparency logRelease files / jaxsr-0.3.0-py3-none-any.whl
| Download URL | jaxsr-0.3.0-py3-none-any.whl |
|---|---|
| Size | 258.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
74f4ee68d17e70dd6b2fa3666bd1948159bcc71ae66af2b8c08f22b1589f19a8
|
|
BLAKE2b-256 checksum How to use checksums |
84f71c31191dba1a766119b86f62020f88d7bf1fb7c1e0c1a196bfbc4915f259
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 3, 2026.
Transparency logSecurity for jaxsr 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for jaxsr-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.