Shinylive Apps for Statistical Computation
    • All Apps

    • The Inverse CDF Method
    • Newton-Raphson on the Cauchy Likelihood
    • Safeguarded Newton-Raphson for Logistic Regression
    • Comparing Gradient Descent and Newton-Raphson Optimization Algorithms
    • Stochastic Gradient Descent
    • Nelder–Mead
    • The EM Algorithm for a Normal Mixture
    • Conjugate Bayesian Updating
    • A Quadrature Grid Missing the Posterior Mode
    • Adaptive Gauss–Hermite Quadrature
    • Envelope Tightness in Rejection Sampling
    • Rejection Sampling from a Gamma Density with a Cauchy Envelope
    • Adaptive Rejection Sampling
    • The Failure of Importance Sampling
    • Importance Sampling of a Student-t Interval
    • Sorted Importance Weights
    • Hamiltonian Monte Carlo Tuning

Rejection Sampling from a Gamma Density with a Cauchy Envelope

Author

Longhai Li

Published

October 6, 2026

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 610
library(shiny)

log_g_gamma <- function(x, alpha) {
    (alpha - 1) * (log(alpha - 1) - 1) - log(1 + (x - (alpha - 1))^2 / (2 * alpha - 1))
}
g_gamma <- function(x, alpha) exp(log_g_gamma(x, alpha))

ui <- fluidPage(
    titlePanel("Shinylive App for Rejection Sampling from a Gamma Density with a Cauchy Envelope"),
    sidebarLayout(
        sidebarPanel(
            width = 3,
            sliderInput("alpha", "shape alpha (target: Gamma(alpha, 1))",
                        min = 1.1, max = 8, value = 3, step = 0.05),
            helpText("The envelope is only valid (g >= f everywhere) for ",
                     "alpha > 2. Below alpha = 2 the target pokes through ",
                     "the envelope near the mode, and the histogram falls ",
                     "slightly short of the true density there."),
            actionButton("resample", "draw new sample")
        ),
        mainPanel(
            width = 9,
            plotOutput("panels", height = "420px")
        )
    )
)

server <- function(input, output, session) {

    draws <- eventReactive(list(input$alpha, input$resample), {
        set.seed(input$resample + 1)
        a <- input$alpha; n <- 2000
        out <- numeric(0); tries <- 0; max_tries <- 2e6
        while (length(out) < n && tries < max_tries) {
            m <- n - length(out)
            x <- rcauchy(m) * sqrt(2 * a - 1) + (a - 1)
            tries <- tries + m
            keep <- log(runif(m)) < dgamma(x, a, log = TRUE) - log_g_gamma(x, a)
            out <- c(out, x[keep])
        }
        list(x = out[seq_len(min(n, length(out)))], accept = length(out) / tries)
    })

    output$panels <- renderPlot({
        d <- draws(); a <- input$alpha
        hi <- max(15, qgamma(0.999, a) * 1.3)
        xs <- seq(1e-3, hi, length.out = 500)
        ymax <- max(dgamma(xs, a), na.rm = TRUE) * 1.6   # scale to the target's
        # peak, not the envelope's: g touches f at the mode but can be much
        # looser far from it (especially for alpha far above 2), which would
        # otherwise blow up the y-axis and hide the interesting region

        par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))

        plot(xs, g_gamma(xs, a), type = "l", lwd = 3, col = "steelblue",
             ylim = c(0, ymax), xlab = "x", ylab = "density",
             main = sprintf("target vs. envelope (alpha = %.2f)", a))
        lines(xs, dgamma(xs, a), lwd = 3, col = "firebrick")
        bad <- dgamma(xs, a) > g_gamma(xs, a)
        if (any(bad)) lines(xs[bad], dgamma(xs, a)[bad], col = "black", lwd = 5)
        legend("topright", bty = "n", lwd = c(3, 3, 5), col = c("firebrick", "steelblue", "black"),
               legend = c("target f", "envelope g", "f > g: invalid"))

        hist(d$x, breaks = 30, freq = FALSE, col = "steelblue", border = "white",
             xlim = c(0, hi), xlab = "x",
             main = sprintf("%d draws (accept rate %.1f%%)", length(d$x), 100 * d$accept))
        curve(dgamma(x, a), add = TRUE, lwd = 3, col = "firebrick")
        legend("topright", bty = "n", lwd = 3, col = "firebrick", legend = "true Gamma density")
    })
}

