R/lav_mvreg_cluster.R

Defines functions lav_mvreg_cluster_information_firstorder lav_mvreg_cluster_scores_2l lav_mvreg_cluster_dlogl_2l_samplestats lav_mvreg_cluster_loglik_samplestats_2l lav_mvreg_cluster_2l2implied lav_mvreg_cluster_implied22l

# loglikelihood clustered/twolevel data -- conditional.x = TRUE

# YR: first version around Sept 2021

# take model-implied mean+variance matrices, and reorder/augment them
# to facilitate computing of (log)likelihood in the two-level case

# when conditional.x = TRUE:
# - sigma.w and sigma.b: same dimensions, level-1 'Y' variables only
# - sigma.zz: level-2 variables only
# - sigma.yz: cov(level-1, level-2)
# - beta.w: beta y within part
# - beta.b: beta y between part
# - beta.z: beta z (between-only)
lav_mvreg_cluster_implied22l <- function(Lp = NULL,
                                         implied = NULL,
                                         Res.Int.W = NULL,
                                         Res.Int.B = NULL,
                                         Res.Pi.W = NULL,
                                         Res.Pi.B = NULL,
                                         Res.Sigma.W = NULL,
                                         Res.Sigma.B = NULL) {
  if (!is.null(implied)) {
    # FIXME: only for single-group analysis!
    Res.Sigma.W <- implied$res.cov[[1]]
    Res.Int.W <- implied$res.int[[1]]
    Res.Pi.W <- implied$res.slopes[[1]]

    Res.Sigma.B <- implied$res.cov[[2]]
    Res.Int.B <- implied$res.int[[2]]
    Res.Pi.B <- implied$res.slopes[[2]]
  }

  # within/between idx
  within.x.idx <- Lp$within.x.idx[[1]]
  between.y.idx <- Lp$between.y.idx[[2]]
  between.x.idx <- Lp$between.x.idx[[2]]

  # ov.idx per level
  ov.idx <- Lp$ov.idx

  # 'tilde' matrices: ALL variables within and between
  p.tilde <- length(unique(c(ov.idx[[1]], ov.idx[[2]])))

  # only 'y'
  ov.y.idx <- Lp$ov.y.idx

  # two levels only (for now)
  ov.y.idx1 <- ov.y.idx[[1]]
  ov.y.idx2 <- ov.y.idx[[2]]

  # Sigma.W.tilde
  Sigma.W.tilde <- matrix(0, p.tilde, p.tilde)
  Sigma.W.tilde[ov.y.idx1, ov.y.idx1] <- Res.Sigma.W

  # INT.W.tilde
  INT.W.tilde <- matrix(0, p.tilde, 1L)
  INT.W.tilde[ov.y.idx1, 1L] <- Res.Int.W

  # PI.W.tilde
  PI.W.tilde <- matrix(0, p.tilde, ncol(Res.Pi.W))
  PI.W.tilde[ov.y.idx1, ] <- Res.Pi.W

  BETA.W.tilde <- rbind(t(INT.W.tilde), t(PI.W.tilde))



  # Sigma.B.tilde
  Sigma.B.tilde <- matrix(0, p.tilde, p.tilde)
  Sigma.B.tilde[ov.y.idx2, ov.y.idx2] <- Res.Sigma.B

  # INT.B.tilde
  INT.B.tilde <- matrix(0, p.tilde, 1L)
  INT.B.tilde[ov.y.idx2, 1L] <- Res.Int.B

  # PI.B.tilde
  PI.B.tilde <- matrix(0, p.tilde, ncol(Res.Pi.B))
  PI.B.tilde[ov.y.idx2, ] <- Res.Pi.B

  BETA.B.tilde <- rbind(t(INT.B.tilde), t(PI.B.tilde))

  if (length(between.y.idx) > 0L) {
    rm.idx <- c(within.x.idx, between.x.idx, between.y.idx) # between AND x
    beta.z <- BETA.B.tilde[, between.y.idx, drop = FALSE]
    beta.b <- BETA.B.tilde[, -rm.idx, drop = FALSE]
    beta.w <- BETA.W.tilde[, -rm.idx, drop = FALSE]
    sigma.zz <- Sigma.B.tilde[between.y.idx, between.y.idx, drop = FALSE]
    sigma.yz <- Sigma.B.tilde[-rm.idx, between.y.idx, drop = FALSE]
    sigma.b <- Sigma.B.tilde[-rm.idx, -rm.idx, drop = FALSE]
    sigma.w <- Sigma.W.tilde[-rm.idx, -rm.idx, drop = FALSE]
  } else {
    rm.idx <- c(within.x.idx, between.x.idx) # all 'x'
    beta.z <- matrix(0, 0L, 0L)
    sigma.zz <- matrix(0, 0L, 0L)
    beta.b <- BETA.B.tilde[, -rm.idx, drop = FALSE]
    beta.w <- BETA.W.tilde[, -rm.idx, drop = FALSE]
    sigma.b <- Sigma.B.tilde[-rm.idx, -rm.idx, drop = FALSE]
    sigma.w <- Sigma.W.tilde[-rm.idx, -rm.idx, drop = FALSE]
    sigma.yz <- matrix(0, nrow(sigma.w), 0L)
  }


  # beta.wb # FIXme: not correct if some 'x' are splitted (overlap)
  # but because we ALWAYS treat splitted-x as 'y', this is not a problem
  beta.wb <- rbind(beta.w, beta.b[-1, , drop = FALSE])
  beta.wb[1, ] <- beta.wb[1, , drop = FALSE] + beta.b[1, , drop = FALSE]

  list(
    sigma.w = sigma.w, sigma.b = sigma.b, sigma.zz = sigma.zz,
    sigma.yz = sigma.yz, beta.w = beta.w, beta.b = beta.b, beta.z = beta.z,
    beta.wb = beta.wb
  )
}


# recreate implied matrices from 2L matrices
lav_mvreg_cluster_2l2implied <- function(Lp,
                                         sigma.w = NULL,
                                         sigma.b = NULL,
                                         sigma.zz = NULL,
                                         sigma.yz = NULL,
                                         beta.w = NULL,
                                         beta.b = NULL,
                                         beta.z = NULL) {
  # within/between idx
  within.x.idx <- Lp$within.x.idx[[1]]
  between.y.idx <- Lp$between.y.idx[[2]]
  between.x.idx <- Lp$between.x.idx[[2]]

  # ov.idx per level
  ov.idx <- Lp$ov.idx

  # 'tilde' matrices: ALL variables within and between
  p.tilde <- length(unique(c(ov.idx[[1]], ov.idx[[2]])))

  # only 'y'
  ov.y.idx <- Lp$ov.y.idx

  # two levels only (for now)
  ov.y.idx1 <- ov.y.idx[[1]]
  ov.y.idx2 <- ov.y.idx[[2]]

  # Sigma.W.tilde
  Sigma.W.tilde <- matrix(0, p.tilde, p.tilde)
  Sigma.W.tilde[ov.y.idx1, ov.y.idx1] <- sigma.w

  # INT.W.tilde
  INT.W.tilde <- matrix(0, p.tilde, 1L)
  INT.W.tilde[ov.y.idx1, 1L] <- beta.w[1L, ]

  # PI.W.tilde
  PI.W.tilde <- matrix(0, p.tilde, nrow(beta.w) - 1L)
  PI.W.tilde[ov.y.idx1, ] <- t(beta.w[-1L, ])

  # Sigma.B.tilde
  Sigma.B.tilde <- matrix(0, p.tilde, p.tilde)
  Sigma.B.tilde[ov.y.idx1, ov.y.idx1] <- sigma.b

  # INT.B.tilde
  INT.B.tilde <- matrix(0, p.tilde, 1L)
  INT.B.tilde[ov.y.idx1, 1L] <- beta.b[1L, ]

  # PI.B.tilde
  PI.B.tilde <- matrix(0, p.tilde, nrow(beta.b) - 1L)
  PI.B.tilde[ov.y.idx1, ] <- t(beta.b[-1L, ])

  if (length(between.y.idx) > 0L) {
    INT.B.tilde[between.y.idx, 1L] <- beta.z[1L, ]
    PI.B.tilde[between.y.idx, ] <- t(beta.z[-1L, ])
    Sigma.B.tilde[between.y.idx, between.y.idx] <- sigma.zz
    Sigma.B.tilde[ov.y.idx1, between.y.idx] <- sigma.yz
    Sigma.B.tilde[between.y.idx, ov.y.idx1] <- t(sigma.yz)
  }

  Res.Sigma.W <- Sigma.W.tilde[ov.y.idx1, ov.y.idx1, drop = FALSE]
  Res.Int.W <- INT.W.tilde[ov.y.idx1, , drop = FALSE]
  Res.Pi.W <- PI.W.tilde[ov.y.idx1, , drop = FALSE]

  Res.Sigma.B <- Sigma.B.tilde[ov.y.idx2, ov.y.idx2, drop = FALSE]
  Res.Int.B <- INT.B.tilde[ov.y.idx2, , drop = FALSE]
  Res.Pi.B <- PI.B.tilde[ov.y.idx2, , drop = FALSE]

  implied <- list(
    res.cov = list(Res.Sigma.W, Res.Sigma.B),
    res.int = list(Res.Int.W, Res.Int.B),
    res.slopes = list(Res.Pi.W, Res.Pi.B)
  )

  # Note: cov.x and mean.x must be added by the caller
  implied
}

lav_mvreg_cluster_loglik_samplestats_2l <- function(YLp = NULL,
                                                    Lp = NULL,
                                                    Res.Sigma.W = NULL,
                                                    Res.Int.W = NULL,
                                                    Res.Pi.W = NULL,
                                                    Res.Sigma.B = NULL,
                                                    Res.Int.B = NULL,
                                                    Res.Pi.B = NULL,
                                                    out = NULL, # 2l
                                                    Sinv.method = "eigen",
                                                    log2pi = FALSE,
                                                    minus.two = TRUE) {
  # map implied to 2l matrices
  if (is.null(out)) {
    out <- lav_mvreg_cluster_implied22l(
      Lp = Lp, implied = NULL,
      Res.Sigma.W = Res.Sigma.W,
      Res.Int.W = Res.Int.W, Res.Pi.W = Res.Pi.W,
      Res.Sigma.B = Res.Sigma.B,
      Res.Int.B = Res.Int.B, Res.Pi.B = Res.Pi.B
    )
  }
  sigma.w <- out$sigma.w
  sigma.b <- out$sigma.b
  sigma.zz <- out$sigma.zz
  sigma.yz <- out$sigma.yz
  beta.w <- out$beta.w
  beta.b <- out$beta.b
  beta.z <- out$beta.z
  beta.wb <- out$beta.wb

  # check for beta.wb
  if (is.null(out$beta.wb)) {
    beta.wb <- rbind(beta.w, beta.b[-1, , drop = FALSE])
    beta.wb[1, ] <- beta.wb[1, , drop = FALSE] + beta.b[1, , drop = FALSE]
  }

  # log 2*pi
  LOG.2PI <- log(2 * pi)

  # Lp
  nclusters <- Lp$nclusters[[2]]
  cluster.size <- Lp$cluster.size[[2]]
  cluster.sizes <- Lp$cluster.sizes[[2]]
  ncluster.sizes <- Lp$ncluster.sizes[[2]]
  n.s <- Lp$cluster.size.ns[[2]]

  # dependent 'y' level-2 ('Z') only variables?
  between.y.idx <- Lp$between.y.idx[[2]]

  # extract (the many) sample statistics from YLp
  sample.wb <- YLp[[2]]$sample.wb
  sample.YYres.wb1 <- YLp[[2]]$sample.YYres.wb1
  sample.XX.wb1 <- YLp[[2]]$sample.XX.wb1
  sample.wb2 <- YLp[[2]]$sample.wb2
  sample.YYres.wb2 <- YLp[[2]]$sample.YYres.wb2
  sample.YresX.wb2 <- YLp[[2]]$sample.YresX.wb2
  sample.XX.wb2 <- YLp[[2]]$sample.XX.wb2
  sample.clz.Y2.res <- YLp[[2]]$sample.clz.Y2.res
  sample.clz.Y2.XX <- YLp[[2]]$sample.clz.Y2.XX
  sample.clz.Y2.B <- YLp[[2]]$sample.clz.Y2.B
  if (length(between.y.idx) > 0L) {
    sample.clz.ZZ.res <- YLp[[2]]$sample.clz.ZZ.res
    sample.clz.ZZ.XX <- YLp[[2]]$sample.clz.ZZ.XX
    sample.clz.ZZ.B <- YLp[[2]]$sample.clz.ZZ.B
    sample.clz.YZ.res <- YLp[[2]]$sample.clz.YZ.res
    sample.clz.YZ.XX <- YLp[[2]]$sample.clz.YZ.XX
    sample.clz.YresXZ <- YLp[[2]]$sample.clz.YresXZ # zero?
    sample.clz.XWZres <- YLp[[2]]$sample.clz.XWZres
  }

  # reconstruct S.PW
  wb1.diff <- sample.wb - beta.wb
  Y1Y1.wb.res <- (sample.YYres.wb1 +
    t(wb1.diff) %*% sample.XX.wb1 %*% (wb1.diff))

  # this one is weighted -- not the same as crossprod(Y2w.res)
  wb2.diff <- sample.wb2 - beta.wb
  Y2Y2w.res <- (sample.YYres.wb2 +
    sample.YresX.wb2 %*% (wb2.diff) +
    t(wb2.diff) %*% t(sample.YresX.wb2) +
    t(wb2.diff) %*% sample.XX.wb2 %*% (wb2.diff))
  S.PW <- (Y1Y1.wb.res - Y2Y2w.res) / sum(cluster.size - 1)

  # common parts:
  sigma.w.inv <- lav_matrix_symmetric_inverse(
    S = sigma.w,
    logdet = TRUE
  )
  sigma.w.logdet <- attr(sigma.w.inv, "logdet")
  if (length(between.y.idx) > 0L) {
    sigma.zz.inv <- lav_matrix_symmetric_inverse(
      S = sigma.zz,
      logdet = TRUE
    )
    sigma.zz.logdet <- attr(sigma.zz.inv, "logdet")
    sigma.yz.zi <- sigma.yz %*% sigma.zz.inv
    sigma.zi.zy <- t(sigma.yz.zi)
    sigma.b.z <- sigma.b - sigma.yz %*% sigma.zi.zy
  } else {
    sigma.b.z <- sigma.b
  }

  # min 2* logliklihood
  DIST <- numeric(ncluster.sizes)
  LOGDET <- numeric(ncluster.sizes)
  CONST <- numeric(ncluster.sizes)
  for (clz in seq_len(ncluster.sizes)) {
    # cluster size
    nj <- cluster.sizes[clz]

    # data between
    nj.idx <- which(cluster.size == nj)
    y2.diff <- sample.clz.Y2.B[[clz]] - beta.wb
    Y2Yc.yy <- (sample.clz.Y2.res[[clz]] +
      t(y2.diff) %*% sample.clz.Y2.XX[[clz]] %*% (y2.diff))
    if (length(between.y.idx) > 0L) {
      zz.diff <- sample.clz.ZZ.B[[clz]] - beta.z
      Y2Yc.zz <- (sample.clz.ZZ.res[[clz]] +
        t(zz.diff) %*% sample.clz.ZZ.XX[[clz]] %*% (zz.diff))
      Y2Yc.yz <- (sample.clz.YZ.res[[clz]] +
        sample.clz.YresXZ[[clz]] %*% zz.diff + # zero?
        t(y2.diff) %*% sample.clz.XWZres[[clz]] +
        t(y2.diff) %*% sample.clz.YZ.XX[[clz]] %*% zz.diff)
    }

    # construct sigma.j
    sigma.j <- (nj * sigma.b.z) + sigma.w
    sigma.j.inv <- lav_matrix_symmetric_inverse(
      S = sigma.j,
      logdet = TRUE
    )
    sigma.j.logdet <- attr(sigma.j.inv, "logdet")

    if (length(between.y.idx) > 0L) {
      sigma.ji.yz.zi <- sigma.j.inv %*% sigma.yz.zi

      # part 1 -- zz
      Vinv.11 <- sigma.zz.inv + nj * (sigma.zi.zy %*% sigma.ji.yz.zi)
      q.zz <- sum(Vinv.11 * Y2Yc.zz)

      # part 2 -- yz
      q.yz <- -nj * sum(sigma.ji.yz.zi * Y2Yc.yz)
    } else {
      q.zz <- q.yz <- sigma.zz.logdet <- 0
    }

    # part 5 -- yyc
    q.yyc <- -nj * sum(sigma.j.inv * Y2Yc.yy)

    if (log2pi) {
      P <- nj * nrow(sigma.w) + nrow(sigma.zz)
      CONST[clz] <- P * LOG.2PI
    }
    LOGDET[clz] <- sigma.zz.logdet + sigma.j.logdet
    DIST[clz] <- q.zz + 2 * q.yz - q.yyc
  }
  # q.yya + q.yyb
  q.W <- sum(cluster.size - 1) * sum(sigma.w.inv * S.PW)
  # logdet within part
  L.W <- sum(cluster.size - 1) * sigma.w.logdet

  # -2*times logl (without the constant) (for optimization)
  loglik <- sum(LOGDET * n.s) + sum(DIST) + q.W + L.W

  if (log2pi) {
    loglik <- loglik + sum(CONST * n.s)
  }

  # functions below compute -2 * logl
  if (!minus.two) {
    loglik <- loglik / (-2)
  }

  loglik
}

