R/MxEval.R

Defines functions omxBootstrapEvalByName mxBootstrapEvalByName mxBootstrapEval omxBootstrapEvalCov omxBootstrapEval BootstrapEvalInternal mxEvalByName generateLabelsMatrix retainLabel generateLabelsHelper imxGenerateLabels assignDimnames evaluateAlgebraWithContext evaluateMxObject computeAlgebra evaluateAlgebra labelToValue computeMatrix evaluateMatrix evaluateSymbol evaluateExpression translateErrorFormula translateErrorSymbol EvalInternal mxEval

Documented in mxBootstrapEval mxBootstrapEvalByName mxEval mxEvalByName omxBootstrapEval omxBootstrapEvalByName omxBootstrapEvalCov

#
#   Copyright 2007-2021 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.

mxEval <- function(expression, model, compute = FALSE, show = FALSE, defvar.row = 1L,
			cache = new.env(parent = emptyenv()), cacheBack = FALSE, .extraBack=0L) {
	if (missing(expression)) {
		stop("'expression' argument is mandatory in call to mxEval function")
	} else if (missing(model)) {
		stop("'model' argument is mandatory in call to mxEval function")
	}
	expression <- match.call()$expression
	modelvariable <- match.call()$model
  #for (x in 1:sys.nframe()) cat(x-1, head(ls(parent.frame(x))), fill=TRUE)
	EvalInternal(expression, model, modelvariable, compute, show, defvar.row,
		       cache, cacheBack, 2L + .extraBack)
}

EvalInternal <- function(expression, model, modelvariable, compute, show, defvar.row,
		       cache, cacheBack, back) {
	if (!is.numeric(defvar.row) || length(defvar.row) != 1 || is.na(defvar.row)) {
		stop("'defvar.row' argument must be a single numeric value")
	}
	labelsData <- imxGenerateLabels(model)
	env <- parent.frame(back)
	if (compute) {
		model <- imxPreprocessModel(model)
		expression <- formulaEliminateObjectiveFunctions(expression)
		namespace <- imxGenerateNamespace(model)
		model <- imxFlattenModel(model, namespace, FALSE)
		expression <- qualifyNamesFormula(expression, model@name, namespace)
	}
	if (show) {
		tuple <- evaluateExpression(expression, deparse(expression), model,
			labelsData, env, compute, show = TRUE, outsideAlgebra = TRUE,
			cache = new.env(parent = emptyenv()))
		showResult <- tuple[[1]]
		showResult <- eval(substitute(substitute(x, list(.zzz = modelvariable)), list(x = showResult)))
		print(deparse(showResult), width.cutoff = 450L)
	}
	tuple <- evaluateExpression(expression, deparse(expression), model,
		labelsData, env, compute, show = FALSE, outsideAlgebra = TRUE, defvar.row,
		cache = cache)
	result <- tuple[[1]]
	cache <- tuple[[2]]
	if (!is.vector(result)) {
		result <- eval(result, envir = env)
	}
	if (!is.matrix(result)) {
    if (is(result, "MxAlgebra")) {
      stop(paste("You have referenced algebra",
                 omxQuotes(result$name), "outside of the model"))
    }
    result <- as.matrix(result)
  }
	if (cacheBack) {
		return(list(result, cache))
	} else {
		return(result)
	}
}

translateErrorSymbol <- function(symbol, model) {
	charsymbol <- as.character(symbol)
	object <- model[[charsymbol]]
	if (!is.null(object) && is(object, "MxMatrix") && length(object@display) == 1) {
		return(as.symbol(object@display))
	} else {
		return(symbol)
	}
}

translateErrorFormula <- function(formula, model) {
	if(length(formula) == 1) {
		if(!identical(as.character(formula), "")){
			formula <- translateErrorSymbol(formula, model)
		}
	} else {
		formula <- lapply(formula, translateErrorFormula, model)
		formula <- as.call(formula)
	}
	return(formula)
}

