tests/testthat/test-gradients.R

test_that("Gradients for singlechannel-NHMM are correct", {
  set.seed(123)
  M <- 4
  S <- 3
  n_id <- 5
  n_time <- 10
  
  data <- data.table(
    y = sample(factor(letters[1:M]), n_id * n_time, replace = TRUE), 
    x = stats::rnorm(n_id * n_time), 
    z = stats::rnorm(n_id * n_time),
    time = rep(1:n_time, each = n_id),
    id = rep(1:n_id, n_time)
  )
  data[data$id < 3 & data$time > 6, c("y", "x", "z")] <- NA
  data[data$time > 9, c("y", "x", "z")] <- NA
  data$x[12:15] <- 0
  data$y[10:25] <- NA
  data <- stats::na.omit(data, cols = c("x", "z"))
  model <- build_nhmm(
    S, initial_formula = ~ x, transition_formula = ~ z,
    emission_formula = y ~ x, data = data, time = "time", id = "id")
  
  np_pi <- attr(model, "np_pi")
  np_A <- attr(model, "np_A")
  np_B <- attr(model, "np_B")
  X_pi <- model$X_pi
  X_A <- model$X_A
  X_B <- model$X_B
  K_pi <- K(X_pi)
  K_A <- K(X_A)
  K_B <- K(X_B)
  obs <- create_obs(model)
  pars <- stats::rnorm(np_pi + np_A + np_B)
  
  f <- function(pars) {
    eta_pi <- create_eta_pi_nhmm(pars[seq_len(np_pi)], S, K_pi)
    eta_A <- create_eta_A_nhmm(pars[np_pi + seq_len(np_A)], S, K_A)
    eta_B <- create_eta_B_nhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B
    )
    -Rcpp_log_objective_nhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, FALSE, FALSE, FALSE,
      TRUE, TRUE, TRUE, TRUE, eta_pi, eta_A, eta_B
    )$loglik
    
  }
  g <- function(pars) {
    eta_pi <- create_eta_pi_nhmm(pars[seq_len(np_pi)], S, K_pi)
    eta_A <- create_eta_A_nhmm(pars[np_pi + seq_len(np_A)], S, K_A)
    eta_B <- create_eta_B_nhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B
    )
    -unname(unlist(Rcpp_log_objective_nhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, FALSE, FALSE, FALSE,
      TRUE, TRUE, TRUE, TRUE, eta_pi, eta_A, eta_B)[-1])
    )
  }
  expect_equal(g(pars), numDeriv::grad(f, pars))
})

test_that("Gradients for multichannel-NHMM are correct", {
  set.seed(123)
  M <- c(2, 5)
  S <- 3
  n_id <- 5
  n_time <- 15
  data <- data.table(
    y1 = sample(factor(letters[1:M[1]]), n_id * n_time, replace = TRUE), 
    y2 = sample(factor(LETTERS[1:M[2]]), n_id * n_time, replace = TRUE), 
    x = stats::rnorm(n_id * n_time), 
    z = stats::rnorm(n_id * n_time),
    time = rep(1:n_time, each = n_id),
    id = rep(1:n_id, n_time)
  )
  
  data[data$id < 3 & data$time > 6, c("y1", "y2", "x", "z")] <- NA
  data[data$time > 9, c("y1", "y2", "x", "z")] <- NA
  data$x[12:15] <- 0
  data$y1[10:25] <- NA
  data$y2[10:35] <- NA
  data <- stats::na.omit(data, cols = c("x", "z"))
  model <- build_nhmm(
    S, initial_formula = ~ x, transition_formula = ~z,
    emission_formula = c(y1, y2) ~ z, data = data, time = "time", id = "id")
  
  np_pi <- attr(model, "np_pi")
  np_A <- attr(model, "np_A")
  np_B <- attr(model, "np_B")
  X_pi <- model$X_pi
  X_A <- model$X_A
  X_B <- model$X_B
  K_pi <- K(X_pi)
  K_A <- K(X_A)
  K_B <- K(X_B)
  obs <- create_obs(model)
  pars <- stats::rnorm(np_pi + np_A + np_B)
  
  f <- function(pars) {
    eta_pi <- create_eta_pi_nhmm(pars[seq_len(np_pi)], S, K_pi)
    eta_A <- create_eta_A_nhmm(pars[np_pi + seq_len(np_A)], S, K_A)
    eta_B <- create_eta_B_nhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B
    )
    -Rcpp_log_objective_nhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, io(X_pi), io(X_A), io(X_B),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B), eta_pi, eta_A, eta_B
    )$loglik
    
  }
  g <- function(pars) {
    eta_pi <- create_eta_pi_nhmm(pars[seq_len(np_pi)], S, K_pi)
    eta_A <- create_eta_A_nhmm(pars[np_pi + seq_len(np_A)], S, K_A)
    eta_B <- create_eta_B_nhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B
    )
    -unname(unlist(Rcpp_log_objective_nhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, io(X_pi), io(X_A), io(X_B),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B), eta_pi, eta_A, eta_B
    )[-1]))
  }
  expect_equal(g(pars), numDeriv::grad(f, pars))
})

