Result Interpretation¶
After fitting, the package returns structured result objects that carry fitted parameters, uncertainties, diagnostics, and metadata. This page explains how to read and validate those results.
NLSQ Results¶
NLSQResult is returned
by fit_nlsq_jax and fit_nlsq_multi_phi.
Key fields¶
parameters(np.ndarray)Fitted parameter values in the order given by
parameter_names.parameter_names(list[str])Ordered list of parameter names matching
parameters.success(bool)Whether the optimiser converged.
message(str)Termination message from the solver.
uncertainties(np.ndarray | None)One-sigma uncertainties derived from the diagonal of the covariance matrix.
Noneif the covariance could not be estimated (e.g., singular Jacobian).covariance(np.ndarray | None)Full parameter covariance matrix. Use
result.get_correlation_matrix()to obtain the normalised correlation matrix for detecting parameter degeneracies.reduced_chi_squared(float | None)\(\chi^2 / \nu\) where \(\nu\) is the number of degrees of freedom (data points minus free parameters).
residuals(np.ndarray | None)Residual vector (data minus model) for the upper triangle.
fitted_correlation(np.ndarray | None)The model \(C_2\) matrix evaluated at the best-fit parameters.
Convenience methods¶
# Access a single parameter by name
d0_ref = result.get_param("D0_ref")
# Get uncertainty for a parameter
d0_ref_unc = result.get_uncertainty("D0_ref")
# All parameters as a dictionary
params = result.params_dict # property, not a method call
# Validate fit quality (returns list of warning strings)
warnings = result.validate()
for w in warnings:
print(f"WARNING: {w}")
CMC Results¶
CMCResult is returned by
the Bayesian pipeline.
Key fields¶
posterior_mean(np.ndarray)Mean of the posterior distribution for each parameter.
posterior_std(np.ndarray)Standard deviation of the posterior for each parameter.
credible_intervals(dict[str, dict[str, float]])Per-parameter credible intervals. Each entry contains
lower_95,upper_95,lower_89,upper_89.convergence_passed(bool)Whether all convergence diagnostics passed.
r_hat(np.ndarray | None)Gelman–Rubin \(\hat{R}\) statistic for each parameter. Values below 1.01 indicate convergence.
ess_bulk/ess_tail(np.ndarray | None)Effective sample sizes for the bulk and tails of the posterior. Values above 400 are generally adequate.
bfmi(list[float] | None)Bayesian Fraction of Missing Information for each chain. Values below 0.3 suggest the sampler is struggling with the energy landscape.
samples(dict[str, np.ndarray] | None)Raw posterior samples keyed by parameter name. Shape is
(n_chains * n_draws,)or(n_chains, n_draws).
Convenience methods¶
# Summary for one parameter
summary = result.get_param_summary("D0_ref")
# {'mean': ..., 'std': ..., 'lower_95': ..., 'upper_95': ..., 'r_hat': ..., 'ess_bulk': ...}
# All posterior means as dict
means = result.params_dict() # method call (not property)
# Validate convergence against thresholds
warnings = result.validate_convergence(
r_hat_threshold=1.1,
min_ess=100,
min_bfmi=0.3,
)
Goodness of Fit¶
Reduced chi-squared¶
The primary goodness-of-fit metric for NLSQ is the reduced chi-squared:
where \(\nu = N_\text{data} - N_\text{params}\) is the number of degrees of freedom.
\(\chi^2_\text{red} \approx 1.0\) – Good fit. The model explains the data within its noise level.
\(\chi^2_\text{red} \gg 1\) – Poor fit. The model is missing systematic features, or the data has structure not captured by the 14-parameter model.
\(\chi^2_\text{red} \ll 1\) – Possible overfit. The error bars may be overestimated, or the model has too many free parameters relative to the information content.
Parameter Uncertainties¶
From NLSQ (covariance matrix)¶
The covariance matrix \(\mathbf{C}\) is estimated from the Jacobian at the optimum:
One-sigma uncertainties are \(\sigma_i = \sqrt{C_{ii}}\). These are local estimates valid near the optimum and assume the posterior is approximately Gaussian.
From CMC (posterior samples)¶
The Bayesian posterior provides the full marginal distribution for each parameter. Credible intervals are computed directly from the sample quantiles and do not assume Gaussianity. Use the 89% HDI (Highest Density Interval) as a robust summary, or the 95% interval for traditional reporting.
# Extract 95% credible interval
ci = result.credible_intervals["D0_ref"]
print(f"D0_ref: [{ci['lower_95']:.2e}, {ci['upper_95']:.2e}]")
Comparing NLSQ and CMC¶
When both results are available, compare them to validate consistency:
from heterodyne.optimization.cmc.results import compare_cmc_nlsq
comparison = compare_cmc_nlsq(cmc_result, nlsq_result, consistency_sigma=2.0)
print(f"Consistent parameters: {comparison['n_consistent']}/{len(comparison['common_parameters'])}")
for name, consistent in comparison["consistent"].items():
if not consistent:
dev = comparison["relative_deviations"][name]
print(f" {name}: {dev:.1f} sigma from CMC mean")
Parameters that disagree by more than 2 posterior standard deviations may indicate that the NLSQ solution is trapped in a local minimum, or that the posterior is strongly non-Gaussian.