Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2fc3950
Refactor covariance modeling into Term-based stacked covariance
beykyle Aug 11, 2026
73a6c09
Port and extend example notebooks for the Term API
beykyle Aug 11, 2026
ec94284
Update docs, CI, and packaging for the covariance refactor
beykyle Aug 11, 2026
3b93979
Fix EXFOR-database download race in the parallel notebooks job
beykyle Aug 11, 2026
eda0b1c
Port reaction models to the jitr 3.0 array-based workspace API
beykyle Sep 9, 2026
2621085
Add rxmc.transforms: one Transform type with derivative and composition
beykyle Sep 9, 2026
0025ccd
Deduplicate measurement kwargs in the reaction observations
beykyle Sep 9, 2026
74ec43b
Collapse covariance terms into one generic Term
beykyle Sep 9, 2026
739be7d
Add comparison-space transforms to Observation
beykyle Sep 9, 2026
57e4f04
Add point masks and held-out complements
beykyle Sep 9, 2026
b8fe476
Replace ScaledModel classes with parametric model transforms
beykyle Sep 9, 2026
6e2c3ef
Add momentum_transfer and the k property to the elastic observation
beykyle Sep 9, 2026
8c2d878
Add rxmc.model_comparison for sample-based predictive checks and evid…
beykyle Sep 9, 2026
4a123bf
Update README, design doc and API reference for the transform/mask/Te…
beykyle Sep 9, 2026
d7e0bd1
Stop tracking Sphinx autosummary stubs
beykyle Sep 9, 2026
2bc0771
Fix Constraint consuming its observations argument; harden masks
beykyle Sep 9, 2026
5368034
Validate term magnitudes, guard Term reuse, drop discrepancy_term
beykyle Sep 9, 2026
ff448c2
Fix model_comparison sample handling, jitter and model-only draws
beykyle Sep 9, 2026
75fb281
Polish transforms, model and observation edges from the review
beykyle Sep 9, 2026
b8bc7d8
Sync docs and example notebooks with the reviewed API
beykyle Sep 9, 2026
78f0083
Add ground-up design proposal and review bug list to docs
beykyle Sep 9, 2026
7380776
Fix the bugs and driver inconsistencies listed in docs/bugs_found.md
beykyle Sep 9, 2026
5fbab65
Rewrite the ground-up design as the plan of record and add the recipe…
beykyle Sep 10, 2026
f13abba
Trim four recipes and renumber the rest
beykyle Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]
python-version: ["3.12"]

steps:
- uses: actions/checkout@v4
Expand All @@ -61,12 +61,24 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e '.[validation]'

- name: Locate x4i3 database directory
run: echo "X4I3_DATA_DIR=$(python -c 'import importlib.util, pathlib; print(pathlib.Path(importlib.util.find_spec("x4i3").origin).parent / "data")')" >> "$GITHUB_ENV"

- name: Restore EXFOR database cache
uses: actions/cache@v4
with:
path: ${{ env.X4I3_DATA_DIR }}
key: exfor-db-${{ runner.os }}-${{ hashFiles('requirements.txt') }}

- name: Warm EXFOR database
run: python -c "import x4i3"

- name: Run unit tests
run: python -m pytest test

notebooks:
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 60

steps:
- uses: actions/checkout@v4
Expand All @@ -79,10 +91,25 @@ jobs:
- name: Install validation dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e '.[validation]'
python -m pip install -e '.[validation]' pytest-xdist

- name: Locate x4i3 database directory
run: echo "X4I3_DATA_DIR=$(python -c 'import importlib.util, pathlib; print(pathlib.Path(importlib.util.find_spec("x4i3").origin).parent / "data")')" >> "$GITHUB_ENV"

- name: Restore EXFOR database cache
uses: actions/cache@v4
with:
path: ${{ env.X4I3_DATA_DIR }}
key: exfor-db-${{ runner.os }}-${{ hashFiles('requirements.txt') }}

# single serial download; the concurrent notebook kernels spawned by
# xdist must find the database already in place (x4i3 downloads at
# import time outside pytest, so parallel kernels would race it)
- name: Warm EXFOR database
run: python -c "import x4i3"

- name: Run notebooks with pytest
run: python -m pytest examples
run: python -m pytest -n 4 --nbmake --nbmake-timeout=1200 examples

docs:
runs-on: ubuntu-latest
Expand All @@ -100,6 +127,18 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e '.[docs]'

- name: Locate x4i3 database directory
run: echo "X4I3_DATA_DIR=$(python -c 'import importlib.util, pathlib; print(pathlib.Path(importlib.util.find_spec("x4i3").origin).parent / "data")')" >> "$GITHUB_ENV"

- name: Restore EXFOR database cache
uses: actions/cache@v4
with:
path: ${{ env.X4I3_DATA_DIR }}
key: exfor-db-${{ runner.os }}-${{ hashFiles('requirements.txt') }}