shinyApp(ui, server)

About the app

Shows how the efficiency of rejection sampling for the Gamma distribution changes with the shape parameter while the draws remain exact. The app above shows the target \(f(x)=\text{Gamma}(\alpha,1)\) density against the Cauchy-based envelope \(g(x)=\exp(\texttt{log\_g\_gamma}(x,\alpha))\) as \(\alpha\) is dragged (left), and a histogram of 2000 fresh rejection-sampled draws against the true density (right) — an exact match whenever the envelope is valid.

Drag \(\alpha\) below \(2\) and the target pokes through the envelope, as in Table 10.2; the crossing is a modest one for this particular envelope, so the histogram only dips slightly short of the curve in the affected region rather than breaking outright, but the sampler is no longer drawing from the exact target.

NoteR source for this app
library(shiny)

log_g_gamma <- function(x, alpha) {
    (alpha - 1) * (log(alpha - 1) - 1) - log(1 + (x - (alpha - 1))^2 / (2 * alpha - 1))
}
g_gamma <- function(x, alpha) exp(log_g_gamma(x, alpha))

ui <- fluidPage(
    titlePanel("Shinylive App for Rejection Sampling from a Gamma Density with a Cauchy Envelope"),
    sidebarLayout(
        sidebarPanel(
            width = 3,
            sliderInput("alpha", "shape alpha (target: Gamma(alpha, 1))",
                        min = 1.1, max = 8, value = 3, step = 0.05),
            helpText("The envelope is only valid (g >= f everywhere) for ",
                     "alpha > 2. Below alpha = 2 the target pokes through ",
                     "the envelope near the mode, and the histogram falls ",
                     "slightly short of the true density there."),
            actionButton("resample", "draw new sample")
        ),
        mainPanel(
            width = 9,
            plotOutput("panels", height = "420px")
        )
    )
)

server <- function(input, output, session) {

    draws <- eventReactive(list(input$alpha, input$resample), {
        set.seed(input$resample + 1)
        a <- input$alpha; n <- 2000
        out <- numeric(0); tries <- 0; max_tries <- 2e6
        while (length(out) < n && tries < max_tries) {
            m <- n - length(out)
            x <- rcauchy(m) * sqrt(2 * a - 1) + (a - 1)
            tries <- tries + m
            keep <- log(runif(m)) < dgamma(x, a, log = TRUE) - log_g_gamma(x, a)
            out <- c(out, x[keep])
        }
        list(x = out[seq_len(min(n, length(out)))], accept = length(out) / tries)
    })

    output$panels <- renderPlot({
        d <- draws(); a <- input$alpha
        hi <- max(15, qgamma(0.999, a) * 1.3)
        xs <- seq(1e-3, hi, length.out = 500)
        ymax <- max(dgamma(xs, a), na.rm = TRUE) * 1.6   # scale to the target's
        # peak, not the envelope's: g touches f at the mode but can be much
        # looser far from it (especially for alpha far above 2), which would
        # otherwise blow up the y-axis and hide the interesting region

        par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))

        plot(xs, g_gamma(xs, a), type = "l", lwd = 3, col = "steelblue",
             ylim = c(0, ymax), xlab = "x", ylab = "density",
             main = sprintf("target vs. envelope (alpha = %.2f)", a))
        lines(xs, dgamma(xs, a), lwd = 3, col = "firebrick")
        bad <- dgamma(xs, a) > g_gamma(xs, a)
        if (any(bad)) lines(xs[bad], dgamma(xs, a)[bad], col = "black", lwd = 5)
        legend("topright", bty = "n", lwd = c(3, 3, 5), col = c("firebrick", "steelblue", "black"),
               legend = c("target f", "envelope g", "f > g: invalid"))

        hist(d$x, breaks = 30, freq = FALSE, col = "steelblue", border = "white",
             xlim = c(0, hi), xlab = "x",
             main = sprintf("%d draws (accept rate %.1f%%)", length(d$x), 100 * d$accept))
        curve(dgamma(x, a), add = TRUE, lwd = 3, col = "firebrick")
        legend("topright", bty = "n", lwd = 3, col = "firebrick", legend = "true Gamma density")
    })
}

shinyApp(ui, server)

This app accompanies Rejection Sampling in the book.