Edge Priors
The edge prior framework controls which edges are included in the graph. All priors inherit from BaseEdgePrior in src/priors/edge_prior.h.
BaseEdgePrior interface
class BaseEdgePrior {
virtual void update(const arma::imat& edge_indicators,
arma::mat& inclusion_probability,
int num_variables, int num_pairwise,
SafeRNG& rng) = 0;
virtual std::unique_ptr<BaseEdgePrior> clone() const = 0;
virtual bool has_allocations() const { return false; }
virtual arma::ivec get_allocations() const { return arma::ivec(); }
virtual bool has_inclusion_parameter() const { return false; }
virtual double get_inclusion_parameter() const { return NA_REAL; }
};The update() method is called once per MCMC iteration (when edge selection is active). It receives the current edge indicators and updates the inclusion probability matrix in place. The updated probabilities are used by the model’s update_edge_indicators() method for the next round of edge proposals.
clone() produces an independent copy for parallel chain execution. has_inclusion_parameter() marks priors that carry a sampled shared inclusion probability (the Beta-Bernoulli \(\pi\)); when true, the chain runner stores get_inclusion_parameter() each retained iteration, surfaced in R as fit$inclusion_parameter_samples.
BernoulliEdgePrior
The simplest prior: each edge has a fixed inclusion probability that does not change during sampling. The update() method is a no-op.
This prior is used when the user passes edge_prior = bernoulli_prior(p), which fixes the inclusion probability matrix at p. bernoulli_prior(0.5) is the default edge prior in bgms.
BetaBernoulliEdgePrior
A conjugate prior that learns a shared inclusion probability from the data. At each iteration:
- Count the number of included edges: \(k = \sum_{i<j} \gamma_{ij}\)
- Count the total number of possible edges: \(m = p(p-1)/2\)
- Draw a new inclusion probability from its full conditional. On discrete models this is the plain conjugate draw \(\pi \sim \text{Beta}(\alpha + k,\; \beta + m - k)\). On continuous and mixed models under the joint specification, a correction table is attached and the draw instead targets the corrected conditional carrying the \(1/C(\pi)\) factor.
- Set all entries of the inclusion probability matrix to \(\pi\)
The hyperparameters \(\alpha\) and \(\beta\) default to 1 (uniform prior on the inclusion probability). The sampled \(\pi\) is exposed through has_inclusion_parameter() / get_inclusion_parameter() and stored per iteration.
StochasticBlockEdgePrior
The stochastic block model (SBM) prior assigns variables to latent clusters and allows different inclusion probabilities for within-cluster and between-cluster edges. See SBM Prior for the full formulation.
The StochasticBlockEdgePrior class stores:
cluster_allocations_— Current cluster assignment for each variablecluster_prob_— \(K \times K\) matrix of block inclusion probabilitieslog_Vn_— Log partition coefficients for the MFM prior on cluster count
Its update() method runs collapsed Gibbs sampling to update cluster assignments and block probabilities (delegated to functions in sbm_edge_prior.cpp). On continuous and mixed models under the joint specification, the sweeps read the attached correction data: a per-edge tilt slope enters each allocation move, and the new-cluster collapsed marginal integrates the corrected per-pair curve (see SBM Prior).
has_allocations() returns true, and get_allocations() returns the current cluster assignment vector. These are stored in ChainResult::allocation_samples for post-hoc analysis.
Normalizing-constant corrections
On continuous (GGM) and mixed models under precision_graph_prior = "joint", the graph marginal of the spike-and-slab prior is not the edge prior \(\pi(\Gamma)\) but \(\pi(\Gamma)\, Z(\Gamma)\) — the edge prior reweighted by each graph’s share of the determinant-tilted precision prior’s mass (see GGM Internals — Determinant tilt). For the hierarchical edge priors this bias propagates into the hyperparameter updates, and two symbols carry the correction. In this section \(\theta\) is the prior inclusion probability, the quantity the Beta-Bernoulli prior treats as random; it is never a precision entry here, whatever it denotes on the sampler pages. The correction itself enters through one scalar curve and two views of it:
- \(C(\theta)\), the graph-prior mean of the per-graph normalizer \(Z(\Gamma)\) when every edge is included with probability \(\theta\): how much the positive-definite cone reweights the average graph at that density.
- \(f(\theta) = \log C(\theta) / E\), the same curve per candidate pair, with \(E\) the number of pairs. Only differences of \(f\) ever enter, so its additive constant is immaterial.
- \(f'(d)\), the slope of that curve read at a local edge density \(d\), which lets a single edge’s update use the correction without evaluating the whole-graph curve.
\(C(\theta)\) enters the full conditional of the inclusion probability and of the block structure, so the plain conjugate draws would target the wrong marginals.
The correction machinery (edge_prior_correction.h, built at fit setup in R/correction_tables.R from the tilted prior sampler and cached on disk via tools::R_user_dir("bgms", "cache")) supplies:
- Beta-Bernoulli — the whole-graph \(\log C(\theta)\) curve. The corrected conditional \(p(\theta \mid \Gamma) \propto \theta^{a-1}(1-\theta)^{b-1} e^{-\log C(\theta)}\) is drawn by inverse-CDF on a fine grid (\(N = 401\)) centered at the current \(\theta\) with half-width ten conjugate-posterior standard deviations, so resolution in posterior-SD units is constant as \(\theta\) approaches the boundaries. Only differences of \(\log C\) enter, so the curve’s additive constant is immaterial; outside the tabulated range it is extended linearly with the boundary slope.
- SBM — local reads of the same table: the per-edge tilt slope \(f'(d)\), evaluated at a local density \(d\) built from the edge’s two endpoints (each endpoint has an expected degree density under the current block probabilities, taken over the continuous subgraph, and the smaller of the two is used; the slope is extended as a constant beyond the tabulated range), and the per-pair \(f(\theta)\) curve pre-interpolated onto a uniform quadrature grid for the new-cluster collapsed marginal.
On the mixed-MRF path the tilt acts on the continuous precision block only, so the correction reads continuous-continuous pairs exclusively (a per-node mask carries the variable types); with fewer than two continuous variables no edge is tilted and the plain conjugate updates apply unchanged. Under the default precision_graph_prior = "hierarchical" no correction is needed at all — the per-graph normalization makes the graph marginal exactly \(\pi(\Gamma)\) and the conjugate updates are already correct; the cost moves into the edge moves instead.
Prior selection in R
The edge_prior argument in bgm() takes a bgms_indicator_prior object. The same family of constructors is also accepted by bgmCompare() via its difference_prior argument, which governs the inclusion of contrast parameters (difference selection). The family field of that object selects the C++ class:
| R constructor | family |
C++ class | Behavior |
|---|---|---|---|
bernoulli_prior() |
"Bernoulli" |
BernoulliEdgePrior |
Fixed inclusion probabilities |
beta_bernoulli_prior() |
"Beta-Bernoulli" |
BetaBernoulliEdgePrior |
Learns shared inclusion rate |
sbm_prior() |
"Stochastic-Block" |
StochasticBlockEdgePrior |
Cluster-structured inclusion |
Legacy character strings ("Bernoulli", "Beta-Bernoulli", "Stochastic-Block") are still accepted on the R side but emit a lifecycle warning and are forwarded to the equivalent constructor.
When edge_selection = FALSE, no edge prior is instantiated and all edges remain included throughout sampling.
Factory dispatch
The create_edge_prior() factory in edge_prior.h maps an EdgePrior enum value to the concrete class. The enum is produced from the prior object’s family field by edge_prior_from_string() in common_helpers.h.
Execution order
Within each MCMC iteration, the edge prior update runs after the model’s edge indicator and parameter updates:
model.update_edge_indicators()— add/delete moves using the current inclusion probability matrixsampler->step(model, iteration)— parameter updatesedge_prior.update(...)— update the inclusion probability matrix based on the new edge indicators
The updated probabilities take effect in the next iteration’s edge indicator proposals.