tests/testthat/test-events.R

context("events")

test_that("change variables", {
  target_r <- function(t, y, p) {
    -y * p
  }
  event_r <- function(t, y, p) {
    y * 2
  }
  target_c <- "exponential"
  event_c <- "double_variables"
  dllname <- "dde_growth"

  set.seed(1)
  y0 <- runif(5)
  p <- rep(1, length(y0))
  tol <- 1e-8
  tt <- seq(0, 5, length.out = 51) # 5001

  cmp1_r <- dopri(y0, tt, target_r, p, atol = tol, rtol = tol)
  cmp2_r <- dopri(y0, tt, target_r, p, tcrit = 1:5, atol = tol, rtol = tol)
  cmp1_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  atol = tol, rtol = tol)
  cmp2_c <- dopri(y0, tt, target_c, p, dllname = dllname, tcrit = 1:5,
                  atol = tol, rtol = tol)

  expect_equal(cmp1_r, cmp2_r, tolerance = 1e-6)
  expect_equal(cmp1_r, cmp1_c, tolerance = 1e-12)
  expect_equal(cmp2_r, cmp2_c, tolerance = 1e-12)

  te <- 1:5

  ## First, the dummy event:
  res1_r <- dopri(y0, tt, target_r, p,
                  event_time = te, event_function = function(t, y, p) y,
                  atol = tol, rtol = tol)
  expect_equal(res1_r, cmp2_r, tolerance = 1e-12)

  ## Then the real event where we double the output variable:
  res2_r <- dopri(y0, tt, target_r, p,
                  event_time = te, event_function = event_r,
                  atol = tol, rtol = tol)

  ## This does seem to throw the tolerances out of whack quite badly,
  ## but then they're being computed on totally different quantities.
  m <- 2^findInterval(tt, te + 1e-8,
                      rightmost.closed = TRUE)
  expect_equal(res2_r[, -1], cmp2_r[, -1] * m, tolerance = 1e-5)

  ## Confirm that the C cases agree fairly well:
  res1_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  event_time = te, event_function = "identity",
                  atol = tol, rtol = tol)
  expect_equal(res1_c, res1_r, tolerance = 1e-12)
  res2_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  event_time = te, event_function = event_c,
                  atol = tol, rtol = tol)
  expect_equal(res2_c, res2_r, tolerance = 1e-12)
})

test_that("change parameters", {
  target_r <- function(t, y, p) {
    p
  }
  event_r <- function(t, y, p) {
    attr(y, "parms") <- p * 2
    y
  }

  target_c <- "linear"
  event_c <- "double_parameters"
  dllname <- "dde_growth"

  set.seed(1)
  y0 <- runif(5)
  p <- runif(5)
  p0 <- p
  tol <- 1e-8
  tt <- seq(0, 5, length.out = 51) # 5001

  cmp1_r <- dopri(y0, tt, target_r, p, atol = tol, rtol = tol)
  cmp2_r <- dopri(y0, tt, target_r, p, tcrit = 1:5, atol = tol, rtol = tol)
  cmp1_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  atol = tol, rtol = tol)
  cmp2_c <- dopri(y0, tt, target_c, p, dllname = dllname, tcrit = 1:5,
                  atol = tol, rtol = tol)

  expect_equal(cmp1_r, cmp2_r, tolerance = 1e-6)
  expect_equal(cmp1_r, cmp1_c, tolerance = 1e-12)
  expect_equal(cmp2_r, cmp2_c, tolerance = 1e-12)

  ## TODO: This should surely imply that the final event should fire
  ## too?  But it's not firing here.  I think that this way around is
  ## correct though (but we *should* fire on starting events).  It's
  ## as if events fire at t + eps, with eps lim -> 0
  te <- 1:5

  ## First, the dummy event (duplicates the test above really)
  res1_r <- dopri(y0, tt, target_r, p,
                  event_time = te, event_function = function(t, y, p) y,
                  atol = tol, rtol = tol)
  expect_equal(res1_r, cmp2_r, tolerance = 1e-12)

  ## Then the real event where we double the parameters
  res2_r <- dopri(y0, tt, target_r, p,
                  event_time = te, event_function = event_r,
                  atol = tol, rtol = tol)
  expect_identical(p, p0) # unchanged -- this is important!

  cmp <- matrix(NA, length(tt), length(y0))
  cmp[1, ] <- y0
  ## Then start the process of computing the trajectory.  This is
  ## actually quite a faff; surprisingly more than the above because I
  ## don't know how to do this sort of thing very effectively.
  times <- c(tt[1], te)
  for (i in seq_along(te)) {
    j <- which(tt >= times[i] & tt <= times[i + 1])
    cmp[j, ] <- t(cmp[j[1], ] + outer(p * 2 ^ (i - 1), tt[j] - tt[j][1]))
  }

  expect_equal(res2_r[, -1], cmp, tolerance = 1e-5)

  ## Confirm that the C cases agree fairly well:
  res1_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  event_time = te, event_function = "identity",
                  atol = tol, rtol = tol)
  expect_equal(res1_c, res1_r, tolerance = 1e-12)
  res2_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                  event_time = te, event_function = event_c,
                  atol = tol, rtol = tol)
  expect_equal(res2_c, res2_r, tolerance = 1e-12)
})