test_that("Gradients for singlechannel-MNHMM are correct", {
  set.seed(123)
  M <- 4
  S <- 3
  D <- 2
  n_id <- 5
  n_time <- 10
  data <- data.table(
    y = sample(factor(letters[1:M]), n_id * n_time, replace = TRUE), 
    x = stats::rnorm(n_id * n_time), 
    z = stats::rnorm(n_id * n_time),
    time = rep(1:n_time, each = n_id),
    id = rep(1:n_id, n_time)
  )
  data[data$id < 3 & data$time > 6, c("y", "x", "z")] <- NA
  data[data$time > 9, c("y", "x", "z")] <- NA
  data$x[12:15] <- 0
  data$y[10:25] <- NA
  data <- stats::na.omit(data, cols = c("x", "z"))
  model <- build_mnhmm(
    S, D, emission_formula = y ~ z, initial_formula = ~ x, 
    transition_formula = ~ z, cluster_formula = ~ z, data = data, 
    time = "time", id = "id"
  )
  
  np_pi <- attr(model, "np_pi")
  np_A <- attr(model, "np_A")
  np_B <- attr(model, "np_B")
  np_omega <- attr(model, "np_omega")
  X_pi <- model$X_pi
  X_A <- model$X_A
  X_B <- model$X_B
  X_omega <- model$X_omega
  K_omega <- K(X_omega)
  K_pi <- K(X_pi)
  K_A <- K(X_A)
  K_B <- K(X_B)
  obs <- create_obs(model)
  pars <- stats::rnorm(np_pi + np_A + np_B + np_omega)
  f <- function(pars) {
    eta_pi <- create_eta_pi_mnhmm(pars[seq_len(np_pi)], S, K_pi, D)
    eta_A <- create_eta_A_mnhmm(pars[np_pi + seq_len(np_A)], S, K_A, D)
    eta_B <- create_eta_B_mnhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B, D
    )
    eta_omega <- create_eta_omega_mnhmm(
      pars[np_pi + np_A + np_B + seq_len(np_omega)], D, K_omega
    )
    -Rcpp_log_objective_mnhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, X_omega, 
      io(X_pi), io(X_A), io(X_B), io(X_omega),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B),
      eta_pi, eta_A, eta_B, eta_omega
    )$loglik
    
  }
  g <- function(pars) {
    eta_pi <- create_eta_pi_mnhmm(pars[seq_len(np_pi)], S, K_pi, D)
    eta_A <- create_eta_A_mnhmm(pars[np_pi + seq_len(np_A)], S, K_A, D)
    eta_B <- create_eta_B_mnhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B, D
    )
    eta_omega <- create_eta_omega_mnhmm(
      pars[np_pi + np_A + np_B + seq_len(np_omega)],  D, K_omega
    )
    -unname(unlist(Rcpp_log_objective_mnhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, X_omega, 
      io(X_pi), io(X_A), io(X_B), io(X_omega),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B),
      eta_pi, eta_A, eta_B, eta_omega)[-1]))
  }
  expect_equal(g(pars), numDeriv::grad(f, pars))
})

