Nothing
# Tests for the NONMEM-style per-subject xi statistic and the individual-gamma
# controller built on it.
#
# xi (NM7 Technical Guide eq. 1.73/1.75) is the mean importance weight normalized
# so the proposal kernel is 1 at its center:
#
# xi_i = (1/r) sum_k [pi(eta_k) / pi(center)] / e(eta_k)
#
# It equals 1 when the proposal matches the posterior shape, is < 1 when the
# proposal is over-dispersed, and is > 1 when the posterior has heavier tails
# than the Gaussian proposal. It is a DIFFERENT statistic from the Kish
# effective sample size (impNeffFrac) and the two are not comparable.
nmTest({
.xiModel <- function() {
ini({
tka <- 0.45; tcl <- 1; tv <- 3.45
eta.cl ~ 0.1
add.sd <- 0.7
})
model({
ka <- exp(tka)
cl <- exp(tcl + eta.cl)
v <- exp(tv)
linCmt() ~ add(add.sd)
})
}
test_that("xi: per-subject xi and its trace are exposed and well-formed", {
.d <- nlmixr2data::theo_sd
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 5L,
isample = 300L, covMethod = "")))
.E <- .f$env
# one xi per subject, all finite and strictly positive
expect_false(is.null(.E$impXi))
expect_length(.E$impXi, length(unique(.d$ID)))
expect_true(all(is.finite(.E$impXi)))
expect_true(all(.E$impXi > 0))
# the trace has one entry per EM iteration actually run, alongside the
# Kish-ESS trace it must not be confused with
expect_length(.E$impXiTrace, .E$impIter)
expect_length(.E$impNeffFrac, .E$impIter)
expect_true(all(is.finite(.E$impXiTrace)))
# xi and the Kish ESS fraction are different statistics: on a real fit they
# must not coincide, which is exactly why $runInfo has to name which is which
expect_false(isTRUE(all.equal(.E$impXiTrace, .E$impNeffFrac)))
})
test_that("xi decreases monotonically as the proposal is widened (gamma)", {
# This is the property that makes xi targetable by the individual-gamma
# controller: widening the proposal must lower xi, monotonically. Compared
# at iteration 1 across fits so every run sees identical starting parameters.
.d <- nlmixr2data::theo_sd
.xi1 <- vapply(c(1.0, 2.0, 4.0), function(g) {
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 1L,
isample = 500L, gamma = g,
covMethod = "")))
.f$env$impXiTrace[1]
}, numeric(1))
expect_true(all(is.finite(.xi1)))
expect_true(all(diff(.xi1) < 0))
})
test_that("gammaMethod: control accepts all three modes and defaults to auto", {
expect_equal(impmapControl()$gammaMethod, "auto")
expect_equal(impmapControl(gammaMethod = "global")$gammaMethod, "global")
expect_equal(impmapControl(gammaMethod = "individual")$gammaMethod, "individual")
expect_error(impmapControl(gammaMethod = "nonsense"))
# round-trips through the control constructor (do.call re-entry)
.c <- impmapControl(gammaMethod = "individual")
expect_equal(do.call(impmapControl, .c)$gammaMethod, "individual")
# stripped when down-converting to a plain foceiControl
expect_true("gammaMethod" %in% .impmapIsControlNames)
})
test_that("gammaMethod='auto' resolves on the model's distribution", {
# The unit of the decision: transformably-normal -> "global" (gamma = 1 is
# already the efficient proposal), anything else -> "individual".
.norm <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; add.sd <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
linCmt() ~ add(add.sd)})
}
.ll <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; sd1 <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv); cp <- linCmt()
ll(cp) ~ -0.5 * log(2 * pi) - log(sd1) - 0.5 * ((DV - cp) / sd1)^2})
}
expect_equal(.impmapResolveGammaMethod("auto", rxode2::rxode2(.norm)), "global")
expect_equal(.impmapResolveGammaMethod("auto", rxode2::rxode2(.ll)), "individual")
# an explicit choice is never overridden
expect_equal(.impmapResolveGammaMethod("global", rxode2::rxode2(.ll)), "global")
expect_equal(.impmapResolveGammaMethod("individual", rxode2::rxode2(.norm)), "individual")
# a ui with no usable predDf falls back to the historical behaviour
expect_equal(.impmapResolveGammaMethod("auto", list()), "global")
# ... as does an NA distribution (all(NA == "x") is NA, which would error an if)
expect_equal(.impmapResolveGammaMethod(
"auto", list(predDf = data.frame(distribution = c("norm", NA)))), "global")
# "norm" and "dnorm" are rxode2 ALIASES for the same Gaussian family, so the
# exact-likelihood (Laplace) form of a plain normal endpoint must still
# resolve to "global" -- its posterior is as Gaussian as the add() form
.dn <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; add.sd <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
linCmt() ~ add(add.sd) + dnorm()})
}
.udn <- rxode2::rxode2(.dn)
expect_equal(as.character(.udn$predDf$distribution), "dnorm") # premise
expect_equal(.impmapResolveGammaMethod("auto", .udn), "global")
# lognormal residuals ride predDf$transform with distribution "norm", so
# they are Gaussian for this purpose too
.lnm <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; add.sd <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
linCmt() ~ lnorm(add.sd)})
}
expect_equal(.impmapResolveGammaMethod("auto", rxode2::rxode2(.lnm)), "global")
})
test_that("gammaMethod='auto' requires EVERY endpoint to be Gaussian", {
# predDf has one row per endpoint, so the decision is an all() over rows:
# a model is only "global" if every endpoint is Gaussian. One non-normal
# endpoint alongside normal ones makes the joint posterior non-Gaussian and
# must select "individual".
.m2n <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; teff <- 1
eta.cl ~ 0.1; add.sd <- 0.7; eff.sd <- 0.5})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
d/dt(depot) <- -ka * depot
d/dt(cen) <- ka * depot - cl / v * cen
cp <- cen / v
eff <- teff * cp
cp ~ add(add.sd)
eff ~ add(eff.sd)})
}
.mMix <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; tl <- 1; eta.cl ~ 0.1; add.sd <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
d/dt(depot) <- -ka * depot
d/dt(cen) <- ka * depot - cl / v * cen
cp <- cen / v
lam <- exp(tl)
cp ~ add(add.sd)
eff ~ pois(lam)})
}
.u2n <- rxode2::rxode2(.m2n)
.uMix <- rxode2::rxode2(.mMix)
expect_equal(nrow(.u2n$predDf), 2L) # premise: multiple rows
expect_equal(nrow(.uMix$predDf), 2L)
# all endpoints Gaussian -> global
expect_equal(.impmapResolveGammaMethod("auto", .u2n), "global")
# ONE non-Gaussian endpoint is enough to select individual
expect_equal(.impmapResolveGammaMethod("auto", .uMix), "individual")
# and the canonicalizer must vectorize across rows rather than collapse
expect_equal(unname(rxode2::rxPreferredDistributionName(
as.character(.uMix$predDf$distribution))), c("dnorm", "pois"))
})
test_that("gammaMethod='auto' re-resolves when a control is reused", {
# Resolution overwrites gammaMethod in place. A control lifted off a
# finished fit and reused on a different model must re-resolve from the
# user's original "auto" rather than carry the previous model's answer.
.norm <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; add.sd <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
linCmt() ~ add(add.sd)})
}
.uNorm <- rxode2::rxode2(.norm)
# simulate a control already resolved to "individual" by an earlier ll fit
.ctl <- impmapControl()
.ctl$gammaMethodUser <- "auto"
.ctl$gammaMethod <- "individual"
.gm <- .ctl$gammaMethodUser
expect_equal(.impmapResolveGammaMethod(.gm, .uNorm), "global")
# whereas an explicitly-set control keeps the user's choice on reuse
.ctl2 <- impmapControl(gammaMethod = "individual")
expect_equal(.impmapResolveGammaMethod(.ctl2$gammaMethod, .uNorm), "individual")
})
test_that("gammaMethod='auto' end-to-end: normal stays global, ll goes individual", {
.d <- nlmixr2data::theo_sd
.fn <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 10L, covMethod = "")))
expect_equal(.fn$env$impGammaMethod, "global")
expect_equal(length(unique(round(.fn$env$impGammaInd, 8))), 1L)
.ll <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; sd1 <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv); cp <- linCmt()
ll(cp) ~ -0.5 * log(2 * pi) - log(sd1) - 0.5 * ((DV - cp) / sd1)^2})
}
.fl <- suppressWarnings(nlmixr2(.ll, .d, "impmap",
impmapControl(print = 0L, nIter = 10L, covMethod = "")))
expect_equal(.fl$env$impGammaMethod, "individual")
expect_gt(length(unique(round(.fl$env$impGammaInd, 8))), 1L)
})
test_that("$runInfo names which efficiency statistic the fit is reporting", {
# xi and the Kish effective-sample fraction are both always stashed, are
# both governed by iaccept, and are NOT the same quantity. Every fit has to
# say which one its number is, or the two get read interchangeably.
.msgG <- .impmapGammaRunInfo("global", "auto", 0.4)
.msgI <- .impmapGammaRunInfo("individual", "auto", 0.4)
# each names its own statistic ...
expect_match(.msgG, "Kish effective-sample fraction")
expect_match(.msgG, "impNeffFrac", fixed = TRUE)
expect_match(.msgI, "xi")
expect_match(.msgI, "impXiTrace", fixed = TRUE)
# ... and both state plainly that the two cannot be compared
expect_match(.msgG, "not\\s+comparable")
expect_match(.msgI, "not\\s+comparable")
# auto explains itself; an explicit choice does not editorialize
expect_match(.msgI, "auto", fixed = TRUE)
expect_false(grepl("auto", .impmapGammaRunInfo("individual", "individual", 0.4), fixed = TRUE))
# the target value is reported, not hardcoded in the prose
expect_match(.impmapGammaRunInfo("individual", "auto", 0.25), "0.25", fixed = TRUE)
})
test_that("$runInfo note reaches the fit on both paths", {
.d <- nlmixr2data::theo_sd
.fn <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 5L, covMethod = "")))
.ri <- .fn$runInfo
expect_true(any(grepl("gammaMethod=\"global\"", .ri, fixed = TRUE)))
expect_true(any(grepl("not comparable", .ri, fixed = TRUE)))
.ll <- function() {
ini({tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; sd1 <- 0.7})
model({ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv); cp <- linCmt()
ll(cp) ~ -0.5 * log(2 * pi) - log(sd1) - 0.5 * ((DV - cp) / sd1)^2})
}
.fl <- suppressWarnings(nlmixr2(.ll, .d, "impmap",
impmapControl(print = 0L, nIter = 5L, covMethod = "")))
.ril <- .fl$runInfo
expect_true(any(grepl("gammaMethod=\"individual\"", .ril, fixed = TRUE)))
expect_true(any(grepl("not comparable", .ril, fixed = TRUE)))
})
test_that("gammaMethod='global' keeps one shared scale", {
.d <- nlmixr2data::theo_sd
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 10L,
covMethod = "",
gammaMethod = "global")))
.E <- .f$env
expect_equal(.E$impGammaMethod, "global")
# every subject on the same scale, equal to the reported scalar
expect_equal(length(unique(round(.E$impGammaInd, 12))), 1L)
expect_equal(unname(.E$impGammaInd[1]), unname(.E$impGammaUsed), tolerance = 1e-10)
})
test_that("gammaMethod='individual' gives per-subject scales that target iaccept", {
.d <- nlmixr2data::theo_sd
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 30L,
covMethod = "",
gammaMethod = "individual")))
.E <- .f$env
expect_equal(.E$impGammaMethod, "individual")
# subjects genuinely diverge -- this is the whole point of the mode
expect_gt(length(unique(round(.E$impGammaInd, 8))), 1L)
# every scale respects the ISCALE bounds
expect_true(all(.E$impGammaInd >= 0.1 - 1e-8))
expect_true(all(.E$impGammaInd <= 10 + 1e-8))
expect_true(all(is.finite(.E$impGammaInd)))
# the controller drives mean xi onto the iaccept target (default 0.4). The
# global rule leaves it near 1 on this near-Gaussian model, so this is a
# genuine discriminator between the two laws, not a tautology.
expect_equal(tail(.E$impXiTrace, 1), 0.4, tolerance = 0.1)
})
test_that("gammaMethod does not move the estimates, only the variance", {
# The importance weights correct for gamma, so both laws must agree on the
# parameters even though they sample at very different proposal widths.
.d <- nlmixr2data::theo_sd
.g <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 25L, covMethod = "",
gammaMethod = "global")))
.i <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 25L, covMethod = "",
gammaMethod = "individual")))
# The two laws really did sample at different widths -- but the difference
# is in the SPREAD across subjects, not in the aggregate. "global" gives
# every subject one gamma; "individual" gives each its own (here spanning
# ~0.97-1.36, sd ~0.12). The aggregate impGammaUsed is nearly identical
# between them (1.248 vs 1.224) because individual gamma REDISTRIBUTES width
# rather than inflating the mean, so comparing aggregates -- as this test
# used to -- asserted the one quantity that barely moves.
expect_length(unique(round(.g$env$impGammaInd, 8)), 1L)
expect_gt(length(unique(round(.i$env$impGammaInd, 8))), 1L)
expect_gt(stats::sd(.i$env$impGammaInd), 0.05)
# ... but agree on the fixed effects and the objective
expect_equal(unname(.i$theta), unname(.g$theta), tolerance = 0.02)
expect_equal(.i$env$impObj, .g$env$impObj, tolerance = 0.5)
})
test_that("individual gamma respects a tightened iscaleMax clamp", {
.d <- nlmixr2data::theo_sd
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 20L, covMethod = "",
gammaMethod = "individual",
iscaleMax = 1.2)))
# the unclamped controller wants gamma ~ 1.8 here, so 1.2 must bind
expect_true(all(.f$env$impGammaInd <= 1.2 + 1e-8))
expect_gt(max(.f$env$impGammaInd), 1.0)
})
test_that("covMethod='imp' uses the converged scales under individual gamma", {
# impComputeCov used to read impGammaProp() -- the control's INITIAL gamma --
# so with gammaMethod="individual" it evaluated the covariance with a single
# scale of 1.0 while the fit had converged near 1.8 per subject. The
# importance weights correct for gamma, so that was consistent rather than
# biased, but it sampled from a proposal the fit had already rejected as
# poorly matched, which inflates Monte-Carlo noise in the FD Hessian. This
# pins that the covariance is built and stays well-formed on that path.
.d <- nlmixr2data::theo_sd
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 15L,
covMethod = "imp",
gammaMethod = "individual")))
.E <- .f$env
# The scales really did move away from the control's initial gamma = 1:
# mean ~1.17 here, spanning ~1.02-1.39 with sd ~0.11. Asserted on the MEAN
# rather than the MINIMUM -- a well-matched subject legitimately needs no
# inflation and sits near 1 (or below), so min() pinned the whole test to
# the single least-informative subject and failed at 1.015.
expect_gt(mean(.E$impGammaInd), 1.05)
expect_gt(stats::sd(.E$impGammaInd), 0)
.cv <- as.matrix(.E$cov)
expect_true(all(is.finite(.cv)))
expect_equal(.cv, t(.cv), tolerance = 1e-8) # symmetric
expect_true(all(diag(.cv) > 0)) # positive diagonal
expect_true(all(is.finite(.E$impSeTheta)))
expect_true(all(.E$impSeTheta > 0))
})
test_that("individual gamma is stable at high eta dimension (no limit cycle)", {
# The damping exponent has to scale with neta. For a Gaussian posterior
# xi = gamma^(-neta/2), so gamma' = gamma*(xi/iaccept)^p linearizes to an
# error multiplier lambda = 1 - p*neta/2. A FIXED p = 1/2 gives
# lambda = -1 at neta = 8, i.e. a period-2 limit cycle that never settles
# (observed: gamma 1.32/1.24/1.30/1.23..., xi 0.36/0.45/0.37/0.46...).
# p = 2/neta gives lambda = 0 for every dimension. This test fails on the
# fixed-exponent controller and passes on the dimension-aware one.
skip_on_cran()
.testSeed(7); rxode2::rxSetSeed(7)
.mk <- function() {
tt <- c(0.25, 1, 2, 4, 8, 16, 24)
do.call(rbind, lapply(1:12, function(id) {
ka <- exp(0.4 + stats::rnorm(1, 0, .3)); cl <- exp(1 + stats::rnorm(1, 0, .3))
v <- exp(3.4 + stats::rnorm(1, 0, .3))
cp <- 100 * ka / (v * (ka - cl / v)) * (exp(-cl / v * tt) - exp(-ka * tt))
cp <- pmax(cp, 1e-3) * exp(stats::rnorm(length(tt), 0, .1))
rbind(data.frame(id = id, time = 0, dv = NA_real_, amt = 100, evid = 1, cmt = "depot"),
data.frame(id = id, time = tt, dv = cp, amt = 0, evid = 0, cmt = "cen"))
}))
}
.d <- .mk(); .d <- .d[order(.d$id, .d$time, -.d$evid), ]
m8 <- function() {
ini({
tka <- 0.4; tcl <- 1; tv <- 3.4; tq <- 0.8; tv2 <- 3; tf <- 0; tlag <- -2; tke0 <- -1
eta.ka ~ .3; eta.cl ~ .3; eta.v ~ .3; eta.q ~ .3
eta.v2 ~ .3; eta.f ~ .2; eta.lag ~ .2; eta.ke0 ~ .2
add.sd <- 0.3
})
model({
ka <- exp(tka + eta.ka); cl <- exp(tcl + eta.cl); v <- exp(tv + eta.v)
q <- exp(tq + eta.q); v2 <- exp(tv2 + eta.v2); f <- exp(tf + eta.f)
lag <- exp(tlag + eta.lag); ke0 <- exp(tke0 + eta.ke0)
d/dt(depot) <- -ka * depot
d/dt(cen) <- ka * depot - cl / v * cen - q / v * cen + q / v2 * peri + 0 * lag + 0 * ke0
d/dt(peri) <- q / v * cen - q / v2 * peri
cp <- f * cen / v
cp ~ add(add.sd)
})
}
.f <- suppressWarnings(nlmixr2(m8, .d, "impmap",
impmapControl(print = 0L, nIter = 18L, isample = 200L,
covMethod = "", gammaMethod = "individual")))
expect_equal(nrow(.f$omega), 8L)
.tail <- tail(.f$env$impXiTrace, 10)
# a period-2 limit cycle has lag-1 autocorrelation ~ -1; a settled trace
# with Monte-Carlo noise sits well above that
expect_gt(stats::cor(head(.tail, -1), .tail[-1]), -0.8)
# and the swing must be small relative to the target it is holding
expect_lt(diff(range(.tail)), 0.15)
})
test_that("xi is near 1 for a well-matched proposal on a near-Gaussian model", {
# gamma = 1 makes the proposal the Laplace approximation itself, so on a
# model whose individual posterior is close to Gaussian xi should sit near 1.
# Deliberately a loose band -- this pins the scale/orientation of the
# statistic (a sign error or a missing normalizer would blow it out by
# orders of magnitude), not its precise value.
.d <- nlmixr2data::theo_sd
# Read a SETTLED xi, not impXiTrace[1]. The first entry is an
# initialization transient -- measured 4.4e6 at iteration 1, then 0.41,
# 0.395, 0.394, ... stable from iteration 2 on -- because the iteration-1
# normalization (qCenter against the starting mode/Hessian) is not yet
# meaningful. Asserting on it pinned the test to the one value that carries
# no information. The band is deliberately orders-of-magnitude wide, which
# is what this test is for: a sign error or a missing normalizer moves xi by
# powers of ten, not by tenths. The settled value here is ~0.39 -- below 1
# because gamma = 1.0 leaves the proposal over-dispersed for this model,
# exactly as the statistic's definition says it should be.
.f <- suppressWarnings(nlmixr2(.xiModel, .d, "impmap",
impmapControl(print = 0L, nIter = 5L,
isample = 500L, gamma = 1.0,
covMethod = "")))
.xiSettled <- .f$env$impXiTrace[length(.f$env$impXiTrace)]
expect_gt(.xiSettled, 0.1)
expect_lt(.xiSettled, 10.0)
})
test_that("gammaRule selects the shared-scale adaptation law", {
skip_on_cran()
.d <- nlmixr2data::theo_sd
.m <- function() {
ini({ tka <- 0.45; tcl <- 1; tv <- 3.45; eta.cl ~ 0.1; add.sd <- 0.7 })
model({ ka <- exp(tka); cl <- exp(tcl + eta.cl); v <- exp(tv)
linCmt() ~ add(add.sd) })
}
.ctl <- function(rule) {
impmapControl(print = 0L, nIter = 100L, isample = 300L, nConvWindow = 10L,
covMethod = "", gammaRule = rule)
}
# control surface: both levels accepted, "floor" is the default
expect_equal(impmapControl()$gammaRule, "target") # NONMEM's rule is default
expect_equal(impmapControl(gammaRule = "floor")$gammaRule, "floor")
# the tuned constants travel WITH the rule
expect_equal(impmapControl()$nConvWindow, 20L)
expect_equal(impmapControl(gammaRule = "floor")$nConvWindow, 10L)
expect_equal(impmapControl(gammaRule = "target", nConvWindow = 7L)$nConvWindow, 7L)
expect_error(impmapControl(gammaRule = "nope"))
.fl <- suppressWarnings(nlmixr2(.m, .d, "impmap", .ctl("floor")))
.tg <- suppressWarnings(nlmixr2(.m, .d, "impmap", .ctl("target")))
# both resolve to the shared ("global") scale, so gammaRule is what differs
expect_equal(.fl$env$impGammaMethod, "global")
expect_equal(.tg$env$impGammaMethod, "global")
# "floor" treats iaccept as a one-sided floor: a well-covered proposal is
# never inflated, so gamma is left at its efficient starting value.
expect_equal(unname(.fl$env$impGammaUsed), 1.0, tolerance = 1e-8)
expect_true(all(.fl$env$impGammaTrace == 1.0))
# "target" is NONMEM's rule -- gamma is adjusted BOTH ways until xi
# approximates IACCEPT. Assert the mechanism, not a pinned value: gamma must
# move off 1, and must move DOWN at least once (a one-sided rule cannot).
.g <- .tg$env$impGammaTrace
expect_gt(max(.g), 1.0)
expect_true(any(diff(.g) < 0))
.ia <- .tg$env$impmapControl$iaccept
expect_equal(unname(tail(.tg$env$impXiTrace, 1)), .ia, tolerance = 0.1)
# both must still SETTLE -- the target rule tracks a Monte-Carlo statistic, so
# its convergence gate is the drift of the gamma window mean, not the
# instantaneous step (which never reaches the floor rule's 1e-3).
expect_true(isTRUE(.fl$env$impConverged))
expect_true(isTRUE(.tg$env$impConverged))
# the rule moves the VARIANCE of the estimates, not their expectation
expect_equal(.fl$objf, .tg$objf, tolerance = 0.5)
})
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.