test_that("vector of events", {
  target_r <- function(t, y, p) {
    -y * p
  }
  event_r1 <- function(t, y, p) {
    y * 2
  }
  event_r2 <- function(t, y, p) {
    y / 2
  }
  target_c <- "exponential"
  event_c1 <- "double_variables"
  event_c2 <- "halve_variables"
  dllname <- "dde_growth"

  set.seed(1)
  y0 <- runif(5)
  p <- rep(1, length(y0))
  tol <- 1e-8
  tt <- seq(0, 3, length.out = 31) # 5001

  te <- c(1, 2)
  events_r <- list(event_r1, event_r2)
  events_c <- list(event_c1, event_c2)

  res_r <- dopri(y0, tt, target_r, p,
                 event_time = te, event_function = events_r,
                 atol = tol, rtol = tol)
  res_c <- dopri(y0, tt, target_c, p, dllname = dllname,
                 event_time = te, event_function = events_c,
                 atol = tol, rtol = tol)

  cmp <- t(y0 * exp(outer(-p, tt)))
  m <- 1 + findInterval(tt, te + 1e-8, rightmost.closed = TRUE) %% 2

  expect_equal(res_r[, -1], cmp * m, tolerance = 1e-5)
  expect_equal(res_r, res_c, tolerance = 1e-12)
})

test_that("interleave events and critical", {
  ## We'll use the same trick as earlier to detect if tcrit was used;
  ## check the number of steps taken as it should be smaller.  This
  ## means that our target is going to be a step function.
  target <- function(t, y, p) {
    if (t > 2) -p else p
  }
  event1 <- function(t, y, p) {
    y * 2
  }
  event2 <- function(t, y, p) {
    y / 2
  }

  te <- c(1, 3)
  tcrit <- 2
  events <- list(event1, event2)

  set.seed(1)
  y0 <- runif(5)
  p <- runif(length(y0))
  tol <- 1e-8
  tt <- seq(0, 4, length.out = 41) # 5001

  res1 <- dopri(y0, tt, target, p,
                event_time = te, event_function = events,
                atol = tol, rtol = tol, return_statistics = TRUE)

  res2 <- dopri(y0, tt, target, p,
                event_time = te, event_function = events, tcrit = 2,
                atol = tol, rtol = tol, return_statistics = TRUE)

  s1 <- attr(res1, "statistics")
  s2 <- attr(res2, "statistics")
  expect_lt(s2[["n_step"]], s1[["n_step"]])
  expect_lt(s2[["n_reject"]], s1[["n_reject"]])

  cmp <- outer(p, tt) + y0
  ## The first double
  j <- which(tt >= 1 & tt <= 2)
  cmp[, j[-1]] <- cmp[, j[-1]] + cmp[, j[[1]]]
  ## Flips around:
  j <- which(tt >= 2)
  cmp[, j[-1]] <- outer(-p, tt[j[-1]] - tt[j[[1]]]) + cmp[, j[[1]]]
  ## The halve
  j <- which(tt >= 3)
  cmp[, j[-1]] <- cmp[, j[-1]] - cmp[, j[[1]]] / 2

  expect_equal(res2[, -1], t(cmp), tolerance = 1e-5)
  expect_equal(res1[, -1], res2[, -1], tolerance = 1e-5)
})

