# Performs the optimisation routine corresponding to the given optimiser selected.
# Remark: this function was originally based on the "mkinfit" function, version 0.1.
#
# optimiser: A character type indicating which optimiser should be used, one of 'OLS', 'IRLS', 'MCMC'
# cake.model: The model to perform the fit on (as generated by CakeModel.R).
# observed: Observation data to fit to.
# parms.ini: Initial values for the parameters being fitted.
# state.ini: Initial state (i.e. initial values for concentration, the dependent variable being modelled).
# lower: Lower bounds to apply to parameters.
# upper: Upper bound to apply to parameters.
# fixed_parms: A vector of names of parameters that are fixed to their initial values.
# fixed_initials: A vector of compartments with fixed initial concentrations.
# quiet: Whether the internal cost functions should execute more quietly than normal (less output).
# niter: The number of MCMC iterations to apply.
# atol: The tolerance to apply to the ODE solver.
# dfopDtMaxIter: The maximum number of iterations to apply to DFOP DT calculation.
# control: ...
# useExtraSolver: Whether to use the extra solver for this fit.
CakeFit <- function(optimiser,
cake.model,
observed,
parms.ini,
state.ini,
lower = 0,
upper = Inf,
fixed_parms = NULL,
fixed_initials = names(cake.model$diffs)[-1],
quiet = FALSE,
niter = 1000,
verbose = TRUE,
seed = NULL,
atol = 1e-6,
dfopDtMaxIter = 10000,
control = list(),
useExtraSolver = FALSE) {
env = environment()
# Get model variable names
mod_vars <- names(cake.model$diffs)
# Subset dataframe with mapped (modelled) variables
observed <- subset(observed, name %in% names(cake.model$map))
ERR <- rep(1, nrow(observed))
observed <- cbind(observed, err = ERR)
# Get names of observed variables
obs_vars <- unique(as.character(observed$name))
# Parameters to be optimised
parms.fixed <- parms.ini[fixed_parms]
optim_parms <- setdiff(names(parms.ini), fixed_parms)
parms.optim <- parms.ini[optim_parms]
state.ini.fixed <- state.ini[fixed_initials]
optim_initials <- setdiff(names(state.ini), fixed_initials)
state.ini.optim <- state.ini[optim_initials]
state.ini.optim.boxnames <- names(state.ini.optim)
if (length(state.ini.optim) > 0) {
names(state.ini.optim) <- paste(names(state.ini.optim), "0", sep = "_")
}
# Decide if the solution of the model can be based on a simple analytical formula
# or a numeric ode solver from the deSolve package
if (length(cake.model$map) == 1) {
solution <- "analytical"
} else {
solution <- "deSolve"
}
# Create a function calculating the differentials specified by the model
# if necessary
if (solution == "deSolve") {
mkindiff <- function(t, state, parms) {
# numerical method, replace with analytical for parent-only fits
time <- t
diffs <- vector()
for (box in mod_vars) {
diffname <- paste("d", box, sep = "_")
diffs[diffname] <- with(as.list(c(time, state, parms)),
eval(parse(text = cake.model$diffs[[box]])))
}
return(list(c(diffs)))
}
}
fitted_with_extra_solver <- FALSE
optimiserSpecificSetup <- GetOptimiserSpecificSetup(optimiser)
optimiserSpecificSetupOutput <- optimiserSpecificSetup(cake.model,
state.ini.optim,
state.ini.optim.boxnames,
state.ini.fixed,
parms.fixed,
observed,
mkindiff = mkindiff,
quiet,
atol = atol,
solution = solution,
control = control,
seed = seed)
list2env(optimiserSpecificSetupOutput, env)
pnames <- names(c(state.ini.optim, parms.optim))
costForExtraSolver <- function(P) {
names(P) <- pnames
FF <<- costFunctions$cost(P)
return(FF$model)
}
optimiserFitRoutine <- GetOptimisationRoutine(optimiser)
optimiserFitResult <- optimiserFitRoutine(costFunctions,
costForExtraSolver,
useExtraSolver,
c(state.ini.optim, parms.optim),
lower,
upper,
control,
cake.model = cake.model,
tol = tol,
fitted_with_extra_solver = fitted_with_extra_solver,
observed = observed,
maxIter = maxIter,
costWithStatus = costWithStatus,
niter = niter,
verbose = verbose)
list2env(optimiserFitResult, env)
optimiserSpecificWrapUp <- GetOptimiserSpecificWrapUp(optimiser)
optimiserSpecificWrapUpOutput <- optimiserSpecificWrapUp(fit,
parms.fixed = parms.fixed,
state.ini = state.ini,
observed = observed,
res = res,
seed = seed,
costWithStatus = costWithStatus)
list2env(optimiserSpecificWrapUpOutput, env)
# We need to return some more data for summary and plotting
fit$solution <- solution
if (solution == "deSolve") {
fit$mkindiff <- mkindiff
}
fit$map <- cake.model$map
fit$diffs <- cake.model$diffs
fit$topology <- cake.model$topology
fit$start <- data.frame(initial = c(state.ini.optim, parms.optim))
fit$start$type <- c(rep("state", length(state.ini.optim)), rep("deparm", length(parms.optim)))
fit$start$lower <- lower
fit$start$upper <- upper
fit$fixed <- data.frame(value = c(state.ini.fixed, parms.fixed))
fit$fixed$type <- c(rep("state", length(state.ini.fixed)), rep("deparm", length(parms.fixed)))
# Ensure initial state is at time 0
obstimes <- unique(c(0, observed$time))
out_predicted <- CakeOdeSolve(fit, obstimes, solution = solution, atol)
out_transformed <- PostProcessOdeOutput(out_predicted, cake.model, atol)
predicted_long <- wide_to_long(out_transformed, time = "time")
fit$predicted <- out_transformed
# Calculate chi2 error levels according to FOCUS (2006)
fit$errmin <- CakeChi2(cake.model, observed, predicted_long, obs_vars, parms.optim, state.ini.optim, state.ini, parms.ini, fit$fixed)
# Calculate dissipation times DT50 and DT90 and formation fractions
fit$distimes <- data.frame(DT50 = rep(NA, length(obs_vars)), DT90 = rep(NA, length(obs_vars)), row.names = obs_vars)
fit$extraDT50 <- data.frame(k1 = rep(NA, length(names(cake.model$map))), k2 = rep(NA, length(names(cake.model$map))), row.names = names(cake.model$map))
for (compartment.name in names(cake.model$map)) {
type <- names(cake.model$map[[compartment.name]])[1]
fit$distimes[compartment.name,] <- CakeDT(type, compartment.name, parms.all, dfopDtMaxIter)
fit$extraDT50[compartment.name,] <- CakeExtraDT(type, compartment.name, parms.all)
}
fit$ioreRepDT <- CakeIORERepresentativeDT("Parent", parms.all)
fit$fomcRepDT <- CakeFOMCBackCalculatedDT(parms.all)
fit$penalties <- CakePenaltiesLong(parms.all, out_transformed, observed)
# Collect observed, predicted and residuals
data <- merge(data, predicted_long, by = c("time", "name"))
names(data) <- c("time", "variable", "observed", "err-var", "predicted")
data$residual <- data$observed - data$predicted
data$variable <- ordered(data$variable, levels = obs_vars)
fit$data <- data[order(data$variable, data$time),]
fit$costFunctions <- costFunctions
fit$upper <- upper
fit$lower <- lower
fit$atol <- atol
if (optimiser == "MCMC") {
sq <- data$residual * data$residual
fit$ssr <- sum(sq)
}
return(fit)
}