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

Newton-Raphson on the Cauchy Likelihood

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

### ---------- Cauchy location model ------------------------------------------
## Negative log-likelihood of a location theta for data x (the constant
## n*log(pi) is dropped), its gradient g = L' and its curvature J = L''.
nll  <- function(th, x)
    vapply(th, function(t) sum(log1p((x - t)^2)), numeric(1))
gfun <- function(th, x)
    vapply(th, function(t) -sum(2 * (x - t) / (1 + (x - t)^2)), numeric(1))
jfun <- function(th, x)
    vapply(th, function(t) sum(2 * (1 - (x - t)^2) / (1 + (x - t)^2)^2),
           numeric(1))

### ---------- robustification of the curvature -------------------------------
## The Newton step is -g / J.  Where J <= 0 (the tails) it points uphill, and
## where J is close to 0 it is enormous.  Two repairs replace J by J~ > 0:
##   ridge   : J~ = J + tau, with tau >= 0 the smallest value giving
##             J~ >= rho * n/2   (Levenberg-Marquardt; in one dimension this
##             is the same as flooring the curvature at rho * n/2)
##   scoring : J~ = n/2, the expected information of n Cauchy observations
adjust_curv <- function(J, method, n, rho) {
    switch(method,
           none    = J,
           ridge   = J + max(0, rho * n / 2 - J),
           scoring = n / 2)
}

### ---------- Newton-Raphson path --------------------------------------------
## Returns the iterates with L, g, J, the curvature actually used (J~), the
## proposed step and the fraction s of it that was accepted.  Stops on
## convergence, or when the iteration breaks down.
newton_path <- function(th0, x, maxit = 25, halve = FALSE, robust = "none",
                        rho = 0.2, tol = 1e-9) {
    n <- length(x)
    cn <- c("theta", "nll", "g", "J", "Jt", "step", "s")
    P <- matrix(NA_real_, maxit + 1, length(cn), dimnames = list(NULL, cn))
    fill <- function(i, th) {
        J <- jfun(th, x)
        P[i, 1:5] <<- c(th, nll(th, x), gfun(th, x), J,
                        adjust_curv(J, robust, n, rho))
    }
    th <- th0; fill(1, th)
    n_ok <- 1; note <- "maximum number of iterations reached"
    for (i in seq_len(maxit)) {
        g <- P[i, 3]; Jt <- P[i, 5]
        if (abs(g) < 1e-12) { note <- "converged: the gradient is zero"; break }
        if (!is.finite(Jt) || Jt == 0) {
            note <- "the curvature is zero: the Newton step is undefined"
            break
        }
        step <- -g / Jt
        P[i, 6] <- step
        s <- 1
        ## step halving: shrink s until the Armijo condition
        ## L(theta + s*step) <= L(theta) + c1 * s * g * step holds
        if (halve) {
            if (g * step >= 0) {
                note <- paste("the step points uphill (the curvature is not",
                              "positive), so halving cannot help: stopped")
                break
            }
            f0 <- P[i, 2]
            while (s > 1e-12) {
                fn <- nll(th + s * step, x)
                if (is.finite(fn) && fn <= f0 + 1e-4 * s * g * step) break
                s <- s / 2
            }
            if (s <= 1e-12) { note <- "halving found no decrease: stopped"; break }
        }
        th <- th + s * step
        if (!is.finite(th)) { note <- "the iterate is no longer finite: diverged"; break }
        P[i, 7] <- s
        fill(i + 1, th)
        n_ok <- i + 1
        if (abs(th) > 1e6) { note <- "diverged: the iterate has run off to infinity"; break }
        if (abs(s * step) < tol) { note <- "converged"; break }
    }
    list(P = P[seq_len(n_ok), , drop = FALSE], n = n_ok, note = note)
}

### ---------- UI --------------------------------------------------------------
## an icon-only playback button; the title shows as a tooltip
ctrl_btn <- function(id, icon_name, title, class = "btn-default")
    actionButton(id, NULL, icon = icon(icon_name), title = title, class = class,
                 style = "flex: 1; padding: 6px 0;")