test_that("event ordering when stacked", {
  target <- function(t, y, p) {
    0
  }
  called <- numeric(0)
  event1 <- function(t, y, p) {
    called <<- c(called, c(1, t))
    y
  }
  event2 <- function(t, y, p) {
    called <<- c(called, c(2, t))
    y
  }

  te <- c(1, 1)
  events <- list(event1, event2)

  set.seed(1)
  y0 <- runif(5)
  p <- runif(length(y0))
  tol <- 1e-8
  tt <- seq(0, 2, length.out = 21) # 5001

  res1 <- dopri(y0, tt, target, p,
                event_time = te, event_function = events,
                atol = tol, rtol = tol, return_statistics = TRUE)
  m <- matrix(called, 2)
  expect_equal(m[1, ], c(1, 2))
  expect_equal(m[2, ], te)

  ## And again, with more events:
  called <- numeric(0)
  te <- rep(c(0.8, 1.1), each = 4)
  i <- sample(2, length(te), replace = TRUE)
  res2 <- dopri(y0, tt, target, p,
                event_time = te, event_function = events[i],
                atol = tol, rtol = tol, return_statistics = TRUE)
  m <- matrix(called, 2)
  expect_equal(m[1, ], i)
  expect_equal(m[2, ], te)
})

test_that("events colliding with tcrit", {
  target <- function(t, y, p) {
    0
  }
  called <- numeric(0)
  event1 <- function(t, y, p) {
    called <<- c(called, c(1, t))
    y
  }
  event2 <- function(t, y, p) {
    called <<- c(called, c(2, t))
    y
  }
  events <- rep(list(event1, event2), 5)

  te <- rep(1:5, each = 2)
  tcrit <- c(2, 3.5, 4)
  ans <- check_events(te, events, tcrit)
  expect_equal(ans$tcrit, sort(c(te, setdiff(tcrit, te))))

  set.seed(1)
  y0 <- 0
  p <- NULL
  tt <- seq(0, 6)

  res <- dopri(y0, tt, target, p,
               tcrit = tcrit, event_time = te, event_function = events)
  m <- matrix(called, 2)
  expect_equal(m[1, ], rep(1:2, 5))
  expect_equal(m[2, ], te)
})

test_that("single event", {
  target <- function(t, y, p) {
    0
  }
  event <- function(t, y, p) {
    message("this is an event")
    y
  }
  expect_message(dopri(0, 0:4, target, NULL,
                       event_time = 1, event_function = event),
                 "this is an event")
})

test_that("no crash after serialisation", {
  dllname <- "dde_growth"
  target <- "exponential"
  ## TODO: passing in $address here does not work:
  event <- getNativeSymbolInfo("double_variables", dllname)

  y0 <- 1
  p <- 0
  tt <- seq(0, 2, length.out = 21)
  res1 <- dopri(y0, tt, target, p, dllname = dllname,
                event_time = 1, event_function = event)
  expect_equal(res1[, 2], ifelse(tt > 1.0, 2, 1))

  event0 <- make_null_pointer(event)
  expect_error(dopri(y0, tt, target, p, dllname = dllname,
                     event_time = 1, event_function = event0),
               "Was passed null pointer for events[1]", fixed = TRUE)
})

test_that("error cases", {
  target <- function(t, y, p) {
    0
  }
  event <- function(t, y, p) {
    message("this is an event")
    y
  }

  expect_error(dopri(0, 0:4, target, NULL,
                     event_time = NULL, event_function = event),
               "'event_function' given without 'event_time'")
  expect_error(dopri(0, 0:4, target, NULL,
                     event_time = 1, event_function = NULL),
               "'event_time' given without 'event_function'")
  expect_error(dopri(0, 0:4, target, NULL,
                     event_time = 1, event_function = "double_variables"),
               "'event_function' must be an R function")
  expect_error(dopri(0, 0:4, "exponential", NULL, dllname = "dde_growth",
                     event_time = 1, event_function = event),
               "'event_function' must be a compiled function")
  expect_error(dopri(0, 0:4, target, NULL,
                     event_time = 1:3, event_function = list(event, event)),
               "'event_function' must be a single event or a list of length 3")
})
richfitz/dde documentation built on Jan. 19, 2024, 5:42 p.m.