library(testthat)
context("discrete")
library(OpenMx)

skip_on_cran()

# to test:
# check equivalence of parameterizations (total var = 1 vs loading = 1) ??

verifyFrontBackMatch <- function(m1) {
  t1 <- mxGetExpected(m1, "thresholds")

  m1 <- mxRun(mxModel(m1, mxComputeSequence(list(
    mxComputeOnce('expectation'),
    mxComputeReportExpectation()))))

  t2 <- m1$expectation$output$thresholds

  t2 <- t2[,colnames(t1),drop=FALSE]

  expect_equivalent(is.na(t1), is.na(t2))

  mask <- !is.na(t1)
  expect_equal(t1[mask], t2[mask], 1e-9)
}

# from package countreg
qzinbinom <- function(p, mu, theta, size, pi, lower.tail = TRUE, log.p = FALSE) {
  if(any(pi < 0) | any(pi > 1))  warning("'pi' must be in [0, 1]")
  if(!missing(theta) & !missing(size)) stop("only 'theta' or 'size' may be specified")
  if(!missing(size)) theta <- size
  if(log.p) p <- exp(p)
  if(!lower.tail) p <- 1 - p
  p <- pmax(0, (p - pi)/(1 - pi))
  rval <- qnbinom(p, mu = mu, size = theta, lower.tail = TRUE, log.p = FALSE)
  rval
}

test_that("Normal", {
  library(OpenMx)
  library(testthat)

  RNGversion("4.0")
  set.seed(1)

  factorModel <- mxModel(
    "One Factor",
    mxMatrix(nrow=1, ncol=5, free=FALSE, values=0, name="M"),
    mxMatrix("Full", 5, 1, values=0.8,
             free=TRUE, name="A"),
    mxMatrix("Symm", 1, 1, values=1,
             free=FALSE, name="L"),
    mxMatrix("Diag", 5, 5, values=1,
             free=FALSE, name="U"),
    mxAlgebra(A %*% L %*% t(A) + U, name="R"),
    mxMatrix(nrow=3, ncol=3,
             values=c(.01, 1.1, NA,
                      .01, 2, NA,
                      .01, 4, .5),
             dimnames=list(c(), paste0('x',1:3)),
             name="D"),
    mxExpectationNormal(covariance = "R",
                        dimnames = paste0('x',1:5),
                        discrete = "D",
                        discreteSpec = matrix(c(4, 1, 5, 1, 6, 2), ncol=3,
                                              dimnames=list(c(), paste0('x',1:3))),
                        means = "M"),
    mxFitFunctionML())

  thr <- mxGetExpected(factorModel, "thresholds")
  expect_equal(thr[1:4,'x1'], c(-0.53, 0.68, 1.65, 2.5), .01)
  expect_equal(thr[1:5,'x2'], c(-1.36, -0.28, 0.6, 1.38, 2.08), .01)
  expect_equal(thr[1:6,'x3'], c(-1.87, -1.1, -0.49, 0.02, 0.46, 0.86), .01)

  factorModel <- mxGenerateData(factorModel, 400, returnModel = TRUE)

  # raw counts as integer
  factorModel$data$observed$x1 <-
    unclass(factorModel$data$observed$x1) - 1L

  # raw counts as numeric
  factorModel$data$observed$x2 <-
    unclass(factorModel$data$observed$x2) - 1.0

  factorModel$D$free <- !is.na(factorModel$D$values)

  fit <- mxRun(factorModel)
  expect_equal(fit$output$fit, 6256.76, .01)
  dv <- fit$D$values
  dv <- dv[!is.na(dv)]
  expect_equal(dv, c(0, 1.047, 0.022, 2.072, 0.013, 2.91, 0.411), .01)

  factorModel$expectation$discreteSpec <-
    factorModel$expectation$discreteSpec[,c(3,1,2)]
  expect_error(mxRun(factorModel),
               "must have the same column names")
  expect_error(mxGetExpected(factorModel, "thresholds"),
               "must have the same column names")
})

# ---------------

