Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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.10", "3.11", "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"
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,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 +215,4 @@ cython_debug/
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
docs/jupyter_execute/
120 changes: 86 additions & 34 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,55 @@ 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
(support,) = rxmc.covariance.stacked_supports([obs])
constraint = rxmc.constraint.Constraint(
[obs], model, extra_terms=obs.systematic_terms(support)
)
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(support)` passed to `Constraint(extra_terms=...)`.


## Installation

Expand All @@ -32,20 +77,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 +126,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,31 +153,45 @@ for:

### `Observation`

Represents measured data and its covariance structure, including:

- statistical errors,
- systematic normalization errors,
- systematic offset errors,
- or fixed covariance matrices.
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(support)` turns the
metadata into explicit covariance terms when you ask.

### `PhysicalModel`

Maps model parameters to predicted observables for a given `Observation`.
`ScaledModel` / `PerObservationScaledModel` wrap any model with latent
normalization parameters (Kennedy–O'Hagan style).

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

Every uncertainty beyond the statistical diagonal is an explicit additive
contribution to the constraint's stacked covariance. Factory helpers cover the
common modes:

- `normalization_term` / `offset_term` — correlated systematics, fixed
magnitude or free nuisance,
- `noise_term` / `noise_fraction_term` — unknown statistical noise,
- `model_error_term` — uncorrelated model error,
- `discrepancy_term` — Gaussian-process model discrepancy using sklearn
kernels.

### `LikelihoodModel`
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.

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

- 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.
`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`

Expand All @@ -158,10 +204,16 @@ 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
71 changes: 57 additions & 14 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,72 @@ Core building blocks
rxmc.constraint.Constraint
rxmc.evidence.Evidence
rxmc.observation.Observation
rxmc.observation.FixedCovarianceObservation
rxmc.params.Parameter
rxmc.physical_model.PhysicalModel
rxmc.physical_model.Polynomial
rxmc.physical_model.ScaledModel
rxmc.physical_model.PerObservationScaledModel

Likelihood models
-----------------
Covariance terms
----------------

The stacked covariance of a :class:`~rxmc.constraint.Constraint` is assembled
additively from :class:`~rxmc.covariance.Term` objects. The factory helpers
are the primary authoring API; the term primitives underneath are available for
custom modes. Context-dependent terms and basis callables receive a
``StackContext`` bundling the stacked ``x``/``y``/``ym`` arrays and block
supports (see the :mod:`rxmc.covariance` module docstring).

.. autosummary::
:toctree: generated/
:nosignatures:

rxmc.covariance.statistical_term
rxmc.covariance.normalization_term
rxmc.covariance.offset_term
rxmc.covariance.noise_term
rxmc.covariance.noise_fraction_term
rxmc.covariance.model_error_term
rxmc.covariance.discrepancy_term
rxmc.covariance.stacked_supports
rxmc.covariance.Term
rxmc.covariance.DenseTerm
rxmc.covariance.DiagonalTerm
rxmc.covariance.RankOneTerm
rxmc.covariance.KernelTerm
rxmc.covariance.ConstraintCovariance

Likelihood functionals
----------------------

Thin functionals of the pre-computed Mahalanobis statistics
``(d2, logdet, n)``; all covariance modeling lives on the
:class:`~rxmc.covariance.ConstraintCovariance`.

.. autosummary::
:toctree: generated/
:nosignatures:

rxmc.likelihood_model.Likelihood
rxmc.likelihood_model.GaussianLikelihood
rxmc.likelihood_model.StudentT
rxmc.likelihood_model.Chi2
rxmc.likelihood_model.mahalanobis_distance_sqr_cholesky
rxmc.likelihood_model.log_likelihood

Predictive utilities
--------------------

Posterior-predictive helpers, including Gaussian-process discrepancy
propagation.

.. autosummary::
:toctree: generated/
:nosignatures:

rxmc.likelihood_model.LikelihoodModel
rxmc.likelihood_model.FixedCovarianceLikelihood
rxmc.likelihood_model.Chi2LikelihoodModel
rxmc.likelihood_model.ParametricLikelihoodModel
rxmc.likelihood_model.UnknownNoiseErrorModel
rxmc.likelihood_model.UnknownNoiseFractionErrorModel
rxmc.likelihood_model.UnknownNormalizationModel
rxmc.likelihood_model.UnknownNormalizationErrorModel
rxmc.likelihood_model.UnknownModelError
rxmc.likelihood_model.StudentTLikelihoodModel
rxmc.correlated_discrepancy_likelihood_model.SklearnKernelGPDiscrepancyModel
rxmc.predictive.predictive_band
rxmc.predictive.gp_posterior_predictive
rxmc.predictive.total_predictive_band

Sampling
--------
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@
"scipy": ("https://docs.scipy.org/doc/scipy", None),
}

exclude_patterns = ["_build", "**.ipynb_checkpoints"]
exclude_patterns = ["_build", "jupyter_execute", "**.ipynb_checkpoints"]
Loading
Loading