evaluateExpression <- function(formula, contextString, model, labelsData,
	env, compute, show, outsideAlgebra, defvar.row = 1, cache = new.env(parent = emptyenv())) {
	len <- length(formula)
	if (len == 0) {
		stop("mxEval has reached an invalid state")
	} else if (len == 1) {
		if (!identical(as.character(formula), "")) {
			tuple <- evaluateSymbol(formula, contextString, model,
				labelsData, env, compute, show, outsideAlgebra, defvar.row,
				cache)
			return(tuple)
		} else {
			return(list(formula, cache))
		}
	}
	originalFormula <- formula
	for(i in 2:length(formula)) {
		tuple <- evaluateExpression(formula[[i]], contextString,
			model, labelsData, env, compute, show,
			outsideAlgebra, defvar.row, cache)
		formula[[i]] <- tuple[[1]]
		cache <- tuple[[2]]
	}
	if (len == 4 && identical(as.character(formula[1]), '[')) {
		formula$drop <- FALSE
		if(is.matrix(formula[[3]]) && nrow(formula[[3]]) == 0 && ncol(formula[[3]]) == 0) {
			formula[[3]] <- TRUE
		}
		if(is.matrix(formula[[4]]) && nrow(formula[[4]]) == 0 && ncol(formula[[4]]) == 0) {
			formula[[4]] <- TRUE
		}
	}
	operator <- as.character(formula[1])
	if (len == 3 && operator %in% c('*', '^', '/', '+', '-')) {
		if (!is.numeric(formula[[2]]) || length(formula[[2]]) > 1){
			formula[[2]] <- substitute(as.matrix(x), list(x = formula[[2]]))
		} else if (is.matrix(formula[[2]]) && length(formula[[2]]) == 1) {
			formula[[2]] <- substitute(as.numeric(x), list(x = formula[[2]]))
		}
		if (!is.numeric(formula[[3]]) || length(formula[[3]]) > 1){
			formula[[3]] <- substitute(as.matrix(x), list(x = formula[[3]]))
		} else if (is.matrix(formula[[3]]) && length(formula[[3]]) == 1) {
			formula[[3]] <- substitute(as.numeric(x), list(x = formula[[3]]))
		}
	}
	if (show) {
		return(list(result, cache))
	}
	result <- tryCatch(do.call(as.character(formula[[1]]), as.list(formula[-1]), envir = env),
			error = function(x) {
				originalFormula <- translateErrorFormula(originalFormula, model)
				formulaString <- deparse(originalFormula, width.cutoff = 500L)
				if(identical(formulaString, contextString)) {
					msg <- paste("The following error occurred while",
						"evaluating the expression", omxQuotes(formulaString),
						"in model", omxQuotes(model@name),
						":", x$message)
				} else {
					msg <- paste("The following error occurred while",
						"evaluating the subexpression",
						omxQuotes(formulaString), "during the evaluation of",
						omxQuotes(contextString), "in model", omxQuotes(model@name),
						":", x$message)
				}
				stop(msg, call. = FALSE)
		})
	return(list(result, cache))
}

evaluateSymbol <- function(symbol, contextString, model, labelsData,
			env, compute, show, outsideAlgebra, defvar.row = 1,
			cache) {
	key <- deparse(symbol)
	if (exists(key, envir = cache)) {
		result <- get(key, envir = cache)
	} else {
		index <- match(key, rownames(labelsData))
		if (!is.na(index)) {
			targetmodel <- labelsData[index, "model"]
			matrix <- labelsData[index, "matrix"]
			fullname <- imxIdentifier(targetmodel, matrix)
			row <- labelsData[index, "row"]
			col <- labelsData[index, "col"]
			value <- model[[fullname]]@values[row,col]
			if (show) {
				result <- substitute(.zzz[[x]]@values[y,z], list(x = fullname, y = row, z = col))
			} else {
				result <- value
			}
		} else {
			lookup <- model[[key]]
			if (is.list(lookup) && length(lookup) == 2 && is(lookup[[2]], "MxFitFunction")) {
				lookup <- lookup[[2]]
			}
			if (is.list(lookup) && length(lookup) == 2 && is(lookup[[2]], "MxExpectation")) {
				lookup <- lookup[[2]]
			}
			if (imxIsDefinitionVariable(key)) {
				result <- definitionStartingValue(key, contextString, model, defvar.row)
			} else if (is.null(lookup)) {
				if (!show && !outsideAlgebra && exists(key, envir = env)) {
					result <- try(as.matrix(get(key, envir = env)), silent=TRUE)
          if (is(result, 'try-error')){
          	stop(paste(
          		"cannot coerce object with symbol", omxQuotes(key), "to a matrix\n",
          		"(hint: did you neglect to put an object named", omxQuotes(key), "into model",
          		omxQuotes(model@name),"?)"))
          }
				} else {
					result <- symbol
				}
			} else if (is(lookup, "MxMatrix")) {
				tuple <- evaluateMatrix(lookup, model, labelsData, env, show, compute, defvar.row, cache)
				result <- tuple[[1]]
				cache <- tuple[[2]]
			} else if (is(lookup, "MxAlgebra")) {
				tuple <- evaluateAlgebra(lookup, model, labelsData, env, show, compute, defvar.row, cache)
				result <- tuple[[1]]
				cache <- tuple[[2]]
			} else if (is(lookup, "MxPenalty")) {
        result <- evaluatePenalty(lookup, model, labelsData, env, show, compute)
			} else if (is(lookup, "MxData")) {
				if (show) {
					result <- substitute(.zzz[[x]]@observed, list(x = key))
				} else {
					result <- lookup@observed
				}
			} else if (is(lookup, "MxFitFunction")) {
				if (length(lookup@result) == 0) {
					if (compute) {
						result <- genericFitInitialMatrix(lookup, model)
					} else {
						result <- matrix(0, 0, 0)
					}
				} else if (show) {
					result <- substitute(.zzz[[x]]@result, list(x = key))
				} else {
					result <- lookup@result
				}
			} else if (is(lookup, "MxExpectation")) {
				stop("mxEval for Expectations is not yet implemented.")
				#signpost
				#if (length(lookup@result) == 0) {
				#	if (compute) {
				#		result <- genericFitInitialMatrix(lookup, model)
				#	} else {
				#		result <- matrix(0, 0, 0)
				#	}
				#} else if (show) {
				#	result <- substitute(.zzz[[x]]@result, list(x = key))
				#} else {
				#	result <- lookup@result
				#}
			} else {
				stop(paste("Cannot evaluate the object",
					omxQuotes(key), "in the model",
					omxQuotes(model@name)))
			}
		}
		assign(key, result, envir = cache)
	}
	return(list(result, cache))
}

evaluateMatrix <- function(matrix, model, labelsData, env, show, compute, defvar.row, cache) {
	if (show) {
		return(list(substitute(.zzz[[x]]@values, list(x = matrix@name)), cache))
	} else if (compute) {
		tuple <- computeMatrix(matrix, model, labelsData, defvar.row, env, cache)
	} else {
		tuple <- list(matrix@values, cache)
	}
	tuple[[1]] <- assignDimnames(matrix, tuple[[1]])
	return(tuple)
}

computeMatrix <- function(matrix, model, labelsData, defvar.row, env, cache) {
	matrix <- populateDefVarMatrix(matrix, model, defvar.row)
	values <- matrix@values
	labels <- matrix@labels
	select <- matrix@.squareBrackets
	if (all(!select)) {
		return(list(values, cache))
	}
	subs <- labels[select]
	rows <- row(labels)[select]
	cols <- col(labels)[select]
	for (i in 1:length(subs)) {
		substitution <- subs[[i]]
		row <- rows[[i]]
		col <- cols[[i]]
		components <- splitSubstitution(substitution)
		expression <- parse(text = paste('`', components[[1]], '`[',
			as.integer(components[[2]]), ',', as.integer(components[[3]]), ']', sep=''))[[1]]
		contextString <- paste("label at row ", row, " and column ", col, " of matrix ",
			omxQuotes(simplifyName(matrix@name, model@name)), sep = '')
		tuple <- evaluateExpression(expression, contextString, model, labelsData, env,
			compute=TRUE, show=FALSE, outsideAlgebra=FALSE, defvar.row=defvar.row, cache=cache)
		result <- tuple[[1]]
		cache <- tuple[[2]]
		result <- as.matrix(result)
		if (nrow(result) != 1 || ncol(result) != 1) {
			stop(paste("The label",
				omxQuotes(substitution),
				"of matrix", omxQuotes(simplifyName(matrix@name, model@name)),
				"in model", omxQuotes(model@name),
				"does not evaluate to a (1 x 1) matrix."), call. = FALSE)
		}
		values[row, col] <- result[1,1]
	}
	return(list(values, cache))
}

