diff options
-rw-r--r-- | R/mkinfit.R | 100 | ||||
-rw-r--r-- | tests/testthat/test_error_models.R | 42 |
2 files changed, 105 insertions, 37 deletions
diff --git a/R/mkinfit.R b/R/mkinfit.R index bc8b9d11..224dc7bd 100644 --- a/R/mkinfit.R +++ b/R/mkinfit.R @@ -34,6 +34,7 @@ mkinfit <- function(mkinmod, observed, quiet = FALSE,
atol = 1e-8, rtol = 1e-10, n.outtimes = 100,
error_model = c("const", "obs", "tc"),
+ error_model_algorithm = c("direct", "twostep", "threestep", "fourstep", "IRLS"),
trace_parms = FALSE,
...)
{
@@ -91,7 +92,7 @@ mkinfit <- function(mkinmod, observed, if (length(wrongpar.names) > 0) {
warning("Initial parameter(s) ", paste(wrongpar.names, collapse = ", "),
" not used in the model")
- parms.ini <- parms.ini[setdiff(names(parms.ini), wrongpar.names)]
+ parms.ini <- parms.ini[setdiff(names(parms.ini), wrongpar.names)]
}
# Warn that the sum of formation fractions may exceed one if they are not
@@ -244,6 +245,7 @@ mkinfit <- function(mkinmod, observed, # Get the error model
err_mod <- match.arg(error_model)
+ error_model_algorithm = match.arg(error_model_algorithm)
errparm_names <- switch(err_mod,
"const" = "sigma",
"obs" = paste0("sigma_", obs_vars),
@@ -276,34 +278,48 @@ mkinfit <- function(mkinmod, observed, length.out = n.outtimes))))
# Define log-likelihood function for optimisation, including (back)transformations
- nlogLik <- function(P, trans = TRUE, OLS = FALSE, local = FALSE, update_data = TRUE, ...)
+ nlogLik <- function(P, trans = TRUE, OLS = FALSE, fixed_degparms = FALSE, fixed_errparms = FALSE, update_data = TRUE, ...)
{
assign("calls", calls + 1, inherits = TRUE) # Increase the model solution counter
- P.orig <- P
# Trace parameter values if requested and if we are actually optimising
if(trace_parms & update_data) cat(P, "\n")
- # If we do a local optimisation of the error model, the initials
- # for the state variabels and the parameters are given as 'local'
- if (local[1] != FALSE) {
- P <- local
+ if (fixed_degparms[1] != FALSE) {
+ degparms <- fixed_degparms
+ errparms <- P # This version of errparms is local to the function
+ degparms_fixed = TRUE
+ } else {
+ degparms_fixed = FALSE
+ }
+
+ if (fixed_errparms[1] != FALSE) {
+ degparms <- P
+ errparms <- fixed_errparms # Local to the function
+ errparms_fixed = TRUE
+ } else {
+ errparms_fixed = FALSE
+ }
+
+ if (OLS) {
+ degparms <- P
+ }
+
+ if (!OLS & !degparms_fixed & !errparms_fixed) {
+ degparms <- P[1:(length(P) - length(errparms))]
+ errparms <- P[(length(degparms) + 1):length(P)]
}
# Initial states for t0
if(length(state.ini.optim) > 0) {
- odeini <- c(P[1:length(state.ini.optim)], state.ini.fixed)
+ odeini <- c(degparms[1:length(state.ini.optim)], state.ini.fixed)
names(odeini) <- c(state.ini.optim.boxnames, state.ini.fixed.boxnames)
} else {
odeini <- state.ini.fixed
names(odeini) <- state.ini.fixed.boxnames
}
- if (OLS | identical(P, local)) {
- odeparms.optim <- P[(length(state.ini.optim) + 1):length(P)]
- } else {
- odeparms.optim <- P[(length(state.ini.optim) + 1):(length(P) - length(errparms))]
- }
+ odeparms.optim <- degparms[(length(state.ini.optim) + 1):length(degparms)]
if (trans == TRUE) {
odeparms <- c(odeparms.optim, transparms.fixed)
@@ -322,23 +338,20 @@ mkinfit <- function(mkinmod, observed, method.ode = method.ode,
atol = atol, rtol = rtol, ...)
- # Get back the original parameter vector to get the error model parameters
- P <- P.orig
-
out_long <- mkin_wide_to_long(out, time = "time")
if (err_mod == "const") {
- observed$std <- P["sigma"]
+ observed$std <- errparms["sigma"]
}
if (err_mod == "obs") {
std_names <- paste0("sigma_", observed$name)
- observed$std <- P[std_names]
+ observed$std <- errparms[std_names]
}
if (err_mod == "tc") {
tmp <- merge(observed, out_long, by = c("time", "name"))
tmp$name <- ordered(tmp$name, levels = obs_vars)
tmp <- tmp[order(tmp$name, tmp$time), ]
- observed$std <- sqrt(P["sigma_low"]^2 + tmp$value.y^2 * P["rsd_high"]^2)
+ observed$std <- sqrt(errparms["sigma_low"]^2 + tmp$value.y^2 * errparms["rsd_high"]^2)
}
data_log_lik <- merge(observed[c("name", "time", "value", "std")], out_long,
@@ -426,31 +439,44 @@ mkinfit <- function(mkinmod, observed, # In the inital run, we assume a constant standard deviation and do
# not optimise it, as this is unnecessary and leads to instability of the
# fit (most obvious when fitting the IORE model)
- if (!quiet) message("Ordinary least squares optimisation")
- parms.start <- c(state.ini.optim, transparms.optim)
- fit.ols <- nlminb(parms.start, nlogLik, control = control,
- lower = lower[names(parms.start)],
- upper = upper[names(parms.start)], OLS = TRUE, ...)
-
- if (err_mod == "const") {
+ degparms <- c(state.ini.optim, transparms.optim)
+
+ if (err_mod == "const" | error_model_algorithm != "direct") {
+ if (!quiet) message("Ordinary least squares optimisation")
+ fit <- nlminb(degparms, nlogLik, control = control,
+ lower = lower[names(degparms)],
+ upper = upper[names(degparms)], OLS = TRUE, ...)
+ degparms <- fit$par
# Get the maximum likelihood estimate for sigma at the optimum parameter values
data_errmod$residual <- data_errmod$value.observed - data_errmod$value.predicted
- sigma_mle = sqrt(sum(data_errmod$residual^2)/nrow(data_errmod))
+ sigma_mle <- sqrt(sum(data_errmod$residual^2)/nrow(data_errmod))
- errparms = c(sigma = sigma_mle)
- fit <- fit.ols
- fit$logLik <- - nlogLik(c(fit$par, sigma = sigma_mle), OLS = FALSE)
- } else {
- # After the OLS stop we fit the error model with fixed degradation model
- # parameters
+ if (err_mod == "const") {
+ errparms <- c(sigma = sigma_mle)
+ }
+
+ nlogLik.current <- nlogLik(c(degparms, errparms), OLS = FALSE)
+ fit$logLik <- - nlogLik.current
+ }
+ if (error_model_algorithm %in% c("threestep", "fourstep")) {
if (!quiet) message("Optimising the error model")
- fit.err <- nlminb(errparms, nlogLik, control = control,
+ fit <- nlminb(errparms, nlogLik, control = control,
lower = lower[names(errparms)],
upper = upper[names(errparms)],
- local = fit.ols$par, ...)
- errparms.tmp <- fit.err$par
+ degparms_fixed = degparms, ...)
+ errparms <- fit$par
+ }
+ if (error_model_algorithm == "fourstep") {
+ if (!quiet) message("Optimising the degradation model")
+ fit <- nlminb(degparms, nlogLik, control = control,
+ lower = lower[names(degparms)],
+ upper = upper[names(degparms)],
+ errparms_fixed = errparms, ...)
+ degparms <- fit$par
+ }
+ if (error_model_algorithm %in% c("direct", "twostep", "threestep", "fourstep")) {
if (!quiet) message("Optimising the complete model")
- parms.start <- c(fit.ols$par, errparms.tmp)
+ parms.start <- c(degparms, errparms)
fit <- nlminb(parms.start, nlogLik,
lower = lower[names(parms.start)],
upper = upper[names(parms.start)],
diff --git a/tests/testthat/test_error_models.R b/tests/testthat/test_error_models.R index d7a5cea8..94703e7f 100644 --- a/tests/testthat/test_error_models.R +++ b/tests/testthat/test_error_models.R @@ -177,3 +177,45 @@ test_that("Reweighting method 'tc' produces reasonable variance estimates", { # from 15 datasets expect_true(all(abs(tcf_met_2_15_tc_error_model_errors) < 0.10)) }) + +test_that("The two-component error model finds the best known AIC values for parent models", { + skip_on_cran() + experimental_data_for_UBA_2019 + library(parallel) + source("~/git/mkin/R/mkinfit.R") + source("~/git/mkin/R/mmkin.R") + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data) + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data, + error_model = "tc", error_model_algorithm = "direct") + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data, + error_model = "tc", error_model_algorithm = "twostep") + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data, + error_model = "tc", error_model_algorithm = "threestep") + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data, + error_model = "tc", error_model_algorithm = "fourstep") + f_9 <- mkinfit("SFO", experimental_data_for_UBA_2019[[9]]$data, + error_model = "tc", error_model_algorithm = "IRLS") + AIC(f_9) + f_tc_exp <- mmkin(c("SFO"), + lapply(experimental_data_for_UBA_2019, function(x) x$data), + error_model = "tc", + error_model_algorithm = "direct", + quiet = TRUE) + f_tc_exp <- mmkin(c("SFO"), + lapply(experimental_data_for_UBA_2019, function(x) x$data), + error_model = "tc", + error_model_algorithm = "twostep", + quiet = TRUE) + f_tc_exp <- mmkin(c("SFO"), + lapply(experimental_data_for_UBA_2019, function(x) x$data), + error_model = "tc", + error_model_algorithm = "threestep", + quiet = TRUE) + + AIC_exp <- lapply(f_tc_exp, AIC) + dim(AIC_exp) <- dim(f_tc_exp) + dimnames(AIC_exp) <- dimnames(f_tc_exp) + expect_equivalent(round(AIC_exp["SFO", c(9, 11, 12)], 1), c(134.9, 125.5, 82.0)) +}) + + |