# first derivative -2*logl wrt Beta.W, Beta.B, Sigma.W, Sigma.B
lav_mvreg_cluster_dlogl_2l_samplestats <- function(YLp = NULL,
                                                   Lp = NULL,
                                                   Res.Sigma.W = NULL,
                                                   Res.Int.W = NULL,
                                                   Res.Pi.W = NULL,
                                                   Res.Sigma.B = NULL,
                                                   Res.Int.B = NULL,
                                                   Res.Pi.B = NULL,
                                                   out = NULL, # 2l
                                                   return.list = FALSE,
                                                   Sinv.method = "eigen") {
  # map implied to 2l matrices
  if (is.null(out)) {
    out <- lav_mvreg_cluster_implied22l(
      Lp = Lp, implied = NULL,
      Res.Sigma.W = Res.Sigma.W,
      Res.Int.W = Res.Int.W, Res.Pi.W = Res.Pi.W,
      Res.Sigma.B = Res.Sigma.B,
      Res.Int.B = Res.Int.B, Res.Pi.B = Res.Pi.B
    )
  }
  sigma.w <- out$sigma.w
  sigma.b <- out$sigma.b
  sigma.zz <- out$sigma.zz
  sigma.yz <- out$sigma.yz
  beta.w <- out$beta.w
  beta.b <- out$beta.b
  beta.z <- out$beta.z
  beta.wb <- out$beta.wb

  # check for beta.wb
  if (is.null(out$beta.wb)) {
    beta.wb <- rbind(beta.w, beta.b[-1, , drop = FALSE])
    beta.wb[1, ] <- beta.wb[1, , drop = FALSE] + beta.b[1, , drop = FALSE]
  }

  # Lp
  nclusters <- Lp$nclusters[[2]]
  cluster.size <- Lp$cluster.size[[2]]
  cluster.sizes <- Lp$cluster.sizes[[2]]
  ncluster.sizes <- Lp$ncluster.sizes[[2]]
  n.s <- Lp$cluster.size.ns[[2]]

  within.x.idx <- Lp$within.x.idx[[1]]
  between.y.idx <- Lp$between.y.idx[[2]]

  w1.idx <- seq_len(length(within.x.idx) + 1L)
  b1.idx <- c(1L, seq_len(nrow(beta.wb))[-w1.idx])

  # extract (the many) sample statistics from YLp
  sample.wb <- YLp[[2]]$sample.wb
  sample.YYres.wb1 <- YLp[[2]]$sample.YYres.wb1
  sample.XX.wb1 <- YLp[[2]]$sample.XX.wb1
  sample.wb2 <- YLp[[2]]$sample.wb2
  sample.YYres.wb2 <- YLp[[2]]$sample.YYres.wb2
  sample.YresX.wb2 <- YLp[[2]]$sample.YresX.wb2
  sample.XX.wb2 <- YLp[[2]]$sample.XX.wb2
  sample.clz.Y2.res <- YLp[[2]]$sample.clz.Y2.res
  sample.clz.Y2.XX <- YLp[[2]]$sample.clz.Y2.XX
  sample.clz.Y2.B <- YLp[[2]]$sample.clz.Y2.B
  if (length(between.y.idx) > 0L) {
    sample.clz.ZZ.res <- YLp[[2]]$sample.clz.ZZ.res
    sample.clz.ZZ.XX <- YLp[[2]]$sample.clz.ZZ.XX
    sample.clz.ZZ.B <- YLp[[2]]$sample.clz.ZZ.B
    sample.clz.YZ.res <- YLp[[2]]$sample.clz.YZ.res
    sample.clz.YZ.XX <- YLp[[2]]$sample.clz.YZ.XX
    sample.clz.YresXZ <- YLp[[2]]$sample.clz.YresXZ # zero?
    sample.clz.XWZres <- YLp[[2]]$sample.clz.XWZres
  }

  # reconstruct S.PW
  wb1.diff <- sample.wb - beta.wb
  Y1Y1.wb.res <- (sample.YYres.wb1 +
    t(wb1.diff) %*% sample.XX.wb1 %*% (wb1.diff))

  # this one is weighted -- not the same as crossprod(Y2w.res)
  wb2.diff <- sample.wb2 - beta.wb
  Y2Y2w.res <- (sample.YYres.wb2 +
    sample.YresX.wb2 %*% (wb2.diff) +
    t(wb2.diff) %*% t(sample.YresX.wb2) +
    t(wb2.diff) %*% sample.XX.wb2 %*% (wb2.diff))
  S.PW <- (Y1Y1.wb.res - Y2Y2w.res) / sum(cluster.size - 1)

  # common parts:
  sigma.w.inv <- lav_matrix_symmetric_inverse(S = sigma.w)

  G.beta.w <- matrix(0, ncluster.sizes, length(beta.w))
  G.beta.b <- matrix(0, ncluster.sizes, length(beta.b))
  G.beta.wb <- matrix(0, ncluster.sizes, length(beta.wb))
  G.sigma.w1 <- matrix(0, ncluster.sizes, length(lav_matrix_vech(sigma.w)))
  G.sigma.b <- matrix(0, ncluster.sizes, length(lav_matrix_vech(sigma.b)))

  if (length(between.y.idx) > 0L) {
    G.beta.z <- matrix(0, ncluster.sizes, length(beta.z))
    G.sigma.zz <- matrix(0, ncluster.sizes, length(lav_matrix_vech(sigma.zz)))
    G.sigma.yz <- matrix(0, ncluster.sizes, length(sigma.yz))

    sigma.zz.inv <- lav_matrix_symmetric_inverse(S = sigma.zz)
    sigma.yz.zi <- sigma.yz %*% sigma.zz.inv
    sigma.zi.zy <- t(sigma.yz.zi)
    sigma.b.z <- sigma.b - sigma.yz %*% sigma.zi.zy

    for (clz in seq_len(ncluster.sizes)) {
      # cluster size
      nj <- cluster.sizes[clz]

      y2.diff <- sample.clz.Y2.B[[clz]] - beta.wb
      XX.y2.diff <- sample.clz.Y2.XX[[clz]] %*% y2.diff
      Y2Yc.yy <- sample.clz.Y2.res[[clz]] + crossprod(y2.diff, XX.y2.diff)

      zz.diff <- sample.clz.ZZ.B[[clz]] - beta.z
      Y2Yc.zz <- (sample.clz.ZZ.res[[clz]] +
        t(zz.diff) %*% sample.clz.ZZ.XX[[clz]] %*% (zz.diff))
      Y2Yc.yz <- (sample.clz.YZ.res[[clz]] +
        sample.clz.YresXZ[[clz]] %*% zz.diff + # zero?
        t(y2.diff) %*% sample.clz.XWZres[[clz]] +
        t(y2.diff) %*% sample.clz.YZ.XX[[clz]] %*% zz.diff)

      # construct sigma.j
      sigma.j <- (nj * sigma.b.z) + sigma.w
      sigma.j.inv <- lav_matrix_symmetric_inverse(S = sigma.j)
      sigma.ji.yz.zi <- sigma.j.inv %*% sigma.yz.zi
      sigma.zi.zy.ji <- t(sigma.ji.yz.zi)
      sigma.ji.yz <- sigma.j.inv %*% sigma.yz
      ns.sigma.j.inv <- n.s[clz] * sigma.j.inv
      ns.sigma.zz.inv <- n.s[clz] * sigma.zz.inv
      ns.sigma.yz <- n.s[clz] * sigma.yz
      ns.sigma.ji.yz.zi <- n.s[clz] * sigma.ji.yz.zi

      # common parts
      ZZ.zi.yz.ji <- Y2Yc.zz %*% sigma.zi.zy.ji
      ji.YZ.zi <- sigma.j.inv %*% Y2Yc.yz %*% sigma.zz.inv

      jYZj.yy <- sigma.j.inv %*% Y2Yc.yy %*% sigma.j.inv
      jYZj.yz <- tcrossprod(ji.YZ.zi, sigma.ji.yz)
      jYZj.zz <- sigma.ji.yz.zi %*% ZZ.zi.yz.ji

      jYZj <- nj * (jYZj.yy + jYZj.zz - jYZj.yz - t(jYZj.yz))

      # SIGMA.W (between part)
      g.sigma.w1 <- ns.sigma.j.inv - jYZj
      tmp <- g.sigma.w1 * 2
      diag(tmp) <- diag(g.sigma.w1)
      G.sigma.w1[clz, ] <- lav_matrix_vech(tmp)

      # SIGMA.B
      g.sigma.b <- nj * g.sigma.w1
      tmp <- g.sigma.b * 2
      diag(tmp) <- diag(g.sigma.b)
      G.sigma.b[clz, ] <- lav_matrix_vech(tmp)

      # SIGMA.ZZ
      YZ1 <- ZZ.zi.yz.ji %*% sigma.yz
      YZ2 <- crossprod(Y2Yc.yz, sigma.ji.yz)
      tmp <- (t(sigma.yz) %*% g.sigma.w1 %*% sigma.yz
        - 1 / nj * Y2Yc.zz - t(YZ1) - YZ1 + t(YZ2) + YZ2)
      g.sigma.zz <- (ns.sigma.zz.inv +
        nj * sigma.zz.inv %*% tmp %*% sigma.zz.inv)
      tmp <- g.sigma.zz * 2
      diag(tmp) <- diag(g.sigma.zz)
      G.sigma.zz[clz, ] <- lav_matrix_vech(tmp)

      # SIGMA.ZY
      tmp1 <- crossprod(ZZ.zi.yz.ji, sigma.zz.inv)
      tmp2 <- ns.sigma.ji.yz.zi
      tmp3 <- ji.YZ.zi
      tmp4 <- jYZj %*% sigma.yz.zi
      g.sigma.yz <- 2 * nj * (tmp1 - tmp2 - tmp3 + tmp4)
      G.sigma.yz[clz, ] <- lav_matrix_vec(g.sigma.yz)

      # BETA.Z
      A <- (sigma.zz.inv + nj * (sigma.zi.zy.ji %*% sigma.yz.zi)) # symm!
      B <- nj * (sigma.zi.zy.ji)
      tmp.z <- (sample.clz.ZZ.XX[[clz]] %*% zz.diff %*% A -
        (t(sample.clz.YresXZ[[clz]]) +
          t(sample.clz.YZ.XX[[clz]]) %*% y2.diff) %*% t(B))
      G.beta.z[clz, ] <- as.vector(-2 * tmp.z)

      # BETA.W (between part only) + BETA.B
      tmp <- (sample.clz.XWZres[[clz]] +
        sample.clz.YZ.XX[[clz]] %*% zz.diff)
      out.b <- tmp %*% sigma.zi.zy.ji - XX.y2.diff %*% sigma.j.inv
      out.w <- out.b + XX.y2.diff %*% sigma.w.inv
      tmp.b <- out.b[b1.idx, , drop = FALSE]
      tmp.w <- out.w[w1.idx, , drop = FALSE]
      G.beta.b[clz, ] <- as.vector(2 * nj * tmp.b)
      G.beta.w[clz, ] <- as.vector(2 * nj * tmp.w)
    } # clz

    d.beta.w1 <- matrix(colSums(G.beta.w), nrow(beta.w), ncol(beta.w))
    d.beta.b <- matrix(colSums(G.beta.b), nrow(beta.b), ncol(beta.b))
    d.sigma.w1 <- lav_matrix_vech_reverse(colSums(G.sigma.w1))
    d.sigma.b <- lav_matrix_vech_reverse(colSums(G.sigma.b))

    # z
    d.beta.z <- matrix(colSums(G.beta.z), nrow(beta.z), ncol(beta.z))
    d.sigma.zz <- lav_matrix_vech_reverse(colSums(G.sigma.zz))
    d.sigma.yz <- matrix(colSums(G.sigma.yz), nrow(sigma.yz), ncol(sigma.yz))
  } # between.y.idx

  else { # no beween.y.idx

    for (clz in seq_len(ncluster.sizes)) {
      # cluster size
      nj <- cluster.sizes[clz]

      y2.diff <- sample.clz.Y2.B[[clz]] - beta.wb
      XX.y2.diff <- sample.clz.Y2.XX[[clz]] %*% y2.diff
      Y2Yc.yy <- sample.clz.Y2.res[[clz]] + crossprod(y2.diff, XX.y2.diff)

      # construct sigma.j
      sigma.j <- (nj * sigma.b) + sigma.w
      sigma.j.inv <- lav_matrix_symmetric_inverse(S = sigma.j)

      # common part
      jYYj <- nj * sigma.j.inv %*% Y2Yc.yy %*% sigma.j.inv

      # SIGMA.W (between part)
      g.sigma.w1 <- (n.s[clz] * sigma.j.inv) - jYYj
      tmp <- g.sigma.w1 * 2
      diag(tmp) <- diag(g.sigma.w1)
      G.sigma.w1[clz, ] <- lav_matrix_vech(tmp)

      # SIGMA.B
      g.sigma.b <- nj * g.sigma.w1
      tmp <- g.sigma.b * 2
      diag(tmp) <- diag(g.sigma.b)
      G.sigma.b[clz, ] <- lav_matrix_vech(tmp)

      # BETA.W (between part only) + BETA.B
      out.b <- -1 * XX.y2.diff %*% sigma.j.inv
      out.w <- out.b + XX.y2.diff %*% sigma.w.inv
      tmp.b <- out.b[b1.idx, , drop = FALSE]
      tmp.w <- out.w[w1.idx, , drop = FALSE]
      G.beta.b[clz, ] <- as.vector(2 * nj * tmp.b)
      G.beta.w[clz, ] <- as.vector(2 * nj * tmp.w)
    } # cl

    d.beta.w1 <- matrix(colSums(G.beta.w), nrow(beta.w), ncol(beta.w))
    d.beta.b <- matrix(colSums(G.beta.b), nrow(beta.b), ncol(beta.b))
    d.sigma.w1 <- lav_matrix_vech_reverse(colSums(G.sigma.w1))
    d.sigma.b <- lav_matrix_vech_reverse(colSums(G.sigma.b))

    # z
    d.beta.z <- matrix(0, 0L, 0L)
    d.sigma.zz <- matrix(0, 0L, 0L)
    d.sigma.yz <- matrix(0, 0L, 0L)
  } # no-between-y

  # Sigma.W (bis)
  d.sigma.w2 <- sum(cluster.size - 1) * (sigma.w.inv
  - sigma.w.inv %*% S.PW %*% sigma.w.inv)
  tmp <- d.sigma.w2 * 2
  diag(tmp) <- diag(d.sigma.w2)
  d.sigma.w2 <- tmp

  d.sigma.w <- d.sigma.w1 + d.sigma.w2

  # beta.w (bis)
  d.beta.w2 <- -2 * (sample.XX.wb1 %*% (sample.wb - beta.wb))[w1.idx, , drop = FALSE] %*% sigma.w.inv

  d.beta.w <- d.beta.w1 + d.beta.w2

  # rearrange
  dimplied <- lav_mvreg_cluster_2l2implied(Lp,
    sigma.w = d.sigma.w, sigma.b = d.sigma.b,
    sigma.zz = d.sigma.zz, sigma.yz = d.sigma.yz,
    beta.w = d.beta.w, beta.b = d.beta.b, beta.z = d.beta.z
  )

  if (return.list) {
    return(dimplied)
  }

  # as a single vector
  out <- c(
    drop(dimplied$res.int[[1]]),
    lav_matrix_vec(dimplied$res.slopes[[1]]),
    lav_matrix_vech(dimplied$res.cov[[1]]),
    drop(dimplied$res.int[[2]]),
    lav_matrix_vec(dimplied$res.slopes[[2]]),
    lav_matrix_vech(dimplied$res.cov[[2]])
  )
  out
}

