Mixed MRF

Implementation details of the mixed Markov random field in bgms. Source: src/models/mixed/mixed_mrf_model.h and four .cpp files (mixed_mrf_model.cpp, mixed_mrf_gradient.cpp, mixed_mrf_likelihoods.cpp, mixed_mrf_metropolis.cpp).

Block structure

The mixed MRF handles \(p\) discrete and \(q\) continuous variables. The parameter space is organized into five blocks — two main-effect blocks and three interaction blocks:

Block Parameters Size
Thresholds Discrete category thresholds \(\sum_x m_x\) (with \(m_x\) the number of threshold parameters of discrete variable \(x\): its category count for an ordinal variable, 2 for Blume-Capel)
Means Continuous variable means \(q\)
Discrete-discrete Pairwise discrete interactions (symmetric) \(p(p-1)/2\)
Cross-type Discrete-continuous interactions \(p \times q\)
Continuous-continuous Null-space theta block of the precision matrix \(q + \lvert E_{yy} \rvert\) active

All five blocks are updated jointly by NUTS. The continuous-block precision matrix \(\boldsymbol{\Theta}\) is parameterized exactly as in the GGM: an upper Cholesky factor \(\boldsymbol{\Theta} = \boldsymbol{\Phi}^\top \boldsymbol{\Phi}\) with log-scale diagonals \(\psi_q\) and per-column null-space coordinates \(f_q\) for the off-diagonals, so positive definiteness and excluded continuous-continuous edges are guaranteed by construction (see Constrained Cholesky Parameterization). The model owns its own GraphConstraintStructure for the continuous subgraph and reuses the GGM’s gradient engine (yy_engine_, a GGMGradientEngine instance) for the forward map and the theta gradient of this block.

Parameter vector layout

The model distinguishes three vector types:

Active vector (parameter_dimension())

Contains the parameters that NUTS updates, excluding edges set to zero by edge selection:

[ thresholds | discrete interactions (active) | means | cross-type (active) | (f_q, psi_q) theta block (q + |E_yy|) ]

The gradient is dimensioned to this vector; discrete-discrete and cross edges are dropped entirely when excluded, and the continuous block shrinks through its null-space coordinates.

Full vector (full_parameter_dimension())

The zero-padded fixed-slot layout with a slot for every possible parameter, used for mass-matrix estimation (see NUTS — Mass matrix).

Storage vector (storage_dimension())

The fixed output layout saved to ChainResult::samples at each iteration; the continuous block is stored as raw precision entries, not theta coordinates.

Sampling strategy

Each MCMC iteration updates the full active parameter vector (all five blocks) in a single NUTS step. The logp_and_gradient() method computes the log-pseudoposterior and gradient for the entire vector, including the Cholesky block with its log-determinant Jacobian.

Excluded edges need no special integration: discrete-discrete and cross edges are dropped from the active vector, and excluded continuous-continuous edges are held exactly at zero by the constrained Cholesky parameterization of the \(\boldsymbol{\Theta}_{yy}\) block.

Edge indicator updates (when active) operate on all three interaction blocks. Under the hierarchical graph prior, only the continuous-continuous moves carry a Z-ratio term, evaluated on the continuous subgraph (continuous_subgraph()); the edge-prior normalizing-constant correction likewise reads continuous-continuous pairs only, through the per-node continuous mask. Discrete and cross edge moves are plain spike-and-slab updates. All three blocks record Rao-Blackwellized inclusion draws and odds accumulators, aligned to the indicator vector order — see Rao-Blackwellized inclusion draws.

The model also provides a component-wise adaptive Metropolis fallback (do_one_metropolis_step()), used when the sampler is set to "adaptive-metropolis". In Metropolis mode, each parameter is updated individually with Robbins–Monro-tuned proposal SDs targeting an acceptance rate of 0.44. Precision-block updates reuse the GGM’s in-place machinery — per-sweep caches of the covariance and log-determinant advanced by Sherman–Morrison–Woodbury rank-1/rank-2 updates, with one Cholesky refresh at the end of the sweep.

Gradient computation

