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

Adaptive Gauss–Hermite Quadrature

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: 760
library(shiny)

ui <- fluidPage(
    titlePanel("Shinylive App for Adaptive Gauss–Hermite Quadrature"),
    sidebarLayout(
        sidebarPanel(
            width = 4,
            numericInput("mu_hat", "mu_hat (data location)", value = 8, step = 1),
            sliderInput("n_pts", "number of grid points in u (over 0 to 1)",
                        min = 2, max = 25, value = 9, step = 1),
            helpText("The grid is a midpoint rule, uniform in u = Phi((mu - ",
                     "mode_L)/se_L), where mode_L and se_L come from the ",
                     "Laplace approximation recomputed for the current ",
                     "mu_hat. Try mu_hat = 8 or 20 (values that broke the ",
                     "plain logistic grid in the earlier app) and watch the ",
                     "quadrature estimate stay close to the true value.")
        ),
        mainPanel(
            width = 8,
            plotOutput("squeeze", height = "560px")
        )
    )
)

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

    output$squeeze <- renderPlot({
        n_eff <- 3; sigma_prior <- 5
        L     <- function(mu) dnorm(input$mu_hat, mu, 1 / sqrt(n_eff))
        prior <- function(mu) dnorm(mu, 0, sigma_prior)
        z     <- function(mu) L(mu) * prior(mu)

        log_sum_exp <- function(lx) { m <- max(lx); m + log(sum(exp(lx - m))) }

        ### true value, exactly as in the Unit08 / earlier Unit09 apps
        log_marlik <- log(integrate(z, -Inf, Inf)$value)

        ### Laplace approximation for THIS mu_hat: mode and se define the transform
        h_neg <- function(mu) -(dnorm(input$mu_hat, mu, 1 / sqrt(n_eff), log = TRUE) +
                                   dnorm(mu, 0, sigma_prior, log = TRUE))
        op       <- nlm(h_neg, p = input$mu_hat, hessian = TRUE)
        lap_mode <- op$estimate
        lap_se   <- 1 / sqrt(op$hessian[1, 1])

        lo <- min(-8, input$mu_hat - 6)
        hi <- max(14, input$mu_hat + 6)
        mu_grid <- seq(lo, hi, length.out = 500)

        u_pts  <- (seq_len(input$n_pts) - 0.5) / input$n_pts   # midpoint rule, uniform on (0, 1)
        mu_pts <- qnorm(u_pts, lap_mode, lap_se)                # mu = Laplace-CDF^{-1}(u)

        ### quadrature estimate built from this Laplace-shaped grid
        log_z           <- function(mu) dnorm(input$mu_hat, mu, 1 / sqrt(n_eff), log = TRUE) +
            dnorm(mu, 0, sigma_prior, log = TRUE)
        h_step          <- 1 / input$n_pts
        log_jac         <- dnorm(mu_pts, lap_mode, lap_se, log = TRUE)   # log du/dmu
        log_marlik_quad <- log_sum_exp(log_z(mu_pts) - log_jac) + log(h_step)

        par(mar = c(4, 4, 2, 4))
        plot(mu_grid, z(mu_grid), type = "l", lwd = 3, col = "firebrick",
             xlab = expression(mu), ylab = expression(z(mu) == pi(mu) * L(mu)))
        polygon(c(mu_grid, rev(mu_grid)), c(z(mu_grid), rep(0, length(mu_grid))),
                col = adjustcolor("firebrick", 0.15), border = NA)
        segments(mu_pts, 0, mu_pts, z(mu_pts), col = "gray30", lwd = 1.5)
        points(mu_pts, z(mu_pts), pch = 19, col = "gray30")

        par(new = TRUE)
        plot(mu_grid, pnorm(mu_grid, lap_mode, lap_se), type = "l", lwd = 3, col = "steelblue",
             axes = FALSE, xlab = "", ylab = "", ylim = c(0, 1))
        axis(4, col.axis = "steelblue", col.ticks = "steelblue")
        mtext(expression(u == Phi((mu - hat(mu)[L]) / s[L])), side = 4, line = 2.5, col = "steelblue")

        guide_col <- adjustcolor("gray50", 0.6)
        segments(mu_pts, 0, mu_pts, u_pts, col = guide_col, lty = 3)
        segments(mu_pts, u_pts, max(mu_grid), u_pts, col = guide_col, lty = 3)
        points(mu_pts, u_pts, pch = 19, col = "gray30")

        legend("topleft", bty = "n", lwd = c(3, 3, NA, NA), col = c("firebrick", "steelblue", NA, NA),
               legend = c(expression(z(mu) == pi(mu) * L(mu)),
                          expression(u == Phi((mu - hat(mu)[L]) / s[L])),
                          as.expression(bquote("true"~log~P(y) == .(round(log_marlik, 3)))),
                          as.expression(bquote("quadrature, this grid"~log~P(y) == .(round(log_marlik_quad, 3))))))
    })
}

shinyApp(ui, server)

About the app

Shows how centring and scaling the quadrature grid with the Laplace approximation lets a few grid points capture the posterior wherever it sits. Because \(u_i\) is uniform in Laplace-normalized space, the grid automatically concentrates where the true posterior mass lives.

NoteR source for this app
library(shiny)

ui <- fluidPage(
    titlePanel("Shinylive App for Adaptive Gauss–Hermite Quadrature"),
    sidebarLayout(
        sidebarPanel(
            width = 4,
            numericInput("mu_hat", "mu_hat (data location)", value = 8, step = 1),
            sliderInput("n_pts", "number of grid points in u (over 0 to 1)",
                        min = 2, max = 25, value = 9, step = 1),
            helpText("The grid is a midpoint rule, uniform in u = Phi((mu - ",
                     "mode_L)/se_L), where mode_L and se_L come from the ",
                     "Laplace approximation recomputed for the current ",
                     "mu_hat. Try mu_hat = 8 or 20 (values that broke the ",
                     "plain logistic grid in the earlier app) and watch the ",
                     "quadrature estimate stay close to the true value.")
        ),
        mainPanel(
            width = 8,
            plotOutput("squeeze", height = "560px")
        )
    )
)

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

    output$squeeze <- renderPlot({
        n_eff <- 3; sigma_prior <- 5
        L     <- function(mu) dnorm(input$mu_hat, mu, 1 / sqrt(n_eff))
        prior <- function(mu) dnorm(mu, 0, sigma_prior)
        z     <- function(mu) L(mu) * prior(mu)

        log_sum_exp <- function(lx) { m <- max(lx); m + log(sum(exp(lx - m))) }

        ### true value, exactly as in the Unit08 / earlier Unit09 apps
        log_marlik <- log(integrate(z, -Inf, Inf)$value)

        ### Laplace approximation for THIS mu_hat: mode and se define the transform
        h_neg <- function(mu) -(dnorm(input$mu_hat, mu, 1 / sqrt(n_eff), log = TRUE) +
                                   dnorm(mu, 0, sigma_prior, log = TRUE))
        op       <- nlm(h_neg, p = input$mu_hat, hessian = TRUE)
        lap_mode <- op$estimate
        lap_se   <- 1 / sqrt(op$hessian[1, 1])

        lo <- min(-8, input$mu_hat - 6)
        hi <- max(14, input$mu_hat + 6)
        mu_grid <- seq(lo, hi, length.out = 500)

        u_pts  <- (seq_len(input$n_pts) - 0.5) / input$n_pts   # midpoint rule, uniform on (0, 1)
        mu_pts <- qnorm(u_pts, lap_mode, lap_se)                # mu = Laplace-CDF^{-1}(u)

        ### quadrature estimate built from this Laplace-shaped grid
        log_z           <- function(mu) dnorm(input$mu_hat, mu, 1 / sqrt(n_eff), log = TRUE) +
            dnorm(mu, 0, sigma_prior, log = TRUE)
        h_step          <- 1 / input$n_pts
        log_jac         <- dnorm(mu_pts, lap_mode, lap_se, log = TRUE)   # log du/dmu
        log_marlik_quad <- log_sum_exp(log_z(mu_pts) - log_jac) + log(h_step)

        par(mar = c(4, 4, 2, 4))
        plot(mu_grid, z(mu_grid), type = "l", lwd = 3, col = "firebrick",
             xlab = expression(mu), ylab = expression(z(mu) == pi(mu) * L(mu)))
        polygon(c(mu_grid, rev(mu_grid)), c(z(mu_grid), rep(0, length(mu_grid))),
                col = adjustcolor("firebrick", 0.15), border = NA)
        segments(mu_pts, 0, mu_pts, z(mu_pts), col = "gray30", lwd = 1.5)
        points(mu_pts, z(mu_pts), pch = 19, col = "gray30")

        par(new = TRUE)
        plot(mu_grid, pnorm(mu_grid, lap_mode, lap_se), type = "l", lwd = 3, col = "steelblue",
             axes = FALSE, xlab = "", ylab = "", ylim = c(0, 1))
        axis(4, col.axis = "steelblue", col.ticks = "steelblue")
        mtext(expression(u == Phi((mu - hat(mu)[L]) / s[L])), side = 4, line = 2.5, col = "steelblue")

        guide_col <- adjustcolor("gray50", 0.6)
        segments(mu_pts, 0, mu_pts, u_pts, col = guide_col, lty = 3)
        segments(mu_pts, u_pts, max(mu_grid), u_pts, col = guide_col, lty = 3)
        points(mu_pts, u_pts, pch = 19, col = "gray30")

        legend("topleft", bty = "n", lwd = c(3, 3, NA, NA), col = c("firebrick", "steelblue", NA, NA),
               legend = c(expression(z(mu) == pi(mu) * L(mu)),
                          expression(u == Phi((mu - hat(mu)[L]) / s[L])),
                          as.expression(bquote("true"~log~P(y) == .(round(log_marlik, 3)))),
                          as.expression(bquote("quadrature, this grid"~log~P(y) == .(round(log_marlik_quad, 3))))))
    })
}

shinyApp(ui, server)

This app accompanies Laplace Approximation in the book.