Comparison of sampling algorithms for calibration of a line with unknown model error#
from collections import OrderedDict
import corner
import numpy as np
from matplotlib import pyplot as plt
from scipy import stats
import rxmc
Using database version X4-2025-12-31 located in: /mnt/home/beyerkyl/x4db/unpack_exfor-2025/X4-2025-12-31
true_params = OrderedDict(
[
("m", 2),
("b", 4),
]
)
rng = np.random.default_rng(43)
noise = 0.05
N = 12
# x_data = rng.random(N)
x_data = np.linspace(0, 1, N)
y_true = true_params["m"] * x_data + true_params["b"]
y_err = np.array([rng.normal(0, noise * y) for y in y_true])
y_data = y_true + y_err
# Define priors: (mean, variance) for normal, (alpha, beta) for inverse gamma
reported_stat_err = noise * np.mean(y_data)
m_prior = (2, 10)
b_prior = (1, 10)
sigma2_prior = (3, reported_stat_err / (3 - 1))
class LinearModel(rxmc.physical_model.PhysicalModel):
def __init__(self):
params = [
rxmc.params.Parameter("m", float, "no-units"),
rxmc.params.Parameter("b", float, "y-units"),
]
super().__init__(params)
def evaluate(self, observation, m, b):
return self.y(observation.x, m, b)
def y(self, x, m, b):
# useful to have a function hat takes in an array-like x
# rather than an Observation, e.g. for plotting
return m * x + b
prior_mean = OrderedDict(
[
("m", 1),
("b", 5),
]
)
prior_std_dev = OrderedDict(
[
("m", 0.5),
("b", 0.5),
]
)
covariance = np.diag(list(prior_std_dev.values())) ** 2
mean = np.array(list(prior_mean.values()))
prior_distribution = stats.multivariate_normal(mean, covariance)
my_model = LinearModel()
observation = rxmc.observation.Observation(
x=x_data,
y=y_data,
y_stat_err=y_true * noise,
)
plt.errorbar(
x_data,
y_data,
noise * y_data,
color="k",
marker="o",
linestyle="none",
label="experiment with bias",
)
plt.plot(x_data, y_true, "k", label="truth")
[<matplotlib.lines.Line2D at 0x14f21fc9ed50>]
likelihood = rxmc.likelihood_model.UnknownNoiseFractionErrorModel()
evidence = rxmc.evidence.Evidence(
parametric_constraints=[
rxmc.constraint.Constraint(
[observation],
my_model,
likelihood,
)
]
)
Sampling configurations for the model parameters#
def proposal_distribution(x, rng):
return stats.multivariate_normal.rvs(
mean=x, cov=prior_distribution.cov / 100, random_state=rng
)
metropolis_model = rxmc.param_sampling.MetropolisHastingsSampler(
params=my_model.params,
starting_location=prior_distribution.mean,
proposal=proposal_distribution,
prior=prior_distribution,
)
adaptive_model = rxmc.param_sampling.BatchedAdaptiveMetropolisSampler(
params=my_model.params,
starting_location=prior_distribution.mean,
prior=prior_distribution,
initial_proposal_cov=prior_distribution.cov / 1000,
)
Sampling configurations for the likelihood parameters#
noise_prior = stats.norm(loc=np.log(0.01), scale=1)
def proposal_distribution_log_noise(x, rng):
return np.atleast_1d(stats.norm.rvs(loc=x, scale=0.1, random_state=rng))
metropolis_likelihood = rxmc.param_sampling.MetropolisHastingsSampler(
params=likelihood.params,
starting_location=np.array(noise_prior.mean()),
proposal=proposal_distribution_log_noise,
prior=noise_prior,
)
adaptive_likelihood = rxmc.param_sampling.BatchedAdaptiveMetropolisSampler(
params=likelihood.params,
starting_location=np.array(noise_prior.mean()),
prior=noise_prior,
initial_proposal_cov=np.array([[1]]),
)
Walkers#
walker_metropolis = rxmc.walker.Walker(
metropolis_model,
evidence,
likelihood_samplers=[metropolis_likelihood],
)
walker_adaptive = rxmc.walker.Walker(
adaptive_model,
evidence,
likelihood_samplers=[adaptive_likelihood],
)
%%time
walker_adaptive.walk(n_steps=5000, burnin=1000, batch_size=100)
Burn-in batch 1/10 completed, 100 steps.
Burn-in batch 2/10 completed, 100 steps.
Burn-in batch 3/10 completed, 100 steps.
Burn-in batch 4/10 completed, 100 steps.
Burn-in batch 5/10 completed, 100 steps.
Burn-in batch 6/10 completed, 100 steps.
Burn-in batch 7/10 completed, 100 steps.
Burn-in batch 8/10 completed, 100 steps.
Burn-in batch 9/10 completed, 100 steps.
Burn-in batch 10/10 completed, 100 steps.
Batch: 1/50 completed, 100 steps.
Model parameter acceptance fraction: 0.310
Likelihood parameter acceptance fractions: [0.46]
Batch: 2/50 completed, 100 steps.
Model parameter acceptance fraction: 0.310
Likelihood parameter acceptance fractions: [0.53]
Batch: 3/50 completed, 100 steps.
Model parameter acceptance fraction: 0.650
Likelihood parameter acceptance fractions: [0.49]
Batch: 4/50 completed, 100 steps.
Model parameter acceptance fraction: 0.390
Likelihood parameter acceptance fractions: [0.39]
Batch: 5/50 completed, 100 steps.
Model parameter acceptance fraction: 0.320
Likelihood parameter acceptance fractions: [0.44]
Batch: 6/50 completed, 100 steps.
Model parameter acceptance fraction: 0.330
Likelihood parameter acceptance fractions: [0.43]
Batch: 7/50 completed, 100 steps.
Model parameter acceptance fraction: 0.290
Likelihood parameter acceptance fractions: [0.44]
Batch: 8/50 completed, 100 steps.
Model parameter acceptance fraction: 0.600
Likelihood parameter acceptance fractions: [0.42]
Batch: 9/50 completed, 100 steps.
Model parameter acceptance fraction: 0.380
Likelihood parameter acceptance fractions: [0.51]
Batch: 10/50 completed, 100 steps.
Model parameter acceptance fraction: 0.290
Likelihood parameter acceptance fractions: [0.52]
Batch: 11/50 completed, 100 steps.
Model parameter acceptance fraction: 0.440
Likelihood parameter acceptance fractions: [0.39]
Batch: 12/50 completed, 100 steps.
Model parameter acceptance fraction: 0.500
Likelihood parameter acceptance fractions: [0.26]
Batch: 13/50 completed, 100 steps.
Model parameter acceptance fraction: 0.330
Likelihood parameter acceptance fractions: [0.47]
Batch: 14/50 completed, 100 steps.
Model parameter acceptance fraction: 0.410
Likelihood parameter acceptance fractions: [0.48]
Batch: 15/50 completed, 100 steps.
Model parameter acceptance fraction: 0.440
Likelihood parameter acceptance fractions: [0.41]
Batch: 16/50 completed, 100 steps.
Model parameter acceptance fraction: 0.380
Likelihood parameter acceptance fractions: [0.44]
Batch: 17/50 completed, 100 steps.
Model parameter acceptance fraction: 0.190
Likelihood parameter acceptance fractions: [0.46]
Batch: 18/50 completed, 100 steps.
Model parameter acceptance fraction: 0.450
Likelihood parameter acceptance fractions: [0.34]
Batch: 19/50 completed, 100 steps.
Model parameter acceptance fraction: 0.350
Likelihood parameter acceptance fractions: [0.49]
Batch: 20/50 completed, 100 steps.
Model parameter acceptance fraction: 0.370
Likelihood parameter acceptance fractions: [0.5]
Batch: 21/50 completed, 100 steps.
Model parameter acceptance fraction: 0.450
Likelihood parameter acceptance fractions: [0.52]
Batch: 22/50 completed, 100 steps.
Model parameter acceptance fraction: 0.380
Likelihood parameter acceptance fractions: [0.44]
Batch: 23/50 completed, 100 steps.
Model parameter acceptance fraction: 0.240
Likelihood parameter acceptance fractions: [0.48]
Batch: 24/50 completed, 100 steps.
Model parameter acceptance fraction: 0.330
Likelihood parameter acceptance fractions: [0.5]
Batch: 25/50 completed, 100 steps.
Model parameter acceptance fraction: 0.470
Likelihood parameter acceptance fractions: [0.34]
Batch: 26/50 completed, 100 steps.
Model parameter acceptance fraction: 0.380
Likelihood parameter acceptance fractions: [0.51]
Batch: 27/50 completed, 100 steps.
Model parameter acceptance fraction: 0.310
Likelihood parameter acceptance fractions: [0.43]
Batch: 28/50 completed, 100 steps.
Model parameter acceptance fraction: 0.540
Likelihood parameter acceptance fractions: [0.45]
Batch: 29/50 completed, 100 steps.
Model parameter acceptance fraction: 0.290
Likelihood parameter acceptance fractions: [0.45]
Batch: 30/50 completed, 100 steps.
Model parameter acceptance fraction: 0.350
Likelihood parameter acceptance fractions: [0.42]
Batch: 31/50 completed, 100 steps.
Model parameter acceptance fraction: 0.390
Likelihood parameter acceptance fractions: [0.37]
Batch: 32/50 completed, 100 steps.
Model parameter acceptance fraction: 0.410
Likelihood parameter acceptance fractions: [0.52]
Batch: 33/50 completed, 100 steps.
Model parameter acceptance fraction: 0.450
Likelihood parameter acceptance fractions: [0.46]
Batch: 34/50 completed, 100 steps.
Model parameter acceptance fraction: 0.440
Likelihood parameter acceptance fractions: [0.49]
Batch: 35/50 completed, 100 steps.
Model parameter acceptance fraction: 0.310
Likelihood parameter acceptance fractions: [0.42]
Batch: 36/50 completed, 100 steps.
Model parameter acceptance fraction: 0.430
Likelihood parameter acceptance fractions: [0.51]
Batch: 37/50 completed, 100 steps.
Model parameter acceptance fraction: 0.310
Likelihood parameter acceptance fractions: [0.58]
Batch: 38/50 completed, 100 steps.
Model parameter acceptance fraction: 0.420
Likelihood parameter acceptance fractions: [0.46]
Batch: 39/50 completed, 100 steps.
Model parameter acceptance fraction: 0.260
Likelihood parameter acceptance fractions: [0.59]
Batch: 40/50 completed, 100 steps.
Model parameter acceptance fraction: 0.360
Likelihood parameter acceptance fractions: [0.38]
Batch: 41/50 completed, 100 steps.
Model parameter acceptance fraction: 0.550
Likelihood parameter acceptance fractions: [0.38]
Batch: 42/50 completed, 100 steps.
Model parameter acceptance fraction: 0.200
Likelihood parameter acceptance fractions: [0.44]
Batch: 43/50 completed, 100 steps.
Model parameter acceptance fraction: 0.610
Likelihood parameter acceptance fractions: [0.43]
Batch: 44/50 completed, 100 steps.
Model parameter acceptance fraction: 0.430
Likelihood parameter acceptance fractions: [0.38]
Batch: 45/50 completed, 100 steps.
Model parameter acceptance fraction: 0.160
Likelihood parameter acceptance fractions: [0.32]
Batch: 46/50 completed, 100 steps.
Model parameter acceptance fraction: 0.330
Likelihood parameter acceptance fractions: [0.56]
Batch: 47/50 completed, 100 steps.
Model parameter acceptance fraction: 0.410
Likelihood parameter acceptance fractions: [0.37]
Batch: 48/50 completed, 100 steps.
Model parameter acceptance fraction: 0.320
Likelihood parameter acceptance fractions: [0.36]
Batch: 49/50 completed, 100 steps.
Model parameter acceptance fraction: 0.230
Likelihood parameter acceptance fractions: [0.4]
Batch: 50/50 completed, 100 steps.
Model parameter acceptance fraction: 0.390
Likelihood parameter acceptance fractions: [0.43]
CPU times: user 4.8 s, sys: 224 ms, total: 5.02 s
Wall time: 4.78 s
%%time
walker_metropolis.walk(n_steps=20000, burnin=1000, batch_size=1000)
Burn-in batch 1/1 completed, 1000 steps.
Batch: 1/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.670
Likelihood parameter acceptance fractions: [0.825]
Batch: 2/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.750
Likelihood parameter acceptance fractions: [0.825]
Batch: 3/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.799
Likelihood parameter acceptance fractions: [0.839]
Batch: 4/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.870
Likelihood parameter acceptance fractions: [0.83]
Batch: 5/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.725
Likelihood parameter acceptance fractions: [0.827]
Batch: 6/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.797
Likelihood parameter acceptance fractions: [0.839]
Batch: 7/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.690
Likelihood parameter acceptance fractions: [0.839]
Batch: 8/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.614
Likelihood parameter acceptance fractions: [0.847]
Batch: 9/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.651
Likelihood parameter acceptance fractions: [0.835]
Batch: 10/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.784
Likelihood parameter acceptance fractions: [0.834]
Batch: 11/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.722
Likelihood parameter acceptance fractions: [0.82]
Batch: 12/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.821
Likelihood parameter acceptance fractions: [0.816]
Batch: 13/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.766
Likelihood parameter acceptance fractions: [0.819]
Batch: 14/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.637
Likelihood parameter acceptance fractions: [0.815]
Batch: 15/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.768
Likelihood parameter acceptance fractions: [0.839]
Batch: 16/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.642
Likelihood parameter acceptance fractions: [0.851]
Batch: 17/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.621
Likelihood parameter acceptance fractions: [0.833]
Batch: 18/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.646
Likelihood parameter acceptance fractions: [0.838]
Batch: 19/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.753
Likelihood parameter acceptance fractions: [0.849]
Batch: 20/20 completed, 1000 steps.
Model parameter acceptance fraction: 0.718
Likelihood parameter acceptance fractions: [0.834]
CPU times: user 13.2 s, sys: 130 ms, total: 13.4 s
Wall time: 13.4 s
def plot_chains(walker, model, true_params):
fig, axes = plt.subplots(
walker.model_sampler.chain.shape[1] + 3, 1, figsize=(8, 8), sharex=True
)
for i in range(walker.model_sampler.chain.shape[1]):
axes[i].plot(walker.model_sampler.chain[:, i])
axes[i].set_ylabel(f"${model.params[i].latex_name}$ [{model.params[i].unit}]")
true_value = true_params[model.params[i].name]
axes[i].hlines(
true_value, 0, len(walker.model_sampler.chain), "r", linestyle="--"
)
axes[-3].plot(walker.model_sampler.logp_chain)
axes[-3].set_ylabel(r"$\log{\mathcal{L}(\alpha_i | \mathcal{O})}$")
lmp = walker.likelihood_samplers[0].params[0]
axes[-2].plot(walker.likelihood_samplers[0].chain)
axes[-2].set_ylabel(f"${lmp.latex_name}$ [{lmp.unit}]")
axes[-2].hlines(
np.log(noise), 0, len(walker.likelihood_samplers[0].chain), "r", linestyle="--"
)
axes[-1].plot(walker.likelihood_samplers[0].logp_chain)
axes[-1].set_ylabel(r"$\log{\mathcal{L}(\alpha_i | \mathcal{O})}$")
axes[-1].set_xlabel(r"$i$")
# plt.legend(title="chains", ncol=3,)
plot_chains(walker_metropolis, my_model, true_params)
plot_chains(walker_adaptive, my_model, true_params)
fig = corner.corner(
np.hstack(
[
walker_metropolis.model_sampler.chain,
walker_metropolis.likelihood_samplers[0].chain,
]
),
labels=[p.name for p in my_model.params]
+ [walker_metropolis.likelihood_samplers[0].params[0].name],
label="posterior",
truths=[true_params["m"], true_params["b"], np.log(noise)],
color="tab:blue",
)
corner.corner(
np.hstack(
[
walker_adaptive.model_sampler.chain,
walker_adaptive.likelihood_samplers[0].chain,
]
),
fig=fig,
color="tab:orange",
)
fig.suptitle("posterior")
Text(0.5, 0.98, 'posterior')
x = np.linspace(-0.5, 1.5, 10)
n_posterior_samples = walker_metropolis.model_sampler.chain.shape[0]
y1 = np.zeros((n_posterior_samples, len(x)))
for i in range(n_posterior_samples):
sample = walker_metropolis.model_sampler.chain[i, :]
y1[i, :] = my_model.y(x, *sample)
n_posterior_samples = walker_adaptive.model_sampler.chain.shape[0]
y2 = np.zeros((n_posterior_samples, len(x)))
for i in range(n_posterior_samples):
sample = walker_adaptive.model_sampler.chain[i, :]
y2[i, :] = my_model.y(x, *sample)
# Metropolis Hastings
upper, med, lower = np.percentile(y1, [5, 50, 95], axis=0)
p = plt.fill_between(
x, lower, upper, alpha=0.3, zorder=2, color="tab:blue", label="Metropolis-Hastings"
)
plt.plot(x, med, ":", color=p.get_facecolor())
# adaptive
upper, med, lower = np.percentile(y2, [5, 50, 95], axis=0)
p = plt.fill_between(
x,
lower,
upper,
alpha=0.3,
zorder=2,
color="tab:orange",
label="Adaptive Metropolis",
)
plt.plot(x, med, ":", color=p.get_facecolor())
# truth
plt.plot(x, my_model.y(x, *true_params.values()), "k--", label="truth")
plt.errorbar(
x_data,
y_data,
noise * y_data,
color="k",
marker="o",
linestyle="none",
label="experiment with bias",
)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.legend()
<matplotlib.legend.Legend at 0x14f21fca6120>