test_that("Gradients for multichannel-MNHMM are correct", {
  set.seed(123)
  M <- c(2, 5)
  S <- 3
  D <- 4
  n_id <- 5
  n_time <- 15
  data <- data.table(
    y1 = sample(factor(letters[1:M[1]]), n_id * n_time, replace = TRUE), 
    y2 = sample(factor(LETTERS[1:M[2]]), n_id * n_time, replace = TRUE), 
    x = stats::rnorm(n_id * n_time), 
    z = stats::rnorm(n_id * n_time),
    time = rep(1:n_time, each = n_id),
    id = rep(1:n_id, n_time)
  )
  
  data[data$id < 3 & data$time > 6, c("y1", "y2", "x", "z")] <- NA
  data[data$time > 9, c("y1", "y2", "x", "z")] <- NA
  data$x[12:15] <- 0
  data$y1[10:25] <- NA
  data$y2[10:35] <- NA
  data <- stats::na.omit(data, cols = c("x", "z"))
  model <- build_mnhmm(
    S, D, initial_formula = ~ x, transition_formula = ~z,
    emission_formula = c(y1, y2) ~ z, cluster_formula = ~ z, data = data, 
    time = "time", id = "id")
  
  np_pi <- attr(model, "np_pi")
  np_A <- attr(model, "np_A")
  np_B <- attr(model, "np_B")
  np_omega <- attr(model, "np_omega")
  X_pi <- model$X_pi
  X_A <- model$X_A
  X_B <- model$X_B
  X_omega <- model$X_omega
  K_omega <- K(X_omega)
  K_pi <- K(X_pi)
  K_A <- K(X_A)
  K_B <- K(X_B)
  obs <- create_obs(model)
  pars <- stats::rnorm(np_pi + np_A + np_B + np_omega)
  f <- function(pars) {
    eta_pi <- create_eta_pi_mnhmm(pars[seq_len(np_pi)], S, K_pi, D)
    eta_A <- create_eta_A_mnhmm(pars[np_pi + seq_len(np_A)], S, K_A, D)
    eta_B <- create_eta_B_mnhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B, D
    )
    eta_omega <- create_eta_omega_mnhmm(
      pars[np_pi + np_A + np_B + seq_len(np_omega)], D, K_omega
    )
    -Rcpp_log_objective_mnhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, X_omega, 
      io(X_pi), io(X_A), io(X_B), io(X_omega),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B),
      eta_pi, eta_A, eta_B, eta_omega
    )$loglik
    
  }
  g <- function(pars) {
    eta_pi <- create_eta_pi_mnhmm(pars[seq_len(np_pi)], S, K_pi, D)
    eta_A <- create_eta_A_mnhmm(pars[np_pi + seq_len(np_A)], S, K_A, D)
    eta_B <- create_eta_B_mnhmm(
      pars[np_pi + np_A + seq_len(np_B)], S, M, K_B, D
    )
    eta_omega <- create_eta_omega_mnhmm(
      pars[np_pi + np_A + np_B + seq_len(np_omega)], D, K_omega
    )
    -unname(unlist(Rcpp_log_objective_mnhmm(
      obs, model$sequence_lengths, M, X_pi, X_A, X_B, X_omega, 
      io(X_pi), io(X_A), io(X_B), io(X_omega),
      iv(X_A), iv(X_B), tv(X_A), tv(X_B),
      eta_pi, eta_A, eta_B, eta_omega)[-1]))
  }
  expect_equal(g(pars), numDeriv::grad(f, pars))
})

Try the seqHMM package in your browser

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

seqHMM documentation built on June 8, 2025, 10:16 a.m.