# model gradient
lav_model_gradient <- function(lavmodel = NULL,
GLIST = NULL,
lavsamplestats = NULL,
lavdata = NULL,
lavcache = NULL,
type = "free",
verbose = FALSE,
forcePD = TRUE,
group.weight = TRUE,
Delta = NULL,
m.el.idx = NULL,
x.el.idx = NULL) {
nmat <- lavmodel@nmat
estimator <- lavmodel@estimator
representation <- lavmodel@representation
meanstructure <- lavmodel@meanstructure
categorical <- lavmodel@categorical
group.w.free <- lavmodel@group.w.free
fixed.x <- lavmodel@fixed.x
conditional.x <- lavmodel@conditional.x
num.idx <- lavmodel@num.idx
th.idx <- lavmodel@th.idx
nx.free <- lavmodel@nx.free
# state or final?
if(is.null(GLIST)) GLIST <- lavmodel@GLIST
if(estimator == "REML") warning("analytical gradient not implement; use numerical approximation")
# group.weight
# FIXME --> block.weight
if(group.weight) {
if(estimator %in% c("ML","PML","FML","MML","REML","NTRLS")) {
group.w <- (unlist(lavsamplestats@nobs)/lavsamplestats@ntotal)
} else {
# FIXME: double check!
group.w <- ((unlist(lavsamplestats@nobs)-1)/lavsamplestats@ntotal)
}
} else {
group.w <- rep(1.0, lavmodel@nblocks)
}
# do we need WLS.est?
if(estimator %in% c("WLS", "DWLS", "ULS", "GLS", "NTRLS")) {
# always compute WLS.est
WLS.est <- lav_model_wls_est(lavmodel = lavmodel, GLIST = GLIST) #,
# cov.x = lavsamplestats@cov.x)
}
if(estimator %in% c("ML", "PML", "FML", "REML", "NTRLS")) {
# compute moments for all groups
#if(conditional.x) {
# Sigma.hat <- computeSigmaHatJoint(lavmodel = lavmodel,
# GLIST = GLIST,
# extra = (estimator %in% c("ML", "REML","NTRLS")))
#} else {
Sigma.hat <- computeSigmaHat(lavmodel = lavmodel, GLIST = GLIST,
extra = (estimator %in% c("ML", "REML", "NTRLS")))
#}
# ridge here?
if(meanstructure) {
#if(conditional.x) {
# Mu.hat <- computeMuHat(lavmodel = lavmodel, GLIST = GLIST)
#} else {
Mu.hat <- computeMuHat(lavmodel = lavmodel, GLIST = GLIST)
#}
}
if(categorical) {
TH <- computeTH(lavmodel = lavmodel, GLIST = GLIST)
}
if(conditional.x) {
PI <- computePI(lavmodel = lavmodel, GLIST = GLIST)
} else if(estimator == "PML") {
PI <- vector("list", length = lavmodel@nblocks)
}
if(group.w.free) {
GW <- computeGW(lavmodel = lavmodel, GLIST = GLIST)
}
} else if(estimator == "MML") {
TH <- computeTH( lavmodel = lavmodel, GLIST = GLIST)
THETA <- computeTHETA(lavmodel = lavmodel, GLIST = GLIST)
GW <- computeGW( lavmodel = lavmodel, GLIST = GLIST)
}
# four approaches (FIXME!!!! merge this!)
# - ML approach: using Omega (and Omega.mu)
# Omega = 'POST' = Sigma.inv %*% (S - Sigma) %*% t(Sigma.inv)
# (still 2x faster than Delta method)
# - WLS/DWLS/GLS: using Delta + WLS.V; support for fixed.x, conditional.x
# - (ML)/NTRLS: using Delta, no support for fixed.x, conditional.x
# - PML/FML/MML: custom
# 1. ML approach
if( (estimator == "ML" || estimator == "REML") &&
lavdata@nlevels == 1L &&
!lavmodel@conditional.x ) {
if(meanstructure) {
Omega <- computeOmega(Sigma.hat=Sigma.hat, Mu.hat=Mu.hat,
lavsamplestats=lavsamplestats,
estimator=estimator,
meanstructure=TRUE,
conditional.x = conditional.x)
Omega.mu <- attr(Omega, "mu")
} else {
Omega <- computeOmega(Sigma.hat=Sigma.hat, Mu.hat=NULL,
lavsamplestats=lavsamplestats,
estimator=estimator,
meanstructure=FALSE,
conditional.x = conditional.x)
Omega.mu <- vector("list", length = lavmodel@nblocks)
}
# compute DX (for all elements in every model matrix)
DX <- vector("list", length=length(GLIST))
for(g in 1:lavmodel@nblocks) {
# which mm belong to group g?
mm.in.group <- 1:nmat[g] + cumsum(c(0,nmat))[g]
mm.names <- names( GLIST[mm.in.group] )
if(representation == "LISREL") {
DX.group <- derivative.F.LISREL(GLIST[mm.in.group],
Omega[[g]],
Omega.mu[[g]])
# FIXME!!!
# add empty gamma
if(lavmodel@conditional.x) {
DX.group$gamma <- lavmodel@GLIST$gamma
}
# only save what we need
DX[mm.in.group] <- DX.group[ mm.names ]
} else {
stop("only representation LISREL has been implemented for now")
}
# weight by group
if(lavmodel@nblocks > 1L) {
for(mm in mm.in.group) {
DX[[mm]] <- group.w[g] * DX[[mm]]
}
}
}
# extract free parameters
if(type == "free") {
dx <- numeric( nx.free )
for(g in 1:lavmodel@nblocks) {
mm.in.group <- 1:nmat[g] + cumsum(c(0,nmat))[g]
for(mm in mm.in.group) {
m.free.idx <- lavmodel@m.free.idx[[mm]]
x.free.idx <- lavmodel@x.free.idx[[mm]]
dx[x.free.idx] <- DX[[mm]][m.free.idx]
}
}
# handle equality constraints
#if(lavmodel@eq.constraints && constraints) {
# dx <- as.numeric( t(lavmodel@eq.constraints.K) %*% dx )
#}
} else {
dx <- DX
# handle equality constraints
### FIXME!!!! TODO!!!!
}
} else # ML
# 2. using Delta - *LS family
if(estimator %in% c("WLS", "DWLS", "ULS", "GLS", "NTGLS")) {
if(type != "free") {
if(is.null(Delta))
stop("FIXME: Delta should be given if type != free")
#stop("FIXME: WLS gradient with type != free needs fixing!")
} else {
Delta <- computeDelta(lavmodel = lavmodel, GLIST. = GLIST)
}
for(g in 1:lavmodel@nblocks) {
#diff <- as.matrix(lavsamplestats@WLS.obs[[g]] - WLS.est[[g]])
#group.dx <- -1 * ( t(Delta[[g]]) %*% lavsamplestats@WLS.V[[g]] %*% diff)
# 0.5-17: use crossprod twice; treat DWLS/ULS special
if(estimator == "WLS" ||
estimator == "GLS" ||
estimator == "NTRLS") {
# full weight matrix
diff <- lavsamplestats@WLS.obs[[g]] - WLS.est[[g]]
# full weight matrix
if(estimator == "GLS" || estimator == "WLS") {
WLS.V <- lavsamplestats@WLS.V[[g]]
group.dx <- -1 * crossprod(Delta[[g]],
crossprod(WLS.V, diff))
} else if(estimator == "NTRLS") {
stopifnot(!conditional.x)
#WLS.V <- lav_samplestats_Gamma_inverse_NT(
# ICOV = attr(Sigma.hat[[g]],"inv")[,,drop=FALSE],
# COV = Sigma.hat[[g]][,,drop=FALSE],
# MEAN = Mu.hat[[g]],
# x.idx = lavsamplestats@x.idx[[g]],
# fixed.x = fixed.x,
# conditional.x = conditional.x,
# meanstructure = meanstructure,
# slopestructure = conditional.x)
S <- lavsamplestats@cov[[g]]
Sigma <- Sigma.hat[[g]]
Sigma.inv <- attr(Sigma, "inv")
nvar <- NROW(Sigma)
if(meanstructure) {
MEAN <- lavsamplestats@mean[[g]]; Mu <- Mu.hat[[g]]
POST.Sigma <- lav_matrix_duplication_pre(
matrix((Sigma.inv %*% (S - Sigma) %*% t(Sigma.inv)) %*%
(diag(nvar) + (S - Sigma) %*% Sigma.inv) +
(Sigma.inv %*% tcrossprod(MEAN - Mu) %*% Sigma.inv),
ncol = 1) )
POST.Mu <- as.numeric(2 * Sigma.inv %*% (MEAN - Mu))
POST <- c(POST.Mu, POST.Sigma)
} else {
POST <- lav_matrix_duplication_pre(
matrix((Sigma.inv %*% (S - Sigma) %*% t(Sigma.inv)) %*%
(diag(nvar) + (S - Sigma) %*% Sigma.inv), ncol = 1))
}
group.dx <- as.numeric( -1 * crossprod(Delta[[g]], POST) )
}
} else
if(estimator == "DWLS" || estimator == "ULS") {
# diagonal weight matrix
diff <- lavsamplestats@WLS.obs[[g]] - WLS.est[[g]]
group.dx <- -1 * crossprod(Delta[[g]],
lavsamplestats@WLS.VD[[g]] * diff)
}
group.dx <- group.w[g] * group.dx
if(g == 1) {
dx <- group.dx
} else {
dx <- dx + group.dx
}
} # g
if(type == "free") {
# nothing to do
} else {
# make a GLIST
dx <- lav_model_x2GLIST(lavmodel = lavmodel, x = dx,
type = "custom", setDelta = FALSE,
m.el.idx = m.el.idx,
x.el.idx = x.el.idx)
}
} # WLS
else if(estimator == "ML" && lavmodel@conditional.x) {
if(type != "free") {
if(is.null(Delta))
stop("FIXME: Delta should be given if type != free")
#stop("FIXME: WLS gradient with type != free needs fixing!")
} else {
Delta <- computeDelta(lavmodel = lavmodel, GLIST. = GLIST)
}
for(g in 1:lavmodel@nblocks) {
# augmented mean.x + cov.x matrix
mean.x <- lavsamplestats@mean.x[[g]]
cov.x <- lavsamplestats@cov.x[[g]]
C3 <- rbind(c(1,mean.x),
cbind(mean.x, cov.x + tcrossprod(mean.x)))
Sigma <- Sigma.hat[[g]]
Mu.g <- Mu.hat[[g]]
PI.g <- PI[[g]]
Sigma.inv <- attr(Sigma, "inv")
nvar <- NROW(Sigma)
S <- lavsamplestats@res.cov[[g]]
# beta
OBS <- t( cbind(lavsamplestats@res.int[[g]],
lavsamplestats@res.slopes[[g]]) )
EST <- t( cbind(Mu.g, PI.g) )
#obs.beta <- c(lavsamplestats@res.int[[g]],
# lav_matrix_vec(lavsamplestats@res.slopes[[g]]))
#est.beta <- c(Mu.g, lav_matrix_vec(PI.g))
#beta.COV <- C3 %x% Sigma.inv
#a <- t(obs.beta - est.beta)
#b <- as.matrix(obs.beta - est.beta)
#K <- lav_matrix_commutation(m = nvar, n = nvar)
#AB <- (K %x% diag(NROW(C3)*NROW(C3))) %*%
# (diag(nvar) %x% lav_matrix_vec(C3) %x% diag(nvar))
#K <- lav_matrix_commutation(m = nvar, n = NROW(C3))
#AB <- ( diag(NROW(C3)) %x% K %x% diag(nvar) ) %*%
# (lav_matrix_vec(C3) %x% diag( nvar * nvar) )
#POST.beta <- 2 * beta.COV %*% (obs.beta - est.beta)
d.BETA <- C3 %*% (OBS - EST) %*% Sigma.inv
# NOTE: the vecr here, unlike lav_mvreg_dlogl_beta
# this is because DELTA has used vec(t(BETA)),
# instead of vec(BETA)
#POST.beta <- 2 * lav_matrix_vecr(d.BETA)
# NOT any longer, since 0.6-1!!!
POST.beta <- 2 * lav_matrix_vec(d.BETA)
#POST.sigma1 <- lav_matrix_duplication_pre(
# (Sigma.inv %x% Sigma.inv) %*% t(AB) %*% (t(a) %x% b) )
# Sigma
#POST.sigma2 <- lav_matrix_duplication_pre(
# matrix( lav_matrix_vec(
# Sigma.inv %*% (S - Sigma) %*% t(Sigma.inv)), ncol = 1L))
W.tilde <- S + t(OBS - EST) %*% C3 %*% (OBS - EST)
d.SIGMA <- (Sigma.inv - Sigma.inv %*% W.tilde %*% Sigma.inv)
d.vechSigma <- as.numeric( lav_matrix_duplication_pre(
as.matrix(lav_matrix_vec(d.SIGMA)) ) )
POST.sigma <- -1 * d.vechSigma
#POST <- c(POST.beta, POST.sigma1 + POST.sigma2)
POST <- c(POST.beta, POST.sigma)
group.dx <- as.numeric( -1 * crossprod(Delta[[g]], POST) )
# because we still use obj/2, we need to divide by 2!
group.dx <- group.dx / 2 # fixed in 0.6-1
group.dx <- group.w[g] * group.dx
if(g == 1) {
dx <- group.dx
} else {
dx <- dx + group.dx
}
} # g
if(type == "free") {
# nothing to do
} else {
# make a GLIST
dx <- lav_model_x2GLIST(lavmodel = lavmodel, x = dx,
type = "custom", setDelta = FALSE,
m.el.idx = m.el.idx,
x.el.idx = x.el.idx)
}
} # ML + conditional.x
else if(estimator == "ML" && lavdata@nlevels > 1L) {
if(type != "free") {
stop("FIXME: type != free in lav_model_gradient for estimator ML for nlevels > 1")
} else {
Delta <- computeDelta(lavmodel = lavmodel, GLIST. = GLIST)
}
# for each upper-level group....
for(g in 1:lavmodel@ngroups) {
DX <- lav_mvnorm_cluster_dlogl_2l_samplestats(
YLp = lavsamplestats@YLp[[g]],
Lp = lavdata@Lp[[g]],
Mu.W = Mu.hat[[(g-1)*2 + 1]],
Sigma.W = Sigma.hat[[(g-1)*2 + 1]],
Mu.B = Mu.hat[[(g-1)*2 + 2]],
Sigma.B = Sigma.hat[[(g-1)*2 + 2]],
Sinv.method = "eigen")
group.dx <- as.numeric( DX %*% Delta[[g]] )
# group weights (if any)
group.dx <- group.w[g] * group.dx
if(g == 1) {
dx <- group.dx
} else {
dx <- dx + group.dx
}
} # g
# divide by 2 * N
dx <- dx / (2 * lavsamplestats@ntotal)
#cat("dx1 (numerical) = \n"); print( zapsmall(dx1) )
#cat("dx (analytic) = \n"); print( zapsmall(dx ) )
} # ML + two-level
else if(estimator == "PML" || estimator == "FML" ||
estimator == "MML") {
if(type != "free") {
stop("FIXME: type != free in lav_model_gradient for estimator PML")
} else {
Delta <- computeDelta(lavmodel = lavmodel, GLIST. = GLIST)
}
for(g in 1:lavmodel@nblocks) {
#print(GLIST)
#print(lav_model_get_parameters(lavmodel = lavmodel, GLIST = GLIST))
#print(Sigma.hat[[g]])
#print(TH[[g]])
#cat("*****\n")
# compute partial derivative of logLik with respect to
# thresholds/means, slopes, variances, correlations
if(estimator == "PML") {
if(lavdata@nlevels > 1L) {
stop("psindex ERROR: PL gradient + multilevel not implemented; try optim.gradient = \"numerical\"")
} else if(conditional.x) {
d1 <- pml_deriv1(Sigma.hat = Sigma.hat[[g]],
Mu.hat = Mu.hat[[g]],
TH = TH[[g]],
th.idx = th.idx[[g]],
num.idx = num.idx[[g]],
X = lavdata@X[[g]],
lavcache = lavcache[[g]],
eXo = lavdata@eXo[[g]],
PI = PI[[g]],
missing = lavdata@missing)
} else {
d1 <- pml_deriv1(Sigma.hat = Sigma.hat[[g]],
Mu.hat = Mu.hat[[g]],
TH = TH[[g]],
th.idx = th.idx[[g]],
num.idx = num.idx[[g]],
X = lavdata@X[[g]],
lavcache = lavcache[[g]],
eXo = NULL,
PI = NULL,
missing = lavdata@missing)
} # not conditional.x
# chain rule (fmin)
group.dx <-
as.numeric(t(d1) %*% Delta[[g]])
} # PML
else if(estimator == "FML") {
d1 <- fml_deriv1(Sigma.hat = Sigma.hat[[g]],
TH = TH[[g]],
th.idx = th.idx[[g]],
num.idx = num.idx[[g]],
X = lavdata@X[[g]],
lavcache = lavcache[[g]])
# chain rule (fmin)
group.dx <-
as.numeric(t(d1) %*% Delta[[g]])/lavsamplestats@nobs[[g]]
} else if(estimator == "MML") {
group.dx <-
lav_model_gradient_mml(lavmodel = lavmodel,
GLIST = GLIST,
THETA = THETA[[g]],
TH = TH[[g]],
group = g,
lavdata = lavdata,
sample.mean = lavsamplestats@mean[[g]],
sample.mean.x = lavsamplestats@mean.x[[g]],
lavcache = lavcache)
}
# group weights (if any)
group.dx <- group.w[g] * group.dx
if(g == 1) {
dx <- group.dx
} else {
dx <- dx + group.dx
}
} # g
} else {
stop("psindex ERROR: no analytical gradient available for estimator ",
estimator)
}
# group.w.free for ML
if(lavmodel@group.w.free &&
estimator %in% c("ML","MML","FML","PML","REML")) {
#est.prop <- unlist( computeGW(lavmodel = lavmodel, GLIST = GLIST) )
#obs.prop <- unlist(lavsamplestats@group.w)
# FIXME: G2 based -- ML and friends only!!
#dx.GW <- - (obs.prop - est.prop)
# poisson version
est.freq <- exp(unlist(computeGW(lavmodel = lavmodel, GLIST = GLIST)))
obs.freq <- unlist(lavsamplestats@group.w) * lavsamplestats@ntotal
dx.GW <- - (obs.freq - est.freq)
# divide by N (to be consistent with the rest of psindex)
dx.GW <- dx.GW / lavsamplestats@ntotal
# remove last element (fixed LAST group to zero)
# dx.GW <- dx.GW[-length(dx.GW)]
# fill in in dx
gw.mat.idx <- which(names(lavmodel@GLIST) == "gw")
gw.x.idx <- unlist( lavmodel@x.free.idx[gw.mat.idx] )
dx[gw.x.idx] <- dx.GW
}
# dx is 1xnpar matrix of LIST (type != "free")
if(is.matrix(dx)) {
dx <- as.numeric(dx)
}
dx
}
# for testing purposes only
#computeDeltaNumerical <- function(lavmodel = NULL, GLIST = NULL, g = 1L) {
#
# # state or final?
# if(is.null(GLIST)) GLIST <- lavmodel@GLIST
#
# compute.moments <- function(x) {
# GLIST <- lav_model_x2GLIST(lavmodel = NULL, x=x, type="free")
# Sigma.hat <- computeSigmaHat(lavmodel = NULL, GLIST = GLIST)
# S.vec <- lav_matrix_vech(Sigma.hat[[g]])
# if(lavmodel@meanstructure) {
# Mu.hat <- computeMuHat(lavmodel = NULL, GLIST=GLIST)
# out <- c(Mu.hat[[g]], S.vec)
# } else {
# out <- S.vec
# }
# out
# }
#
# x <- lav_model_get_parameters(lavmodel = NULL, GLIST=GLIST, type="free")
# Delta <- lav_func_jacobian_complex(func=compute.moments, x = x)
#
# Delta
#}
### FIXME: should we here also:
### - weight for groups? (no, for now)
### - handle equality constraints? (yes, for now)
computeDelta <- function(lavmodel = NULL, GLIST. = NULL,
m.el.idx. = NULL, x.el.idx. = NULL) {
representation <- lavmodel@representation
categorical <- lavmodel@categorical
conditional.x <- lavmodel@conditional.x
group.w.free <- lavmodel@group.w.free
nmat <- lavmodel@nmat
nblocks <- lavmodel@nblocks
nvar <- lavmodel@nvar
num.idx <- lavmodel@num.idx
th.idx <- lavmodel@th.idx
nexo <- lavmodel@nexo
parameterization <- lavmodel@parameterization
# number of thresholds per group (if any)
nth <- sapply(th.idx, function(x) sum(x > 0L))
# state or final?
if(is.null(GLIST.))
GLIST <- lavmodel@GLIST
else
GLIST <- GLIST.
# type = "free" or something else?
type <- "nonfree"
m.el.idx <- m.el.idx.; x.el.idx <- x.el.idx.
if(is.null(m.el.idx) && is.null(x.el.idx))
type <- "free"
# number of rows in DELTA.group
pstar <- integer(nblocks)
for(g in 1:nblocks) {
pstar[g] <- as.integer(nvar[g] * (nvar[g] + 1) / 2)
if(lavmodel@meanstructure) {
pstar[g] <- nvar[g] + pstar[g] # first the means, then sigma
}
if(categorical) {
pstar[g] <- pstar[g] - nvar[g] # remove variances
pstar[g] <- pstar[g] - nvar[g] # remove means
pstar[g] <- pstar[g] + nth[g] # add thresholds
pstar[g] <- pstar[g] + length(num.idx[[g]]) # add num means
pstar[g] <- pstar[g] + length(num.idx[[g]]) # add num vars
}
if(conditional.x && nexo[g] > 0L) {
pstar[g] <- pstar[g] + (nvar[g] * nexo[g]) # add slopes
}
if(group.w.free) {
pstar[g] <- pstar[g] + 1L # add group weight
}
}
# number of columns in DELTA + m.el.idx/x.el.idx
if(type == "free") {
NCOL <- lavmodel@nx.free
m.el.idx <- x.el.idx <- vector("list", length=length(GLIST))
for(mm in 1:length(GLIST)) {
m.el.idx[[mm]] <- lavmodel@m.free.idx[[mm]]
x.el.idx[[mm]] <- lavmodel@x.free.idx[[mm]]
# handle symmetric matrices
if(lavmodel@isSymmetric[mm]) {
# since we use 'x.free.idx', only symmetric elements
# are duplicated (not the equal ones, only in x.free.free)
dix <- duplicated(x.el.idx[[mm]])
if(any(dix)) {
m.el.idx[[mm]] <- m.el.idx[[mm]][!dix]
x.el.idx[[mm]] <- x.el.idx[[mm]][!dix]
}
}
}
} else {
## FIXME: this does *not* take into account symmetric
## matrices; hence NCOL will be too large, and empty
## columns will be added
## this is ugly, but it doesn't hurt
## alternative could be:
## NCOL <- sum(unlist(lapply(x.el.idx, function(x) length(unique(x)))))
#NCOL <- sum(unlist(lapply(m.el.idx, length)))
NCOL <- sum(unlist(lapply(x.el.idx, function(x) length(unique(x)))))
# sanity check
#nx <- sum(unlist(lapply(x.el.idx, length)))
#stopifnot(NCOL == nx)
}
# compute Delta
Delta <- vector("list", length=nblocks)
for(g in 1:nblocks) {
Delta.group <- matrix(0, nrow=pstar[g], ncol=NCOL)
# which mm belong to group g?
mm.in.group <- 1:nmat[g] + cumsum(c(0,nmat))[g]
# label rows of Delta.group --- FIXME!!!
#if(categorical) {
# # 1. th (means interleaved?)
# # 2. pi
# # 3. var num + cor
#} else {
# if(meanstructure) {
# }
#}
#if(group.w.free) {
#}
# if theta, do some preparation
if(parameterization == "theta") {
sigma.hat <- computeSigmaHat.LISREL(MLIST=GLIST[mm.in.group],
delta=FALSE)
dsigma <- diag(sigma.hat)
# dcor/dcov for sigma
R <- lav_deriv_cov2cor(sigma.hat, num.idx = lavmodel@num.idx[[g]])
theta.var.idx <- lav_matrix_diagh_idx(nvar[g])
}
for(mm in mm.in.group) {
mname <- names(lavmodel@GLIST)[mm]
# skip empty ones
if(!length(m.el.idx[[mm]])) next
# get Delta columns for this model matrix
if(representation == "LISREL") {
# Sigma
DELTA <- dxSigma <-
derivative.sigma.LISREL(m = mname,
idx = m.el.idx[[mm]],
MLIST = GLIST[ mm.in.group ],
delta = parameterization == "delta")
if(categorical && parameterization == "theta") {
DELTA <- R %*% DELTA
}
if(categorical) {
# reorder: first variances (of numeric), then covariances
cov.idx <- lav_matrix_vech_idx(nvar[g])
covd.idx <- lav_matrix_vech_idx(nvar[g], diagonal = FALSE)
var.idx <- which(is.na(match(cov.idx,
covd.idx)))[num.idx[[g]]]
cor.idx <- match(covd.idx, cov.idx)
DELTA <- rbind(DELTA[var.idx,,drop=FALSE],
DELTA[cor.idx,,drop=FALSE])
}
if(!categorical) {
if(conditional.x && lavmodel@nexo[g] > 0L) {
# ATTENTION: we need to change the order here
# lav_mvreg_scores_* uses 'Beta' where the
# the intercepts are just the first row
# using the col-major approach, we need to
# interweave the intercepts with the slopes!
DELTA.mu <- derivative.mu.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
DELTA.pi <- derivative.pi.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
nEls <- NROW(DELTA.mu) + NROW(DELTA.pi)
# = (nexo + 1 int) * nvar
# intercepts on top
tmp <- rbind(DELTA.mu, DELTA.pi)
# change row index
row.idx <- lav_matrix_vec(matrix(seq.int(nEls),
nrow = lavmodel@nexo[g] + 1L,
ncol = lavmodel@nvar[g], byrow = TRUE))
DELTA.beta <- tmp[row.idx,,drop = FALSE]
DELTA <- rbind(DELTA.beta, DELTA)
} else if(!conditional.x && lavmodel@meanstructure) {
DELTA.mu <- derivative.mu.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
DELTA <- rbind(DELTA.mu, DELTA)
}
}
else if(categorical) {
DELTA.th <- derivative.th.LISREL(m=mname,
idx=m.el.idx[[mm]],
th.idx=th.idx[[g]],
MLIST=GLIST[ mm.in.group ],
delta = TRUE)
if(parameterization == "theta") {
# dy/ddsigma = -0.5/(ddsigma*sqrt(ddsigma))
dDelta.dx <-
( dxSigma[theta.var.idx,,drop=FALSE] *
-0.5 / (dsigma*sqrt(dsigma)) )
dth.dDelta <-
derivative.th.LISREL(m = "delta",
idx = 1:nvar[g],
MLIST = GLIST[ mm.in.group ],
th.idx = th.idx[[g]])
# add dth.dDelta %*% dDelta.dx
no.num.idx <- which(th.idx[[g]] > 0)
DELTA.th[no.num.idx,] <-
DELTA.th[no.num.idx,,drop=FALSE] +
(dth.dDelta %*% dDelta.dx)[no.num.idx,,drop=FALSE]
}
if(conditional.x && lavmodel@nexo[g] > 0L) {
DELTA.pi <-
derivative.pi.LISREL(m=mname,
idx=m.el.idx[[mm]],
MLIST=GLIST[ mm.in.group ])
if(parameterization == "theta") {
dpi.dDelta <-
derivative.pi.LISREL(m = "delta",
idx = 1:nvar[g],
MLIST = GLIST[ mm.in.group ])
# add dpi.dDelta %*% dDelta.dx
no.num.idx <-
which(!seq.int(1L,nvar[g]) %in% num.idx[[g]])
no.num.idx <- rep(seq.int(0,nexo[g]-1) * nvar[g],
each=length(no.num.idx)) + no.num.idx
DELTA.pi[no.num.idx,] <-
DELTA.pi[no.num.idx,,drop=FALSE] +
(dpi.dDelta %*% dDelta.dx)[no.num.idx,,drop=FALSE]
}
DELTA <- rbind(DELTA.th, DELTA.pi, DELTA)
} else {
DELTA <- rbind(DELTA.th, DELTA)
}
}
if(group.w.free) {
DELTA.gw <- derivative.gw.LISREL(m=mname,
idx=m.el.idx[[mm]],
MLIST=GLIST[ mm.in.group ])
DELTA <- rbind(DELTA.gw, DELTA)
}
} else {
stop("representation", representation, "not implemented yet")
}
Delta.group[ ,x.el.idx[[mm]]] <- DELTA
} # mm
# save(Delta.group, file=paste0("delta_NO_EQ",g,".Rdata"))
# if type == "free" take care of equality constraints
#if(type == "free" && lavmodel@eq.constraints) {
# Delta.group <- Delta.group %*% lavmodel@eq.constraints.K
#}
#Delta.eq <- Delta.group
# save(Delta.eq, file=paste0("delta_NO_EQ",g,".Rdata"))
Delta[[g]] <- Delta.group
} # g
# if multilevel, rbind levels within group
if(lavmodel@multilevel) {
DELTA <- vector("list", length = lavmodel@ngroups)
for(g in 1:lavmodel@ngroups) {
DELTA[[g]] <- rbind( Delta[[(g-1)*2 + 1]],
Delta[[(g-1)*2 + 2]] )
}
Delta <- DELTA
}
Delta
}
computeDeltaDx <- function(lavmodel = NULL, GLIST = NULL, target = "lambda") {
# state or final?
if(is.null(GLIST)) GLIST <- lavmodel@GLIST
representation <- lavmodel@representation
nmat <- lavmodel@nmat
nblocks <- lavmodel@nblocks
num.idx <- lavmodel@num.idx
th.idx <- lavmodel@th.idx
# number of columns in DELTA + m.el.idx/x.el.idx
type <- "free"
#if(type == "free") {
NCOL <- lavmodel@nx.free
m.el.idx <- x.el.idx <- vector("list", length=length(GLIST))
for(mm in 1:length(GLIST)) {
m.el.idx[[mm]] <- lavmodel@m.free.idx[[mm]]
x.el.idx[[mm]] <- lavmodel@x.free.idx[[mm]]
# handle symmetric matrices
if(lavmodel@isSymmetric[mm]) {
# since we use 'x.free.idx', only symmetric elements
# are duplicated (not the equal ones, only in x.free.free)
dix <- duplicated(x.el.idx[[mm]])
if(any(dix)) {
m.el.idx[[mm]] <- m.el.idx[[mm]][!dix]
x.el.idx[[mm]] <- x.el.idx[[mm]][!dix]
}
}
}
#} else {
# NCOL <- sum(unlist(lapply(x.el.idx, function(x) length(unique(x)))))
#}
# compute Delta per group
Delta <- vector("list", length=nblocks)
for(g in 1:nblocks) {
mm.in.group <- 1:nmat[g] + cumsum(c(0,nmat))[g]
Delta.group <- NULL
for(mm in mm.in.group) {
mname <- names(lavmodel@GLIST)[mm]
# skip empty ones
if(!length(m.el.idx[[mm]])) next
# get Delta columns for this model matrix
if(representation == "LISREL") {
if(target == "lambda") {
DELTA <- derivative.lambda.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "th") {
DELTA <- derivative.th.LISREL(m=mname, th.idx = th.idx[[g]],
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ],
delta=TRUE)
} else if(target == "mu") {
DELTA <- derivative.mu.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "nu") {
DELTA <- derivative.nu.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "tau") {
DELTA <- derivative.tau.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "theta") {
DELTA <- derivative.theta.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "gamma") {
DELTA <- derivative.gamma.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "beta") {
DELTA <- derivative.beta.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "alpha") {
DELTA <- derivative.alpha.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "psi") {
DELTA <- derivative.psi.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ])
} else if(target == "sigma") {
DELTA <- derivative.sigma.LISREL(m=mname,
idx=m.el.idx[[mm]], MLIST=GLIST[ mm.in.group ],
delta=TRUE)
} else {
stop("psindex ERROR: target ", target, " not implemented yet")
}
# initialize?
if(is.null(Delta.group)) {
Delta.group <- matrix(0, nrow=nrow(DELTA), ncol=NCOL)
}
Delta.group[ ,x.el.idx[[mm]]] <- DELTA
}
} # mm
#if(type == "free" && lavmodel@eq.constraints) {
# Delta.group <- Delta.group %*% lavmodel@eq.constraints.K
#}
Delta[[g]] <- Delta.group
} # g
Delta
}
computeOmega <- function(Sigma.hat=NULL, Mu.hat=NULL,
lavsamplestats=NULL, estimator="ML",
meanstructure=FALSE, conditional.x = FALSE) {
# nblocks
nblocks <- length(Sigma.hat)
Omega <- vector("list", length = nblocks)
Omega.mu <- vector("list", length = nblocks)
for(g in 1:nblocks) {
# ML
if(estimator == "ML" || estimator == "REML") {
if(attr(Sigma.hat[[g]], "po") == FALSE) {
# FIXME: WHAT IS THE BEST THING TO DO HERE??
# CURRENTLY: stop
warning("lav_model_gradient: Sigma.hat is not positive definite\n")
Sigma.hat.inv <- MASS::ginv(Sigma.hat[[g]])
Sigma.hat.log.det <- log(.Machine$double.eps)
} else {
Sigma.hat.inv <- attr(Sigma.hat[[g]], "inv")
Sigma.hat.log.det <- attr(Sigma.hat[[g]], "log.det")
}
if(!lavsamplestats@missing.flag) { # complete data
if(meanstructure) {
if(conditional.x) {
diff <- lavsamplestats@res.int[[g]] - Mu.hat[[g]]
W.tilde <- lavsamplestats@res.cov[[g]] + tcrossprod(diff)
} else {
diff <- lavsamplestats@mean[[g]] - Mu.hat[[g]]
W.tilde <- lavsamplestats@cov[[g]] + tcrossprod(diff)
}
# Browne 1995 eq 4.55
Omega.mu[[g]] <- t(t(diff) %*% Sigma.hat.inv)
Omega[[g]] <-
( Sigma.hat.inv %*% (W.tilde - Sigma.hat[[g]]) %*%
Sigma.hat.inv )
} else {
if(conditional.x) {
W.tilde <- lavsamplestats@res.cov[[g]]
} else {
W.tilde <- lavsamplestats@cov[[g]]
}
Omega[[g]] <-
( Sigma.hat.inv %*% (W.tilde - Sigma.hat[[g]]) %*%
Sigma.hat.inv )
}
} else { # missing data
M <- lavsamplestats@missing[[g]]
nvar <- ncol(lavsamplestats@cov[[g]])
OMEGA <- matrix(0, nvar, nvar)
OMEGA.MU <- matrix(0, nvar, 1)
for(p in 1:length(M)) {
SX <- M[[p]][["SY"]]
MX <- M[[p]][["MY"]]
nobs <- M[[p]][["freq"]]
var.idx <- M[[p]][["var.idx"]]
Sigma.inv <- inv.chol(Sigma.hat[[g]][var.idx, var.idx],
logdet=FALSE)
Mu <- Mu.hat[[g]][var.idx]
W.tilde <- SX + tcrossprod(MX - Mu)
OMEGA.MU[var.idx, 1] <-
( OMEGA.MU[var.idx, 1] + nobs/lavsamplestats@ntotal *
t(t(MX - Mu) %*% Sigma.inv) )
OMEGA[var.idx, var.idx] <-
( OMEGA[var.idx, var.idx] + nobs/lavsamplestats@ntotal *
(Sigma.inv %*%
(W.tilde - Sigma.hat[[g]][var.idx,var.idx]) %*%
Sigma.inv ) )
}
Omega.mu[[g]] <- OMEGA.MU
Omega[[g]] <- OMEGA
} # missing
# GLS
} else if(estimator == "GLS") {
W.inv <- lavsamplestats@icov[[g]]
W <- lavsamplestats@cov[[g]]
Omega[[g]] <- (lavsamplestats@nobs[[g]]-1)/lavsamplestats@nobs[[g]] *
(W.inv %*% (W - Sigma.hat[[g]]) %*% W.inv)
if(meanstructure) {
diff <- as.matrix(lavsamplestats@mean[[g]] - Mu.hat[[g]])
Omega.mu[[g]] <- t( t(diff) %*% W.inv )
}
}
} # g
if(meanstructure) attr(Omega, "mu") <- Omega.mu
Omega
}
lav_model_gradient_DD <- function(lavmodel, GLIST = NULL, group = 1L) {
if(is.null(GLIST)) GLIST <- lavmodel@GLIST
#### FIX th + mu!!!!!
Delta.lambda <- computeDeltaDx(lavmodel, GLIST=GLIST, target="lambda")[[group]]
Delta.tau <- computeDeltaDx(lavmodel, GLIST=GLIST, target="tau" )[[group]]
Delta.nu <- computeDeltaDx(lavmodel, GLIST=GLIST, target="nu" )[[group]]
Delta.theta <- computeDeltaDx(lavmodel, GLIST=GLIST, target="theta" )[[group]]
Delta.beta <- computeDeltaDx(lavmodel, GLIST=GLIST, target="beta" )[[group]]
Delta.psi <- computeDeltaDx(lavmodel, GLIST=GLIST, target="psi" )[[group]]
Delta.alpha <- computeDeltaDx(lavmodel, GLIST=GLIST, target="alpha" )[[group]]
Delta.gamma <- computeDeltaDx(lavmodel, GLIST=GLIST, target="gamma" )[[group]]
ov.y.dummy.ov.idx <- lavmodel@ov.y.dummy.ov.idx[[group]]
ov.x.dummy.ov.idx <- lavmodel@ov.x.dummy.ov.idx[[group]]
ov.y.dummy.lv.idx <- lavmodel@ov.y.dummy.lv.idx[[group]]
ov.x.dummy.lv.idx <- lavmodel@ov.x.dummy.lv.idx[[group]]
ov.dummy.idx <- c(ov.y.dummy.ov.idx, ov.x.dummy.ov.idx)
lv.dummy.idx <- c(ov.y.dummy.lv.idx, ov.x.dummy.lv.idx)
th.idx <- lavmodel@th.idx[[group]]
num.idx <- lavmodel@num.idx[[group]]
ord.idx <- unique( th.idx[th.idx > 0L] )
# fix Delta's...
mm.in.group <- 1:lavmodel@nmat[group] + cumsum(c(0,lavmodel@nmat))[group]
MLIST <- GLIST[ mm.in.group ]
DD <- list()
nvar <- lavmodel@nvar
nfac <- ncol(MLIST$lambda) - length(lv.dummy.idx)
# DD$theta
theta.idx <- lav_matrix_diagh_idx(nvar)
DD$theta <- Delta.theta[theta.idx,,drop=FALSE]
if(length(ov.dummy.idx) > 0L) {
psi.idx <- lav_matrix_diagh_idx( ncol(MLIST$psi) )[lv.dummy.idx]
DD$theta[ov.dummy.idx,] <- Delta.psi[psi.idx,,drop=FALSE]
}
# num only? FIXME or just all of them?
DD$theta <- DD$theta[num.idx,,drop=FALSE]
# DD$nu
DD$nu <- Delta.nu
if(length(ov.dummy.idx) > 0L) {
DD$nu[ov.dummy.idx,] <- Delta.alpha[lv.dummy.idx,]
}
DD$nu <- DD$nu[num.idx,,drop=FALSE] # needed?
# DD$lambda
nr <- nvar; nc <- nfac
lambda.idx <- nr*((1:nc) - 1L) + rep(1:nvar, each=nc)
DD$lambda <- Delta.lambda[lambda.idx,,drop=FALSE]
if(length(ov.dummy.idx) > 0L) {
nr <- nrow(MLIST$beta); nc <- nfac # only the first 1:nfac columns
# beta.idx <- rep(nr*((1:nc) - 1L), each=length(lv.dummy.idx)) + rep(lv.dummy.idx, times=nc) ## FIXME
beta.idx <- rep(nr*((1:nc) - 1L), times=length(lv.dummy.idx)) + rep(lv.dummy.idx, each=nc)
#l.idx <- inr*((1:nc) - 1L) + rep(ov.dummy.idx, each=nc) ## FIXME
# l.idx <- rep(nr*((1:nc) - 1L), each=length(ov.dummy.idx)) + rep(ov.dummy.idx, times=nc)
l.idx <- rep(nr*((1:nc) - 1L), times=length(ov.dummy.idx)) + rep(ov.dummy.idx, each=nc)
DD$lambda[match(l.idx, lambda.idx),] <- Delta.beta[beta.idx,,drop=FALSE]
}
# DD$KAPPA
DD$kappa <- Delta.gamma
if(length(ov.dummy.idx) > 0L) {
nr <- nrow(MLIST$gamma); nc <- ncol(MLIST$gamma)
kappa.idx <- nr*((1:nc) - 1L) + rep(lv.dummy.idx, each=nc)
DD$kappa <- DD$kappa[kappa.idx,,drop=FALSE]
}
# DD$GAMMA
if(!is.null(MLIST$gamma)) {
nr <- nrow(MLIST$gamma); nc <- ncol(MLIST$gamma)
lv.idx <- 1:nfac
# MUST BE ROWWISE!
gamma.idx <- rep(nr*((1:nc) - 1L), times=length(lv.idx)) + rep(lv.idx, each=nc)
DD$gamma <- Delta.gamma[gamma.idx,,drop=FALSE]
}
# DD$BETA
if(!is.null(MLIST$beta)) {
nr <- nc <- nrow(MLIST$beta)
lv.idx <- 1:nfac
# MUST BE ROWWISE!
beta.idx <- rep(nr*((1:nfac) - 1L), times=nfac) + rep(lv.idx, each=nfac)
DD$beta <- Delta.beta[beta.idx,,drop=FALSE]
}
## DD$psi
DD$psi <- Delta.psi
if(length(lv.dummy.idx) > 0L) {
nr <- nc <- nrow(MLIST$psi)
lv.idx <- 1:nfac
# MUST BE ROWWISE!
psi.idx <- rep(nr*((1:nfac) - 1L), times=nfac) + rep(lv.idx, each=nfac)
DD$psi <- DD$psi[psi.idx,,drop=FALSE]
}
## DD$tau
if(!is.null(MLIST$tau)) {
DD$tau <- Delta.tau
}
DD
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.