#
#   Copyright 2007-2018 by the individuals mentioned in the source code history
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
# 
#        http://www.apache.org/licenses/LICENSE-2.0
# 
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

require(OpenMx)
require(nlme)

# Multilevel Long Format Test
# Author: Steve Boker
# Date: Sun Nov 29 14:06:07 EST 2009


# This script is used to test the multilevel long format
# functionality using definition variables as indices.
totalOccasions <- 100
totalSubjects <- 10L
set.seed(42) # repeatibility
tID <- rep(1:totalSubjects, each=totalOccasions)
trueX <- rep(rnorm(totalOccasions, mean=0, sd=2), each=totalSubjects) +
    rnorm(totalOccasions*totalSubjects, mean=0, sd=.2)
trueB <- rep(rnorm(totalSubjects, mean=.8, sd=.3), each=totalOccasions)
tDataFrame <- data.frame(
    ID=tID, X=trueX, Y=trueB*trueX +
	rnorm(totalOccasions*totalSubjects,mean=0, sd=.1),trueB=trueB)
summary(tDataFrame)

manifestVars <- c("X", "Y")
numSubjects <- length(unique(tDataFrame$ID))

# Estimates the sum of the random and fixed effects
multilevelModel2 <- mxModel("Multilevel_2",
    mxMatrix("Full", nrow=numSubjects, ncol=2,
        values=c(.2,0),
        free=c(TRUE, TRUE), 
        name="Rand",
        byrow=TRUE
    ),
    mxMatrix("Full", 2, 2, 
        labels=c(NA,  NA,
                "randrow[1,1]", NA), 
        free=FALSE, 
        name="A", 
        byrow=TRUE
    ),
    mxMatrix("Symm", 2, 2,
        values=c(.9,0,.9),
        free=c(T,
               F, T), 
        labels=c("varX",
                 NA, "varY"), 
        name="S", 
        byrow=TRUE
    ),
	mxMatrix("Full", 2, 2, 
	         values=c(1,0,
		              0,1),
		     free=FALSE, 
		     byrow=TRUE, name="F"),
    mxMatrix("Iden", 2, name="I"),
    mxAlgebra(F %*% solve(I-A) %*% S %*% t(solve(I-A)) %*% t(F), 
        name="R", 
        dimnames = list(manifestVars, manifestVars)
    ),
    mxMatrix("Full", nrow=1, ncol=length(manifestVars),
        values=0,
        free=FALSE,
        labels=c(NA,"randrow[1,2]"),
        dimnames=list(NULL, manifestVars),
        name="M"
    ),
	mxAlgebra(Rand[data.ID,], name="randrow"),
    mxFitFunctionML(),mxExpectationNormal(covariance="R", means="M"),
    mxData(tDataFrame, type="raw")
)

# ----------------------------------
# Fit the model and examine the summary results.

multilevelModel2Fit <- mxRun(multilevelModel2)

summary(multilevelModel2Fit)

lmeOut <- lme(Y~X, random= ~ X | ID, data=tDataFrame)

cbind(multilevelModel2Fit$output$estimate[1:numSubjects], 
      lmeOut$coef$random$ID[,2] + lmeOut$coef$fixed[2],
      trueB[seq(1,totalOccasions*(totalSubjects), by=totalOccasions)])

mean(multilevelModel2Fit$output$estimate[1:numSubjects])

est <- multilevelModel2Fit$output$estimate

omxCheckCloseEnough(mean(est[1:numSubjects]), 
    lmeOut$coef$fixed[2], 0.001)

omxCheckCloseEnough(mean(est[(1:numSubjects) + (1*numSubjects)]), 
    lmeOut$coef$fixed[1], 0.001)

# ----------------------------------
# An OpenMx equivalent to the mixed model

perID <- mxModel(
    "perID", type="RAM", latentVars=c('int', 'slope'),
    mxData(data.frame(ID=1L:totalSubjects), "raw", primaryKey="ID"),
    mxPath(c('int', 'slope'),c('int', 'slope'),'unique.pairs',
	   arrows=2,values=c(1,0,1)))

occa <- mxModel(
    "occa", type="RAM", perID, manifestVars="Y", latentVars="lX",
    mxData(tDataFrame, 'raw'),
    mxPath('Y', arrows=2, values=1),
    mxPath('one', 'Y'),
    mxPath('one', 'lX', labels='data.X', free=FALSE),
    mxPath('lX', 'Y'),
    mxPath('perID.int', 'Y', values=1, free=FALSE, joinKey='ID'),
    mxPath('perID.slope', 'Y', labels='data.X', free=FALSE, joinKey='ID'))

if (0) {
	require(lme4)
	lmer1 <- lmer(Y~X + (X | ID), data=tDataFrame, REML=FALSE)
	pt1 <- occa
	#pt1$perID$cholS$values[,] <- chol(VarCorr(lmer1)$ID)
	pt1$perID$S$values[,] <- VarCorr(lmer1)$ID
	pt1$A$values['Y', 'lX'] <- fixef(lmer1)['X']
	pt1$M$values[,'Y'] <- fixef(lmer1)['(Intercept)']
	pt1$S$values['Y', 'Y'] <- getME(lmer1, "sigma")^2

	pt1 <- mxRun(mxModel(pt1, mxComputeSequence(list(
	    mxComputeOnce('fitfunction', 'fit'),
	    mxComputeReportExpectation()))))

	omxCheckCloseEnough(logLik(pt1), logLik(lmer1), 1e-6)
}

occa <- mxRun(occa)
# a tad better than lme, same as lmer
omxCheckCloseEnough(occa$output$fit, -1725.954, 1e-2)
