Skip to content

Commit 470cba6

Browse files
fix(prior-sampler): acceptance targets, honest family reporting, dead knobs
Brief 33 of the August 2026 audit. Report: dev/review-2026-08/reports/33-sampler-knobs.md Constituent changes: - fix(prior-sampler): remove the no-op step_size argument of sample_ggm_prior() - fix(prior-sampler): forward bgm()'s acceptance target to the joint prior chain - fix(prior-sampler): report the graph-prior family sample_graph_prior() ran - chore(dead-code): drop the unused gradient parameters in the HMC utilities
1 parent afb7594 commit 470cba6

17 files changed

Lines changed: 253 additions & 135 deletions

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ released from this line yet.
4141
follows it, as the measurement that says whether the flag is noise rather than
4242
as the first thing to try. `vignette("diagnostics")` gives the same order.
4343

44+
## Deprecations
45+
46+
* The `step_size` argument of `sample_ggm_prior()` is deprecated and ignored.
47+
The NUTS warmup has always replaced it with its own heuristic value, and the
48+
returned `step_size` field reported a number the run did not use; the field
49+
has been removed.
50+
4451
## Documentation
4552

4653
* New vignette, `vignette("fast-ggm")`, on the Gaussian graphical model route

R/RcppExports.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ ggm_test_forward_map <- function(theta, edge_indicators) {
4545
.Call(`_bgms_ggm_test_forward_map`, theta, edge_indicators)
4646
}
4747

48-
sample_ggm_prior_cpp <- function(p, n_samples, n_warmup = 1000L, pairwise_scale = 2.5, interaction_prior_type = "cauchy", scale_prior_type = "gamma", gamma_shape = 1.0, gamma_rate = 1.0, step_size = 0.1, max_depth = 10L, seed = 1L, verbose = TRUE, edge_indicators_nullable = NULL, delta = 0.0) {
49-
.Call(`_bgms_sample_ggm_prior`, p, n_samples, n_warmup, pairwise_scale, interaction_prior_type, scale_prior_type, gamma_shape, gamma_rate, step_size, max_depth, seed, verbose, edge_indicators_nullable, delta)
48+
sample_ggm_prior_cpp <- function(p, n_samples, n_warmup = 1000L, pairwise_scale = 2.5, interaction_prior_type = "cauchy", scale_prior_type = "gamma", gamma_shape = 1.0, gamma_rate = 1.0, max_depth = 10L, seed = 1L, verbose = TRUE, edge_indicators_nullable = NULL, delta = 0.0) {
49+
.Call(`_bgms_sample_ggm_prior`, p, n_samples, n_warmup, pairwise_scale, interaction_prior_type, scale_prior_type, gamma_shape, gamma_rate, max_depth, seed, verbose, edge_indicators_nullable, delta)
5050
}
5151

5252
.compute_ess_cpp <- function(array3d) {

R/sample_ggm_prior.R

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@
6767
#' \code{s}). Default: \code{exponential_prior(eta = 1)}; with the
6868
#' default \code{normal_prior(scale = 1)} interaction prior this
6969
#' resolves to \eqn{K_{ii}/2 \sim \textrm{Exponential}(1)}.
70-
#' @param step_size Positive numeric. Initial NUTS step size used to seed
71-
#' dual-averaging adaptation. Default \code{0.1}. Used only for
72-
#' \code{spec = "conditional"} (NUTS path); ignored for the
73-
#' \code{"joint"} MH path.
70+
#' @param step_size `r lifecycle::badge("deprecated")` Deprecated and ignored.
71+
#' The NUTS warmup determines its own initial step size; earlier versions
72+
#' accepted this argument but never used it.
7473
#' @param max_depth Integer. Maximum NUTS tree depth. Default \code{10}.
7574
#' Used only for \code{spec = "conditional"}.
7675
#' @param seed Integer. RNG seed for the chain. Default \code{1L}.
@@ -85,9 +84,13 @@
8584
#' \eqn{(K, \Gamma)} jointly from the un-normalised joint prior), or
8685
#' \code{"hierarchical"} (sample \eqn{(K, \Gamma)} from the per-graph
8786
#' normalized specification via the Z-ratio approximation).
88-
#' @param edge_inclusion_prob Probability in \eqn{(0, 1)} for the
89-
#' Bernoulli edge prior used when \code{spec = "joint"}. Default
90-
#' \code{0.5}. Ignored when \code{spec = "conditional"}.
87+
#' @param edge_inclusion_prob Probability in \eqn{(0, 1)} for the Bernoulli
88+
#' edge prior the chain falls back to when \code{edge_prior} is
89+
#' \code{NULL}. Default \code{0.5}. Read only when \code{spec = "joint"}
90+
#' or \code{"hierarchical"} \emph{and} \code{edge_prior = NULL}: it is
91+
#' ignored for \code{spec = "conditional"} (the graph is fixed) and
92+
#' whenever an \code{edge_prior} object is supplied, including
93+
#' \code{bernoulli_prior()}, which carries its own probability.
9194
#' @param update_method One of \code{"adaptive-metropolis"} (default) or
9295
#' \code{"gibbs"}. Sampler driving the \code{spec = "joint"} chain; the
9396
#' Gibbs chain uses the conjugate row and edge updates and needs no
@@ -97,13 +100,18 @@
97100
#' or \code{\link{sbm_prior}()}, or \code{NULL} (default) for a Bernoulli
98101
#' prior with probability \code{edge_inclusion_prob}. Only for
99102
#' \code{spec = "joint"}.
100-
#' @param apply_correction Logical. For the hierarchical edge priors
101-
#' (\code{beta_bernoulli_prior()}, \code{sbm_prior()}), apply the
102-
#' normalizing-constant correction to the hyperparameter updates (default
103-
#' \code{TRUE}; the correction table is built from the tilted prior
104-
#' sampler and cached across calls). With \code{FALSE} the plain conjugate
105-
#' updates are used, whose hyperparameter marginals do not match the
106-
#' hyperpriors under the determinant tilt.
103+
#' @param apply_correction Logical. Apply the normalizing-constant correction
104+
#' to the hyperparameter updates (default \code{TRUE}; the correction table
105+
#' is built from the tilted prior sampler and cached across calls). With
106+
#' \code{FALSE} the plain conjugate updates are used, whose hyperparameter
107+
#' marginals do not match the hyperpriors under the determinant tilt. Read
108+
#' only when \code{spec = "joint"} \emph{and} the edge prior is a
109+
#' hierarchical one (\code{beta_bernoulli_prior()}, \code{sbm_prior()});
110+
#' it is ignored for \code{spec = "conditional"} (the graph is fixed), for
111+
#' \code{spec = "hierarchical"} (the per-edge Z-ratio carries the
112+
#' normalizer instead, and the hyperparameter updates stay clean
113+
#' conjugate), and for a Bernoulli edge prior (no hyperparameters to
114+
#' correct).
107115
#' @param zratio_diagnostics Logical (default \code{TRUE}). Only for
108116
#' \code{spec = "hierarchical"}: run the trust gauge
109117
#' (\code{\link{summarize_zratio_gauge}}) on the returned chain and attach
@@ -196,7 +204,7 @@ sample_ggm_prior = function(
196204
n_warmup = 2e3,
197205
interaction_prior = normal_prior(scale = 1),
198206
precision_scale_prior = exponential_prior(eta = 1),
199-
step_size = 0.1,
207+
step_size = lifecycle::deprecated(),
200208
max_depth = 10L,
201209
seed = 1L,
202210
verbose = TRUE,
@@ -211,6 +219,15 @@ sample_ggm_prior = function(
211219
) {
212220
spec = match.arg(spec)
213221
update_method = match.arg(update_method)
222+
if(lifecycle::is_present(step_size)) {
223+
lifecycle::deprecate_warn(
224+
"0.2.1", "sample_ggm_prior(step_size = )",
225+
details = paste0(
226+
"The NUTS warmup determines its own initial step size; the supplied ",
227+
"value was never used."
228+
)
229+
)
230+
}
214231
ep = if(is.null(edge_prior)) {
215232
NULL
216233
} else {
@@ -235,7 +252,6 @@ sample_ggm_prior = function(
235252
validate_integer(n_samples, "n_samples", min_value = 1L)
236253
validate_integer(n_warmup, "n_warmup", min_value = 0L)
237254
validate_integer(max_depth, "max_depth", min_value = 1L)
238-
validate_finite_scalar(step_size, "step_size", positive = TRUE)
239255
validate_integer(seed, "seed", min_value = 0L)
240256
if(is.null(delta)) {
241257
delta = 0.5 * log(p)
@@ -277,7 +293,6 @@ sample_ggm_prior = function(
277293
scale_prior_type = sp$scale_prior_type,
278294
gamma_shape = sp$scale_shape,
279295
gamma_rate = sp$scale_rate,
280-
step_size = step_size,
281296
max_depth = as.integer(max_depth),
282297
seed = as.integer(seed),
283298
verbose = verbose,
@@ -367,6 +382,16 @@ sample_ggm_prior = function(
367382
no_chains = 1L,
368383
edge_selection = TRUE,
369384
sampler_type = update_method,
385+
# Same target bgm() resolves for this update method (validate_sampler()):
386+
# 0.44 is the componentwise RW MH optimum the adaptive-metropolis chain
387+
# tunes its between-model proposal SDs to, and gibbs tunes nothing. The
388+
# C++ default is 0.80, the NUTS target, so leaving this out gave the
389+
# prior chain a different proposal tuning than the deployed path this
390+
# function is the SBC reference for.
391+
target_acceptance = switch(update_method,
392+
"adaptive-metropolis" = 0.44,
393+
"gibbs" = NA_real_
394+
),
370395
seed = as.integer(seed),
371396
no_threads = 1L,
372397
progress_type = if(verbose) 2L else 0L,
@@ -545,16 +570,6 @@ validate_integer = function(x, name, min_value = 1L) {
545570
invisible(as.integer(x))
546571
}
547572

548-
validate_finite_scalar = function(x, name, positive = FALSE) {
549-
if(!is.numeric(x) || length(x) != 1L || is.na(x) || !is.finite(x)) {
550-
stop(sprintf("'%s' must be a single finite numeric.", name))
551-
}
552-
if(positive && x <= 0) {
553-
stop(sprintf("'%s' must be positive.", name))
554-
}
555-
invisible(x)
556-
}
557-
558573
validate_ggm_prior_edge_indicators = function(edge_indicators, p) {
559574
if(is.null(edge_indicators)) {
560575
return(NULL)

R/sample_graph_prior.R

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ graph_prior_conditioning = function(ep, p, theta, allocations, block_probs) {
142142
#' a Bernoulli or Beta-Bernoulli prior, and \code{allocations} plus
143143
#' \code{block_probs} fix the block structure of a Stochastic-Block prior
144144
#' (reducing it to independent pair flips at the given block probabilities).
145+
#' Either way the graph law that is sampled is a Bernoulli prior at the fixed
146+
#' pair probabilities, under both specifications, so the returned
147+
#' \code{edge_prior} reports \code{"Bernoulli"} and the requested family is
148+
#' returned separately in \code{requested_edge_prior}.
145149
#'
146150
#' Under \code{spec = "joint"} the tilted graph law depends on the precision
147151
#' prior through its normalizer, so \code{interaction_prior},
@@ -192,8 +196,16 @@ graph_prior_conditioning = function(ep, p, theta, allocations, block_probs) {
192196
#' \item{\code{allocations}}{Only with an unconditioned
193197
#' \code{sbm_prior()}: integer matrix (\code{n_samples x p}) of sampled
194198
#' block allocations.}
195-
#' \item{\code{spec}, \code{edge_prior}, \code{p}}{The specification,
196-
#' edge-prior family, and node count of the draw.}
199+
#' \item{\code{edge_prior}}{The edge-prior family the draw actually ran.
200+
#' This is \code{"Bernoulli"} whenever \code{theta} or
201+
#' (\code{allocations}, \code{block_probs}) was supplied, because
202+
#' conditioning fixes the hyperparameters and leaves independent pair
203+
#' flips, whichever family was requested.}
204+
#' \item{\code{requested_edge_prior}}{The family of the \code{edge_prior}
205+
#' argument. Equal to \code{edge_prior} unless a conditioning argument
206+
#' was supplied.}
207+
#' \item{\code{spec}, \code{p}}{The specification and node count of the
208+
#' draw.}
197209
#' }
198210
#'
199211
#' @examples
@@ -256,6 +268,13 @@ sample_graph_prior = function(
256268
ep = unpack_indicator_prior(edge_prior, num_variables = p)
257269
cond_prob = graph_prior_conditioning(ep, p, theta, allocations, block_probs)
258270

271+
# Conditioning fixes the hyperparameters, which collapses either spec to
272+
# independent pair flips at `cond_prob` -- a Bernoulli prior, whichever
273+
# family was requested. Report the family that was run and keep the request
274+
# alongside it, rather than labelling the draw with a hierarchical family
275+
# whose hyperparameters never moved.
276+
run_edge_prior = if(is.null(cond_prob)) ep$edge_prior else "Bernoulli"
277+
259278
pairs = graph_pair_indices(p)
260279
pair_names = paste0(pairs[, 1L], "-", pairs[, 2L])
261280

@@ -282,7 +301,8 @@ sample_graph_prior = function(
282301
theta = draws$theta,
283302
allocations = draws$allocations,
284303
spec = spec,
285-
edge_prior = ep$edge_prior,
304+
edge_prior = run_edge_prior,
305+
requested_edge_prior = ep$edge_prior,
286306
p = p
287307
)
288308
return(out)
@@ -330,7 +350,8 @@ sample_graph_prior = function(
330350
theta = res$theta,
331351
allocations = res$allocations,
332352
spec = spec,
333-
edge_prior = ep$edge_prior,
353+
edge_prior = run_edge_prior,
354+
requested_edge_prior = ep$edge_prior,
334355
p = p
335356
)
336357
}

man/sample_ggm_prior.Rd

Lines changed: 23 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/sample_graph_prior.Rd

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ BEGIN_RCPP
219219
END_RCPP
220220
}
221221
// sample_ggm_prior
222-
Rcpp::List sample_ggm_prior(int p, int n_samples, int n_warmup, double pairwise_scale, const std::string& interaction_prior_type, const std::string& scale_prior_type, double gamma_shape, double gamma_rate, double step_size, int max_depth, int seed, bool verbose, Rcpp::Nullable<Rcpp::IntegerMatrix> edge_indicators_nullable, double delta);
223-
RcppExport SEXP _bgms_sample_ggm_prior(SEXP pSEXP, SEXP n_samplesSEXP, SEXP n_warmupSEXP, SEXP pairwise_scaleSEXP, SEXP interaction_prior_typeSEXP, SEXP scale_prior_typeSEXP, SEXP gamma_shapeSEXP, SEXP gamma_rateSEXP, SEXP step_sizeSEXP, SEXP max_depthSEXP, SEXP seedSEXP, SEXP verboseSEXP, SEXP edge_indicators_nullableSEXP, SEXP deltaSEXP) {
222+
Rcpp::List sample_ggm_prior(int p, int n_samples, int n_warmup, double pairwise_scale, const std::string& interaction_prior_type, const std::string& scale_prior_type, double gamma_shape, double gamma_rate, int max_depth, int seed, bool verbose, Rcpp::Nullable<Rcpp::IntegerMatrix> edge_indicators_nullable, double delta);
223+
RcppExport SEXP _bgms_sample_ggm_prior(SEXP pSEXP, SEXP n_samplesSEXP, SEXP n_warmupSEXP, SEXP pairwise_scaleSEXP, SEXP interaction_prior_typeSEXP, SEXP scale_prior_typeSEXP, SEXP gamma_shapeSEXP, SEXP gamma_rateSEXP, SEXP max_depthSEXP, SEXP seedSEXP, SEXP verboseSEXP, SEXP edge_indicators_nullableSEXP, SEXP deltaSEXP) {
224224
BEGIN_RCPP
225225
Rcpp::RObject rcpp_result_gen;
226226
Rcpp::RNGScope rcpp_rngScope_gen;
@@ -232,13 +232,12 @@ BEGIN_RCPP
232232
Rcpp::traits::input_parameter< const std::string& >::type scale_prior_type(scale_prior_typeSEXP);
233233
Rcpp::traits::input_parameter< double >::type gamma_shape(gamma_shapeSEXP);
234234
Rcpp::traits::input_parameter< double >::type gamma_rate(gamma_rateSEXP);
235-
Rcpp::traits::input_parameter< double >::type step_size(step_sizeSEXP);
236235
Rcpp::traits::input_parameter< int >::type max_depth(max_depthSEXP);
237236
Rcpp::traits::input_parameter< int >::type seed(seedSEXP);
238237
Rcpp::traits::input_parameter< bool >::type verbose(verboseSEXP);
239238
Rcpp::traits::input_parameter< Rcpp::Nullable<Rcpp::IntegerMatrix> >::type edge_indicators_nullable(edge_indicators_nullableSEXP);
240239
Rcpp::traits::input_parameter< double >::type delta(deltaSEXP);
241-
rcpp_result_gen = Rcpp::wrap(sample_ggm_prior(p, n_samples, n_warmup, pairwise_scale, interaction_prior_type, scale_prior_type, gamma_shape, gamma_rate, step_size, max_depth, seed, verbose, edge_indicators_nullable, delta));
240+
rcpp_result_gen = Rcpp::wrap(sample_ggm_prior(p, n_samples, n_warmup, pairwise_scale, interaction_prior_type, scale_prior_type, gamma_shape, gamma_rate, max_depth, seed, verbose, edge_indicators_nullable, delta));
242241
return rcpp_result_gen;
243242
END_RCPP
244243
}
@@ -999,7 +998,7 @@ static const R_CallMethodDef CallEntries[] = {
999998
{"_bgms_ggm_test_gibbs_sweep", (DL_FUNC) &_bgms_ggm_test_gibbs_sweep, 8},
1000999
{"_bgms_ggm_test_logp_and_gradient", (DL_FUNC) &_bgms_ggm_test_logp_and_gradient, 5},
10011000
{"_bgms_ggm_test_forward_map", (DL_FUNC) &_bgms_ggm_test_forward_map, 2},
1002-
{"_bgms_sample_ggm_prior", (DL_FUNC) &_bgms_sample_ggm_prior, 14},
1001+
{"_bgms_sample_ggm_prior", (DL_FUNC) &_bgms_sample_ggm_prior, 13},
10031002
{"_bgms_compute_ess_cpp", (DL_FUNC) &_bgms_compute_ess_cpp, 1},
10041003
{"_bgms_compute_rhat_cpp", (DL_FUNC) &_bgms_compute_rhat_cpp, 1},
10051004
{"_bgms_compute_indicator_ess_cpp", (DL_FUNC) &_bgms_compute_indicator_ess_cpp, 1},

0 commit comments

Comments
 (0)