labelToValue <- function(label, labelsData, model) {
  index <- match(label, rownames(labelsData))
  targetmodel <- labelsData[index, "model"]
  matrix <- labelsData[index, "matrix"]
  fullname <- imxIdentifier(targetmodel, matrix)
  row <- labelsData[index, "row"]
  col <- labelsData[index, "col"]
  mapply(function(fn,r,c) model[[fn]]@values[r,c], fullname, row, col)
}

evaluateAlgebra <- function(algebra, model, labelsData, env, show, compute, defvar.row, cache) {
	if (compute && show) {
		return(computeAlgebra(algebra, model, labelsData, show = TRUE, defvar.row, env, cache))
	} else if (compute) {
		tuple <- computeAlgebra(algebra, model, labelsData, show = FALSE, defvar.row, env, cache)
		tuple[[1]] <- as.matrix(tuple[[1]])
	} else if (show) {
		result <- substitute(.zzz[[x]]@result, list(x = algebra@name))
		return(list(result, cache))
	} else {
		tuple <- list(algebra@result, cache)
	}
	if (!is.null(dimnames(algebra))) {
		tuple[[1]] <- assignDimnames(algebra, tuple[[1]])
	}
	return(tuple)
}

computeAlgebra <- function(algebra, model, labelsData, show, defvar.row, env, cache) {
	contextString <- simplifyName(algebra@name, model@name)
	tuple <- evaluateExpression(algebra@formula, contextString, model, labelsData, env,
		compute = TRUE, show, outsideAlgebra = FALSE, defvar.row, cache)
	return(tuple)
}

evaluateMxObject <- function(objname, flatModel, labelsData, cache) {
	if (flatModel@unsafe) {
		#stop("evaluateMxObject must be avoided in unsafe context")
		# mxFitFunctionRow genericFitAddEntities still needs this
	}
	return(eval(substitute(evaluateSymbol(x, objname, flatModel,
			labelsData, globalenv(), compute = TRUE,
			show = FALSE, outsideAlgebra = FALSE, defvar.row = 1,
			cache = cache),
			list(x = quote(as.symbol(objname))))))
}

evaluateAlgebraWithContext <- function(algebra, context, flatModel, labelsData, cache) {
	return(evaluateExpression(algebra@formula, context, flatModel, labelsData, globalenv(),
		compute = TRUE, show = FALSE, outsideAlgebra = FALSE, cache = cache))
}

assignDimnames <- function(object, values) {
	newnames <- dimnames(object)
	if (!is.null(newnames)) {
		if (length(newnames) != 2) {
			entity <- class(object)
			msg <- paste("The 'dimnames' argument to", entity,
				omxQuotes(object@name), "must have a length of 2")
			stop(msg, call. = FALSE)
		}
		if (!is.null(newnames[[1]]) && length(newnames[[1]]) != nrow(values) ||
			!is.null(newnames[[2]]) && length(newnames[[2]]) != ncol(values)) {
			entity <- class(object)
			msg <- paste("The", entity, omxQuotes(object@name),
				"has specified dimnames with dimensions",
				length(newnames[[1]]), "x", length(newnames[[2]]), "but the", entity,
					"is of dimensions", nrow(values), "x", ncol(values))
			stop(msg, call. = FALSE)
		}
	}
	dimnames(values) <- dimnames(object)
	return(values)
}

##' imxGenerateLabels
##'
##' This is an internal function exported for those people who know
##' what they are doing.
##'
##' @param model model
imxGenerateLabels <- function(model) {
	return(generateLabelsHelper(model, data.frame()))
}

generateLabelsHelper <- function(model, labelsData) {
	if (length(model@matrices) > 0) {
		for(i in 1:length(model@matrices)) {
			labelsData <- generateLabelsMatrix(model@name, model@matrices[[i]], labelsData)
		}
	}
	if (length(model@submodels) > 0) {
		for(i in 1:length(model@submodels)) {
			labelsData <- generateLabelsHelper(model@submodels[[i]], labelsData)
		}
	}
	return(labelsData)
}

retainLabel <- function(label, existing) {
    return(!imxIsDefinitionVariable(label) &&
			!hasSquareBrackets(label) &&
			!(label %in% existing))
}

generateLabelsMatrix <- function(modelName, matrix, labelsData) {
	labels <- matrix@labels
	test <- !is.na(labels)
	select <- labels[test]
	rows <- row(labels)[test]
	cols <- col(labels)[test]
	if (length(select) > 0) {
		retain <- !duplicated(select) & sapply(select,
			retainLabel, rownames(labelsData))
		select <- select[retain]
		rows <- rows[retain]
		cols <- cols[retain]
		if (length(select) > 0) {
			newLabels <- data.frame(model=modelName,
				matrix=matrix@name, row=rows,
				col=cols, stringsAsFactors=FALSE)
			rownames(newLabels) <- select
			labelsData <- rbind(labelsData, newLabels)
		}
	}
	return(labelsData)
}

mxEvalByName <- function(name, model, compute=FALSE, show=FALSE, defvar.row = 1L,
		cache = new.env(parent = emptyenv()), cacheBack = FALSE, .extraBack=0L) {
   if((length(name) != 1) || typeof(name) != "character") {
      stop("'name' argument must be a character argument")
   }
   warnModelCreatedByOldVersion(model)
#for (x in 1:sys.nframe()) cat(x-1, head(ls(parent.frame(x))), fill=TRUE)
   eval(substitute(mxEval(x, model, compute, show, defvar.row, cache, cacheBack, 1L + .extraBack),
      list(x = as.symbol(name))))
}

BootstrapEvalInternal <- function(expression, model, modelvariable, defvar.row, ...) {
	bootData <- omxGetBootstrapReplications(model)
  mle <- cvectorize(EvalInternal(expression, model, modelvariable,
				 compute=TRUE, show=FALSE, defvar.row, cache=new.env(parent = emptyenv()),
						 cacheBack=FALSE, back=2L))
  result <- matrix(NA, nrow=nrow(bootData), ncol=length(mle))
  for (rx in 1:nrow(bootData)) {
	  bmodel <- omxSetParameters(model, labels=colnames(bootData), values=unlist(bootData[rx,]))
	  result[rx,] <- cvectorize(EvalInternal(expression, bmodel, modelvariable,
						 compute=TRUE, show=FALSE, defvar.row, cache=new.env(parent = emptyenv()),
						 cacheBack=FALSE, back=2L))
  }
	list(mle=mle, result=result)
}

omxBootstrapEval <- function(expression, model, defvar.row = 1L, ...) {
  expression <- match.call()$expression
  modelvariable <- match.call()$model
  BootstrapEvalInternal(expression, model, modelvariable, defvar.row, ...)$result
}

omxBootstrapEvalCov <- function(expression, model, defvar.row = 1L, ...) {
  expression <- match.call()$expression
  modelvariable <- match.call()$model
  cov(BootstrapEvalInternal(expression, model, modelvariable, defvar.row, ...)$result)
}

mxBootstrapEval <- function(expression, model, defvar.row = 1L, ..., bq=c(.25,.75),
				method=c('bcbci','quantile')) {
  warnModelCreatedByOldVersion(model)
  expression <- match.call()$expression
  modelvariable <- match.call()$model
	method <- match.arg(method)
  got <- BootstrapEvalInternal(expression, model, modelvariable, defvar.row, ...)
	cbind("SE"=apply(got$result, 2, sd),
	      summarizeBootstrap(got$mle, got$result, bq, method))
}

mxBootstrapEvalByName <- function(name, model, defvar.row=1L, ..., bq=c(.25,.75),
				  method=c('bcbci','quantile')) {
   if((length(name) != 1) || typeof(name) != "character") {
      stop("'name' argument must be a character argument")
   }
  warnModelCreatedByOldVersion(model)
   method <- match.arg(method)
   eval(substitute(mxBootstrapEval(x, model, defvar.row, bq=bq, method=method),
      list(x = as.symbol(name))))
}

omxBootstrapEvalByName <- function(name, model, defvar.row=1L, ...) {
   if((length(name) != 1) || typeof(name) != "character") {
      stop("'name' argument must be a character argument")
   }
   method <- match.arg(method)
   eval(substitute(omxBootstrapEval(x, model, defvar.row),
      list(x = as.symbol(name))))
}
OpenMx/OpenMx documentation built on April 17, 2024, 3:32 p.m.