From 3ce915f490419d4b144bc0727b65d42b48b99af5 Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 13:54:28 -0400 Subject: [PATCH 1/8] fix docstrings --- src/rxmc/ias_pn_model.py | 4 +- src/rxmc/ias_pn_observation.py | 2 +- src/rxmc/likelihood_model.py | 140 +++++++++++++++++++-------------- src/rxmc/observation.py | 2 +- 4 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/rxmc/ias_pn_model.py b/src/rxmc/ias_pn_model.py index 2aab3d9..43daeea 100644 --- a/src/rxmc/ias_pn_model.py +++ b/src/rxmc/ias_pn_model.py @@ -29,8 +29,8 @@ class IsobaricAnalogPNXSModel(PhysicalModel): Each potential takes in an arbitrary tuple of params, which are calculated from the model parameters via the `calculate_params` function. - The `calculate_params` function should have the signature: - (ws: jitr.xs.quasielastic_pn.Workspace, *params: tuple) -> tuple + The ``calculate_params`` function should have the signature: + ``(ws: jitr.xs.quasielastic_pn.Workspace, *params: tuple) -> tuple`` and return a tuple of five elements, each being a tuple of parameters to be passed to the corresponding potential function in the order listed above. """ diff --git a/src/rxmc/ias_pn_observation.py b/src/rxmc/ias_pn_observation.py index 1dc223d..c87f245 100644 --- a/src/rxmc/ias_pn_observation.py +++ b/src/rxmc/ias_pn_observation.py @@ -55,7 +55,7 @@ def __init__( """ Initialize a Observation instance for the (p,n) IAS reaction. - Parameters: + Parameters ---------- x : np.ndarray Measured angle grid in degrees. diff --git a/src/rxmc/likelihood_model.py b/src/rxmc/likelihood_model.py index c6982e8..6344e13 100644 --- a/src/rxmc/likelihood_model.py +++ b/src/rxmc/likelihood_model.py @@ -33,19 +33,23 @@ class LikelihoodModel: to a PhysicalModel. The default behavior uses the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where $sigma^2_{i}^{stat}$ is the statistical variance of the i-th observation, (`observation.statistical_covariance`) and $\gamma$ is the fractional uncorrelated error (`self.frac_err`). Here, $Sigma_{ij}^{sys}$ is the systematic covariance matrix: - \[ - \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, - \] + + .. math:: + + \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, + where $\eta$ is the uncertainty in the overall normalization of the observation (`observation.y_sys_err_normalization`) and $\omega$ is the uncertainty in the additive normalization to the observation @@ -87,19 +91,23 @@ def covariance(self, observation: Observation, ym: np.ndarray): override this. Returns the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where $sigma^2_{i}^{stat}$ is the statistical variance of the i-th observation, (`observation.statistical_covariance`) and $\gamma$ is the fractional uncorrelated error (`self.frac_err`). Here, $Sigma_{ij}^{sys}$ is the systematic covariance matrix: - \[ - \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, - \] + + .. math:: + + \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, + where $\eta$ is the uncertainty in the overall normalization of the observation (`observation.y_sys_err_normalization`) and $\omega$ is the uncertainty in the additive normalization to the observation @@ -396,9 +404,9 @@ class UnknownNoiseErrorModel(ParametricLikelihoodModel): No matter the Observation, the statistical contribution to the covariance thus always takes the form: - \[ + .. math:: + \Sigma_{ij}^{stat} = \epsilon^2 \delta_{ij} - \] where $\epsilon$ is the statistical `noise` parameter. """ @@ -416,18 +424,20 @@ def __init__(self): def covariance(self, observation: Observation, ym: np.ndarray, log_epsilon: float): r""" Returns the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where sigma^2_{i}^{stat} is the statistical variance of the i-th observation, which is dependent on the parameter $\epsilon$, which is the statistical `noise`: - \[ + .. math:: + \sigma^2_{i}^{stat} = \epsilon^2 \delta_{ij} - \] (note this class ignores `observation.statistical_covariance`, replacing it with $\epsilon$). @@ -463,9 +473,9 @@ class UnknownNoiseFractionErrorModel(ParametricLikelihoodModel): This implies the statistical contribution to the covariance takes the form: - \[ + .. math:: + \Sigma_{ij}^{stat} = \epsilon^2 y(x_i)^2 \delta_{ij} - \] """ @@ -484,18 +494,20 @@ def __init__(self): def covariance(self, observation: Observation, ym: np.ndarray, log_epsilon: float): r""" Returns the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where sigma^2_{i}^{stat} is the statistical variance of the i-th observation, which is dependent on the parameter $\epsilon$, which is the statistical `noise_fraction`: - \[ + .. math:: + \Sigma_{ij}^{stat} = \epsilon^2 y(x_i)^2 \delta_{ij} - \] (note this class ignores `observation.statistical_covariance`, substituting it with the variable `noise_fraction` multiplied by `ym`) and $\gamma$ is the @@ -532,9 +544,10 @@ class UnknownNormalizationModel(ParametricLikelihoodModel): the normalization, as the data are explicitly re-normalized. This corresponds to a statistical model: - \[ - y_i + \epsilon_i = \rho y_m(x_i, \alpha) + \epsilon_i + \dots - \] + + .. math:: + + y_i + \epsilon_i = \rho y_m(x_i, \alpha) + \epsilon_i + \dots where $\rho$ is a free parameter corresponding to the multiplicative normalization factor. This corresponds to the Kennedy & O'Hagan @@ -564,9 +577,11 @@ def __init__(self): def covariance(self, observation: Observation, ym: np.ndarray, log_rho: float): r""" Returns the statistical covariance matrix: - \[ - \Sigma_{ij}^{stat} = \sigma^2_{i}^{stat} \delta_{ij} - \] + + .. math:: + + \Sigma_{ij}^{stat} = \sigma^2_{i}^{stat} \delta_{ij} + where $\sigma^2_{i}^{stat}$ is the statistical variance of the i-th observation, (`observation.statistical_covariance`). @@ -590,7 +605,7 @@ def residual(self, observation: Observation, ym: np.ndarray, log_rho: float): Returns the residual between the renormalized model prediction ym and observation.y - Parameters: + Parameters ---------- observation : Observation The observation object containing the observed data. @@ -665,9 +680,9 @@ class UnknownNormalizationErrorModel(ParametricLikelihoodModel): This implies the systematic normalization contribution to the covariance takes the form: - \[ - \Sigma_{ij}^{sys norm} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) - \] + .. math:: + + \Sigma_{ij}^{sys norm} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) where $\eta$ is a free parameter. """ @@ -686,19 +701,23 @@ def __init__(self): def covariance(self, observation: Observation, ym: np.ndarray, log_eta: float): r""" Returns the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where $sigma^2_{i}^{stat}$ is the statistical variance of the i-th observation, (`observation.statistical_covariance`) and $\gamma$ is the fractional uncorrelated error (`self.frac_err`). Here, $Sigma_{ij}^{sys}$ is the systematic covariance matrix: - \[ - \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, - \] + + .. math:: + + \Sigma_{ij}^{sys} = \eta**2 y_m(x_i, \alpha) y_m(x_j, \alpha) + \omega, + where $\eta$ is the uncertainty in the overall normalization of the observation (`y_sys_err_normalization` - in this case, this value is a parameter, and corresponding value in `observation` is ignored) and $\omega$ is @@ -737,9 +756,9 @@ class UnknownModelError(ParametricLikelihoodModel): is a free parameter $\gamma$, such that the covariance due to the uncorrelated model error takes the form: - \[ + .. math:: + \Sigma_{ij}^{uncorrelated} = \gamma^2 y_m(x_i, \alpha)^2 \delta_{ij} - \] where $\gamma$ is a free parameter. @@ -750,7 +769,6 @@ def __init__(self, averaging=True): """ Initializes the UnknownModelError instance. - Parameters Parameters ---------- averaging : bool, optional @@ -777,11 +795,13 @@ def covariance( override this. Returns the following covariance matrix: - \[ - \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} - + \Sigma_{ij}^{sys} - + \gamma^2 y_m^2(x_i, \alpha) - \] + + .. math:: + + \Sigma_{ij} = \sigma^2_{i}^{stat} \delta_{ij} + + \Sigma_{ij}^{sys} + + \gamma^2 y_m^2(x_i, \alpha) + where $\gamma$ is the fractional uncorrelated error (`frac_err`), treated here as a free parameter, and all other definitions are the same as `LikelihoodModel.covariance` diff --git a/src/rxmc/observation.py b/src/rxmc/observation.py index 2812e75..eb49756 100644 --- a/src/rxmc/observation.py +++ b/src/rxmc/observation.py @@ -10,7 +10,7 @@ class Observation: and offset of all or some of the data points of the the dependent variable y. - Attributes: + Attributes ---------- x : np.ndarray The independent variable data. From 21f782f099281acc48e1bb07fe847f2fb37c92da Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 13:54:46 -0400 Subject: [PATCH 2/8] keep generated files --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 5d1d4f4..5376c16 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,4 +62,4 @@ "scipy": ("https://docs.scipy.org/doc/scipy", None), } -exclude_patterns = ["_build", "**.ipynb_checkpoints", "generated"] +exclude_patterns = ["_build", "**.ipynb_checkpoints"] From 8136a509a671e674b89365515afac78e67e71916 Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 13:55:05 -0400 Subject: [PATCH 3/8] fix header formatting --- examples/sampling_algos.ipynb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/sampling_algos.ipynb b/examples/sampling_algos.ipynb index 9cf7b95..1e0ea57 100644 --- a/examples/sampling_algos.ipynb +++ b/examples/sampling_algos.ipynb @@ -267,9 +267,7 @@ "cell_type": "markdown", "id": "de01662a-532d-40ae-b284-232a64e29f82", "metadata": {}, - "source": [ - "### Sampling configurations for the model parameters" - ] + "source": "## Sampling configurations for the model parameters" }, { "cell_type": "code", @@ -311,9 +309,7 @@ "cell_type": "markdown", "id": "f9852933-dad1-484e-89e8-b13e19e8d166", "metadata": {}, - "source": [ - "### Sampling configurations for the likelihood parameters" - ] + "source": "## Sampling configurations for the likelihood parameters" }, { "cell_type": "code", @@ -888,4 +884,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From fe8e31ee63e637de69c542a8e50ba1a55f498a9e Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 13:55:25 -0400 Subject: [PATCH 4/8] update to correct numpy version reqs --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 54c6907..6bc134a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ scikit-learn>=1.0.0 -numpy<=2.3 +numpy>=2.2.6 pandas>=2.2 scipy>=1.15 pint>=0.2 From 6f8b881aad6875a9110cd24471784ed0f6a9b03e Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 14:30:27 -0400 Subject: [PATCH 5/8] safe numpy squeezing --- src/rxmc/adaptive_metropolis.py | 4 ++-- src/rxmc/metropolis_hastings.py | 4 ++-- src/rxmc/walker.py | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rxmc/adaptive_metropolis.py b/src/rxmc/adaptive_metropolis.py index a63a573..04535af 100644 --- a/src/rxmc/adaptive_metropolis.py +++ b/src/rxmc/adaptive_metropolis.py @@ -76,7 +76,7 @@ def adaptive_metropolis( accepted = 0 x = x0.copy() - logp = log_posterior(x) + logp = float(log_posterior(x)) scale = 2.38**2 / dim for i in range(start, start + n_steps): @@ -95,7 +95,7 @@ def adaptive_metropolis( chain[i, :] = x logp_chain[i - start] = logp continue - logp_new = log_posterior(x_new) + logp_new = float(log_posterior(x_new)) log_ratio = min(0, logp_new - logp) if np.log(rng.random()) < log_ratio: diff --git a/src/rxmc/metropolis_hastings.py b/src/rxmc/metropolis_hastings.py index d3d4c18..b012d24 100644 --- a/src/rxmc/metropolis_hastings.py +++ b/src/rxmc/metropolis_hastings.py @@ -50,7 +50,7 @@ def metropolis_hastings( """ chain = np.zeros((n_steps, x0.size)) logp_chain = np.zeros((n_steps,)) - logp = log_posterior(x0) + logp = float(log_posterior(x0)) accepted = 0 x = x0 for i in range(n_steps): @@ -59,7 +59,7 @@ def metropolis_hastings( chain[i, ...] = x logp_chain[i] = logp continue - logp_new = log_posterior(x_new) + logp_new = float(log_posterior(x_new)) log_ratio = min(0, logp_new - logp) xi = np.log(rng.random()) if xi < log_ratio: diff --git a/src/rxmc/walker.py b/src/rxmc/walker.py index 151e172..467c844 100644 --- a/src/rxmc/walker.py +++ b/src/rxmc/walker.py @@ -158,10 +158,12 @@ def log_prior(self, model_params, likelihood_params): float Sum of log prior densities for model and likelihood parameters. """ - return self.model_sampler.prior.logpdf(model_params) + sum( + lp = self.model_sampler.prior.logpdf(model_params) + lp += sum( lm.prior.logpdf(likelihood_params[i]) for i, lm in enumerate(self.likelihood_samplers) ) + return float(np.squeeze(lp)) def walk( self, From 6c254c70d45c30fe6a94290015aea29d71782d7f Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 14:45:31 -0400 Subject: [PATCH 6/8] add docs buiild to PR CI --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7e8226..177421a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,27 @@ jobs: - name: Run notebooks with pytest run: python -m pytest examples + docs: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + + - name: Install docs dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install -e '.[docs]' + + - name: Build HTML docs + run: | + ln -sf ../examples docs/examples + sphinx-build docs docs/_build/html -W --keep-going + build: runs-on: ubuntu-latest From f768c6f64c2c3f770cf80a12ca7d6fd370c7932f Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 15:04:57 -0400 Subject: [PATCH 7/8] safe numpy squeezing --- src/rxmc/adaptive_metropolis.py | 4 ++-- src/rxmc/metropolis_hastings.py | 4 ++-- src/rxmc/walker.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rxmc/adaptive_metropolis.py b/src/rxmc/adaptive_metropolis.py index 04535af..44862df 100644 --- a/src/rxmc/adaptive_metropolis.py +++ b/src/rxmc/adaptive_metropolis.py @@ -76,7 +76,7 @@ def adaptive_metropolis( accepted = 0 x = x0.copy() - logp = float(log_posterior(x)) + logp = float(np.squeeze(log_posterior(x))) scale = 2.38**2 / dim for i in range(start, start + n_steps): @@ -95,7 +95,7 @@ def adaptive_metropolis( chain[i, :] = x logp_chain[i - start] = logp continue - logp_new = float(log_posterior(x_new)) + logp_new = float(np.squeeze(log_posterior(x_new))) log_ratio = min(0, logp_new - logp) if np.log(rng.random()) < log_ratio: diff --git a/src/rxmc/metropolis_hastings.py b/src/rxmc/metropolis_hastings.py index b012d24..6d158a1 100644 --- a/src/rxmc/metropolis_hastings.py +++ b/src/rxmc/metropolis_hastings.py @@ -50,7 +50,7 @@ def metropolis_hastings( """ chain = np.zeros((n_steps, x0.size)) logp_chain = np.zeros((n_steps,)) - logp = float(log_posterior(x0)) + logp = float(np.squeeze(log_posterior(x0))) accepted = 0 x = x0 for i in range(n_steps): @@ -59,7 +59,7 @@ def metropolis_hastings( chain[i, ...] = x logp_chain[i] = logp continue - logp_new = float(log_posterior(x_new)) + logp_new = float(np.squeeze(log_posterior(x_new))) log_ratio = min(0, logp_new - logp) xi = np.log(rng.random()) if xi < log_ratio: diff --git a/src/rxmc/walker.py b/src/rxmc/walker.py index 467c844..e7aafee 100644 --- a/src/rxmc/walker.py +++ b/src/rxmc/walker.py @@ -128,9 +128,10 @@ def run_likelihood_batches( ym = constraint.predict(*model_params) def log_posterior_lm(x): - return sampler.prior.logpdf(x) + constraint.marginal_log_likelihood( + lp = sampler.prior.logpdf(x) + constraint.marginal_log_likelihood( ym, *np.atleast_1d(x) ) + return float(np.squeeze(lp)) x0 = starting_locations[i] sampler.sample(n_steps, x0, self.rng, log_posterior_lm, burn=burn) From b6450f68e9ab78f72a345347de7b4845577c7d95 Mon Sep 17 00:00:00 2001 From: Kyle Andrew Beyer Date: Tue, 9 Jun 2026 15:18:57 -0400 Subject: [PATCH 8/8] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 177421a..7309c18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: - name: Build HTML docs run: | - ln -sf ../examples docs/examples + test -L docs/examples || ln -sf ../examples docs/examples sphinx-build docs docs/_build/html -W --keep-going build: