Chapter A11: Implementation Companion for Independent Normal-Gamma

Kjell Nygren

2026-04-30

library(glmbayes)

Chapter A11: Implementation Companion for Independent Normal-Gamma

1. Introduction

This vignette documents the implementation of the independent Normal-Gamma extension for Gaussian regression, focusing on the internal workflow of:

  1. rIndepNormalGammaReg (non-_std) - the top-level director of traffic for the independent Normal-Gamma sampling path; and
  2. EnvelopeOrchestrator - which composes the coefficient-envelope construction (EnvelopeBuild) with the dispersion-aware refinement (EnvelopeDispersionBuild).

The discussion then deep-dives into the accept-reject sampler used after the envelope is constructed: rIndepNormalGammaReg_std (serial) / rIndepNormalGammaReg_std_parallel (parallel).

Theory justification and the underlying accept-reject / envelope construction are described in Chapter A07 (the independent Normal-Gamma extension theory) and in (Nygren and Nygren 2006) for the base likelihood subgradient approach. Implementation of independent Normal–Gamma regression is discussed in (Griffin and Brown 2010).

For the fixed-dispersion coefficient-envelope implementation details, see Chapter A05.

Prior construction and the Gaussian calibration of shape, rate, dispersion, and coefficient covariances returned by Prior_Setup() are derived in Chapter A12 (https://knygren.r-universe.dev/articles/glmbayes/Chapter-A12.html).

2. Core Function #1: rIndepNormalGammaReg (non-_std) - Internal Workflow

The entry point in C++ is src/rIndepNormalGammaReg.cpp, function rIndepNormalGammaReg(...). At a high level, it performs the following stages:

  1. Dispersion centering / refinement via EnvelopeCentering() to obtain:
    • dispersion2 (the dispersion anchor used for the envelope), and
    • RSS_Post2 (the expected posterior weighted RSS used to update the Gamma precision parameters).
  2. Posterior mode optimization via optim() (BFGS) to obtain:
    • posterior mode bstar, and
    • Hessian A1.
  3. Model standardization via glmb_Standardize_Model() to obtain standardized inputs for envelope building: bstar2, A, x2_std, mu2_std, P2_std, and transform matrices for back-transformation.
  4. Envelope construction via EnvelopeOrchestrator() (Step D):
    • coefficient envelope via EnvelopeBuild,
    • dispersion-aware refinement via EnvelopeDispersionBuild, and
    • sorting of envelope components via EnvelopeSort.
  5. Sampling by delegating to the standardized accept-reject sampler:
    • rIndepNormalGammaReg_std (serial) or
    • rIndepNormalGammaReg_std_parallel (parallel), depending on use_parallel and n.
  6. Back-transform to the original parameterization and assemble outputs.

2.1 Stage A: EnvelopeCentering() for dispersion2 and RSS_Post2

rIndepNormalGammaReg calls:

centering_out = EnvelopeCentering(y, x, mu, P, offset, wt, shape, rate, Gridtype, verbose)
dispersion2 = centering_out$dispersion
RSS_Post2   = centering_out$RSS_post

In implementation terms, EnvelopeCentering(): - initializes dispersion2 from a weighted residual variance computed from lm.wfit, - iteratively refines dispersion2 using a fixed-point style loop, and - updates the Gamma parameters using the expected posterior weighted RSS.

The key output for the dispersion-aware refinement is RSS_Post2, which drives the precision-rate update inside EnvelopeOrchestrator.

2.2 Stage B: Posterior mode (bstar) and Hessian (A1)

After centering, rIndepNormalGammaReg performs posterior mode optimization using R’s optim() with: - BFGS, - supplied functions f2 and f3 (negative log-posterior and its gradient), - hessian = TRUE to obtain a Hessian estimate A1.

The mode bstar and curvature A1 are then used to standardize the model into a form suitable for envelope construction.

2.3 Stage C: Standardization (glmb_Standardize_Model)

glmb_Standardize_Model() transforms the model so that the prior mean is zero and the posterior curvature is represented in a standardized coordinate system.

In implementation terms, the code extracts: - bstar2 (standardized mode), - A (standardized Hessian / precision contribution), - x2_std, mu2_std, P2_std (standardized design and prior components), - L2Inv, L3Inv (matrices used later to back-transform sampled coefficients).

For the full theoretical and implementation details of standardization, see Chapter A05.

2.4 Stage D: EnvelopeOrchestrator() - envelope + dispersion refinement

rIndepNormalGammaReg delegates the construction of the coefficient envelope and the dispersion-aware extension to EnvelopeOrchestrator.

It passes: - standardized mode and curvature objects (bstar2, A), - standardized design/prior terms (x2_std, mu2_std, P2_std), - offsets/weights (alpha, wt), - envelope grid controls (n, Gridtype, n_envopt), - Gamma prior hyperparameters (shape, rate), - and centering outputs (RSS_Post2, plus bounds / truncation controls).

The orchestrator returns: - Env (sorted envelope object including the PLSD component), - gamma_list (tilt parameters and dispersion bounds), - UB_list (the various constants used by the sampler accept-reject test), - and additional diagnostics including low / upp.

2.5 Stage E: Delegation to rIndepNormalGammaReg_std / _parallel

After envelope construction, the code selects: - serial sampler rIndepNormalGammaReg_std(...), or - parallel sampler rIndepNormalGammaReg_std_parallel(...), based on use_parallel and n.

This is where the accept-reject loop actually executes and where iters_out is recorded.

2.6 Stage F: Back-transform and return values

The standardized samples beta_out are mapped back to the original parameterization using: - the inverse standardization transforms (L2Inv, L3Inv), - and the prior mean shift (mu).

The returned structure mirrors the expected R-level contract: - samples (coefficients and dispersion), - plus runtime / iteration diagnostics (iters_out, weight_out).

3. Core Function #2: EnvelopeOrchestrator - Composition of Envelope Steps

src/EnvelopeOrchestrator.cpp provides a single orchestrator that composes:

  1. EnvelopeBuild() - fixed-dispersion coefficient envelope, and
  2. EnvelopeDispersionBuild() - dispersion-aware refinement for the independent Normal-Gamma extension, followed by:
  3. EnvelopeSort() on the R side.

3.1 Orchestrator inputs and internal Gamma anchoring

Inside EnvelopeOrchestrator, the Gamma posterior parameters for the dispersion precision are computed from: - the prior hyperparameters (shape, rate), - and the centering output RSS_Post2.

The implementation computes: - shape2 = shape + n_w/2, - rate3 = rate + RSS_Post2/2, - and the dispersion anchor: d1_star = rate3 / (shape2 - 1).

This anchor is used to rescale weights (wt2 = wt / d1_star) when building the coefficient envelope.

3.2 Step 1: EnvelopeBuild() call (coefficient-envelope)

The orchestrator calls EnvelopeBuild(...) in C++ with: - standardized mode (bstar2), - standardized curvature / precision object (A), - standardized design/prior pieces (x2, mu2, P2), - offsets (alpha) and weight scaling (wt2), - family/link fixed to Gaussian/identity.

Implementation detail worth noting: the orchestrator uses internal policy around grid size and sorting to avoid redundant work when followed by the dispersion-aware build.

3.3 Step 2 (Deep Dive): EnvelopeDispersionBuild() - dispersion-aware extension

This subsection focuses on EnvelopeDispersionBuild() as the extension-specific computational workhorse for dispersion.

3.3.1 Inputs and the role of RSS_Post2

EnvelopeDispersionBuild receives: - the coefficient-envelope object Env from EnvelopeBuild, - prior hyperparameters (shape, rate), - the standardized model pieces (P, y, x2, alpha, mu, wt), - the number of observations, - and critically: - RSS_post = the centering output RSS_Post2, - RSS_ML (currently NA in this path).

At a conceptual level, RSS_post determines where the dispersion-axis Gamma proposal is anchored and how the dispersion refinement changes the proposal shape/rate parameters.

3.3.2 Dispersion interval construction (low, upp)

The implementation uses: - max_disp_perc (default 0.99) as a central truncation mass level, - and optional disp_lower / disp_upper overrides, to produce the final dispersion truncation interval: [low, upp] on the dispersion scale.

This interval becomes the domain over which all envelope components must dominate.

3.3.3 Anchor dispersion and face slopes

The function computes an anchor dispersion (often the mode of the surrogate Gamma distribution on the dispersion scale) and uses it to evaluate: - face slopes / gradient-related quantities used in the dispersion-axis refinement.

This anchor is used to extrapolate dispersion-dependent face constants to both ends of the dispersion interval.

3.3.4 RSS/UB minimization and construction of UB_list

The dispersion-aware machinery constructs the constants required by the accept-reject sampler: - global residual-minimum quantity RSS_Min, - per-face UB2min, - along with the various log/linear constants and diagnostic maxima used in the acceptance bound.

These populate UB_list, which is later consumed by rIndepNormalGammaReg_std to compute: - UB1, UB2, UB3A, UB3B, - and the final accept/reject test statistic.

3.3.5 Gamma tilt parameters (gamma_list)

Finally, EnvelopeDispersionBuild updates the Gamma proposal parameters through the dispersion-axis correction logic. It returns: - shape3 and rate2-type parameters for the precision proposal, - plus the resulting dispersion bounds (disp_lower, disp_upper), and a collection of diagnostics fields.

3.3.6 Returned object summary

The main return object is a list with: - Env_out (the updated envelope with dispersion-aware PLSD / mixture structure), - UB_list (accept-reject constants and UB diagnostics), - gamma_list (Gamma proposal tilt params), - and additional diagnostics (e.g., anchor value, computed maxima, UB minima).

This entire structure is consumed by the sampler in rIndepNormalGammaReg_std.

3.4 Step 3: EnvelopeSort() (component reordering)

EnvelopeOrchestrator then calls the R function EnvelopeSort(...) to reorder components based on probability mass in a way that improves simulation runtime.

The sorted envelope and associated UB constants are carried forward into the accept-reject simulation.

4. Simulation Execution: rIndepNormalGammaReg_std - Serial Accept-Reject Loop

After EnvelopeOrchestrator() returns the sorted envelope together with the dispersion proposal parameters and the accept-reject constants, rIndepNormalGammaReg_std generates iid posterior draws using an envelope-based rejection sampler.

Conceptually, each accepted draw consists of two pieces:

  1. a regression coefficient vector beta proposed from an envelope-restricted multivariate normal (face-wise), and
  2. a dispersion/precision draw dispersion (via a truncated inverse-Gamma proposal).

The accept-reject decision is then made by checking that a bound constructed from UB_list and envelope-derived tangency quantities dominates the target density.

4.1 Precomputation (Inv_f3_precompute_disp)

Before the per-draw loop, the sampler builds a cache once:

cache = Inv_f3_precompute_disp(cbars, y, x, mu, P, alpha, wt)

This cache is reused inside the accept-reject loop so that repeated evaluations at dispersion-dependent tangency points avoid rebuilding expensive matrices.

4.2 Proposal generation (per accepted draw)

For each draw index i, the sampler repeats a while loop until the proposal is accepted:

  1. Select an envelope face (component)
    • Draw an index J from the discrete distribution defined by PLSD.
  2. Propose regression coefficients beta on the restricted interval
    • For each coordinate j = 1,...,p, sample from the restricted normal using the envelope bounds loglt[J, ] and logrt[J, ].
    • The restricted normal is centered using the face-specific anchoring values -cbars(J, j).
  3. Propose dispersion
    • Draw dispersion from the truncated proposal using: rinvgamma_ct_safe(shape3, rate2, disp_upper, disp_lower).
  4. Update dispersion-dependent tangency quantities
    • With the proposed dispersion, recompute the dispersion-adjusted tangency point(s) by calling: Inv_f3_with_disp(cache, dispersion, cbars_small).
    • Evaluate the log-likelihood at both:
      • the updated tangency point (stored as LL_New2), and
      • the candidate beta (stored as LL_Test). The likelihood evaluations use weights adjusted as wt2 = wt / dispersion.

4.3 Accept-reject test statistic

With the proposal (beta, dispersion) and the chosen face J, the sampler computes the acceptance bound via four non-negative surplus terms:

  • UB1: tangent-plane style surplus using LL_New2 and the dispersion-adjusted anchoring (cbars and tangency points).
  • UB2: RSS-based surplus derived from rss_face_at_disp(...) and the global/per-face RSS minima RSS_Min and UB2min[J].
  • UB3A: face-specific surplus term involving lg_prob_factor[J] and the function g1_face_at_disp(...).
  • UB3B: dispersion-axis correction term involving the log-tilt components and precomputed maxima.

Implementation-wise, it forms:

test1 = LL_Test - UB1
test  = test1 - (UB2 + UB3A + UB3B)
test  = test - log_U2   (where U2 ~ Uniform(0,1))
accept if test >= 0

Equivalently, since log_U2 = log(U2):

accept iff  log(U2) <= (LL_Test - UB1) - (UB2 + UB3A + UB3B).

The implementation also enforces sign constraints expected from a valid dominating envelope:

  • test1 <= 0,
  • UB2 >= 0 (with extra tolerance/diagnostics when UB2 is numerically tiny),
  • UB3A >= 0 and UB3B >= 0.

If any sign constraint is violated, the code throws a runtime error with context to help diagnose invalid envelope constants.

4.4 Record outputs

Once accepted:

The fields below are the ones from the envelope/UB structures that directly drive the sampler: - PLSD: selects the face index J; - loglt / logrt and cbars: define the restricted normal proposal for beta (and the face-centering); - shape3, rate2, disp_lower, disp_upper: define the dispersion proposal; - RSS_Min, UB2min, lg_prob_factor, lmc1, lmc2, and related precomputed maxima: define UB1-UB3B in the acceptance test.

  • beta_out[i, ] stores the accepted coefficients,
  • disp_out[i] stores the accepted dispersion draw,
  • iters_out[i] records how many rejection attempts were needed for that accepted draw.

5. Dataflow: From EnvelopeOrchestrator Outputs to the Sampler

This section is a “developer dataflow map” connecting EnvelopeOrchestrator / EnvelopeDispersionBuild outputs to the serial accept-reject sampler.

5.1 What the sampler reads from Env

The sampler reads from Env: - PLSD (mixture component selection weights), - loglt / logrt (restricted normal coordinate bounds), - logP / LLconst and related constants used internally, - cbars (face gradients used to center restricted normals).

5.2 What the sampler reads from gamma_list / UB_list

The sampler reads from:

  • gamma_list:
    • shape3 / rate2 parameters,
    • disp_lower / disp_upper truncation parameters on the dispersion proposal.
  • UB_list:
    • RSS_Min,
    • UB2min,
    • and the global constants used for computing UB3A and UB3B,
    • plus diagnostic maxima used in these corrections.

This is the key interface that ensures the dispersion-aware envelope dominates the target on the allowed truncation domain.

6. Practical Knobs and Diagnostics

6.1 Tuning parameters

Key arguments affecting the internal workflow: - n (number of iid draws) - affects how often sorting / envelope sizing is amortized, - Gridtype and n_envopt - controls envelope grid complexity, - use_parallel and use_opencl - controls whether the sampling and/or envelope evaluation is parallelized / accelerated, - max_disp_perc and optional disp_lower / disp_upper - controls truncation interval tightness.

6.2 Interpreting iters_out

The vector iters_out captures the number of accept-reject attempts per accepted draw. Large values indicate a loose envelope (or expensive bounds), while values near 1 suggest a tight envelope and efficient acceptance.

6.3 Minimal examples

For runnable examples that exercise the envelope construction and sampler path, see: - Chapter A07 (independent Normal-Gamma extension theory), - Chapter A05 (fixed-dispersion envelope build), - and the inst/examples/ directory for end-to-end usage patterns.

7. Cross-References

References

Griffin, Jim E., and Philip J. Brown. 2010. “Inference with Normal-Gamma Prior Distributions in Regression Problems.” Bayesian Analysis 5 (1): 171–88. https://doi.org/10.1214/10-BA507.
Nygren, K. N., and L. M. Nygren. 2006. Likelihood Subgradient Densities.” Journal of the American Statistical Association 101 (475): 1144–56. https://doi.org/10.1198/016214506000000357.