The gradient (mixed_mrf_gradient.cpp) covers all five parameter blocks in the NUTS vector. It involves:

  1. Discrete conditionals — Category probabilities via Variable Helpers, contributing gradients for thresholds and discrete pairwise interactions
  2. Cross-type terms — Contributions from discrete-continuous interactions to both discrete and continuous conditional distributions
  3. Continuous means — Gradient of the continuous-variable contribution to the pseudolikelihood
  4. Theta block — The precision adjoint \(\bar{\boldsymbol{\Theta}}\) is computed from the GGM conditional contribution, the conditional-mean coupling, the priors, and the determinant tilt, then mapped to the Cholesky adjoint \(\bar{\boldsymbol{\Phi}}\) and handed to the shared gradient engine (yy_engine_.theta_gradient_from_phi_bar()), which propagates it through the stored Givens rotations to the \((f_q, \psi_q)\) coordinates — including the null-space basis rotation and the QR Jacobian terms, exactly as in GGM Internals. Throughout, a bar denotes the reverse-mode adjoint \(\bar{A} = \partial \ell / \partial A\).

The log-prior gradient adds the interaction-prior penalties (default Normal) on the partial-association scale and the scale-prior penalties (default exponential, eta frame) on diagonal entries; see Parameter Priors. The determinant tilt \(|\boldsymbol{\Theta}_{yy}|^{\delta}\) applies to the continuous block only — the auto-resolved default \(\delta = 0.5 \log q\) counts the continuous variables, not all variables.

Discrete pseudolikelihood

Write \(x_s\) for discrete variable \(s\), \(x_{-s}\) for the remaining discrete variables, and \(\mathbf{D}\) for the \(n \times q\) column-centered continuous data matrix. The discrete full conditionals integrate out the continuous block: the model uses \(\log p(x_s \mid x_{-s})\) rather than conditioning on the observed continuous values. The continuous block uses the exact GGM likelihood \(\log p(y \mid x) \propto \tfrac{n}{2}\log|\boldsymbol{\Theta}| - \tfrac{1}{2}\operatorname{tr}(\boldsymbol{\Theta} \mathbf{D}^\top \mathbf{D})\).

For a cross-model overview and the derivation of the marginal form, see Pseudolikelihood.

After integrating out the continuous block, the discrete conditionals use the marginal interaction matrix

\[ \mathbf{M} = \mathbf{A}_{xx} + 2\,\mathbf{A}_{xy}\,\boldsymbol{\Sigma}_{yy}\,\mathbf{A}_{xy}^\top, \]

where \(\mathbf{A}_{xx}\) is the discrete-discrete interaction block, \(\mathbf{A}_{xy}\) the cross-type (discrete-continuous) interaction block, and \(\boldsymbol{\Sigma}_{yy} = \boldsymbol{\Theta}^{-1}\) the conditional covariance of the continuous variables. (In the notation of the mixed-model guide page, these are the \(\boldsymbol{\Omega}^{xx}\) and \(\boldsymbol{\Omega}^{xy}\) blocks of the interaction matrix.) \(\mathbf{M}\) must be recomputed whenever the precision or cross-type parameters change. The \(\partial \mathbf{M}/\partial\boldsymbol{\Sigma}_{yy}\) chain rule contributes to the precision gradient via \(-2\boldsymbol{\Sigma}_{yy} \mathbf{A}_{xy}^\top\bar{\mathbf{M}} \mathbf{A}_{xy}\boldsymbol{\Sigma}_{yy}\).

Missing data

The mixed MRF tracks missing indices separately for discrete and continuous variables (missing_index_discrete, missing_index_continuous). During imputation:

  • Missing discrete values are sampled from their full conditional categorical distributions
  • Missing continuous values are sampled from their conditional normal distributions given the current precision matrix

Output assembly

The R output builder (build_output_mixed_mrf() in R/build_output_mixed_mrf.R) maps the flat storage vector back to the original variable ordering using fill_mixed_symmetric() (defined in R/build_output.R). This function places discrete-discrete, continuous-continuous, and cross-type interactions into the correct cells of the \((p+q) \times (p+q)\) output matrix, respecting the original column order (which may interleave discrete and continuous variables).