---
title: "Chapter A11: Implementation Companion for Independent Normal-Gamma"
author: "Kjell Nygren"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Chapter A11: Implementation Companion for Independent Normal-Gamma}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
bibliography: REFERENCES.bib
reference-section-title: References
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
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 [@Nygren2006] for the base likelihood subgradient approach. Implementation of independent Normal--Gamma regression is discussed in [@GriffinBrown2010].

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:

```r
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:

```r
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:

```text
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)`:

```text
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

- Chapter A05 for coefficient-envelope construction (`EnvelopeBuild`) and standardization (`glmb_Standardize_Model`).
- Chapter A07 for the independent Normal-Gamma extension theory and the accept-reject decomposition.
- Chapter A06 for dispersion accept-reject foundations in other Gaussian families (as relevant).