test_that("RAM", {
  library(OpenMx)
  library(testthat)

  RNGversion("4.0")
  set.seed(1)

  manifests <- paste0('x',1:5)
  latents <- c("G")
  factorModel <- mxModel(
    "One Factor",
    type="RAM",
    manifestVars = manifests,
    latentVars = latents,
    mxPath(from=latents, to=manifests,values=0.8),
    mxPath(from=manifests, arrows=2,values=1, free=FALSE),
    mxPath(from=latents, arrows=2,
           free=FALSE, values=1.0),
    mxPath(from = 'one', to = manifests, free=FALSE),
    mxMarginalPoisson(paste0("x",1:2), c(4,5), c(1.1, 2)),
    mxMarginalNegativeBinomial("x3", 6, 4, .5))

  trueDv <- factorModel$Discrete$values
  trueDv <- trueDv[!is.na(trueDv)]

  thr <- mxGetExpected(factorModel, "thresholds")
  expect_equal(thr[1:4,'x1'], c(-0.53, 0.68, 1.65, 2.5), .01)
  expect_equal(thr[1:5,'x2'], c(-1.36, -0.28, 0.6, 1.38, 2.08), .01)
  expect_equal(thr[1:6,'x3'], c(-1.87, -1.1, -0.49, 0.02, 0.46, 0.86), .01)

  factorModel <- mxGenerateData(factorModel, 400, returnModel = TRUE)

  verifyFrontBackMatch(factorModel)

  # autodetect maximum count from data
  factorModel$expectation$discreteSpec[1,] <- NA

  fit <- mxRun(factorModel)
  #  summary(fit)
  expect_equal(fit$output$fit, 6256.76, .01)
  dv <- fit$Discrete$values
  dv <- dv[!is.na(dv)]
  expect_equal(dv, c(0, 1.047, 0.022, 2.072, 0.013, 2.91, 0.411), .01)

  expect_equal(colnames(fit$expectation$discreteSpec), paste0('x',1:3))
  verifyFrontBackMatch(fit)
})

test_that("mediation", {
  library(OpenMx)
  library(testthat)
  library(MASS)

  RNGversion("4.0")
  set.seed(1)

  N<-5000
  A<-matrix(0,3,3)
  b_1_2<-.5
  b_2_3<-.5
  b_1_3<-.5

  size<- .5
  prob<- .25
  zif<-.3
  mu<- (1-prob)*size/prob

  A[2,1]<-b_1_2   #X1 to X2
  A[3,2]<-b_2_3   #X2 to X3
  A[3,1]<-b_1_3   #X1 to X3
  S<-diag(c(1,
            1-b_1_2^2,
            1 - b_2_3^2*(1-b_1_2^2) - (b_2_3*b_1_2)^2 - b_1_3^2 -  2*b_1_2*b_2_3*b_1_3
  ), 3)
  R<-solve(diag(3)-A)%*%S%*%t(solve(diag(3)-A))

  z<-mvrnorm(N, rep(0,3), R, empirical=F)
  z[,2]<-qzinbinom(pnorm(z[,2], 0, 1), size=size, mu=mu, pi=zif)

  manifests<-paste0("x",1:3)
  colnames(z)<-manifests

  z.f<-z<-as.data.frame(z)
  z.f[,2]<-mxFactor(z[,2], levels=0:max(z[,2]))

  factorModel<-mxModel(
    name="countMed",type="RAM", manifestVars = manifests, latentVars =NULL,

    mxPath(from=c("x1","x2"), to="x3", arrows=1, free=T),
    mxPath(from="x1", to="x2", arrows=1, free=T),

    mxPath(from=manifests, arrows=2, values=1, free=c(T,F,T) ),
    mxPath(from="one", to=manifests, arrows=1, values=0, free=c(T,F,T)),

    mxMarginalNegativeBinomial(c("x2"), size=1, prob=.5),
    mxData(z.f, type="raw"))

  expect_error(mxGetExpected(factorModel, "thresholds"),
               "maximum observed count")

  fit <-mxRun(factorModel)
#  summary(fit)
  expect_equivalent(fit$Discrete$values, c(zif, size, prob), .025)
  expect_equivalent(fit$M$values, rep(0,3), .01)
  expect_equivalent(fit$S$values[fit$S$free], diag(S)[c(1,3)], .05)
  expect_equivalent(fit$A$values[fit$A$free], A[fit$A$free], .1)
  verifyFrontBackMatch(fit)
})