# cluster-wise scores -2*logl wrt Beta.W, Beta.B, Sigma.W, Sigma.B
lav_mvreg_cluster_scores_2l <- function(Y1 = NULL,
                                        YLp = NULL,
                                        Lp = NULL,
                                        Res.Sigma.W = NULL,
                                        Res.Int.W = NULL,
                                        Res.Pi.W = NULL,
                                        Res.Sigma.B = NULL,
                                        Res.Int.B = NULL,
                                        Res.Pi.B = NULL,
                                        out = NULL, # 2l
                                        Sinv.method = "eigen") {
  # map implied to 2l matrices
  if (is.null(out)) {
    out <- lav_mvreg_cluster_implied22l(
      Lp = Lp, implied = NULL,
      Res.Sigma.W = Res.Sigma.W,
      Res.Int.W = Res.Int.W, Res.Pi.W = Res.Pi.W,
      Res.Sigma.B = Res.Sigma.B,
      Res.Int.B = Res.Int.B, Res.Pi.B = Res.Pi.B
    )
  }
  sigma.w <- out$sigma.w
  sigma.b <- out$sigma.b
  sigma.zz <- out$sigma.zz
  sigma.yz <- out$sigma.yz
  beta.w <- out$beta.w
  beta.b <- out$beta.b
  beta.z <- out$beta.z
  beta.wb <- out$beta.wb

  # check for beta.wb
  if (is.null(out$beta.wb)) {
    beta.wb <- rbind(beta.w, beta.b[-1, , drop = FALSE])
    beta.wb[1, ] <- beta.wb[1, , drop = FALSE] + beta.b[1, , drop = FALSE]
  }

  # Lp
  nclusters <- Lp$nclusters[[2]]
  cluster.size <- Lp$cluster.size[[2]]
  cluster.idx <- Lp$cluster.idx[[2]]

  within.x.idx <- Lp$within.x.idx[[1]]
  between.idx <- Lp$between.idx[[2]]
  between.y.idx <- Lp$between.y.idx[[2]]
  between.x.idx <- Lp$between.x.idx[[2]]

  y1.idx <- Lp$ov.y.idx[[1]]
  x1.idx <- c(within.x.idx, between.x.idx) # in that order

  # residuals for 'Y'
  Y1.wb <- Y1[, y1.idx, drop = FALSE]

  if (length(x1.idx) > 0L) {
    EXO.wb <- cbind(1, Y1[, x1.idx, drop = FALSE])
    Y1.wb.hat <- EXO.wb %*% beta.wb
    Y1.wb.res <- Y1.wb - Y1.wb.hat
  } else {
    Y1.wb.res <- Y1.wb
  }

  # residuals 'Y' (level 2)
  Y2 <- YLp[[2]]$Y2
  if (length(x1.idx) > 0L) {
    EXO.wb2 <- cbind(1, Y2[, x1.idx, drop = FALSE])
    Y2w.res <- Y2[, y1.idx, drop = FALSE] - EXO.wb2 %*% beta.wb
  } else {
    EXO.wb2 <- matrix(1, nrow(Y2), 1L)
    Y2w.res <- Y2[, y1.idx, drop = FALSE]
  }

  # residual 'Z' (level 2)
  if (length(between.y.idx) > 0L) {
    if (length(between.x.idx) > 0L) {
      EXO.z <- cbind(1, Y2[, between.x.idx, drop = FALSE])
      Y2.z <- Y2[, between.y.idx, drop = FALSE]
      Y2z.res <- Y2.z - EXO.z %*% beta.z
      # sample.z
      # XX.z <- crossprod(EXO.z)
      # sample.z <- try(solve(XX.z, crossprod(EXO.z, Y2.z)))
      # if(inherits(sample.z, "try-error")) {
      #    sample.z <- MASS::ginv(XX.z) %*% crossprod(EXO.z, Y2.z)
      # }

      # sample.wb2
      # sample.wb2 <- YLp[[2]]$sample.wb2
    } else {
      Y2z.res <- Y2[, between.y.idx, drop = FALSE]
    }
  }

  # common parts:
  sigma.w.inv <- lav_matrix_symmetric_inverse(S = sigma.w)

  G.beta.w1 <- matrix(0, nclusters, length(beta.w))
  G.beta.b <- matrix(0, nclusters, length(beta.b))
  G.beta.wb <- matrix(0, nclusters, length(beta.wb))
  G.sigma.w1 <- matrix(0, nclusters, length(lav_matrix_vech(sigma.w)))
  G.sigma.b <- matrix(0, nclusters, length(lav_matrix_vech(sigma.b)))


  if (length(between.y.idx) > 0L) {
    G.beta.z <- matrix(0, nclusters, length(beta.z))
    G.sigma.zz <- matrix(0, nclusters, length(lav_matrix_vech(sigma.zz)))
    G.sigma.yz <- matrix(0, nclusters, length(sigma.yz))

    sigma.zz.inv <- lav_matrix_symmetric_inverse(S = sigma.zz)
    sigma.yz.zi <- sigma.yz %*% sigma.zz.inv
    sigma.zi.zy <- t(sigma.yz.zi)
    sigma.b.z <- sigma.b - sigma.yz %*% sigma.zi.zy

    for (cl in seq_len(nclusters)) {
      # cluster size
      nj <- cluster.size[cl]

      # data within for the cluster (centered)
      Y1m <- Y1.wb.res[cluster.idx == cl, , drop = FALSE]
      yc <- Y2w.res[cl, ]

      # data between
      zc <- Y2z.res[cl, ]
      Y2Yc.yy <- tcrossprod(Y2w.res[cl, ])
      Y2Yc.zz <- tcrossprod(Y2z.res[cl, ])
      Y2Yc.yz <- tcrossprod(Y2w.res[cl, ], Y2z.res[cl, ])

      # construct sigma.j
      sigma.j <- (nj * sigma.b.z) + sigma.w
      sigma.j.inv <- lav_matrix_symmetric_inverse(S = sigma.j)
      sigma.ji.yz.zi <- sigma.j.inv %*% sigma.yz.zi
      sigma.zi.zy.ji <- t(sigma.ji.yz.zi)
      sigma.ji.yz <- sigma.j.inv %*% sigma.yz

      # common parts
      ZZ.zi.yz.ji <- Y2Yc.zz %*% sigma.zi.zy.ji
      ji.YZ.zi <- sigma.j.inv %*% Y2Yc.yz %*% sigma.zz.inv

      jYZj.yy <- sigma.j.inv %*% Y2Yc.yy %*% sigma.j.inv
      jYZj.yz <- tcrossprod(ji.YZ.zi, sigma.ji.yz)
      jYZj.zz <- sigma.ji.yz.zi %*% ZZ.zi.yz.ji

      jYZj <- nj * (jYZj.yy + jYZj.zz - jYZj.yz - t(jYZj.yz))

      # SIGMA.W (between part)
      g.sigma.w1 <- sigma.j.inv - jYZj
      tmp <- g.sigma.w1 * 2
      diag(tmp) <- diag(g.sigma.w1)
      G.sigma.w1[cl, ] <- lav_matrix_vech(tmp)

      # SIGMA.W (within part)
      # g.sigma.w2 <- ( (nj-1) * sigma.w.inv
      #    - sigma.w.inv %*% (crossprod(Y1m) - nj*Y2Yc.yy) %*% sigma.w.inv )
      # tmp <- g.sigma.w2*2; diag(tmp) <- diag(g.sigma.w2)
      # G.sigma.w2[cl,] <- lav_matrix_vech(tmp)
      # G.sigma.w[cl,] <- G.sigma.w1[cl,] + G.sigma.w2[cl,]

      # SIGMA.B
      g.sigma.b <- nj * g.sigma.w1
      tmp <- g.sigma.b * 2
      diag(tmp) <- diag(g.sigma.b)
      G.sigma.b[cl, ] <- lav_matrix_vech(tmp)

      # SIGMA.ZZ
      YZ1 <- ZZ.zi.yz.ji %*% sigma.yz
      YZ2 <- crossprod(Y2Yc.yz, sigma.ji.yz)
      tmp <- (t(sigma.yz) %*% g.sigma.w1 %*% sigma.yz
        - (1 / nj * Y2Yc.zz + t(YZ1) + YZ1 - t(YZ2) - YZ2))
      g.sigma.zz <- (sigma.zz.inv +
        nj * sigma.zz.inv %*% tmp %*% sigma.zz.inv)
      tmp <- g.sigma.zz * 2
      diag(tmp) <- diag(g.sigma.zz)
      G.sigma.zz[cl, ] <- lav_matrix_vech(tmp)

      # SIGMA.ZY
      # g.sigma.yz <- 2 * nj * (
      #              (sigma.j.inv %*%
      #                  (sigma.yz.zi %*% Y2Yc.zz - sigma.yz - Y2Yc.yz)
      #                   + jYZj %*% sigma.yz) %*% sigma.zz.inv )
      tmp1 <- crossprod(ZZ.zi.yz.ji, sigma.zz.inv)
      tmp2 <- sigma.ji.yz.zi
      tmp3 <- ji.YZ.zi
      tmp4 <- jYZj %*% sigma.yz.zi
      g.sigma.yz <- 2 * nj * (tmp1 - tmp2 - tmp3 + tmp4)
      G.sigma.yz[cl, ] <- lav_matrix_vec(g.sigma.yz)

      # BETA.Z
      # here, we avoid the (sample.z - beta.z) approach
      exo.z <- cbind(1, Y2[cl, between.x.idx, drop = FALSE])
      tmp1 <- (sigma.zz.inv + nj * (sigma.zi.zy.ji %*% sigma.yz.zi)) %*% zc
      tmp2 <- nj * (sigma.zi.zy.ji) %*% yc
      tmp.z <- crossprod(exo.z, drop(tmp1 - tmp2))
      G.beta.z[cl, ] <- as.vector(-2 * tmp.z)

      # BETA.W
      #            exo.w <- cbind(1,
      #                           Y1[cluster.idx == cl, within.x.idx, drop = FALSE])
      #            G.beta.w[cl,] <- as.vector( 2 * t(exo.w) %*% (
      #                                 matrix(1, nj, 1) %x% (zc %*% sigma.zi.zy.ji -
      #                                                       yc %*% sigma.j.inv +
      #                                                       yc %*% sigma.w.inv) -
      #                                 Y1m %*% sigma.w.inv) )

      # BETA.W (between part only)
      exo2.w <- cbind(1, Y2[cl, within.x.idx, drop = FALSE])
      tmp2 <- (zc %*% sigma.zi.zy.ji -
        yc %*% sigma.j.inv +
        yc %*% sigma.w.inv)
      G.beta.w1[cl, ] <- as.vector(2 * nj * crossprod(exo2.w, tmp2))

      # BETA.W (within part only)
      # exo.w <- cbind(1,
      #               Y1[cluster.idx == cl, within.x.idx, drop = FALSE])
      # tmp1 <- - Y1m %*% sigma.w.inv
      # G.beta.ww <- as.vector( 2 * crossprod(exo.w, tmp1) )
      # G.beta.w[cl,] <- G.beta.w1 + G.beta.ww
      # G.beta.w2[cl,] <- G.beta.ww

      # BETA.B
      exo2.b <- cbind(1, Y2[cl, between.x.idx, drop = FALSE])
      tmp <- (zc %*% sigma.zi.zy.ji - yc %*% sigma.j.inv)
      G.beta.b[cl, ] <- as.vector(2 * nj * crossprod(exo2.b, tmp))
    } # cl
  } # between.y.idx

  else { # no beween.y.idx

    for (cl in seq_len(nclusters)) {
      # cluster size
      nj <- cluster.size[cl]

      # data within for the cluster (centered)
      Y1m <- Y1.wb.res[cluster.idx == cl, , drop = FALSE]
      yc <- Y2w.res[cl, ]

      # data between
      Y2Yc.yy <- tcrossprod(Y2w.res[cl, ])

      # construct sigma.j
      sigma.j <- (nj * sigma.b) + sigma.w
      sigma.j.inv <- lav_matrix_symmetric_inverse(S = sigma.j)

      # common part
      jYYj <- nj * sigma.j.inv %*% Y2Yc.yy %*% sigma.j.inv

      # SIGMA.W
      # g.sigma.w <- ( (nj-1) * sigma.w.inv
      #    - sigma.w.inv %*% (crossprod(Y1m) - nj*Y2Yc.yy) %*% sigma.w.inv
      #    + sigma.j.inv - jYYj )
      # tmp <- g.sigma.w*2; diag(tmp) <- diag(g.sigma.w)
      # G.sigma.w[cl,] <- lav_matrix_vech(tmp)

      # SIGMA.W (between part)
      g.sigma.w1 <- sigma.j.inv - jYYj
      tmp <- g.sigma.w1 * 2
      diag(tmp) <- diag(g.sigma.w1)
      G.sigma.w1[cl, ] <- lav_matrix_vech(tmp)

      # SIGMA.B
      g.sigma.b <- nj * (sigma.j.inv - jYYj)
      tmp <- g.sigma.b * 2
      diag(tmp) <- diag(g.sigma.b)
      G.sigma.b[cl, ] <- lav_matrix_vech(tmp)

      # BETA.W (between part only)
      exo2.w <- cbind(1, Y2[cl, within.x.idx, drop = FALSE])
      tmp2 <- (-yc %*% sigma.j.inv + yc %*% sigma.w.inv)
      G.beta.w1[cl, ] <- as.vector(2 * nj * crossprod(exo2.w, tmp2))

      # BETA.B
      exo2.b <- cbind(1, Y2[cl, between.x.idx, drop = FALSE])
      tmp <- -yc %*% sigma.j.inv
      G.beta.b[cl, ] <- as.vector(2 * nj * crossprod(exo2.b, tmp))
    } # cl
  } # no-between-y

  # beta.w (bis)

  #    d.beta.w2 <- -2 * t(EXO.wb[,1:(length(within.x.idx) + 1L), drop = FALSE]) %*% Y1.wb.res %*% sigma.w.inv

  Y1.wb.res.i <- Y1.wb.res %*% sigma.w.inv
  w1.idx <- seq_len(length(within.x.idx) + 1L)
  a1.idx <- rep(w1.idx, times = ncol(Y1.wb.res.i))
  b1.idx <- rep(seq_len(ncol(Y1.wb.res.i)), each = length(w1.idx))
  TMP <- EXO.wb[, a1.idx, drop = FALSE] * Y1.wb.res.i[, b1.idx, drop = FALSE]
  G.beta.w2 <- -2 * rowsum.default(TMP, cluster.idx,
    reorder = FALSE,
    na.rm = TRUE
  )
  G.beta.w <- G.beta.w1 + G.beta.w2

  # Sigma.W (bis)
  # d.sigma.w2 <- sum(cluster.size - 1) * ( sigma.w.inv
  #               - sigma.w.inv %*% S.PW %*% sigma.w.inv )
  # tmp <- d.sigma.w2*2; diag(tmp) <- diag(d.sigma.w2)
  # d.sigma.w2 <- tmp

  # g.sigma.w2 <- ( (nj-1) * sigma.w.inv
  #        - sigma.w.inv %*% (crossprod(Y1m) - nj*Y2Yc.yy) %*% sigma.w.inv )

  Y1a.res <- Y1.wb.res - Y2w.res[cluster.idx, , drop = FALSE]
  Y1a.res.i <- Y1a.res %*% sigma.w.inv
  idx1 <- lav_matrix_vech_col_idx(nrow(sigma.w))
  idx2 <- lav_matrix_vech_row_idx(nrow(sigma.w))
  SW2 <- matrix(lav_matrix_vech(sigma.w.inv),
    nrow = nclusters,
    length(lav_matrix_vech(sigma.w.inv)), byrow = TRUE
  )
  SW2 <- SW2 * (cluster.size - 1)
  TMP <- Y1a.res.i[, idx1, drop = FALSE] * Y1a.res.i[, idx2, drop = FALSE]
  TMP2 <- rowsum.default(TMP, cluster.idx, reorder = FALSE, na.rm = TRUE)
  G.sigma.w2 <- 2 * (SW2 - TMP2)
  diagh.idx <- lav_matrix_diagh_idx(nrow(sigma.w))
  G.sigma.w2[, diagh.idx] <- G.sigma.w2[, diagh.idx, drop = FALSE] / 2
  G.sigma.w <- G.sigma.w1 + G.sigma.w2



  # rearrange columns to Res.Int.W, Res.Pi.W, Res.Sigma.W,
  #                      Res.Int.B, Res.Pi.B, Res.Sigma.B

  # ov.idx per level
  ov.idx <- Lp$ov.idx

  # 'tilde' matrices: ALL variables within and between
  p.tilde <- length(unique(c(ov.idx[[1]], ov.idx[[2]])))
  p.tilde.star <- p.tilde * (p.tilde + 1) / 2
  B.tilde <- lav_matrix_vech_reverse(seq_len(p.tilde.star))

  # only 'y'
  ov.y.idx <- Lp$ov.y.idx

  # two levels only (for now)
  ov.y.idx1 <- ov.y.idx[[1]]
  ov.y.idx2 <- ov.y.idx[[2]]

  # WITHIN (is easy)
  BETA.W.idx <- matrix(seq_len(length(beta.w)), nrow(beta.w), ncol(beta.w))
  BETA.B.idx <- matrix(seq_len(length(beta.b)), nrow(beta.b), ncol(beta.b))

  Res.Int.W <- G.beta.w[, BETA.W.idx[1L, ], drop = FALSE]
  Res.Pi.W <- G.beta.w[, lav_matrix_vecr(BETA.W.idx[-1L, ]), drop = FALSE]
  Res.Sigma.W <- G.sigma.w

  # Sigma.B
  Sigma.B.tilde <- matrix(0, nclusters, p.tilde.star)
  col.idx <- lav_matrix_vech(B.tilde[ov.y.idx1, ov.y.idx1, drop = FALSE])
  Sigma.B.tilde[, col.idx] <- G.sigma.b

  # Int.B
  BETA.B.tilde <- matrix(seq_len(nrow(beta.b) * p.tilde), nrow(beta.b), p.tilde)
  Int.B <- matrix(0, nclusters, p.tilde)
  Int.B[, ov.y.idx1] <- G.beta.b[, BETA.B.idx[1L, ]]

  # Pi.B
  Pi.B <- matrix(0, nclusters, p.tilde * (nrow(beta.b) - 1L))
  col.idx <- lav_matrix_vecr(BETA.B.tilde[-1L, ov.y.idx1, drop = FALSE])
  Pi.B[, col.idx] <- G.beta.b[, lav_matrix_vecr(BETA.B.idx[-1L, ]), drop = FALSE]

  if (length(between.y.idx) > 0L) {
    # Sigma.B: add yz/zz parts
    col.idx <- lav_matrix_vec(B.tilde[ov.y.idx1, between.y.idx, drop = FALSE])
    Sigma.B.tilde[, col.idx] <- G.sigma.yz
    col.idx <- lav_matrix_vech(B.tilde[between.y.idx, between.y.idx,
      drop = FALSE
    ])
    Sigma.B.tilde[, col.idx] <- G.sigma.zz

    # Int.B: add z-part
    BETA.Z.idx <- matrix(seq_len(length(beta.z)), nrow(beta.z), ncol(beta.z))
    Int.B[, between.y.idx] <- G.beta.z[, BETA.Z.idx[1L, ], drop = FALSE]

    # Pi.B: add beta.z
    col.idx <- lav_matrix_vecr(BETA.B.tilde[-1L, between.y.idx, drop = FALSE])
    Pi.B[, col.idx] <-
      G.beta.z[, lav_matrix_vecr(BETA.Z.idx[-1L, ]), drop = FALSE]
  }

  # only extract ov.y.idx2 for BETWEEN
  col.idx <- lav_matrix_vech(B.tilde[ov.y.idx2, ov.y.idx2, drop = FALSE])
  Res.Sigma.B <- Sigma.B.tilde[, col.idx, drop = FALSE]

  Res.Int.B <- Int.B[, ov.y.idx2, drop = FALSE]

  col.idx <- lav_matrix_vecr(BETA.B.tilde[-1, ov.y.idx2])
  Res.Pi.B <- Pi.B[, col.idx, drop = FALSE]

  SCORES <- cbind(
    Res.Int.W, Res.Pi.W, Res.Sigma.W,
    Res.Int.B, Res.Pi.B, Res.Sigma.B
  )

  SCORES
}