ui <- fluidPage(
    titlePanel("Shinylive App for Newton-Raphson on the Cauchy Likelihood"),
    sidebarLayout(
        sidebarPanel(
            width = 4,
            radioButtons("data", "data",
                         c("one observation, x = 0" = "single",
                           "random Cauchy sample"   = "sample")),
            conditionalPanel(
                "input.data == 'sample'",
                div(style = "display: flex; gap: 10px;",
                    div(style = "flex: 1;",
                        numericInput("n", "sample size n", value = 5, min = 1,
                                     max = 50, step = 1, width = "100%")),
                    div(style = "flex: 1;",
                        numericInput("seed", "random seed", value = 1,
                                     step = 1, width = "100%")))),
            numericInput("th0", HTML("starting value &theta;<sub>0</sub>"),
                         value = 1.2, step = 0.1, width = "100%"),
            helpText("Or click inside either plot to choose a new starting ",
                     "value."),
            selectInput("halve", "step length",
                        c("full Newton step"            = "none",
                          "step halving (Armijo)"       = "halve")),
            selectInput("robust", "robustification of the Hessian",
                        c("none (observed curvature J)"   = "none",
                          "ridge: J + τ, so that J~ ≥ ε" = "ridge",
                          "Fisher scoring: J~ = n/2"      = "scoring")),
            conditionalPanel(
                "input.robust == 'ridge'",
                sliderInput("rho", HTML("floor &epsilon; as a fraction of n/2"),
                            min = 0.02, max = 1, value = 0.2, step = 0.02)),
            sliderInput("W", "half-width of the plotted window", min = 2,
                        max = 30, value = 6, step = 1),
            checkboxInput("compare", "overlay the plain Newton-Raphson path",
                          TRUE),
            checkboxInput("autoplay", "autoplay when the start changes", TRUE),
            sliderInput("iter", "iteration", min = 0, max = 1, value = 0,
                        step = 1),
            div(style = "display: flex; gap: 4px;",
                ctrl_btn("first", "backward-fast", "Back to the start"),
                ctrl_btn("back",  "backward-step", "One step back"),
                ctrl_btn("play",  "play",          "Play", class = "btn-primary"),
                ctrl_btn("pause", "pause",         "Pause"),
                ctrl_btn("step",  "forward-step",  "One step forward"),
                ctrl_btn("toend", "forward-fast",  "To the end")),
            hr(),
            helpText("With one observation at 0, plain Newton-Raphson ",
                     "converges only from starts with |θ₀| < ",
                     "1/√3 ≈ 0.58. Try 0.5, then 0.8 and 1.2, ",
                     "first with both safeguards off, then with each in turn: ",
                     "halving alone stalls once the curvature turns ",
                     "negative (|θ| > 1), because the step then points ",
                     "uphill; a positive curvature J~ repairs the direction, ",
                     "and halving then tames its length.")
        ),
        mainPanel(
            width = 8,
            plotOutput("pL", click = "clickL", height = "260px"),
            plotOutput("pg", click = "clickg", height = "260px"),
            hr(),
            div(style = "max-width: 640px;",
                tableOutput("tab"),
                htmlOutput("note"))
        )
    )
)

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

    playing <- reactiveVal(FALSE)
    play_ms <- 900

    ## clicking either plot moves the starting value
    set_start <- function(cl) {
        if (!is.null(cl) && is.finite(cl$x))
            updateNumericInput(session, "th0", value = round(cl$x, 2))
    }
    observeEvent(input$clickL, set_start(input$clickL))
    observeEvent(input$clickg, set_start(input$clickg))

    x <- reactive({
        if (input$data == "single") return(0)
        req(input$n, input$seed)
        set.seed(input$seed)
        rcauchy(max(1, round(input$n)), location = 0)
    })

    ## the plotted window is centred on the median of the data
    win <- reactive(median(x()) + c(-1, 1) * input$W)

    start <- reactive({ req(is.finite(input$th0)); input$th0 })

    modified <- reactive(input$halve != "none" || input$robust != "none")

    path <- reactive(newton_path(start(), x(), halve = input$halve == "halve",
                                 robust = input$robust,
                                 rho = if (is.null(input$rho)) 0.2 else input$rho))

    ## the plain (unsafeguarded) path, shown for comparison
    ref <- reactive({
        if (!isTRUE(input$compare) || !modified()) return(NULL)
        newton_path(start(), x())
    })

    ## curves on a fixed grid over the window
    grid <- reactive({
        th <- seq(win()[1], win()[2], length.out = 600)
        list(th = th, L = nll(th, x()), g = gfun(th, x()))
    })

    ## current iteration, clamped to the length of the path
    k <- reactive(min(as.integer(input$iter), path()$n - 1))

    ## a new path restarts the display at iteration 0
    observeEvent(path(), {
        updateSliderInput(session, "iter", max = max(1, path()$n - 1), value = 0)
        playing(isTRUE(input$autoplay) && path()$n > 1)
    })

    go_to <- function(i) {
        updateSliderInput(session, "iter",
                          value = max(0, min(i, path()$n - 1)))
    }

    observeEvent(input$play, {
        if (k() >= path()$n - 1) go_to(0)
        if (path()$n > 1) playing(TRUE)
    })
    observeEvent(input$pause, playing(FALSE))
    observeEvent(input$step,  { playing(FALSE); go_to(k() + 1) })
    observeEvent(input$back,  { playing(FALSE); go_to(k() - 1) })
    observeEvent(input$first, { playing(FALSE); go_to(0) })
    observeEvent(input$toend, { playing(FALSE); go_to(path()$n - 1) })

    observe({
        if (!isTRUE(playing())) return()
        kk <- isolate(k())
        if (kk >= isolate(path()$n) - 1) { playing(FALSE); return() }
        invalidateLater(play_ms, session)
        isolate(go_to(kk + 1))
    })

    in_win <- function(v) v >= win()[1] & v <= win()[2]

    ## the marks shared by both plots: data, the grid minimum, the plain path
    base_marks <- function() {
        rug(x(), col = "darkgreen", lwd = 2, ticksize = 0.05)
    }

    output$pL <- renderPlot({
        G <- grid(); P <- path()$P; kk <- k() + 1L; RP <- ref()
        par(mar = c(4, 4.5, 2, 1))
        plot(G$th, G$L, type = "l", lwd = 2, col = "grey30",
             xlab = expression(theta), ylab = expression(L(theta)),
             main = "negative log-likelihood and the Newton iterates")
        base_marks()
        i0 <- which.min(G$L)
        points(G$th[i0], G$L[i0], pch = 4, col = "darkgreen", lwd = 2, cex = 1.3)

        if (!is.null(RP)) {
            Q <- RP$P[seq_len(min(kk, RP$n)), , drop = FALSE]
            Q <- Q[in_win(Q[, 1]), , drop = FALSE]
            if (nrow(Q) > 1) lines(Q[, 1], Q[, 2], col = "grey45", lty = 2, lwd = 2)
            points(Q[, 1], Q[, 2], pch = 21, bg = "grey85", col = "grey45", cex = 0.9)
        }
        Q <- P[1:kk, , drop = FALSE]; Q <- Q[in_win(Q[, 1]), , drop = FALSE]
        if (nrow(Q) > 1) lines(Q[, 1], Q[, 2], col = "steelblue", lwd = 2)
        points(Q[, 1], Q[, 2], pch = 21, bg = "steelblue", cex = 1.1)

        cur <- P[kk, ]
        if (in_win(cur[1])) {
            points(cur[1], cur[2], pch = 21, bg = "firebrick", cex = 1.8)
        } else {
            mtext(sprintf("current iterate (theta = %.3g) is outside the plotted window",
                          cur[1]), side = 3, line = -1.5, col = "firebrick",
                  cex = 0.9)
        }
        leg <- c("current iterate", "minimum in the window", "data")
        legend("top", bg = "#ffffffcc", box.col = NA, cex = 0.85, ncol = 3,
               pch = c(21, 4, NA), pt.bg = c("firebrick", NA, NA),
               lwd = c(NA, 2, 2), lty = c(NA, NA, 1),
               col = c("black", "darkgreen", "darkgreen"), legend = leg)
    })

    output$pg <- renderPlot({
        G <- grid(); P <- path()$P; kk <- k() + 1L; RP <- ref()
        par(mar = c(4, 4.5, 2, 1))
        plot(G$th, G$g, type = "l", lwd = 2, col = "grey30",
             xlab = expression(theta), ylab = expression(g(theta)),
             main = "gradient and the tangent (Newton) steps")
        abline(h = 0, lty = 3)
        base_marks()

        ## each step follows the line through (theta_k, g_k) whose slope is
        ## the curvature that was used, J~, down to the horizontal axis
        draw_steps <- function(Q, col, lwd, lty) {
            m <- nrow(Q)
            if (m < 2) return()
            for (i in seq_len(m - 1)) {
                segments(Q[i, 1], Q[i, 3], Q[i, 1] + Q[i, 6], 0,
                         col = col, lwd = lwd, lty = lty)
                if (isTRUE(Q[i, 7] < 1))      # halved: mark where the full step lands
                    points(Q[i, 1] + Q[i, 6], 0, pch = 4, col = col, cex = 0.9)
                else
                    segments(Q[i + 1, 1], 0, Q[i + 1, 1], Q[i + 1, 3],
                             col = "grey40", lty = 3)
            }
        }
        if (!is.null(RP)) {
            Q <- RP$P[seq_len(min(kk, RP$n)), , drop = FALSE]
            draw_steps(Q, "grey65", 1.5, 2)
            Q <- Q[in_win(Q[, 1]), , drop = FALSE]
            points(Q[, 1], Q[, 3], pch = 21, bg = "grey85", col = "grey45", cex = 0.9)
        }
        draw_steps(P[1:kk, , drop = FALSE], "steelblue", 2, 1)
        Q <- P[1:kk, , drop = FALSE]; Q <- Q[in_win(Q[, 1]), , drop = FALSE]
        points(Q[, 1], Q[, 3], pch = 21, bg = "steelblue", cex = 1.1)
        cur <- P[kk, ]
        if (in_win(cur[1])) {
            points(cur[1], cur[3], pch = 21, bg = "firebrick", cex = 1.8)
        } else {
            mtext(sprintf("current iterate (theta = %.3g) is outside the plotted window",
                          cur[1]), side = 3, line = -1.5, col = "firebrick",
                  cex = 0.9)
        }
    })

    ## significant digits throughout; a diverging run reaches values that would
    ## otherwise be far too wide
    fs <- function(v) ifelse(!is.finite(v), "", formatC(v, digits = 4, format = "g"))

    ## why the run ended, shown as one line under the table
    output$note <- renderUI({
        R <- path(); RP <- ref()
        if (k() + 1 < R$n) return(NULL)
        HTML(paste0("<i>", R$note,
                    if (!is.null(RP)) paste0("; plain Newton-Raphson: ", RP$note),
                    "</i>"))
    })

    output$tab <- renderTable({
        P <- path()$P[seq_len(k() + 1), , drop = FALSE]
        d <- data.frame(iteration = as.character(seq_len(nrow(P)) - 1),
                        theta = fs(P[, 1]), `L(theta)` = fs(P[, 2]),
                        g = fs(P[, 3]), J = fs(P[, 4]), `J~ used` = fs(P[, 5]),
                        `step kept` = ifelse(is.na(P[, 7]), "", fs(P[, 7])),
                        check.names = FALSE, stringsAsFactors = FALSE)
        tail(d, 6)
    }, rownames = FALSE, align = "r", width = "100%")
}

