tests/testthat/test_ar_integration.R

context("AR modeling integration")

simulate_ar_dataset <- function(ar_coeff = numeric(), n_runs = 2, n_time = 30, n_vox = 4) {
  dat_list <- vector("list", n_runs)
  for (r in seq_len(n_runs)) {
    run_mat <- replicate(n_vox, {
      if (length(ar_coeff) == 0) {
        rnorm(n_time)
      } else {
        as.numeric(arima.sim(model = list(ar = ar_coeff), n = n_time))
      }
    })
    dat_list[[r]] <- run_mat
  }
  datamat <- do.call(rbind, dat_list)
  # Create event table with non-decreasing runs
  event_tab <- data.frame(
    run = rep(seq_len(n_runs), each = 2),
    onset = rep(c(5, 15), n_runs),
    cond = factor("A")
  )
  matrix_dataset(datamat, TR = 1, run_length = rep(n_time, n_runs), event_table = event_tab)
}


# Test that AR1 with no correlation matches IID

test_that("iid and ar1 give similar results on white noise", {
  set.seed(1)
  dset <- simulate_ar_dataset(n_runs = 2, ar_coeff = numeric())
  mod_iid <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "iid"))
  mod_ar1 <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "ar1"))
  expect_equal(coef(mod_iid), coef(mod_ar1), tolerance = 0.05)

  # Skip the direct residual AR test - it's not meaningful when HRF regressors
  # absorb temporal structure. The GLM fit comparison below is sufficient.
})

# Test AR1 recovery and SE comparison

test_that("ar1 recovers phi and adjusts standard errors", {
  set.seed(2)
  phi <- 0.4
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 2)

  # Test pure AR recovery on raw data (before GLM)
  Y <- get_data_matrix(dset)
  phi_raw <- .estimate_ar(rowMeans(Y), 1)
  # Raw data should show AR structure, though maybe not exactly phi due to simulation
  expect_equal(as.numeric(phi_raw), phi, tolerance = 0.15)

  mod_iid <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "iid"))
  mod_ar1 <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "ar1"))
  se_iid <- unlist(standard_error(mod_iid))
  se_ar1 <- unlist(standard_error(mod_ar1))
  expect_gt(se_ar1[1], se_iid[1])
})

# Test AR2 recovery

test_that("ar2 recovers coefficients", {
  set.seed(3)
  phi <- c(0.5, -0.25)
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 2)

  # Test on raw data instead of residuals
  Y <- get_data_matrix(dset)
  phi_hat <- .estimate_ar(rowMeans(Y), 2)
  # More tolerance for AR(2) as it's harder to estimate
  expect_equal(as.numeric(phi_hat), phi, tolerance = 0.3)
})

# Test global vs runwise

test_that("cor_global gives similar results", {
  set.seed(4)
  phi <- 0.4
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 2)
  mod_run <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "ar1", global = FALSE))
  mod_global <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                        use_fast_path = TRUE, ar_options = list(struct = "ar1", global = TRUE))
  expect_equal(coef(mod_run), coef(mod_global), tolerance = 1e-6)
})

# Test ar1_exact_first option

test_that("ar1_exact_first runs", {
  set.seed(5)
  phi <- 0.4
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 1)
  expect_error(
    fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
            use_fast_path = TRUE, ar_options = list(struct = "ar1", exact_first = TRUE)),
    NA
  )
})

# Test multiple iterations

test_that("cor_iter > 1 runs", {
  set.seed(6)
  phi <- 0.4
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 1)
  expect_error(
    fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
            use_fast_path = TRUE, ar_options = list(struct = "ar1", iter_gls = 2)),
    NA
  )
})

# Test ARp recovery for p > 2

test_that("arp recovers coefficients", {
  set.seed(7)
  phi <- c(0.6, -0.3, 0.2)
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 2)

  # Test on raw data - AR(3) is very difficult to estimate accurately
  Y <- get_data_matrix(dset)
  phi_hat <- .estimate_ar(rowMeans(Y), length(phi))
  # Very relaxed tolerance for AR(3)
  expect_equal(length(phi_hat), length(phi))
  # Just check the signs are roughly correct
  expect_true(phi_hat[1] > 0.2)  # First coef should be positive
  expect_true(phi_hat[2] < 0)    # Second should be negative

  mod_arp <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "arp", p = length(phi)))
  expect_true(!is.null(coef(mod_arp)))
})

# Test arp with p=1 matches ar1

test_that("arp with p=1 matches ar1", {
  set.seed(8)
  phi <- 0.5
  dset <- simulate_ar_dataset(ar_coeff = phi, n_runs = 2)
  mod_ar1 <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                     use_fast_path = TRUE, ar_options = list(struct = "ar1"))
  mod_arp1 <- fmri_lm(onset ~ hrf(cond), block = ~ run, dataset = dset,
                      use_fast_path = TRUE, ar_options = list(struct = "arp", p = 1))
  expect_equal(coef(mod_ar1), coef(mod_arp1), tolerance = 1e-6)
})
bbuchsbaum/fmrireg documentation built on June 10, 2025, 8:18 p.m.