tests/testthat/test-state-dep-lag.R

rxTest({
  test_that("state-dep alag uses initial state at sort phase (not NA)", {
    # alag(depot) = central * 0.1
    # central=0 -> lag=0; central=5 -> lag=0.5 hours
    mod <- rxode2({
      alag(depot) <- central * 0.1
      d/dt(depot) <- -ka * depot
      d/dt(central) <- ka * depot - kel * central
    })
    et <- et(amt = 100) |> et(seq(0, 24, by = 1))
    s0 <- rxSolve(mod, et, c(ka = 0.5, kel = 0.2),
                  inits = c(depot = 0, central = 0))
    s5 <- rxSolve(mod, et, c(ka = 0.5, kel = 0.2),
                  inits = c(depot = 0, central = 5))
    # Previously produced NA due to ypNA placeholder; now should work
    expect_false(any(is.na(s0$central)))
    expect_false(any(is.na(s5$central)))
    # Different initial lag -> different trajectories
    expect_false(isTRUE(all.equal(s0$central, s5$central)))
  })

  test_that("dur(cmt) <- state with constant alag: stop event at absolute lagged time", {
    # alag=0.5, dur=state-dep -> absolute stop = (t + 0.5) + dur(central)
    mod <- rxode2({
      alag(depot) <- 0.5
      dur(depot) <- central * 0.1 + 1.0
      d/dt(depot) <- -ka * depot
      d/dt(central) <- ka * depot - kel * central
    })
    et <- et(amt = 100, rate = -2) |> et(seq(0, 24, by = 1))
    s <- rxSolve(mod, et, c(ka = 0.5, kel = 0.2),
                 inits = c(depot = 0, central = 3))
    expect_false(any(is.na(s$central)))
    expect_false(any(is.na(s$depot)))
  })

  test_that("state-dep alag: runtime refresh updates lag for multiple doses", {
    mod <- rxode2({
      alag(depot) <- central * 0.1
      d/dt(depot) <- -ka * depot
      d/dt(central) <- ka * depot - kel * central
    })
    et <- et() |>
      et(amt = 100, time = 0) |>
      et(amt = 100, time = 6) |>
      et(seq(0, 24, by = 1))
    # State changes between doses -> lag differs for second dose; no NA
    s <- rxSolve(mod, et, c(ka = 0.5, kel = 0.2),
                 inits = c(depot = 0, central = 5))
    expect_false(any(is.na(s$central)))
  })

  test_that("state-dep alag: dose fires at dosing_time + state_value (constant lag, numeric)", {
    # Use d/dt(state_val)=0 so lag = state_val stays constant -> analytically verifiable.
    # With ka=0.5 (mono-exp depot), depot(t) after a dose at t_fire = 100*exp(-0.5*(t-t_fire)).
    # dose1 raw_t=0, lag=state_val -> fires at t=state_val.
    # dose2 raw_t=3, lag=state_val -> fires at t=3+state_val.
    # At each obs time, depot = sum of contributions from fired doses.
    mod <- rxode2({
      alag(depot) <- state_val
      d/dt(depot) <- -ka * depot
      d/dt(central) <- ka * depot - kel * central
      d/dt(state_val) <- 0
    })
    params <- c(ka = 0.5, kel = 0.2)
    et2dose <- et(amt = 100, time = 0, cmt = "depot") |>
      et(amt = 100, time = 3, cmt = "depot") |>
      et(seq(0, 10, by = 1))

    for (sv in c(0, 1, 2)) {
      s <- rxSolve(mod, et2dose, params,
                   inits = c(depot = 0, central = 0, state_val = sv))
      t_fire1 <- sv          # dose1 fires at t=sv
      t_fire2 <- 3 + sv      # dose2 fires at t=3+sv
      for (tobs in seq(0, 10)) {
        expected <- 0
        if (tobs >= t_fire1) expected <- expected + 100 * exp(-0.5 * (tobs - t_fire1))
        if (tobs >= t_fire2) expected <- expected + 100 * exp(-0.5 * (tobs - t_fire2))
        got <- s$depot[s$time == tobs]
        expect_equal(got, expected, tolerance = 1e-3,
                     label = sprintf("state_val=%d, t=%d: depot", sv, tobs))
      }
    }
  })
})

Try the rxode2 package in your browser

Any scripts or data that you put into this service are public.

rxode2 documentation built on July 28, 2026, 5:08 p.m.