shinyApp(ui, server)

About the app

Shows how the Newton-Raphson step diverges on the univariate Cauchy location likelihood, and how step halving and a robustified Hessian each repair it. The upper plot is the negative log-likelihood \(L(\theta) = \sum_i \log\{1 + (x_i - \theta)^2\}\) with the iterates marked on it; the lower plot is its gradient \(g = L'\), where each Newton step is drawn as the line through \((\theta_k, g_k)\) whose slope is the curvature used, followed down to the horizontal axis.

Click in either plot (or type a value) to choose the starting value \(\theta_0\), then use the playback buttons to step through the iterations or play the path as an animation. With one observation at \(0\) the gradient \(g(\theta) = 2\theta/(1+\theta^2)\) flattens in the tails and the curvature \(J = L''\) turns negative for \(|\theta| > 1\), so the tangent points away from the minimum and the iterates run off to infinity; plain Newton-Raphson converges only from starts with \(|\theta_0| < 1/\sqrt{3}\). Choose a random sample instead to see the same behaviour on a likelihood with several local minima.

The two safeguards act on different parts of the step \(-g/\tilde J\). Step halving shortens a step until the Armijo condition holds, so it tames the length but cannot fix a step that points uphill. Robustifying the Hessian replaces \(J\) by a positive \(\tilde J\), which fixes the direction: ridge adds the smallest \(\tau \ge 0\) that makes \(J + \tau \ge \varepsilon\), and Fisher scoring uses the expected information \(n/2\). In one dimension ridging and flooring the curvature coincide. Switching on either keeps the plain iteration visible as a dashed grey path for comparison; the details of each iteration, and the reason the run ended, are reported under the plots.

NoteR source for this app
library(shiny)

### ---------- Cauchy location model ------------------------------------------
## Negative log-likelihood of a location theta for data x (the constant
## n*log(pi) is dropped), its gradient g = L' and its curvature J = L''.
nll  <- function(th, x)
    vapply(th, function(t) sum(log1p((x - t)^2)), numeric(1))
gfun <- function(th, x)
    vapply(th, function(t) -sum(2 * (x - t) / (1 + (x - t)^2)), numeric(1))
jfun <- function(th, x)
    vapply(th, function(t) sum(2 * (1 - (x - t)^2) / (1 + (x - t)^2)^2),
           numeric(1))

### ---------- robustification of the curvature -------------------------------
## The Newton step is -g / J.  Where J <= 0 (the tails) it points uphill, and
## where J is close to 0 it is enormous.  Two repairs replace J by J~ > 0:
##   ridge   : J~ = J + tau, with tau >= 0 the smallest value giving
##             J~ >= rho * n/2   (Levenberg-Marquardt; in one dimension this
##             is the same as flooring the curvature at rho * n/2)
##   scoring : J~ = n/2, the expected information of n Cauchy observations
adjust_curv <- function(J, method, n, rho) {
    switch(method,
           none    = J,
           ridge   = J + max(0, rho * n / 2 - J),
           scoring = n / 2)
}

### ---------- Newton-Raphson path --------------------------------------------
## Returns the iterates with L, g, J, the curvature actually used (J~), the
## proposed step and the fraction s of it that was accepted.  Stops on
## convergence, or when the iteration breaks down.
newton_path <- function(th0, x, maxit = 25, halve = FALSE, robust = "none",
                        rho = 0.2, tol = 1e-9) {
    n <- length(x)
    cn <- c("theta", "nll", "g", "J", "Jt", "step", "s")
    P <- matrix(NA_real_, maxit + 1, length(cn), dimnames = list(NULL, cn))
    fill <- function(i, th) {
        J <- jfun(th, x)
        P[i, 1:5] <<- c(th, nll(th, x), gfun(th, x), J,
                        adjust_curv(J, robust, n, rho))
    }
    th <- th0; fill(1, th)
    n_ok <- 1; note <- "maximum number of iterations reached"
    for (i in seq_len(maxit)) {
        g <- P[i, 3]; Jt <- P[i, 5]
        if (abs(g) < 1e-12) { note <- "converged: the gradient is zero"; break }
        if (!is.finite(Jt) || Jt == 0) {
            note <- "the curvature is zero: the Newton step is undefined"
            break
        }
        step <- -g / Jt
        P[i, 6] <- step
        s <- 1
        ## step halving: shrink s until the Armijo condition
        ## L(theta + s*step) <= L(theta) + c1 * s * g * step holds
        if (halve) {
            if (g * step >= 0) {
                note <- paste("the step points uphill (the curvature is not",
                              "positive), so halving cannot help: stopped")
                break
            }
            f0 <- P[i, 2]
            while (s > 1e-12) {
                fn <- nll(th + s * step, x)
                if (is.finite(fn) && fn <= f0 + 1e-4 * s * g * step) break
                s <- s / 2
            }
            if (s <= 1e-12) { note <- "halving found no decrease: stopped"; break }
        }
        th <- th + s * step
        if (!is.finite(th)) { note <- "the iterate is no longer finite: diverged"; break }
        P[i, 7] <- s
        fill(i + 1, th)
        n_ok <- i + 1
        if (abs(th) > 1e6) { note <- "diverged: the iterate has run off to infinity"; break }
        if (abs(s * step) < tol) { note <- "converged"; break }
    }
    list(P = P[seq_len(n_ok), , drop = FALSE], n = n_ok, note = note)
}

### ---------- UI --------------------------------------------------------------
## an icon-only playback button; the title shows as a tooltip
ctrl_btn <- function(id, icon_name, title, class = "btn-default")
    actionButton(id, NULL, icon = icon(icon_name), title = title, class = class,
                 style = "flex: 1; padding: 6px 0;")

ui <- fluidPage(
    titlePanel("Shinylive App for Newton-Raphson on the Cauchy Likelihood"),
    sidebarLayout(
        sidebarPanel(
            width = 4,
            radioButtons("data", "data",
                         c("one observation, x = 0" = "single",
                           "random Cauchy sample"   = "sample")),
            conditionalPanel(
                "input.data == 'sample'",
                div(style = "display: flex; gap: 10px;",
                    div(style = "flex: 1;",
                        numericInput("n", "sample size n", value = 5, min = 1,
                                     max = 50, step = 1, width = "100%")),
                    div(style = "flex: 1;",
                        numericInput("seed", "random seed", value = 1,
                                     step = 1, width = "100%")))),
            numericInput("th0", HTML("starting value &theta;<sub>0</sub>"),
                         value = 1.2, step = 0.1, width = "100%"),
            helpText("Or click inside either plot to choose a new starting ",
                     "value."),
            selectInput("halve", "step length",
                        c("full Newton step"            = "none",
                          "step halving (Armijo)"       = "halve")),
            selectInput("robust", "robustification of the Hessian",
                        c("none (observed curvature J)"   = "none",
                          "ridge: J + τ, so that J~ ≥ ε" = "ridge",
                          "Fisher scoring: J~ = n/2"      = "scoring")),
            conditionalPanel(
                "input.robust == 'ridge'",
                sliderInput("rho", HTML("floor &epsilon; as a fraction of n/2"),
                            min = 0.02, max = 1, value = 0.2, step = 0.02)),
            sliderInput("W", "half-width of the plotted window", min = 2,
                        max = 30, value = 6, step = 1),
            checkboxInput("compare", "overlay the plain Newton-Raphson path",
                          TRUE),
            checkboxInput("autoplay", "autoplay when the start changes", TRUE),
            sliderInput("iter", "iteration", min = 0, max = 1, value = 0,
                        step = 1),
            div(style = "display: flex; gap: 4px;",
                ctrl_btn("first", "backward-fast", "Back to the start"),
                ctrl_btn("back",  "backward-step", "One step back"),
                ctrl_btn("play",  "play",          "Play", class = "btn-primary"),
                ctrl_btn("pause", "pause",         "Pause"),
                ctrl_btn("step",  "forward-step",  "One step forward"),
                ctrl_btn("toend", "forward-fast",  "To the end")),
            hr(),
            helpText("With one observation at 0, plain Newton-Raphson ",
                     "converges only from starts with |θ₀| < ",
                     "1/√3 ≈ 0.58. Try 0.5, then 0.8 and 1.2, ",
                     "first with both safeguards off, then with each in turn: ",
                     "halving alone stalls once the curvature turns ",
                     "negative (|θ| > 1), because the step then points ",
                     "uphill; a positive curvature J~ repairs the direction, ",
                     "and halving then tames its length.")
        ),
        mainPanel(
            width = 8,
            plotOutput("pL", click = "clickL", height = "260px"),
            plotOutput("pg", click = "clickg", height = "260px"),
            hr(),
            div(style = "max-width: 640px;",
                tableOutput("tab"),
                htmlOutput("note"))
        )
    )
)

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

    playing <- reactiveVal(FALSE)
    play_ms <- 900

    ## clicking either plot moves the starting value
    set_start <- function(cl) {
        if (!is.null(cl) && is.finite(cl$x))
            updateNumericInput(session, "th0", value = round(cl$x, 2))
    }
    observeEvent(input$clickL, set_start(input$clickL))
    observeEvent(input$clickg, set_start(input$clickg))

    x <- reactive({
        if (input$data == "single") return(0)
        req(input$n, input$seed)
        set.seed(input$seed)
        rcauchy(max(1, round(input$n)), location = 0)
    })

    ## the plotted window is centred on the median of the data
    win <- reactive(median(x()) + c(-1, 1) * input$W)

    start <- reactive({ req(is.finite(input$th0)); input$th0 })

    modified <- reactive(input$halve != "none" || input$robust != "none")

    path <- reactive(newton_path(start(), x(), halve = input$halve == "halve",
                                 robust = input$robust,
                                 rho = if (is.null(input$rho)) 0.2 else input$rho))

    ## the plain (unsafeguarded) path, shown for comparison
    ref <- reactive({
        if (!isTRUE(input$compare) || !modified()) return(NULL)
        newton_path(start(), x())
    })

    ## curves on a fixed grid over the window
    grid <- reactive({
        th <- seq(win()[1], win()[2], length.out = 600)
        list(th = th, L = nll(th, x()), g = gfun(th, x()))
    })

    ## current iteration, clamped to the length of the path
    k <- reactive(min(as.integer(input$iter), path()$n - 1))

    ## a new path restarts the display at iteration 0
    observeEvent(path(), {
        updateSliderInput(session, "iter", max = max(1, path()$n - 1), value = 0)
        playing(isTRUE(input$autoplay) && path()$n > 1)
    })

    go_to <- function(i) {
        updateSliderInput(session, "iter",
                          value = max(0, min(i, path()$n - 1)))
    }

    observeEvent(input$play, {
        if (k() >= path()$n - 1) go_to(0)
        if (path()$n > 1) playing(TRUE)
    })
    observeEvent(input$pause, playing(FALSE))
    observeEvent(input$step,  { playing(FALSE); go_to(k() + 1) })
    observeEvent(input$back,  { playing(FALSE); go_to(k() - 1) })
    observeEvent(input$first, { playing(FALSE); go_to(0) })
    observeEvent(input$toend, { playing(FALSE); go_to(path()$n - 1) })

    observe({
        if (!isTRUE(playing())) return()
        kk <- isolate(k())
        if (kk >= isolate(path()$n) - 1) { playing(FALSE); return() }
        invalidateLater(play_ms, session)
        isolate(go_to(kk + 1))
    })

    in_win <- function(v) v >= win()[1] & v <= win()[2]

    ## the marks shared by both plots: data, the grid minimum, the plain path
    base_marks <- function() {
        rug(x(), col = "darkgreen", lwd = 2, ticksize = 0.05)
    }

    output$pL <- renderPlot({
        G <- grid(); P <- path()$P; kk <- k() + 1L; RP <- ref()
        par(mar = c(4, 4.5, 2, 1))
        plot(G$th, G$L, type = "l", lwd = 2, col = "grey30",
             xlab = expression(theta), ylab = expression(L(theta)),
             main = "negative log-likelihood and the Newton iterates")
        base_marks()
        i0 <- which.min(G$L)
        points(G$th[i0], G$L[i0], pch = 4, col = "darkgreen", lwd = 2, cex = 1.3)

        if (!is.null(RP)) {
            Q <- RP$P[seq_len(min(kk, RP$n)), , drop = FALSE]
            Q <- Q[in_win(Q[, 1]), , drop = FALSE]
            if (nrow(Q) > 1) lines(Q[, 1], Q[, 2], col = "grey45", lty = 2, lwd = 2)
            points(Q[, 1], Q[, 2], pch = 21, bg = "grey85", col = "grey45", cex = 0.9)
        }
        Q <- P[1:kk, , drop = FALSE]; Q <- Q[in_win(Q[, 1]), , drop = FALSE]
        if (nrow(Q) > 1) lines(Q[, 1], Q[, 2], col = "steelblue", lwd = 2)
        points(Q[, 1], Q[, 2], pch = 21, bg = "steelblue", cex = 1.1)

        cur <- P[kk, ]
        if (in_win(cur[1])) {
            points(cur[1], cur[2], pch = 21, bg = "firebrick", cex = 1.8)
        } else {
            mtext(sprintf("current iterate (theta = %.3g) is outside the plotted window",
                          cur[1]), side = 3, line = -1.5, col = "firebrick",
                  cex = 0.9)
        }
        leg <- c("current iterate", "minimum in the window", "data")
        legend("top", bg = "#ffffffcc", box.col = NA, cex = 0.85, ncol = 3,
               pch = c(21, 4, NA), pt.bg = c("firebrick", NA, NA),
               lwd = c(NA, 2, 2), lty = c(NA, NA, 1),
               col = c("black", "darkgreen", "darkgreen"), legend = leg)
    })

    output$pg <- renderPlot({
        G <- grid(); P <- path()$P; kk <- k() + 1L; RP <- ref()
        par(mar = c(4, 4.5, 2, 1))
        plot(G$th, G$g, type = "l", lwd = 2, col = "grey30",
             xlab = expression(theta), ylab = expression(g(theta)),
             main = "gradient and the tangent (Newton) steps")
        abline(h = 0, lty = 3)
        base_marks()

        ## each step follows the line through (theta_k, g_k) whose slope is
        ## the curvature that was used, J~, down to the horizontal axis
        draw_steps <- function(Q, col, lwd, lty) {
            m <- nrow(Q)
            if (m < 2) return()
            for (i in seq_len(m - 1)) {
                segments(Q[i, 1], Q[i, 3], Q[i, 1] + Q[i, 6], 0,
                         col = col, lwd = lwd, lty = lty)
                if (isTRUE(Q[i, 7] < 1))      # halved: mark where the full step lands
                    points(Q[i, 1] + Q[i, 6], 0, pch = 4, col = col, cex = 0.9)
                else
                    segments(Q[i + 1, 1], 0, Q[i + 1, 1], Q[i + 1, 3],
                             col = "grey40", lty = 3)
            }
        }
        if (!is.null(RP)) {
            Q <- RP$P[seq_len(min(kk, RP$n)), , drop = FALSE]
            draw_steps(Q, "grey65", 1.5, 2)
            Q <- Q[in_win(Q[, 1]), , drop = FALSE]
            points(Q[, 1], Q[, 3], pch = 21, bg = "grey85", col = "grey45", cex = 0.9)
        }
        draw_steps(P[1:kk, , drop = FALSE], "steelblue", 2, 1)
        Q <- P[1:kk, , drop = FALSE]; Q <- Q[in_win(Q[, 1]), , drop = FALSE]
        points(Q[, 1], Q[, 3], pch = 21, bg = "steelblue", cex = 1.1)
        cur <- P[kk, ]
        if (in_win(cur[1])) {
            points(cur[1], cur[3], pch = 21, bg = "firebrick", cex = 1.8)
        } else {
            mtext(sprintf("current iterate (theta = %.3g) is outside the plotted window",
                          cur[1]), side = 3, line = -1.5, col = "firebrick",
                  cex = 0.9)
        }
    })

    ## significant digits throughout; a diverging run reaches values that would
    ## otherwise be far too wide
    fs <- function(v) ifelse(!is.finite(v), "", formatC(v, digits = 4, format = "g"))

    ## why the run ended, shown as one line under the table
    output$note <- renderUI({
        R <- path(); RP <- ref()
        if (k() + 1 < R$n) return(NULL)
        HTML(paste0("<i>", R$note,
                    if (!is.null(RP)) paste0("; plain Newton-Raphson: ", RP$note),
                    "</i>"))
    })

    output$tab <- renderTable({
        P <- path()$P[seq_len(k() + 1), , drop = FALSE]
        d <- data.frame(iteration = as.character(seq_len(nrow(P)) - 1),
                        theta = fs(P[, 1]), `L(theta)` = fs(P[, 2]),
                        g = fs(P[, 3]), J = fs(P[, 4]), `J~ used` = fs(P[, 5]),
                        `step kept` = ifelse(is.na(P[, 7]), "", fs(P[, 7])),
                        check.names = FALSE, stringsAsFactors = FALSE)
        tail(d, 6)
    }, rownames = FALSE, align = "r", width = "100%")
}

shinyApp(ui, server)

This app accompanies Optimization for Maximum Likelihood Estimation in the book.