Adaptive Metropolis

The adaptive Metropolis sampler (update_method = "adaptive-metropolis") updates parameters one at a time using scalar random-walk Metropolis–Hastings steps. Proposal standard deviations are tuned during warmup via Robbins–Monro adaptation.

The algorithm function is in src/mcmc/algorithms/metropolis.h and metropolis.cpp. The sampler wrapper is MetropolisSampler in src/mcmc/samplers/metropolis_sampler.h, and the adaptation controller is MetropolisAdaptationController in src/mcmc/samplers/metropolis_adaptation.h.

Algorithm

Each scalar Metropolis step for parameter \(\theta_k\):

  1. Propose — Draw \(\theta_k^* \sim \mathcal{N}(\theta_k,\, \sigma_k^2)\), where \(\sigma_k\) is the current proposal standard deviation for element \(k\).
  2. Accept/reject — Compute the acceptance probability \[ \alpha = \min\!\bigl(1,\; \exp\bigl(\log p(\theta_k^*) - \log p(\theta_k)\bigr)\bigr). \] The proposal distribution is symmetric, so no Hastings ratio correction is needed.
  3. Update — Set \(\theta_k \leftarrow \theta_k^*\) with probability \(\alpha\); otherwise keep \(\theta_k\).

The metropolis_step() function in metropolis.cpp implements this for a single scalar parameter and returns a StepResult containing the accepted state and \(\alpha\).

Model-driven sweeps

Unlike NUTS, where the sampler drives integration over the full parameter vector, MetropolisSampler delegates the sweep structure to the model. The sampler calls model.do_one_metropolis_step(iteration), and each model class defines how to iterate over its parameters:

  • GGMModel — Sweeps over free elements of the precision matrix (or Cholesky factor), updating each element with a scalar Metropolis step against its full conditional log-posterior.
  • OMRFModel — Sweeps over thresholds and pairwise interaction parameters, with each step targeting the pseudolikelihood-based conditional.
  • MixedMRFModel — Sweeps over discrete thresholds, discrete interactions, continuous interactions, and cross-type interactions in separate blocks, each with its own proposal SD matrix.

This design allows each model to exploit problem-specific structure (e.g., conditional independence, sufficient statistics) that a generic vector-level sampler cannot access.

Robbins–Monro adaptation

During warmup, the proposal standard deviations \(\sigma_k\) are adapted toward a target acceptance rate \(\alpha^*\). The MetropolisAdaptationController applies the update rule:

\[ \sigma_k^{(t+1)} = \sigma_k^{(t)} + \bigl(\alpha_k^{(t)} - \alpha^*\bigr)\,w_t, \]

where \(w_t = t^{-0.75}\) is the Robbins–Monro weight with decay exponent 0.75. The updated value is clamped to \([0.001,\; 2.0]\). NaN values (from degenerate proposals) are reset to 1.0.

Under update_method = "adaptive-metropolis" the target is the resolved target_accept, which defaults to 0.44, the componentwise random-walk optimum. Under NUTS the edge-move proposals tuned in stage 3b use a fixed 0.44 instead, because target_accept there sets the step-size adaptation target.

Element-wise adaptation

The controller operates on a matrix of proposal standard deviations (matching the parameter layout of the model) with an index mask that selects which elements to adapt. At each iteration, the controller receives the matrix of acceptance probabilities from the most recent sweep and updates only the masked elements.

Warmup boundary

Adaptation runs only during warmup. The Robbins–Monro weight for the edge-move proposal SDs comes from the schedule’s rm_weight_for_proposal_sd(), which returns a value only on stage-3b iterations, so the policy of which iterations adapt lives in one place. After warmup ends, proposal standard deviations are frozen at their final values. See Warmup Schedule for how stage 3b positions this tuning within the warmup budget.

Acceptance-probability diagnostics

Each sweep, the model records its mean acceptance probability across all updated components, exposed as last_metropolis_mean_accept_prob(). During sampling, the chain runner stores this per-iteration value in the chain result (am_accept_prob_samples), which reaches R as the am_accept_prob__ trace. summarize_am_diagnostics() (in R/diagnostics_am.R) builds fit$am_diag from it — note that fit$am_diag$accept_prob is a separate per-parameter empirical move-rate matrix computed from the stored samples, not the per-iteration trace.

Sampler dispatch

The R interface passes update_method = "adaptive-metropolis" through unchanged; resolve_sampler_spec() in chain_runner.cpp maps that string to SamplerKind::AdaptiveMetropolis, and the factory constructs a MetropolisSampler from the enum. Any string other than the three supported ones ("nuts", "adaptive-metropolis", "gibbs") throws a std::runtime_error. See Sampler Hierarchy for the full dispatch flow.

Initialization

The chain runner calls initialize() eagerly, before the MCMC loop. This invokes model.init_metropolis_adaptation(schedule), which creates MetropolisAdaptationController instances for each parameter block in the model. (A lazy fallback in step() still guards the uninitialized case.)

Source files

File Contents
src/mcmc/algorithms/metropolis.h metropolis_step() declaration
src/mcmc/algorithms/metropolis.cpp metropolis_step() implementation
src/mcmc/samplers/metropolis_sampler.h MetropolisSampler class (header-only)
src/mcmc/samplers/metropolis_adaptation.h MetropolisAdaptationController class (header-only)
src/mcmc/execution/step_result.h update_proposal_sd_with_robbins_monro() utility