# first-order information: outer crossprod of scores per cluster
lav_mvreg_cluster_information_firstorder <- function(Y1 = NULL,
                                                     YLp = NULL,
                                                     Lp = NULL,
                                                     Res.Sigma.W = NULL,
                                                     Res.Int.W = NULL,
                                                     Res.Pi.W = NULL,
                                                     Res.Sigma.B = NULL,
                                                     Res.Int.B = NULL,
                                                     Res.Pi.B = NULL,
                                                     divide.by.two = FALSE,
                                                     Sinv.method = "eigen") {
  N <- NROW(Y1)

  SCORES <- lav_mvreg_cluster_scores_2l(
    Y1 = Y1,
    YLp = YLp,
    Lp = Lp,
    Res.Sigma.W = Res.Sigma.W,
    Res.Int.W = Res.Int.W,
    Res.Pi.W = Res.Pi.W,
    Res.Sigma.B = Res.Sigma.B,
    Res.Int.B = Res.Int.B,
    Res.Pi.B = Res.Pi.B,
    Sinv.method = Sinv.method
  )

  # divide by 2 (if we want scores wrt objective function)
  if (divide.by.two) {
    SCORES <- SCORES / 2
  }

  # unit information
  information <- crossprod(SCORES) / Lp$nclusters[[2]]

  information
}
yrosseel/lavaan documentation built on May 1, 2024, 5:45 p.m.