- name: Warm EXFOR database
run: python -c "import x4i3"

- name: Build HTML docs
run: |
test -L docs/examples || ln -sf ../examples docs/examples
Expand All @@ -121,7 +160,17 @@ jobs:
python -m pip install --upgrade pip build
python -m build

- name: Install wheel smoke test
run: |
python -m pip install "$(ls -t dist/*.whl | head -n 1)"
python -c "import rxmc; import rxmc.config; import rxmc.walker"
- name: Install wheel
run: python -m pip install "$(ls -t dist/*.whl | head -n 1)"

- name: Locate x4i3 database directory
run: echo "X4I3_DATA_DIR=$(python -c 'import importlib.util, pathlib; print(pathlib.Path(importlib.util.find_spec("x4i3").origin).parent / "data")')" >> "$GITHUB_ENV"

- name: Restore EXFOR database cache
uses: actions/cache@v4
with:
path: ${{ env.X4I3_DATA_DIR }}
key: exfor-db-${{ runner.os }}-${{ hashFiles('requirements.txt') }}

- name: Wheel smoke test
run: python -c "import rxmc; import rxmc.config; import rxmc.walker"
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ examples/*.pdf

# docs
docs/_build/
docs/generated/

#slurm
*.out
Expand Down Expand Up @@ -118,7 +119,10 @@ ipython_config.py
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
uv.lock

# agent/session config
.claude/

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
Expand Down Expand Up @@ -212,3 +216,4 @@ cython_debug/
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
docs/jupyter_execute/
153 changes: 117 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# rxmc

`rxmc` is an orchestration layer for Bayesian calibration of reaction models to
large data sets with flexible likelihood modeling.
large data sets with flexible, composable covariance modeling.

It is built around two complementary workflows:

Expand All @@ -15,10 +15,63 @@ The package composes:

- curated experimental data as `Observation` objects,
- model predictions via `PhysicalModel`,
- statistical assumptions via `LikelihoodModel`,
- independent data-model pairings via `Constraint`,
- uncertainty declared as additive covariance `Term`s (statistical,
systematic, unknown-noise, and Gaussian-process discrepancy modes) via
`rxmc.covariance`,
- maximal blocks of mutually-correlated data via `Constraint`,
- and full calibration problems via `Evidence`.

## Quickstart

```python
import numpy as np
from scipy import stats
import rxmc

# measured data: pure data plus (optional) reported systematics as metadata
obs = rxmc.observation.Observation(
x=x, y=y, y_stat_err=y_err, y_sys_err_normalization=0.04
)

# a constraint owns one multivariate likelihood over its stacked observations;
# every correlated mode is an explicit covariance term - nothing is folded in
# silently. Here: the reported normalisation systematic plus an unknown
# constant noise inferred alongside the model
log_eps = rxmc.params.Parameter("log_eps")
constraint = rxmc.constraint.Constraint(
[obs],
model,
extra_terms=[*obs.systematic_terms(), rxmc.covariance.noise_term(log_eps)],
)
evidence = rxmc.evidence.Evidence([constraint])

# calibrate with the in-package Gibbs walker (or wrap in CalibrationConfig
# for emcee / dynesty)
prior = stats.multivariate_normal(mean=prior_mean, cov=prior_cov)
walker = rxmc.walker.Walker(
rxmc.param_sampling.BatchedAdaptiveMetropolisSampler(
params=model.params,
starting_location=prior.mean,
prior=prior,
initial_proposal_cov=prior.cov / 100,
),
evidence,
rng=np.random.default_rng(1),
)
walker.walk(n_steps=10_000, burnin=1_000, batch_size=1_000)
```

> **Note — behavior change from pre-0.1 versions:** an `Observation`'s
> reported systematic errors are never folded into the covariance
> automatically. The default constraint covariance is the statistical diagonal
> only; systematics enter explicitly, e.g. via
> `obs.systematic_terms()` passed to `Constraint(extra_terms=...)`.

> **Note — jitr:** this version requires
> [jitr](https://github.com/beykyle/lagrange-rmatrix) ≥ 3.0 (workspaces take
> potential *arrays* on `ws.radial_grid()`); `requirements.txt` pins
> `jitr>=3.0` from PyPI. Python ≥ 3.12.


## Installation

Expand All @@ -32,20 +85,6 @@ pip install -ve .

It is strongly recommended to use an isolated environment.

### `conda` / `mamba`

```bash
conda env create -f environment.yml
conda activate rxmc
pip install -ve . --no-deps
```

```bash
mamba env create -f environment.yml
mamba activate rxmc
pip install -ve . --no-deps
```

### `venv`

```bash
Expand Down Expand Up @@ -95,7 +134,8 @@ Typical flow:

1. Build `Observation` objects from your measurements.
2. Define a `PhysicalModel`.
3. Choose a `LikelihoodModel`.
3. Declare correlated uncertainty as covariance `Term`s (and pick a
likelihood functional: Gaussian, Student-t, or chi-squared).
4. Combine them into `Constraint` objects and then `Evidence`.
5. Wrap the problem in `ParameterConfig` and `CalibrationConfig`.
6. Hand the resulting object to an external sampler.
Expand All @@ -121,47 +161,88 @@ for:

### `Observation`

Represents measured data and its covariance structure, including:
Pure measured data — `x`, `y`, and the statistical error on `y` — plus the
measurement's reported systematic magnitudes retained as inert metadata
(`y_sys_err_normalization`, `y_sys_err_offset`). It contributes only its
statistical diagonal by default; `obs.systematic_terms()` turns the
metadata into explicit covariance terms when you ask.

- statistical errors,
- systematic normalization errors,
- systematic offset errors,
- or fixed covariance matrices.
An observation also owns its **comparison space**: `Observation(x, y,
transform=rxmc.transforms.log)` takes raw `y`, compares in log space (errors
propagated by the delta method) and the constraint transforms the model
prediction to match. A point-level `mask` (or `obs.masked_where(...)`) selects
which points enter a likelihood — fit/held-out splits without rebuilding
anything.

### `PhysicalModel`

Maps model parameters to predicted observables for a given `Observation`.

### `LikelihoodModel`

Encodes how predictions are compared to observations. Built-in options include:

- Gaussian covariance-based likelihoods,
- unknown noise models,
- unknown normalization / normalization-error models,
- unknown model-error terms,
- Student-t likelihoods,
- and a Gaussian-process discrepancy model using sklearn kernels.
A parametric `transform=` (e.g. `rxmc.transforms.scale()` or
`per_observation_scaling(observations)`) adds latent normalization parameters
(Kennedy–O'Hagan style) to any model.

### Covariance `Term`s (`rxmc.covariance`)

Every uncertainty beyond the statistical diagonal is an explicit additive
contribution to the constraint's stacked covariance. There is one generic
`Term(fn, params, kind=...)` — `fn` is a numpy-style callable of the term's
local `x`/`y`/`ym` and its parameters, `kind` is `"diag"`, `"mode"` or
`"matrix"`, and an optional `coords` transform changes the coordinate the term
lives in. Factory helpers cover the common modes in one line:

- `normalization_term` / `offset_term` / `systematic_term` — correlated
modes, fixed magnitude or free nuisance, prediction-, unit- or user-basis
scaled,
- `noise_term` / `noise_fraction_term` — unknown statistical noise (with an
optional parametric basis, e.g. noise growing with angle),
- `model_error_term` — uncorrelated model error,
- `kernel_term` — Gaussian-process model discrepancy using sklearn kernels,
optionally in transformed coordinates and with a parametric amplitude.

A term whose support spans several observations *couples* them (correlated
datasets); referencing the same `Parameter` object in two terms *shares* one
sampled value between them. `support=None` (the default) means the whole
constraint.

### Likelihood functionals

`GaussianLikelihood` (default), `StudentT` (heavy-tailed, with a
degrees-of-freedom parameter), and `Chi2` are thin functionals over the same
stacked covariance.

### `Constraint`

Pairs observations, a physical model, and a likelihood model.
The maximal block of mutually-correlated data: observations, a physical model,
a covariance assembled from terms, and a likelihood functional.

### `Evidence`

Aggregates multiple independent constraints that share the same physical-model
parameterization.

### Model comparison (`rxmc.model_comparison`)

Sampler-agnostic posterior-predictive draws, coverage/sharpness checks,
held-out scoring on `constraint.complement()`, and log-evidence bookkeeping
(`logz_summary`, `compare_logz`, `log_jacobian` for comparing fits done in
different comparison spaces).

## Examples and tutorials

The `examples/` directory contains richer notebooks and demos. The most useful
entry points are:

- `examples/linear_calibration_demo.ipynb` for the basic workflow,
- `examples/systematic_err_demo.ipynb` for likelihood comparisons and
- `examples/systematic_err_demo.ipynb` for the error-model catalog and
systematic-error handling,
- `examples/measurement_to_calibration.ipynb` for the EXFOR-measurement →
calibration path (units, retained systematics, guardrails),
- `examples/30s_optical_potential_calibration.ipynb` for a realistic optical
potential calibration example,
- `examples/correlated_observations.ipynb` for correlated datasets and shared
systematics (including across cross-section experiments),
- `examples/gp_discrepancy.ipynb` for Gaussian-process model discrepancy,
- `examples/robust_likelihoods.ipynb` for Student-t vs Gaussian likelihoods,
- `examples/normalization_inference.ipynb` for normalization-focused modeling,
- `examples/sampling_algos.ipynb` for sampling comparisons.

Expand Down
Loading
Loading