Skip to content

Instantly share code, notes, and snippets.

@nanxstats
Created February 8, 2026 21:57
Show Gist options
  • Select an option

  • Save nanxstats/803f3b4ac000a2e23c86d69ad1b0d360 to your computer and use it in GitHub Desktop.

Select an option

Save nanxstats/803f3b4ac000a2e23c86d69ad1b0d360 to your computer and use it in GitHub Desktop.

EXTREMEZ and grid range analysis

Setup

library(gsDesign)

Grid range implied by r

The upper end of the Jennison-Turnbull grid in src/gridpts.c is:

$$ z_{\max}(r) = 3 + 4 \log(r) $$

grid_limit <- function(r) 3 + 4 * log(r)

grid_tbl <- data.frame(
  r = c(18, 50, 70, 71, 80),
  z_max = grid_limit(c(18, 50, 70, 71, 80)),
  exceeds_20 = grid_limit(c(18, 50, 70, 71, 80)) > 20
)

signif(grid_tbl, 8)
#>    r    z_max exceeds_20
#> 1 18 14.56149          0
#> 2 50 18.64809          0
#> 3 70 19.99398          0
#> 4 71 20.05072          1
#> 5 80 20.52811          1

Direct checks discussed:

rdbl <- 80
3 + 4 * log(rdbl)
#> [1] 20.52811

rdbl <- 70
3 + 4 * log(rdbl)
#> [1] 19.99398

Skipped-look scenario sensitivity across r

Use a design with trimmed efficacy spending so the first look is effectively "no testing" (upper bound at 20).

skip_summary <- function(r) {
  x <- gsDesign(
    k = 4,
    n.fix = 100,
    sfu = sfTrimmed,
    sfupar = list(sf = sfHSD, param = 1, trange = c(0.3, 0.9)),
    r = r
  )

  data.frame(
    r = r,
    b1 = x$upper$bound[1],
    b2 = x$upper$bound[2],
    p1_h0 = x$upper$prob[1, 1],
    p2_h0 = x$upper$prob[2, 1],
    alpha_h0 = sum(x$upper$prob[, 1])
  )
}

res <- do.call(rbind, lapply(c(18, 50, 70, 71, 80), skip_summary))
signif(res, 16)
#>    r b1       b2        p1_h0      p2_h0   alpha_h0
#> 1 18 20 2.155497 2.753624e-89 0.01554894 0.02421384
#> 2 50 20 2.155497 2.753624e-89 0.01554893 0.02421383
#> 3 70 20 2.155497 2.753624e-89 0.01554893 0.02421383
#> 4 71 20 2.155497 2.753624e-89 0.01554893 0.02421383
#> 5 80 20 2.155497 2.753624e-89 0.01554893 0.02421383

Compare full upper-bound/probability vectors to r=70:

skip_vector <- function(r) {
  x <- gsDesign(
    k = 4,
    n.fix = 100,
    sfu = sfTrimmed,
    sfupar = list(sf = sfHSD, param = 1, trange = c(0.3, 0.9)),
    r = r
  )

  c(x$upper$bound, x$upper$prob[, 1])
}

v70 <- skip_vector(70)
cmp_r <- c(18, 50, 71, 80)

diff_tbl <- data.frame(
  r = cmp_r,
  max_abs_diff_vs_r70 = vapply(cmp_r, function(r) max(abs(skip_vector(r) - v70)), numeric(1))
)

format(diff_tbl, digits = 17)
#>    r    max_abs_diff_vs_r70
#> 1 18 2.5101146006178965e-07
#> 2 50 3.2197817745327484e-09
#> 3 71 5.5082605143752517e-11
#> 4 80 4.6306114498406714e-10

Conclusion

  • The grid-range argument is correct: for r<=70, 3 + 4*log(r) <= 20, so increasing the boundary cap above 20 should have essentially no impact in that range.
  • For 70 < r <= 80, the grid extends slightly above 20 (up to about 20.53 at r=80), so tiny tail differences are possible.
  • In the skipped-look check above, r=70 vs r=80 differs only at a microscopic scale (max absolute difference around 1e-10 in this example).
  • EXTREMEZ is not only a boundary cap. In src/gsbound.c, it is also used as the Newton iteration limit (j++ < EXTREMEZ), so changing it directly to INFINITY is unsafe.
  • A robust change should separate concerns:
    1. BOUND_CAP_Z for boundary capping semantics.
    2. MAX_NR_ITER for convergence safety.
    3. R-side handling for "no bound" output semantics (current code has several hardcoded +/-20 assumptions in plotting and helper paths).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment