Computational Methods¶
Homodyne solves two distinct estimation problems: (1) non-linear least squares (NLSQ) fitting of the two-time correlation function to recover point estimates with uncertainties, and (2) Bayesian posterior sampling via Consensus Monte Carlo (CMC) with NUTS to obtain full posterior distributions. This page describes the mathematical foundations of both methods and the numerical techniques used to make them fast and reliable on CPU hardware.
Non-Linear Least Squares (NLSQ)¶
Problem formulation: Given measured two-time correlation data \(\{c_2^{ij,\mathrm{meas}}\}\) and the theoretical model \(c_2^{ij}(\theta)\), minimize the weighted sum of squared residuals:
where \(\mathcal{S}\) is the set of sampled time-pairs and \(w_{ij}\) are measurement weights (typically \(\propto 1/\sigma_{ij}^2\)).
Levenberg-Marquardt Algorithm¶
Homodyne uses the Trust-Region Levenberg-Marquardt algorithm, which is the gold standard for non-linear least squares. The update step at iteration \(k\) solves:
where \(J_k = \partial r_k / \partial\theta\) is the Jacobian matrix of residuals, \(W = \mathrm{diag}(w_{ij})\) is the weight matrix, \(r_k\) is the residual vector, and \(\lambda_k \geq 0\) is the damping parameter.
When \(\lambda_k \to 0\): reduces to Gauss-Newton (fast convergence near minimum).
When \(\lambda_k \to \infty\): reduces to gradient descent (stable far from minimum).
The trust-region variant adaptively controls \(\lambda_k\) based on the ratio of actual vs predicted reduction, providing global convergence guarantees.
Implementation: Uses the NLSQ library (version \(\geq\) 0.6.4) via
homodyne.optimization.nlsq.adapter.NLSQAdapter. The library provides
GPU-accelerated non-linear least squares; homodyne uses its CPU mode.
Cumulative Trapezoid Integration¶
The diffusion and shear integrals \(\mathcal{D}(t_1, t_2)\) and \(\Gamma(t_1, t_2)\) are always evaluated numerically via cumulative trapezoid integration on the discrete time grid:
This is second-order accurate and numerically stable for monotone integrands.
The two-time integral is vectorized over the full \((t_1, t_2)\) grid using
jax.numpy.cumsum() and broadcasting, giving \(O(N_t^2)\) evaluations in
\(O(N_t)\) operations via the prefix-sum trick:
where \(F(t)\) is computed once as a cumulative sum.
Jacobian Computation¶
The Jacobian \(\partial c_2 / \partial\theta\) is computed by JAX automatic differentiation:
from jax import jacfwd
def residuals(theta):
c2_model = compute_c2(theta, t1, t2, q, phi, h)
return c2_model.ravel() - c2_measured.ravel()
J = jacfwd(residuals)(theta_current)
Forward-mode AD is used because \(n_\mathrm{params} \ll n_\mathrm{residuals}\), making forward mode more efficient than reverse mode (which would require \(n_\mathrm{residuals}\) backward passes).
The Jacobian is JIT-compiled, so the first call pays a compilation overhead (typically 0.5–2 seconds) but subsequent calls are fast.
Memory Management¶
For large datasets (\(> 10^7\) data points), the full Jacobian matrix may exceed available RAM. Homodyne uses two strategies:
Stratified LS (default, \(< 10^7\) points): Full Jacobian fits in memory. Parameters selected by the NLSQ library’s internal strategy.
Hybrid Streaming (\(> 10^7\) points): The data is partitioned into shards that fit in memory. The normal equations \(J^\top W J\) and \(J^\top W r\) are accumulated shard-by-shard:
The LM step then solves \((A + \lambda\,\mathrm{diag}(A))\delta\theta = b\) using the accumulated matrices. This keeps memory usage at \(O(N_\mathrm{params}^2)\) regardless of dataset size.
Configure via:
optimization:
nlsq:
memory_fraction: 0.75 # 75% of RAM triggers streaming
CMA-ES for Multi-Scale Problems¶
When the physical parameters span many orders of magnitude (scale ratio \(> 10^6\), e.g., \(D_0 \sim 10^4\) and \(\dot{\gamma}_0 \sim 10^{-3}\)), gradient-based NLSQ can stagnate in poor local minima. The package provides CMA-ES (Covariance Matrix Adaptation Evolution Strategy) as a global pre-optimizer:
CMA-ES is a derivative-free evolutionary algorithm.
The covariance matrix adapts to the objective landscape, handling ill-conditioning.
After CMA-ES finds a good basin, NLSQ refines to high precision.
Configure via:
optimization:
nlsq:
cmaes:
enable: true
preset: "cmaes-global" # 200 generations
refine_with_nlsq: true
Consensus Monte Carlo (CMC)¶
Homodyne uses Consensus Monte Carlo (Scott et al. 2016) to scale MCMC to large datasets that are too expensive for a single NUTS chain.
Core idea: Partition the data into \(M\) shards of size \(n_s\). Run independent NUTS chains on each shard, obtaining posterior samples \(\{\theta^{(k)}_s\}_{k=1}^K\) from the shard-specific posterior:
Consensus step: Combine the \(M\) shard posteriors into the global posterior estimate. For the consensus algorithm, each shard posterior is treated as a weighted contribution to the full posterior via the Weierstrass consensus rule:
This is exact when the likelihood factors over shards (which it does for independent measurements) and the prior is Gaussian.
NUTS (No-U-Turn Sampler)¶
Each shard runs NUTS, an adaptive HMC algorithm that automatically tunes:
Step size \(\epsilon\): controlled by dual averaging to achieve target acceptance rate \(\delta_\mathrm{target} = 0.8\).
Trajectory length: uses the No-U-Turn criterion to stop leapfrog integration before the trajectory doubles back, avoiding wasted computation.
The NUTS update is:
where \(\rho \sim \mathcal{N}(0, M)\) is the auxiliary momentum (with mass matrix \(M\) estimated during warmup), and \(L\) is the number of leapfrog steps determined adaptively.
Cost per sample: \(O(n_s \cdot L)\) gradient evaluations, where \(L \sim O(\log n_s)\) for typical problems. This is implemented by NumPyro, which JIT-compiles the NUTS kernel via JAX.
Adaptive Sampling¶
To reduce overhead for small shards, homodyne uses adaptive sampling: warmup and sample counts are scaled with shard size:
with a similar formula for \(n_\mathrm{samples}\). This prevents spending most of the wall-time on NUTS warmup for small shards.
Shard size |
Warmup |
Samples |
Reduction |
|---|---|---|---|
50 pts |
140 |
350 |
75% |
5K pts |
250 |
750 |
50% |
50K+ pts |
500 |
1,500 |
None (full) |
Reparameterization (Log-Space Priors)¶
Parameters like \(D_0\) span several orders of magnitude, making the posterior geometry challenging for NUTS. Homodyne uses log-space reparameterization:
where \(t_\mathrm{ref} = \sqrt{\Delta t \cdot t_\mathrm{max}}\) is a reference timescale computed from the data. In reparameterized space, the posterior is approximately Gaussian, improving NUTS efficiency (fewer leapfrog steps, higher acceptance rate).
The reference timescale is computed in homodyne.optimization.cmc.reparameterization.compute_t_ref(),
which raises ValueError for invalid inputs; the caller catches this and falls back
to \(t_\mathrm{ref} = 1.0\).
Diagnostics¶
Convergence is assessed using standard ArviZ diagnostics:
where \(\hat{V}\) is the between-chain variance estimate and \(W\) is the within-chain variance. Good convergence: \(\hat{R} < 1.01\).
Effective sample size:
where \(\rho_t\) is the lag-\(t\) autocorrelation. Adequate sampling: \(\mathrm{ESS} > 400\) per parameter.
BFMI (Bayesian Fraction of Missing Information):
where \(H = -\log p(\theta) - \log p(y|\theta)\) is the Hamiltonian. Good mixing: \(\mathrm{BFMI} > 0.3\).
Quality filtering removes shards with divergence rate \(> 10\%\) before consensus
(configurable via max_divergence_rate).
See also
Per-Angle Scaling and Anti-Degeneracy — per-angle scaling modes
homodyne.optimization.nlsq.adapter— NLSQAdapter implementationhomodyne.optimization.cmc.sampler— SamplingPlan and NUTS executionhomodyne.optimization.cmc.reparameterization— log-space priorshomodyne.optimization.cmc.backends.multiprocessing— parallel CMC backend