test_that("LISREL", {
  library(OpenMx)
  library(testthat)

  RNGversion("4.0")
  set.seed(1)

  manifests <- paste0('x',1:5)
  latents <- c("G")
  factorModel <- mxModel(
    "One Factor",
    type="LISREL",
    manifestVars = manifests,
    latentVars = latents,
    mxPath(from=latents, to=manifests,values=0.8),
    mxPath(from=manifests, arrows=2,values=1, free=FALSE),
    mxPath(from=latents, arrows=2,
           free=FALSE, values=1.0),
    mxPath(from = 'one', to = manifests, free=FALSE),
    mxMarginalPoisson(paste0("x",1:2), c(4,5), c(1.1, 2)),
    mxMarginalNegativeBinomial("x3", 6, 4, .5))

  trueDv <- factorModel$Discrete$values
  trueDv <- trueDv[!is.na(trueDv)]

  thr <- mxGetExpected(factorModel, "thresholds")
  expect_equal(thr[1:4,'x1'], c(-0.53, 0.68, 1.65, 2.5), .01)
  expect_equal(thr[1:5,'x2'], c(-1.36, -0.28, 0.6, 1.38, 2.08), .01)
  expect_equal(thr[1:6,'x3'], c(-1.87, -1.1, -0.49, 0.02, 0.46, 0.86), .01)

  factorModel <- mxGenerateData(factorModel, 400, returnModel = TRUE)

  verifyFrontBackMatch(factorModel)

  # autodetect maximum count from data
  factorModel$expectation$discreteSpec[1,] <- NA

  fit <- mxRun(factorModel)
  #  summary(fit)
  expect_equal(fit$output$fit, 6256.76, .01)
  dv <- fit$Discrete$values
  dv <- dv[!is.na(dv)]
  expect_equal(dv, c(0, 1.047, 0.022, 2.072, 0.013, 2.91, 0.411), .01)

  expect_equal(colnames(fit$expectation$discreteSpec), paste0('x',1:3))
  verifyFrontBackMatch(fit)
})

test_that("probit+poisson ML+WLS", {
  library(OpenMx)
  library(testthat)

  RNGversion("4.0")
  set.seed(1)

  data("jointdata", package ="OpenMx")

  jointdata[,c(2,4,5)] <-
    mxFactor(jointdata[,c(2,4,5)],
             levels=list(c(0,1), c(0, 1, 2, 3), c(0, 1, 2)))

  jointdata$extra <- rnorm(nrow(jointdata))

  build <- function(wls=FALSE) {
    m1 <- mxModel(
      "m1", type="RAM",
      manifestVars = paste0('z',sample.int(5,5)),
      latentVars='G',
      mxData(jointdata[,sample.int(5,5)], "raw", verbose=0L),
      mxPath('one', paste0('z', c(1,3))),
      mxPath(paste0('z', c(1,3)), arrows=2, values=2),
      mxPath(paste0('z', c(2,4,5)), arrows=2, free=FALSE, values=.5),
      mxPath('G', arrows=2, values=1, free=FALSE),
      mxPath('G', paste0('z', 1:5), values=1),
      mxMarginalProbit(paste0('z', c(2,5)), nThresh=c(1,2), free=TRUE),
      mxMarginalPoisson('z4', lambda = .5))
    if (wls) m1 <- mxModel(m1, mxFitFunctionWLS())
    m1
  }

  m1 <- mxRun(build())
  verifyFrontBackMatch(m1)
  m2 <- mxRun(build())
  verifyFrontBackMatch(m2)
  m3 <- mxRun(build())
  verifyFrontBackMatch(m3)
  if (mxOption(key="Default optimizer") == 'NPSOL') {
    expect_equal(m1$output$fit - m2$output$fit, 0, 1e-3)
    expect_equal(m1$output$fit - m3$output$fit, 0, 1e-3)
  } else {
    expect_equal(m1$output$fit - m2$output$fit, 0, 1e-8)
    expect_equal(m1$output$fit - m3$output$fit, 0, 1e-8)
  }

  m1 <- mxRun(build(TRUE))
  verifyFrontBackMatch(m1)
  m2 <- mxRun(build(TRUE))
  verifyFrontBackMatch(m2)
  m3 <- mxRun(build(TRUE))
  verifyFrontBackMatch(m3)
  expect_equal(m1$output$fit - m2$output$fit, 0, 1e-8)
  expect_equal(m1$output$fit - m3$output$fit, 0, 1e-8)
})
