Nothing
## ======================================================================
lc <- function(alpha, n, tau, k0, e0, c0 = NULL, c3 = NULL, c4 = NULL){
## function computes capillary length Lc based on VGM model and
## evaporation rate of a saturated soil; if k0 is not available then
## the approxmation k0 = c3 * (n - c0)^c4 is used
## 2019-11-27 A. Papritz
## approximation for missing k0
if(missing(k0) || is.na(k0)){
if(
any(c(is.null(c0), is.null(c3), is.null(c4))) ||
n < c0
){
return(NA_real_)
} else {
t.k0 <- c3 * (n - c0)^c4
}
} else {
t.k0 <- k0
}
## auxiliary computations
nm1 <- -1 + n
m2p1on <- -2 + 1/n
aux1 <- (1 + (((nm1)/n)^(m2p1on))^n)^(-1 + 1/n)
## Lc
result <- ((nm1)/(-1 + 2*n))^(m2p1on) /
((1 + e0/(4.*t.k0*(-1 + (1 - (aux1)^(n/(nm1)))^((nm1)/n))^2*(aux1)^tau))*n*alpha)
attr(result, "input") <- c(
alpha = unname(alpha), n = unname(n), tau = unname(tau),
k0 = unname(t.k0), e0 = unname(e0))
unname(result)
}
## ======================================================================
lt <- function(n, tau, e0, c0, c1, c2, c3, c4){
## function computes constraining value for capillary length Lc based on
## VGM model and evaporation rate of a saturated soil and approximations
## for alpha and k0
## 2019-11-27 A. Papritz
if(
any(c(missing(c0), missing(c1), missing(c2), missing(c3), missing(c4))) ||
n < c0
){
return(NA_real_)
} else {
lc(
alpha = c1*(n - c0)/(1 + c2*(n - c0)),
n = n, tau = tau,
k0 = c3 * (n - c0)^c4,
e0 = e0
)
}
}
## ======================================================================
sat_model <- function(h, nlp, precBits = NULL, wrc_model = "vg"){
## function computes relative capillary saturation based on van
## Genuchten-Mulalem (vGM) model
## 2019-11-27 A. Papritz
switch(
wrc_model,
vg = {
alpha <- nlp["alpha"]
n <- nlp["n"]
if(!is.null(precBits)) h <- mpfr(h, precBits)
(1 + (h * alpha)^n)^(-1 + 1/n)
},
stop("wrc model '", wrc_model, "' not implemented")
)
}
## ======================================================================
wc_model <- function(h, nlp, lp, precBits = NULL, wrc_model = "vg"){
## function computes water content based on van Genuchten-Mulalem (vGM)
## model
## 2019-11-27 A. Papritz
switch(
wrc_model,
vg = {
lp["thetar"] +
(lp["thetas"] - lp["thetar"]) * sat_model(h, nlp, precBits, wrc_model)
}
)
}
## ======================================================================
hcrel_model <- function(h, nlp, precBits = NULL, hcc_model = "vgm"){
## function computes relative hydraulic conductivity based on
## van Genuchten-Mulalem (vGM) model
## 2019-11-27 A. Papritz
switch(
hcc_model,
vgm = {
n <- nlp["n"]
tau <- nlp["tau"]
if(!is.null(precBits)){
one.mpfr <- mpfr(1, precBits)
} else {
one.mpfr <- 1.
}
sat <- sat_model(h, nlp, precBits, wrc_model = "vg")
(sat^tau) * (one.mpfr - (one.mpfr - sat^(n/(n-1)))^((n-1)/n))^2
},
stop("hcc model'", hcc_model, "' not implemented")
)
}
## ======================================================================
hc_model <- function(h, nlp, lp, precBits = NULL, hcc_model = "vgm"){
## function computes hydraulic conductivity based on
## van Genuchten-Mulalem (vGM) model
## 2019-11-27 A. Papritz
switch(
hcc_model,
vgm = {
lp["k0"] * hcrel_model(h, nlp, precBits, hcc_model)
}
)
}
## ##############################################################################
param_boundf <- function(
alpha = c(0. + 10. * sqrt(.Machine$double.eps), 5.e2),
n = c(1. + 10. * sqrt(.Machine$double.eps), 20.), tau = c(-2., 20.),
thetar = c(0., 1.), thetas = c(0., 1.), k0 = c(0., Inf)
){
## function returns valid range of values for nonlinear parameters
## 2019-11-27 A. Papritz
## 2021-02-28 AP checking of valid boundaries in new function
## check_param_boundf
param_bound <- list(
alpha = alpha, n = n, tau = tau,
thetar = thetar, thetas = thetas,
k0 = k0
)
check_param_boundf(param_bound)
param_bound
}
## ##############################################################################
check_param_boundf <- function(
y, compare_thetar_thetas = TRUE
){
## function checks whether boundaries for box constraints are valid
## 2021-02-27 A. Papritz
## 2021-12-26 AP possibility to check incomplete parameter
## boundaries
## 2022-01-03 AP optional cross-comparison of boundaries of
## thetar and thetas
if(
!is.null(y[["thetar"]]) && (
y[["thetar"]][1] < 0. || y[["thetar"]][2] > 1. ||
y[["thetar"]][1] > y[["thetar"]][2]
)
) stop(
"invalid range of possible values for parameter 'thetar'"
)
if(
!is.null(y[["thetas"]]) && (
y[["thetas"]][1] < 0. || y[["thetas"]][2] > 1. ||
y[["thetas"]][1] > y[["thetas"]][2]
)
) stop(
"invalid range of possible values for parameter 'thetas'"
)
if(
compare_thetar_thetas && !is.null(y[["thetar"]]) && !is.null(y[["thetas"]]) && (
y[["thetas"]][1] < y[["thetar"]][1] ||
y[["thetar"]][2] > y[["thetas"]][2]
)
) stop(
"invalid ranges of possible values for parameters 'thetar' and 'thetas'"
)
if(
!is.null(y[["alpha"]]) && (
y[["alpha"]][1] <= 0. || y[["alpha"]][1] > y[["alpha"]][2]
)
) stop(
"invalid range of possible values for parameter 'alpha'"
)
if(
!is.null(y[["n"]]) && (
y[["n"]][1] < 1. || y[["n"]][1] > y[["n"]][2]
)
) stop(
"invalid range of possible values for parameter 'n'"
)
if(
!is.null(y[["k0"]]) && (
y[["k0"]][1] < 0.|| y[["k0"]][1] > y[["k0"]][2]
)
) stop(
"invalid range of possible values for parameter 'k0'"
)
"no error"
}
## ======================================================================
param_transf <- function(
# thetar = "identity", thetas = "identity",
alpha = c("log", "identity"),
n = c("log1", "identity"),
tau = c("logitlu", "identity"),
k0 = c("log", "identity")
){
## function sets meaningful defaults for transformation of model
## parameters
## 2019-11-27 A. Papritz
alpha <- match.arg(alpha)
n <- match.arg(n)
tau <- match.arg(tau)
k0 <- match.arg(k0)
list(
# thetar = thetar, thetas = thetas,
alpha = alpha, n = n,
k0 = k0,
tau = tau
)
}
## ======================================================================
fwd_transf <- function(
...
){
## definition of forward transformation of model parameters
## 2019-11-27 A. Papritz
list(
log = function(x, ...) log(x),
log1 = function(x, ...) log(x - 1.),
logit01 = function(x, ...) log(x / (1. - x)),
logitlu = function(x, lb, ub) log((x - lb) / (ub - x)),
identity = function(x, ...) x, ...
)
}
## ======================================================================
dfwd_transf<- function(
...
){
## definition of first derivative of forward transformation of model
## parameters
## 2019-11-27 A. Papritz
list(
log = function(x, ...) 1. / x,
log1 = function(x, ...) 1. / (x - 1.),
logit01 = function(x, ...) 1. / (x - x^2),
logitlu = function(x, lb, ub) 1. / (ub - x) + 1. / (x - lb),
identity = function(x, ...) rep(1., length(x)), ...
)
}
## ======================================================================
bwd_transf <- function(
...
){
## definition of backward transformation of model parameters
## 2019-11-27 A. Papritz
list(
log = function(x, ...) exp(x),
log1 = function(x, ...) exp(x) + 1.,
logit01 = function(x, ...){
if(!is.finite(x)){
if(sign(x) < 0.) 0. else 1.
} else exp(x) / (1. + exp(x))
},
logitlu = function(x, lb, ub){
if(!is.finite(x)){
if(sign(x) < 0.) 0. else 1.
} else (ub * exp(x) + lb) / (1. + exp(x))
},
identity = function(x, ...) x, ...
)
}
## ======================================================================
control_nloptr <- function(
...
)
{
## function sets meaningful defaults for selected control arguments of
## function nloptr
## 2019-11-27 A. Papritz
list(
...
)
}
## ======================================================================
control_sce <- function(
reltol = 1.e-8, maxtime = 20, ...
)
{
## function sets meaningful defaults for control arguments of function
## SCEoptim of package hydromad
## 2019-11-27 A. Papritz
list(
reltol = reltol,
maxtime = 20,
...
)
}
## ======================================================================
control_pcmp <-
function(
ncores = detectCores() - 1L,
fork = !identical( .Platform[["OS.type"]], "windows" )
)
{
## function sets meaningful defaults for parallelized computations
## 2014-07-29 AP
## 2015-06-30 AP function and arguments renamed
## 2015-07-29 AP changes for elimination of parallelized computation of gradient or estimating equations
## 2016-07-20 AP renamed function, separate ncores arguments various parallelized computations
list(
ncores = ncores,
fork = fork
)
}
## ======================================================================
### control_fit_wrc_hcc
control_fit_wrc_hcc <- function(
settings = c("uglobal", "ulocal", "clocal", "cglobal", "sce"),
method = c("ml", "mpd", "wls"),
hessian,
nloptr = control_nloptr(),
sce = control_sce(),
wrc_model = "vg", hcc_model = "vgm",
initial_param = c(alpha = 2., n = 1.5, tau = 0.5),
approximation_alpha_k0 = c(
c0 = 1.0, c1 = 5.55, c2 = 1.204, c3 = 2.11, c4 = 1.71
),
variable_weight = c(wrc = 1., hcc = 1.),
gam_k = 6,
gam_n_newdata = 101,
precBits = 256,
min_nobs_wc = 5,
min_nobs_hc = 5,
keep_empty_fits = FALSE,
param_bound = param_boundf(),
param_tf = param_transf(),
fwd_tf = fwd_transf(),
deriv_fwd_tfd = dfwd_transf(),
bwd_tf = bwd_transf(),
pcmp = control_pcmp()
){
## function set meaningful default for settings that control execution of
## function fit_wrc_hcc
## 2019-11-27 A. Papritz
## 2020-01-06 AP computation of Hessian matrix at solution
## 2020-01-23 AP new argument method
## 2020-01-29 AP new default value argument hessian
## 2021-05-21 AP new default values for maxeval for settings == "clocal"
## 2021-05-23 AP new list of allowed algorithms for settings == "cglobal"
## 2021-05-21 AP new default values for maxeval for settings == "cglobal|clocal"
## 2021-10-25 AP new default value "mpd" for methods argument
## 2021-12-06 AP new local constrained algorithm NLOPT_LD_CCSAQ
## 2021-12-07 AP new default local constrained algorithm NLOPT_LD_CCSAQ
## 2021-12-22 AP new default value "ml" for methods argument
## 2022-01-08 AP use of new function model_param_tf_nlp_identity to choose
## identity transformation for nonlinear parameters
#### -- check arguments
## match settings, method, wrc_model, hcc_model arguments
settings <- match.arg(settings)
method <- match.arg(method)
wrc_model <- match.arg(wrc_model)
hcc_model <- match.arg(hcc_model)
## check availability of functions for parameter transformations
if(
!(all(unlist(param_tf) %in% names(fwd_tf)) &&
all(unlist(param_tf) %in% names(deriv_fwd_tfd)) &&
all(unlist(param_tf) %in% names(bwd_tf))
)
) stop(
"undefined transformation of parameters; extend respective function definitions"
)
## set value for hessian if missing
if(missing(hessian)){
hessian <- settings %in% c("uglobal", "ulocal", "sce") &&
method %in% c("ml", "mpd")
}
## check variable weights
names(variable_weight) <- c("wrc", "hcc")
sel <- variable_weight[!is.na(variable_weight)] < 0.
if(length(sel) && any(sel)) stop("'variable_weight' must be positive")
#### -- prepare nloptr options
## match names of arguments of control_nloptr
nloptr.dopt <- nloptr.get.default.options()
names.opts <- NULL
if(length(nloptr)) names(nloptr) <- names.opts <- match.arg(
names(nloptr), c(nloptr.dopt[, "name"], "local_opts"), several.ok = TRUE
)
## check whether specified algorithm is valid
## list of available algorithms
nloptr.algorithms <- unlist(strsplit(
nloptr.dopt[grep("algorithm", nloptr.dopt[, "name"]), "possible_values"],
", ", fixed = TRUE
))
if("algorithm" %in% names.opts){
nloptr[["algorithm"]] <- match.arg(
toupper(nloptr[["algorithm"]]), choices = nloptr.algorithms
)
}
#### --- options for global NLopt optimization algorithms
if(settings %in% c("uglobal", "cglobal")){
## specify algorithm
if("algorithm" %in% names.opts){
## check whether specified algorithm does global optimization
if(length(grep("^NLOPT_G", nloptr[["algorithm"]])) == 0L ) stop(
"local optimization algorithm specified for global optimization"
)
if(settings == "cglobal" &&
!nloptr[["algorithm"]] %in%
c("NLOPT_GN_ISRES", "NLOPT_GN_ORIG_DIRECT", "NLOPT_GN_ORIG_DIRECT_L")
) stop(
"wrong algorithm specified for constrained global optimization"
)
} else {
## choose default global optimization algorithm
if(identical(settings, "uglobal")){
nloptr[["algorithm"]] <- "NLOPT_GN_MLSL_LDS"
} else {
nloptr[["algorithm"]] <- "NLOPT_GN_ISRES"
}
}
#### ---- MLSL algorithm: check and specify local options
if(length(grep("MLSL", nloptr[["algorithm"]]))){
if("local_opts" %in% names.opts){
## local options present
## match names of local options
names.local.opts <- names(nloptr[["local_opts"]])
names.local.opts <- match.arg(
names.local.opts, c("algorithm", "xtol_rel", "ftol_rel"),
several.ok = TRUE
)
names(nloptr[["local_opts"]]) <- names.local.opts
## match name of algorithm for local solver
if(length(grep("algorithm", names.local.opts))){
## match algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- match.arg(
toupper(nloptr[["local_opts"]][["algorithm"]]),
nloptr.algorithms[grep("NLOPT_L", nloptr.algorithms)]
)
}
## check and specify algorithm for local solver if missing
if(length(grep("^NLOPT_GD", nloptr[["algorithm"]]))){
## outer algorithm uses derivatives
if("algorithm" %in% names.local.opts){
## make sure that algorithm of local solver uses derivatives
if(
length(grep("NLOPT_LN", nloptr[["local_opts"]][["algorithm"]]))
) stop("wrong algorithm for local solver")
} else {
## specify default algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- "NLOPT_LN_LBFGS"
}
} else {
## derivative-free outer algorithm
if("algorithm" %in% names.local.opts){
## make sure that algorithm of local solver uses derivatives
if(
length(grep("NLOPT_LD", nloptr[["local_opts"]][["algorithm"]]))
) stop("wrong algoritm for local solver")
} else {
## specify default algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- "NLOPT_LN_BOBYQA"
}
}
## default options controlling convergence of local solver
if(!"xtol_rel" %in% names.local.opts) nloptr[["local_opts"]][["xtol_rel"]] <- -1.
if(!"ftol_rel" %in% names.local.opts) nloptr[["local_opts"]][["ftol_rel"]] <- 1.e-6
} else {
## local options missing: set default values
nloptr[["local_opts"]] <- list(
algorithm = if(length(grep("^NLOPT_GD", nloptr[["algorithm"]])))
"NLOPT_LD_LBFGS" else "NLOPT_LN_BOBYQA",
xtol_rel = -1.,
ftol_rel = 1.e-6
)
}
} else {
## omit local_opts for other algorithms
nloptr <- nloptr[!names(nloptr) %in% "local_opts"]
}
#### ---- overwrite default options
## overwrite options for transformations of nonlinear parameters
tmp <- model_param_tf_nlp_identity(wrc_model)
param_tf[names(tmp)] <- tmp
tmp <- model_param_tf_nlp_identity(hcc_model)
param_tf[names(tmp)] <- tmp
## overwrite default options controlling convergence
if(!"xtol_rel" %in% names.opts) nloptr[["xtol_rel"]] <- -1.
if(!"ftol_rel" %in% names.opts) nloptr[["ftol_rel"]] <- -1.
if(!"maxtime" %in% names.opts) nloptr[["maxtime"]] <- -1.
if(!"maxeval" %in% names.opts){
if(settings == "uglobal"){
nloptr[["maxeval"]] <- 125
} else {
nloptr[["maxeval"]] <- 1000
}
}
} else if(settings %in% c("ulocal", "clocal")){
#### --- options for local NLopt optimization algorithms
## specify algorithm
if("algorithm" %in% names.opts){
## check whether specified algorithm does local optimization
if(length(grep("^NLOPT_L", nloptr[["algorithm"]])) == 0L) stop(
"global optimization algorithm specified for local optimization"
)
if(settings == "clocal" &&
length(grep("COBYLA$|MMA$|CCSAQ$|SLSQP$|AUGLAG$", nloptr[["algorithm"]])) == 0L
) stop(
"wrong algorithm specified for constrained local optimization"
)
if(
all(param_tf %in% "identity") &&
identical(nloptr[["algorithm"]], "NLOPT_LN_NEWUOA")
){
warning(
"Estimating untransformed parameters by algorithm 'NLOPT_LN_NEWUOA'",
" may results in estimates that violate box constraints"
)
}
## AUGLAG algorithm: check and specify local options
if(length(grep("AUGLAG$", nloptr[["algorithm"]]))){
if("local_opts" %in% names.opts){
## local options present
## match names of local options
names.local.opts <- names(nloptr[["local_opts"]])
names.local.opts <- match.arg(
names.local.opts, c("algorithm", "xtol_rel", "ftol_rel"),
several.ok = TRUE
)
names(nloptr[["local_opts"]]) <- names.local.opts
## match name of algorithm for local solver
if(length(grep("algorithm", names.local.opts))){
## match algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- match.arg(
toupper(nloptr[["local_opts"]][["algorithm"]]), c(
"NLOPT_LN_COBYLA", "NLOPT_LD_LBFGS",
"NLOPT_LD_MMA", "NLOPT_LD_CCSAQ", "NLOPT_LD_SLSQP"
)
)
}
## check and specify algorithm for local solver if missing
if(length(grep("^NLOPT_LD", nloptr[["algorithm"]]))){
## outer algorithm uses derivatives
if("algorithm" %in% names.local.opts){
## make sure that algorithm of local solver uses derivatives
if(
length(
grep("MMA$|CCSAQ$|SLSQP$|LBFGS", nloptr[["local_opts"]][["algorithm"]])
) == 0L
) stop("wrong algorithm for local solver")
} else {
## specify default algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- "NLOPT_LD_LBFGS"
}
} else {
## derivative-free outer algorithm
if("algorithm" %in% names.local.opts){
## make sure that algorithm of local solver uses derivatives
if(
length(grep("COBYLA$", nloptr[["local_opts"]][["algorithm"]])) == 0L
) stop("wrong algoritm for local solver")
} else {
## specify default algorithm of local solver
nloptr[["local_opts"]][["algorithm"]] <- "NLOPT_LN_COBYLA"
}
}
## default options controlling convergence of local solver
if(!"xtol_rel" %in% names.local.opts) nloptr[["local_opts"]][["xtol_rel"]] <- -1.
if(!"ftol_rel" %in% names.local.opts) nloptr[["local_opts"]][["ftol_rel"]] <- 1.e-6
} else {
## local options missing: set default values
nloptr[["local_opts"]] <- list(
algorithm = if(length(grep("^NLOPT_LD", nloptr[["algorithm"]])))
"NLOPT_LD_LBFGS" else "NLOPT_LN_COBYLA",
xtol_rel = -1.,
ftol_rel = 1.e-6
)
}
} else {
## omit local_opts for other algorithms
nloptr <- nloptr[!names(nloptr) %in% "local_opts"]
}
} else {
## choose default local optimization algorithm
if(identical(settings, "ulocal")){
nloptr[["algorithm"]] <- "NLOPT_LN_BOBYQA"
} else {
nloptr[["algorithm"]] <- "NLOPT_LD_CCSAQ"
}
}
## overwrite default options controlling convergence
if(!"xtol_rel" %in% names.opts) nloptr[["xtol_rel"]] <- -1.
if(!"ftol_rel" %in% names.opts) nloptr[["ftol_rel"]] <- 1.e-8
if(!"maxtime" %in% names.opts) nloptr[["maxtime"]] <- -1.
if(!"maxeval" %in% names.opts){
if(settings == "ulocal"){
nloptr[["maxeval"]] <- 250
} else {
nloptr[["maxeval"]] <- 1000
}
}
}
#### -- prepare control arguments of SCEoptim
if(identical(settings, "sce")){
## overwrite options for transformations of nonlinear parameters
tmp <- model_param_tf_nlp_identity(wrc_model)
param_tf[names(tmp)] <- tmp
tmp <- model_param_tf_nlp_identity(hcc_model)
param_tf[names(tmp)] <- tmp
}
## set flag for gradient method for NLopt optimizers
use_derivative <- FALSE
if(length(grep("_LD_|_GD_", nloptr[["algorithm"]]))) use_derivative <- TRUE
## set default values for grad_eps criterion
grad_eps <- switch(
settings,
"sce" = ,
"cglobal" = ,
"uglobal" = 1.e-2,
"clocal" = ,
"ulocal" = if(
length(grep("_LD_", nloptr[["algorithm"]]))
){
1.e-5
} else {
1.e-3
}
)
#### -- collect all control arguments
control <- list(
settings = settings, hessian = hessian, method = method,
nloptr = nloptr, sce = sce,
wrc_model = wrc_model, hcc_model = hcc_model,
initial_param = initial_param,
approximation_alpha_k0 = approximation_alpha_k0,
variable_weight = variable_weight,
gam_k = gam_k, gam_n_newdata = gam_n_newdata,
precBits = precBits, delta_sat_0 = 1.e-6,
min_nobs_wc = min_nobs_wc, min_nobs_hc = min_nobs_hc,
grad_eps = grad_eps,
keep_empty_fits = keep_empty_fits,
param_bound = param_bound,
param_tf = param_tf, fwd_tf = fwd_tf, deriv_fwd_tfd = deriv_fwd_tfd, bwd_tf = bwd_tf,
pcmp = pcmp,
use_derivative = use_derivative
)
return(control)
}
# ## ======================================================================
# default.param <- function(
# thetar = NA_real_, thetas = NA_real_,
# alpha = NA_real_, n = NA_real_,
# k0 = NA_real_,
# tau = 2
# ){
#
# ## function sets default flags for fitting parameters
#
# ## 2019-03-22 A. Papritz
#
# c(
# thetar = thetar, thetas = thetas,
# alpha = alpha, n = n,
# k0 = k0,
# tau = tau
# )
#
# }
## ======================================================================
default_fit_param <- function(
alpha = TRUE, n = TRUE, tau = FALSE,
thetar = TRUE, thetas = TRUE, k0 = TRUE
){
## function sets default flags for fitting parameters
## 2019-11-27 A. Papritz
#
c(
alpha = alpha, n = n, tau = tau,
thetar = thetar, thetas = thetas,
k0 = k0
)
}
## ======================================================================
### fit_wrc_hcc
fit_wrc_hcc <- function(
wrc_formula, hcc_formula, data,
subset = NULL, wrc_subset = subset, hcc_subset = subset,
weights = NULL, wrc_weights = weights, hcc_weights = weights,
na.action,
param = NULL, lower_param = NULL, upper_param = NULL,
fit_param = default_fit_param(),
e0 = 2.5e-3,
ratio_lc_lt_bound = c(lower = 0.5, upper = 2.),
control = control_fit_wrc_hcc(),
verbose = 0
){
## user front-end function for estimating parameters of soil
## hydraulic functions
## 2019-11-27 A. Papritz
## 2021-02-27 AP new arguments lower_param, upper_param for specifying
## sample-specific lower and upper parameter bounds
## 2021-04-11 AP correction of error when coding box-constraint for single parameter
## 2021-05-10 AP correction of error when coding box-constraints only for thetar
## 2021-05-22 AP check whether inequality constraints are satisfied
## for constrained estimation
## 2021-06-04 AP correction of error when passing param and fit_param to input.data
## 2021-12-22 AP warning if lower_param or upper_param are vectors
## 2021-12-27 AP check that order of rows in param, fit_param, etc is the same
## as order of samples in output
## 2021-12-29 AP sample_id_variable stored as additional component in output
## new method to match input.data and sample-specific data for param,
## fit_param, lower_param, upper_param, e0 ratio_lc_lt_bound
## 2022-01-13 AP use of new functions model_fit_param_consistent for
## preparing parameter estimation
if(!is.finite(verbose)) browser()
## flags for controlling estimation of water retention and hydraulic
## conductivity curves
wrc <- !missing(wrc_formula)
hcc <- !missing(hcc_formula)
## check whether all mandatory arguments have been provided
if(missing(data) || (!wrc && !hcc)) stop(
"some mandatory arguments are missing"
)
## get call for later use and for building of model frames
cl <- match.call()
mf <- match.call(expand.dots = FALSE)
#### -- prepare parameters and flags for fitting
## match names of param, fit_param, lower_param, upper_param
## !! CARE: code works only if fit_param has not been used before (lazy evaluation)
all.param.name <- names(default_fit_param())
## param
names.param <- NULL
if(!is.null(param)){
if(is.data.frame(param)){
if(!all(sapply(param, is.numeric))) stop(
"'param' must be a dataframe or a matrix with numeric variables"
)
param <- as.matrix(param)
}
if(is.vector(param)) param <- t(as.matrix(param))
names.param <- colnames(param)
names.param <- unname(sapply(
names.param,
function(x, choices) match.arg(x, choices),
choices = all.param.name
))
colnames(param) <- names.param
}
## fit_param
if(is.data.frame(fit_param)){
if(!all(sapply(fit_param, is.logical))) stop(
"'fit_param' must be a dataframe or a matrix with logical variables"
)
fit_param <- as.matrix(fit_param)
}
if(is.vector(fit_param)) fit_param <- t(as.matrix(fit_param))
names.fit_param <- colnames(fit_param)
if(!missing(fit_param)){
names.fit_param <- unname(sapply(
names.fit_param,
function(x, choices) match.arg(x, choices),
choices = all.param.name
))
colnames(fit_param) <- names.fit_param
}
## lower_param
if(is.data.frame(lower_param)){
if(!all(sapply(lower_param, is.numeric))) stop(
"'lower_param' must be a dataframe or a matrix with numeric variables"
)
lower_param <- as.matrix(lower_param)
}
if(is.vector(lower_param)){
warning(
"'lower_param' is a vector; a better way to define parameter bounds ",
"is to use the function 'param_boundf', see help(control_fit_wrc_hcc)"
)
lower_param <- t(as.matrix(lower_param))
}
names.lower_param <- colnames(lower_param)
if(!missing(lower_param)){
names.lower_param <- unname(sapply(
names.lower_param,
function(x, choices) match.arg(x, choices),
choices = all.param.name
))
colnames(lower_param) <- names.lower_param
## set lower bounds for thetas equal to lower bounds of thetar if
## only the latter have been specified
if("thetar" %in% names.lower_param & !"thetas" %in% names.lower_param){
lower_param <- cbind(
lower_param,
thetas = lower_param[, "thetar"]
)
}
}
## upper_param
if(is.data.frame(upper_param)){
if(!all(sapply(upper_param, is.numeric))) stop(
"'upper_param' must a dataframe or a matrix with numeric variables"
)
upper_param <- as.matrix(upper_param)
}
if(is.vector(upper_param)){
warning(
"'upper_param' is a vector; a better way to define parameter bounds ",
"is to use the function 'param_boundf', see help(control_fit_wrc_hcc)"
)
upper_param <- t(as.matrix(upper_param))
}
names.upper_param <- colnames(upper_param)
if(!missing(upper_param)){
names.upper_param <- unname(sapply(
names.upper_param,
function(x, choices) match.arg(x, choices),
choices = all.param.name
))
colnames(upper_param) <- names.upper_param
## set upper bounds for thetar equal to upper bounds of thetas if
## only the latter have been specified
if("thetas" %in% names.upper_param & !"thetar" %in% names.upper_param){
upper_param <- cbind(
upper_param,
thetar = upper_param[, "thetas"]
)
}
}
# print(param)
# print(fit_param)
## consistent coding of flags for fitting
if(wrc){
tmp <- model_fit_param_consistent(
model = control[["wrc_model"]],
fit_param = fit_param, names.fit_param = names.fit_param,
param = param, names.param = names.param,
verbose = verbose
)
fit_param <- tmp[["fit_param"]]
param <- tmp[["param"]]
names.fit_param <- colnames(fit_param)
names.param <- colnames(param)
}
# print(param)
# print(fit_param)
if(hcc){
tmp <- model_fit_param_consistent(
model = control[["hcc_model"]],
fit_param = fit_param, names.fit_param = names.fit_param,
param = param, names.param = names.param,
verbose = verbose
)
fit_param <- tmp[["fit_param"]]
param <- tmp[["param"]]
names.fit_param <- colnames(fit_param)
names.param <- colnames(param)
}
# print(param)
# print(fit_param)
#### -- get variable names for water retention and hydraulic conductivity
## curves
wrc_mf <- NULL
if(wrc){
## variable names for wc, head and sample id
wrc.variables <- as.character(wrc_formula)[-1]
wrc.variables <- c(
wrc.variables[1],
gsub(
"[\\(\\) ]",
"",
unlist(strsplit(wrc.variables[2], "|", fixed = TRUE))
)
)
if(!all(wrc.variables %in% colnames(data))) stop(
"missing variables for water retention curve(s)"
)
## formula without sample id
wrc_formula <- formula(
paste(wrc.variables[1], wrc.variables[2], sep = "~"),
env = environment(wrc_formula)
)
## preparation for model frame
m.wrc <- match(
c(
"wrc_formula", "subset", "wrc_subset",
"weights", "wrc_weights", "na.action"
),
names(mf), 0L
)
wrc_mf <- mf[c(1L, m.wrc)]
## get names of specified arguments
nmes <- names(wrc_mf)
## handle subset and weights arguments
if("wrc_subset" %in% nmes){
if("subset" %in% nmes){
warning(
"both 'subset' and 'wrc_subset' arguments were specified:",
"'subset' will be ignored"
)
ex <- match("subset", nmes)
nmes <- nmes[!nmes == "wrc_subset"]
wrc_mf <- wrc_mf[-ex]
} else {
nmes[nmes == "wrc_subset"] <- "subset"
}
}
if("wrc_weights" %in% nmes){
if("weights" %in% nmes){
warning(
"both 'weights' and 'wrc_weights' arguments were specified:",
"'weights' will be ignored"
)
ex <- match("weights", nmes)
nmes <- nmes[!nmes == "wrc_weights"]
wrc_mf <- wrc_mf[-ex]
} else {
nmes[nmes == "wrc_weights"] <- "weights"
}
}
## rename wrc_formula and wrc_weights
nmes[nmes == "wrc_formula"] <- "formula"
## set names of specified argument of function call
names(wrc_mf) <- nmes
## set remaing argument of call and prepare evalution of model frame
wrc_mf[["formula"]] <- wrc_formula
wrc_mf[["drop.unused.levels"]] <- TRUE
wrc_mf[["data"]] <- as.name("input.data")
wrc_mf[[1L]] <- as.name("model.frame")
} else {
wrc_formula <- NULL
}
hcc_mf <- NULL
if(hcc){
## variable names for hydraulic conductivity, head and sample id
hcc.variables <- as.character(hcc_formula)[-1]
hcc.variables <- c(
hcc.variables[1],
gsub(
"[\\(\\) ]",
"",
unlist(strsplit(hcc.variables[2], "|", fixed = TRUE))
)
)
if(!all(hcc.variables %in% colnames(data))) stop(
"missing variables for hydraulic conductivity curve(s)"
)
if(wrc){
if(!identical(length(wrc.variables), length(hcc.variables))) stop(
"inconsistent formulae for water retention and hydraulic conductivity curves"
)
if(
identical(length(wrc.variables), 3L) &&
!identical(wrc.variables[3], hcc.variables[3])
) stop(
"variable that defines samples differs for water retention and hydraulic conductivity curves"
)
}
## formula without sample id
hcc_formula <- formula(
paste(hcc.variables[1], hcc.variables[2], sep = "~"),
env = environment(hcc_formula)
)
## preparation for model frame
m.hcc <- match(
c(
"hcc_formula", "subset", "hcc_subset",
"weights", "hcc_weights", "na.action"
),
names(mf), 0L
)
hcc_mf <- mf[c(1L, m.hcc)]
## get names of specified arguments
nmes <- names(hcc_mf)
## handle subset and weights arguments
if("hcc_subset" %in% nmes){
if("subset" %in% nmes){
warning(
"both 'subset' and 'hcc_subset' arguments were specified:",
"'subset' will be ignored"
)
ex <- match("subset", nmes)
nmes <- nmes[!nmes == "hcc_subset"]
hcc_mf <- hcc_mf[-ex]
} else {
nmes[nmes == "hcc_subset"] <- "subset"
}
}
if("hcc_weights" %in% nmes){
if("weights" %in% nmes){
warning(
"both 'weights' and 'hcc_weights' argument specified:",
"'weights' will be ignored"
)
ex <- match("weights", nmes)
nmes <- nmes[!nmes == "hcc_weights"]
hcc_mf <- hcc_mf[-ex]
} else {
nmes[nmes == "hcc_weights"] <- "weights"
}
}
## rename hcc_formula and hcc_weights
nmes[nmes == "hcc_formula"] <- "formula"
## set names of specified argument of function call
names(hcc_mf) <- nmes
## set remaing argument of call and prepare evalution of model frame
hcc_mf[["formula"]] <- hcc_formula
hcc_mf[["drop.unused.levels"]] <- TRUE
hcc_mf[["data"]] <- as.name("input.data")
hcc_mf[[1L]] <- as.name("model.frame")
} else {
hcc_formula <- NULL
}
#### -- prepare ratio_lc_lt_bound
if(is.vector(ratio_lc_lt_bound)){
tmp <- names(ratio_lc_lt_bound)
ratio_lc_lt_bound <- matrix(ratio_lc_lt_bound, nrow = 1)
colnames(ratio_lc_lt_bound) <- tmp
}
## check consistency of bounds on ratio Lc/Lt
if(any(apply(ratio_lc_lt_bound, 1, function(x) x[1] > x[2]))) stop(
"lower bound of ratio Lc/Lt > upper bound"
)
#### -- create list of dataframes where each component contains data for a
## single sample
if(wrc){
split.variables <- wrc.variables
} else if(hcc){
split.variables <- hcc.variables
}
if(identical(length(split.variables), 3L)){
input.data <- split(data, data[, split.variables[3]])
} else{
input.data <- list(data)
}
if(identical(length(input.data), 1L)) names(input.data) <- ""
nmes.samples <- names(input.data)
## check that param, fit_param, lower_param, upper_param, e0 and
## ratio_lc_lt_bound have valid (row)names if they contain
## sample-specific information, i.e. when they are matrices or a vector
if(length(input.data) > 1L){
if(!is.null(param) && NROW(param) > 1L){
if(is.null(rownames(param))) stop(
"'param' is a matrix with sample-specific data ",
"but rownames are missing"
)
if(!all(nmes.samples %in% rownames(param))) stop(
"'param' lacks data for some samples"
)
}
if(NROW(fit_param) > 1L){
if(is.null(rownames(fit_param))) stop(
"'fit_param' is a matrix with sample-specific data ",
"but rownames are missing"
)
if(!all(nmes.samples %in% rownames(fit_param))) stop(
"'fit_param' lacks data for some samples"
)
}
if(!is.null(lower_param) && NROW(lower_param) > 1L){
if(is.null(rownames(lower_param))) stop(
"'lower_param' is a matrix with sample-specific data ",
"but rownames are missing"
)
if(!all(nmes.samples %in% rownames(lower_param))) stop(
"'lower_param' lacks data for some samples"
)
}
if(!is.null(upper_param) && NROW(upper_param) > 1L){
if(is.null(rownames(upper_param))) stop(
"'upper_param' is a matrix with sample-specific data ",
"but rownames are missing"
)
if(!all(nmes.samples %in% rownames(upper_param))) stop(
"'upper_param' lacks data for some samples"
)
}
if(length(e0) > 1L){
if(is.null(names(e0))) stop(
"'e0' is a vector with sample-specific data ",
"but names are missing"
)
if(!all(nmes.samples %in% names(e0))) stop(
"'e0' lacks data for some samples"
)
}
if(NROW(ratio_lc_lt_bound) > 1L){
if(is.null(rownames(ratio_lc_lt_bound))) stop(
"'ratio_lc_lt_bound' is a matrix with sample-specific data ",
"but rownames are missing"
)
if(!all(nmes.samples %in% rownames(ratio_lc_lt_bound))) stop(
"'ratio_lc_lt_bound' lacks data for some samples"
)
}
}
## augment list of dataframes with param, fit_param, lower_param,
## upper_param, e0 and ratio_lc_lt_bound components
input.data <- lapply(
1:length(input.data),
function(i){
param.ii <- NULL
if(!is.null(param)){
if(NROW(param) > 1L){
ii <- match(nmes.samples[i], rownames(param))
} else {
ii <- 1L
}
param.ii <- unname(param[ii, ])
names(param.ii) <- colnames(param)
}
if(NROW(fit_param) > 1L){
ii <- match(nmes.samples[i], rownames(fit_param))
} else {
ii <- 1L
}
fit_param.ii <- unname(fit_param[ii, ])
names(fit_param.ii) <- colnames(fit_param)
lower_param.ii <- NULL
if(!is.null(lower_param)){
if(NROW(lower_param) > 1L){
ii <- match(nmes.samples[i], rownames(lower_param))
} else {
ii <- 1L
}
lower_param.ii <- unname(lower_param[ii, ])
names(lower_param.ii) <- colnames(lower_param)
}
upper_param.ii <- NULL
if(!is.null(upper_param)){
if(NROW(upper_param) > 1L){
ii <- match(nmes.samples[i], rownames(upper_param))
} else {
ii <- 1L
}
upper_param.ii <- unname(upper_param[ii, ])
names(upper_param.ii) <- colnames(upper_param)
}
if(length(e0) > 1L){
ii <- match(nmes.samples[i], names(e0))
} else {
ii <- 1L
}
e0.ii <- unname(e0[ii])
if(NROW(ratio_lc_lt_bound) > 1L){
ii <- match(nmes.samples[i], rownames(ratio_lc_lt_bound))
} else {
ii <- 1L
}
ratio_lc_lt_bound.ii <- unname(ratio_lc_lt_bound[ii, ])
names(ratio_lc_lt_bound.ii) <- colnames(ratio_lc_lt_bound)
list(
data = input.data[[i]],
param = param.ii,
fit_param = fit_param.ii,
lower_param = lower_param.ii,
upper_param = upper_param.ii,
e0 = e0.ii,
ratio_lc_lt_bound = ratio_lc_lt_bound.ii
)
}
)
names(input.data) <- nmes.samples
#### -- preparations for parallel processing
## adjust number of cores for parallel processing
control[["pcmp"]][["ncores"]] <- min(
control[["pcmp"]][["ncores"]],
length(input.data)
)
## diagnostic output for parallel processing
if(control[["pcmp"]][["ncores"]] > 1 && verbose >=1. ) verbose <- 0.5
## create environment for storing index of iteration, linear parameter
## and value of objective function
common.env <- new.env()
## estimate VGM parameters for all samples in parallel
## auxiliary function to fit parameters of single sample
f.aux <- function(i){
## note that input.data, wrc, wrc_formula, wrc_mf, hcc, hcc_formula,
## hcc_mf, all.param.name, control, verbose, split.variables are taken
## from parent environment
## extract data, param, fit_param, e0, ratio_lc_lt_bound
input.data.i <- input.data[[i]][["data"]]
param.i <- input.data[[i]][["param"]]
fit_param.i <- input.data[[i]][["fit_param"]]
lower_param.i <- input.data[[i]][["lower_param"]]
upper_param.i <- input.data[[i]][["upper_param"]]
e0.i <- input.data[[i]][["e0"]]
ratio_lc_lt_bound.i <- input.data[[i]][["ratio_lc_lt_bound"]]
if(
verbose >= 2. && identical(length(split.variables), 3L)) cat(
"\n\nprocessing data of sample",
as.character(unique(input.data.i[, split.variables[3]])), "\n"
)
## update default initial values of nonlinear parameter in
## control[["initial.param"]] if lie outside of valid range
lupn <- unique(
c(names(lower_param.i), names(upper_param.i))
)
if(length(lupn)){
## update parameter boundaries in control[["param_bound"]] and
## initial values in control[["initial_param"]]
if(!is.null(lower_param.i)){
tmp <- control[["param_bound"]][names(lower_param.i)]
tmp <- lapply(
names(tmp),
function(i) c(lower_param.i[i], tmp[[i]][2])
)
control[["param_bound"]][names(lower_param.i)] <- unname(tmp)
}
if(!is.null(upper_param.i)){
tmp <- control[["param_bound"]][names(upper_param.i)]
tmp <- lapply(
names(tmp),
function(i) c(tmp[[i]][1],upper_param.i[i])
)
control[["param_bound"]][names(upper_param.i)] <- unname(tmp)
}
## check validity of new parameter boundaries
check_param_bound <- try(
check_param_boundf(control[["param_bound"]]),
silent = TRUE
)
if(identical(class(check_param_bound), "try-error")){
## some boundaries are invalid: return fit with an error
message <- paste(
"an error occurred when estimating parameters",
if(identical(length(split.variables), 3L)) paste(
"for sample", as.character(unique(input.data.i[, split.variables[3]]))
), "\n", as.character(check_param_bound)
)
warning(message)
fit <- list(
converged = NULL,
message = message,
objective = NULL,
gradient = NULL,
evaluation = NULL,
lp = NULL, nlp = NULL,
inequality_constraint = NULL,
variable_weight = NULL,
case.weight = NULL,
model = NULL,
initial_objects = NULL
)
return(fit)
}
## update initial values of nonlinear parameters
ipn <- names(control[["initial_param"]])
if(any(ipn %in% lupn)){
sel.param <- ipn[ipn %in% lupn]
if(length(sel.param)){
result <- sapply(
sel.param,
function(xn){
x <- control[["initial_param"]][xn]
xb <- control[["param_bound"]][[xn]]
unname(ifelse(
x < xb[1], xb[1] + 0.01 * abs(xb[1]),
ifelse(
x > xb[2], xb[2] - 0.01 * abs(xb[2]),
x
)
))
}
)
control[["initial_param"]][sel.param] <- result
}
}
}
## fit models
fit <- try(
fit_wrc_hcc_fit(
i,
input.data.i, param.i, fit_param.i,
e0.i, ratio_lc_lt_bound.i,
wrc, wrc_formula, wrc_mf,
hcc, hcc_formula, hcc_mf,
all.param.name,
control,
common.env,
verbose
)
, silent = TRUE
)
if(identical(class(fit), "try-error")){
## model fit failed
message <- paste(
"an error occurred when estimating parameters",
if(identical(length(split.variables), 3L)) paste(
"for sample", as.character(unique(input.data.i[, split.variables[3]]))
), "\n", as.character(fit)
)
warning(message)
fit <- list(
converged = NULL,
message = message,
objective = NULL,
gradient = NULL,
evaluation = NULL,
lp = NULL, nlp = NULL,
inequality_constraint = NULL,
variable_weight = NULL,
case.weight = NULL,
model = NULL,
initial_objects = NULL
)
}
fit
}
#### -- loop over all samples
if(
control[["pcmp"]][["ncores"]] > 1L &&
!control[["pcmp"]][["fork"]]
){
## create a SNOW cluster on windows OS
options(error = stop_cluster)
junk <- sfInit( parallel = TRUE, cpus = control[["pcmp"]][["ncores"]] )
## export required items to workers (debugging)
# junk <- sfLibrary("quadprog", verbose = FALSE, character.only = TRUE)
# junk <- sfLibrary("mgcv", verbose = FALSE, character.only = TRUE)
# junk <- sfLibrary("nloptr", verbose = FALSE, character.only = TRUE)
# junk <- sfLibrary("Rmpfr", verbose = FALSE, character.only = TRUE)
# junk <- sfLibrary("SoilHyP", verbose = FALSE, character.only = TRUE)
junk <- sfLibrary("soilhypfit", verbose = FALSE, character.only = TRUE)
## export required items to workers (if package is installed)
# junk <- sfExportAll()
export.items <- c(
"input.data", "split.variables",
"wrc", "wrc_formula", "wrc_mf",
"hcc", "hcc_formula", "hcc_mf",
"all.param.name",
"control",
"common.env",
"verbose"
)
junk <- sfExport(list = export.items)
result <- sfLapply(1L:length(input.data), f.aux)
junk <- stop_cluster()
} else {
## fork child processes on non-windows OS
result <- mclapply(
1L:length(input.data),
f.aux,
mc.cores = control[["pcmp"]][["ncores"]],
mc.allow.recursive = FALSE
)
}
#### -- prepare output object
if(missing(na.action)) na.action <- NULL
names(result) <- nmes.samples
## drop empty fits
if(!control[["keep_empty_fits"]]){
ex <- sapply(
result,
function(x) all(
sapply(
x[!names(x) %in% "message"],
function(y) is.null(y)
)
)
)
if(identical(sum(ex), length(result))){
cat(
"\n\nonly 'empty' fits with following error messages:\n\n"
)
bla <- lapply(result, function(x) cat(x[["message"]]))
}
result <- result[!ex]
}
## check wether inequality constraints are satified for
# constrained estimation algorithms
if(control[["settings"]] %in% c("cglobal", "clocal")){
not.ok <- sapply(
result,
function(x){
any(
sapply(
x[["inequality_constraint"]],
function(y) any(y > 0.00001)
)
)
}
)
if(any(not.ok)) warning(
"inequality constraints not satisfied for some samples"
)
}
## create list and set class attribute
if(identical(length(split.variables), 3L)){
tmp <- split.variables[3]
} else {
tmp <- NULL
}
result <- list(
fit = result,
sample_id_variable = tmp,
wrc = wrc, wrc_formula = wrc_formula, wrc_mf = wrc_mf,
hcc = hcc, hcc_formula = hcc_formula, hcc_mf = hcc_mf,
control = control, call = cl, na.action = na.action
)
class(result) <- "fit_wrc_hcc"
invisible(result)
}
## ======================================================================
convergence_message <- function(x, sce = FALSE){
## function prints messages corresponding to covergence codes of nloptr
## und SCEoptim
## 2019-11-27 A. Papritz
xx <- as.character(x)
if(sce){
cat(switch(
xx,
"0" = "Change in solution over [tolsteps] less than specified tolerance (reltol).",
"1" = "Maximum number of function evaluations or iterations reached.",
"2" = "Exceeded maximum time.",
"Unknown convergence code"
))
} else {
cat(switch(
xx,
"1" = "Generic success return value.",
"2" = "Optimization stopped because stopval was reached.",
"3" = "Optimization stopped because ftol_rel or ftol_abs was reached.",
"4" = "Optimization stopped because xtol_rel or xtol_abs was reached.",
"5" = "Optimization stopped because maxeval was reached.",
"6" = "Optimization stopped because maxtime was reached.",
"-1" = "Generic failure code.",
"-2" = "Invalid arguments (e.g. lower bounds are bigger than upper bounds,\n an unknown algorithm was specified, etc).",
"-3" = "Ran out of memory.",
"-4" = "Halted because roundoff errors limited progress. (In this case,\n the optimization still typically returns a useful result).",
"-5" = "Halted because of a forced termination.",
"Unknown convergence code"
))
}
invisible(x)
}
## ======================================================================
select_failed_fits <- function(object){
## function finds soil samples for which the estimaton of parameter
## failed and returns an integer vector with the indices of those samples
## 2019-12-03 A. Papritz
## check consistency of object
sel <- sapply(object[["fit"]], function(x) is.null(x[["objective"]]))
names(object[["fit"]])[sel]
}
## ======================================================================
extract_error_messages <- function(object, start = 1, stop = 80, prt = TRUE){
## function extracts error messages of failed fits and prints a substring
## of them
## 2019-12-03 A. Papritz
## check consistency of object
stopifnot(identical(class(object), "fit_wrc_hcc"))
sel <- select_failed_fits(object)
msg <- sapply(object[["fit"]][sel], function(x) x[["message"]])
if(prt) cat(substr(msg, start, stop))
invisible(msg)
}
## ======================================================================
### prfloglik_sample
prfloglik_sample <- function(
object, values, soil_sample,
ncores = min(detectCores() - 1L, NROW(values)),
verbose = 0
){
## function to compute loglikelihood profile for a single sample of a
## fit_wrc_hcc object function is based on profilelogLik{georob}
## 2021-12-20 Andreas Papritz
## 2021-12-22 AP correction of errors for parallel computations on windows
## 2021-12-28 AP small changes
## 2021-12-31 AP new version calling directly fit_wrc_hcc_fit
## 2022-01-06 AP adjust values of thetar and thetas if outside
## of allowed bounds
if(!is.finite(verbose)) browser()
#### -- auxiliary function
## auxiliary function to fit model and return maximized log-likelihood
## and conditionally estimated parameters
f.aux <- function(i){
## values, soil_sample, input.data, param, fit_param, e0, ratio_lc_lt_bound,
## object, common.env, verbose are taken from parent environment
## set fixed initial values
param[colnames(values)] <- unname(values[i, ])
## estimate parameters
fit <- try(
fit_wrc_hcc_fit(
i = soil_sample,
input.data = input.data,
param = param, fit_param = fit_param,
e0 = e0, ratio_lc_lt_bound = ratio_lc_lt_bound,
wrc = object[["wrc"]], wrc_formula = object[["wrc_formula"]],
wrc_mf = object[["wrc_mf"]],
hcc = object[["hcc"]], hcc_formula = object[["hcc_formula"]],
hcc_mf = object[["hcc_mf"]],
all.param.name = names(default_fit_param()),
control = object[["control"]],
common.env = common.env,
verbose = verbose
)
, silent = TRUE
)
## result
if(identical(class(fit), "try-error")){
param[names(fit_param)[fit_param]] <- NA_real_
gradient <- rep(NA_real_, length(object[["fit"]][[1]][["nlp"]]))
names(gradient) <- names(object[["fit"]][[1]][["nlp"]])
gradient <- gradient[names(gradient) %in% names(param)[is.na(param)]]
names(gradient) <- paste0("gradient.", names(gradient))
result <- c(
loglik = NA_real_,
param,
gradient,
converged = NA_real_
)
} else {
result <- c(
loglik = - fit[["objective"]],
c(fit[["nlp"]], fit[["lp"]]),
gradient = fit[["gradient"]],
converged = fit[["converged"]]
)
}
result
}
#### -- check arguments and contents of object and values
if(missing(object) || missing(values)) stop(
"some mandatory arguments are missing"
)
stopifnot(identical(class(object)[1], "fit_wrc_hcc"))
stopifnot(identical(length(verbose), 1L) && is.numeric(verbose) && verbose >= 0)
stopifnot(identical(length(ncores), 1L) && is.numeric(ncores) && ncores >= 1)
if(!(is.matrix(values) || is.data.frame(values))) stop(
"'values' must be a dataframe or a matrix"
)
if(length(object[["fit"]]) > 1L){
if(missing(soil_sample)) stop(
"argument 'soil_sample' must be a character scalar because 'object' contains ",
" parameter estimates for several samples"
)
if(length(soil_sample) > 1L || !is.character(soil_sample)) stop(
"'soil_sample' must be a character scalar"
)
} else {
if(!missing(soil_sample)){
warning(
"argument 'soil_sample' will be ignored because 'object' contains ",
"parameter estimates for just one sample"
)
}
soil_sample <- ""
}
if(!identical(object[["control"]][["method"]], "ml")) stop(
"to compute profile loglikelihood, parameters must be estimated by ",
"maximum likelihood method using control argument 'method = ml'"
)
if(object[["control"]][["settings"]] %in% c("uglobal", "cglobal", "sce")) warning(
"parameters have been estimated by global algorithm"
)
#### -- prepare objects required for call of fit_wrc_hcc_fit
## select component of object$fit for sample chosen by soil_sample
if(length(object[["fit"]]) > 1L){
if(soil_sample %in% names(object[["fit"]])){
object[["fit"]] <- object[["fit"]][soil_sample]
} else stop(
"parameter estimates missing in 'object' missing for sample ",
soil_sample
)
}
## check whether selected component of object$fit is valid
if(is.null(object[["fit"]][[1]])) stop(
"parameter estimation failed for sample ", soil_sample
)
## update components wrc and hcc in object$fit[[1]] in dependence of
## available data
## no wrc data for selected sample
if(object[["wrc"]] && is.null(object[["fit"]][[1]][["model"]][["wrc"]])){
object[["wrc"]] <- FALSE
}
## no hcc data for selected sample
if(object[["hcc"]] && is.null(object[["fit"]][[1]][["model"]][["hcc"]])){
object[["hcc"]] <- FALSE
}
## get input.data, param, fit_param, e0, ratio_lc_bound for selected
## sample
input.data <- NULL
if(object[["wrc"]]){
input.data <- object[["fit"]][[1]][["model"]][["wrc"]]
}
if(object[["hcc"]]){
if(is.null(input.data)){
input.data <- object[["fit"]][[1]][["model"]][["hcc"]]
} else {
input.data <- merge(
input.data, object[["fit"]][[1]][["model"]][["hcc"]],
all = TRUE
)
}
}
param <- c(
object[["fit"]][[1]][["nlp"]], object[["fit"]][[1]][["lp"]]
)
fit_param <- object[["fit"]][[1]][["initial_objects"]][["fit_param"]]
param_bound <- object[["fit"]][[1]][["initial_objects"]][["param_bound"]]
e0 <- object[["fit"]][[1]][["initial_objects"]][["e0"]]
ratio_lc_lt_bound <- object[["fit"]][[1]][["initial_objects"]][["ratio_lc_lt_bound"]]
## adjust values for thetar and thetas if estimates are outside of
## allowed bounds
if("thetar" %in% names(param)){
param["thetar"] <- min(
max(param["thetar"], param_bound[["thetar"]][1]),
param["thetas"]
)
}
if("thetas" %in% names(param)){
param["thetas"] <- max(
min(param["thetas"], param_bound[["thetas"]][2]),
param["thetar"]
)
}
## omit subset argument from modelframes
if(!is.null(object[["wrc_mf"]])){
x <- as.list(object[["wrc_mf"]])
x <- x[!names(x) %in% "subset"]
object[["wrc_mf"]] <- as.call(x)
}
if(!is.null(object[["hcc_mf"]])){
x <- as.list(object[["hcc_mf"]])
x <- x[!names(x) %in% "subset"]
object[["hcc_mf"]] <- as.call(x)
}
## check names of fixed parameters in values
fixed.param <- colnames(values)
fixed.param <- unname(sapply(
fixed.param, match.arg,
choices = c(names(default_fit_param()))
))
if(any(!fixed.param %in% names(param))) stop(
"column names of 'values' do not match names of parameters in 'object'"
)
colnames(values) <- fixed.param
## check whether values of fixed parameters are within allowed ranges
minmax.values <- lapply(values, range)
check_param_boundf(minmax.values, compare_thetar_thetas = FALSE)
bla <- lapply(
names(minmax.values),
function(i){
if(
minmax.values[[i]][1] < param_bound[[i]][1] ||
minmax.values[[i]][2] > param_bound[[i]][2]
) warning(
"extrema of parameter '", i, "' in 'values' outside of allowed range"
)
}
)
## change fit_param to FALSE for variables present in values
fit_param[fixed.param] <- FALSE
## set ncores = 1L in object$control$pcmp
object[["control"]][["pcmp"]][["ncores"]] <- 1L
## set hessian = FALSE in object$control
object[["control"]][["hessian"]] <- FALSE
## create environment for storing index of iteration, linear parameter
## and value of objective function
common.env <- new.env()
#### -- fit model for sets of parameter values
## loop over all elements of values
values <- as.matrix(values)
## set default value for control of forking if missing (required for
## backward compatibility)
if(
ncores > 1L &&
!object[["control"]][["pcmp"]][["fork"]]
){
## create a SNOW cluster on windows OS
options(error = stop_cluster)
junk <- sfInit( parallel = TRUE, cpus = ncores )
## export required items to workers
junk <- sfLibrary("soilhypfit", verbose = FALSE, character.only = TRUE)
## export required items to workers (if package is installed)
# junk <- sfExportAll()
# export.items <- c("values", "object", "data", "change_function_call_set_onexxx_to_value")
export.items <- c(
"values",
"soil_sample",
"input.data", "param", "fit_param", "e0", "ratio_lc_lt_bound",
"object", "common.env", "verbose"
)
junk <- sfExport(list = export.items)
result <- sfLapply(1L:NROW(values), f.aux)
junk <- stop_cluster()
} else {
## fork child processes on non-windows OS
result <- mclapply(
1L:NROW(values),
f.aux,
mc.cores = ncores,
mc.allow.recursive = FALSE
)
}
#### -- prepare output
## collect results
result <- t(simplify2array(result))
result <- result[, !colnames(result) %in% colnames(values), drop = FALSE]
as.data.frame(cbind(values, result))
}
## ======================================================================
### confint_prfloglik_sample
confint_prfloglik_sample <- function(
object, parm = names(default_fit_param()), soil_sample,
level = 0.95, test = c("F", "Chisq"),
denominator_df = c("nonlinear", "all"),
param_bound = NULL,
root_tol = .Machine$double.eps^0.25,
froot_tol = sqrt(root_tol),
verbose = 0
){
## function to compute lconfidence interval of a parameter for a single
## fit_wrc_hcc fit based on likelihood ratio test
## 2021-12-29 Andreas Papritz
## 2022-01-02 AP new version based on new prfloglik_sample calling
## directly fit_wrc_hcc_fit
## 2022-01-03 AP new criterion to decide whether root has been found
if(!is.finite(verbose)) browser()
#### -- auxiliary function with zero roots for confidence limits
f.aux <- function(
x, parm, object, qtest, maximized_loglik
){
## compute profile loglikelihood for parm = x
values <- data.frame(x = x)
colnames(values) <- parm
tmp <- prfloglik_sample(
object, values = values, ncores = 1L, verbose = verbose
)
## compute and function value
qtest - (maximized_loglik - tmp[1, "loglik"])
}
#### -- check arguments and contents of object
parm <- match.arg(parm)
test <- match.arg(test)
denominator_df <- match.arg(denominator_df)
if(missing(object)) stop(
"some mandatory arguments are missing"
)
stopifnot(identical(class(object)[1], "fit_wrc_hcc"))
stopifnot(identical(length(verbose), 1L) && is.numeric(verbose) && verbose >= 0)
stopifnot(identical(length(level), 1L) && is.numeric(level) && level >= 0 & level <= 1)
if(length(object[["fit"]]) > 1L){
if(missing(soil_sample)) stop(
"argument 'soil_sample' must be a character scalar because 'object' contains ",
" parameter estimates for several samples"
)
if(length(soil_sample) > 1L || !is.character(soil_sample)) stop(
"'soil_sample' must be a character scalar"
)
} else {
if(!missing(soil_sample)){
warning(
"argument 'soil_sample' will be ignored because 'object' contains ",
"parameter estimates for just one sample"
)
}
soil_sample <- ""
}
if(!identical(object[["control"]][["method"]], "ml")) stop(
"to compute profile loglikelihood, parameters must be estimated by ",
"maximum likelihood method using control argument 'method = ml'"
)
if(object[["control"]][["settings"]] %in% c("uglobal", "cglobal", "sce")) warning(
"parameters have been estimated by global algorithm"
)
if(!is.null(param_bound)){
stopifnot(is.numeric(param_bound) && identical(length(param_bound), 2L))
tmp <- list(param_bound)
names(tmp) <- parm
check_param_boundf(tmp)
}
#### -- prepare objects for computing confidence interval
## select component of object$fit for sample chosen by soil_sample
if(length(object[["fit"]]) > 1L){
if(soil_sample %in% names(object[["fit"]])){
object[["fit"]] <- object[["fit"]][soil_sample]
} else stop(
"parameter estimates missing in 'object' missing for sample ",
soil_sample
)
}
## check whether selected component of object$fit is valid
if(is.null(object[["fit"]][[1]])) stop(
"parameter estimation failed for sample ", soil_sample
)
## extract maximized loglik, parameter estimate and boundaries of parm
tmp <- coef(object, gof = TRUE)
param_estimate <- unname(tmp[, parm])
maximized_loglik <- -unname(tmp[, "obj"])
if(is.null(param_bound)){
param_bound <- unname(
object[["fit"]][[1]][["initial_objects"]][["param_bound"]][[parm]]
)
}
## determine number of measurements of wrc and/or hcc
if(!is.null(object[["fit"]][[1]][["model"]][["wrc"]])){
nobs_wrc <- NROW(object[["fit"]][[1]][["model"]][["wrc"]])
} else {
nobs_wrc <- 0L
}
if(!is.null(object[["fit"]][[1]][["model"]][["hcc"]])){
nobs_hcc <- NROW(object[["fit"]][[1]][["model"]][["hcc"]])
} else {
nobs_hcc <- 0L
}
nobs <- nobs_wrc + nobs_hcc
## check type of test given type of data
if(identical(test, "F") && nobs_wrc * nobs_hcc > 0){
test <- "Chisq"
warning(
"Chisq-Test will be used because both water retention and", "
hydraulic conductivity data were used to estimate the parameters"
)
}
## determine how many (nonlinear) parameters were fitted
nme_param <- switch(
denominator_df,
nonlinear = names(object[["fit"]][[1]][["nlp"]]),
all = c(
names(object[["fit"]][[1]][["nlp"]]),
names(object[["fit"]][[1]][["lp"]])
),
stop("unknown value for 'denominator_df'")
)
n_nlp <- sum(object[["fit"]][[1]][["initial_objects"]][["fit_param"]][nme_param])
## compute (transformed) quantile of chi2- or f-distribution
qtest <- switch(
test,
"Chisq" = 0.5 * qchisq(level, 1),
"F" = 0.5 * nobs * log(1 + 1/(nobs - n_nlp) * qf(level, 1, nobs - n_nlp)),
stop("unknown test")
)
### -- compute limits of confidence interval
## lower limit
flower <- f.aux(
param_bound[1], parm, object, qtest, maximized_loglik
)
fupper <- f.aux(
param_estimate, parm, object, qtest, maximized_loglik
)
if(identical(sign(flower * fupper), -1.)){
t.lower <- uniroot(
f.aux,
lower = param_bound[1], upper = param_estimate,
f.lower = flower, f.upper = fupper, tol = root_tol,
parm = parm, object = object, qtest = qtest,
maximized_loglik = maximized_loglik
)
if(abs(t.lower[["f.root"]]) < froot_tol ){
lower_limit <- t.lower[["root"]]
} else {
warning("failure to compute lower confidence limit")
lower_limit <- NA_real_
}
lower_froot <- t.lower[["f.root"]]
} else {
lower_limit <- lower_froot <- NA_real_
}
## upper limit
flower<- f.aux(
param_estimate, parm, object, qtest, maximized_loglik
)
fupper <- f.aux(
param_bound[2], parm, object, qtest, maximized_loglik
)
if(identical(sign(flower * fupper), -1.)){
t.upper <- uniroot(
f.aux,
upper = param_bound[2], lower = param_estimate,
f.lower = flower, f.upper = fupper, tol = root_tol,
parm = parm, object = object, qtest = qtest,
maximized_loglik = maximized_loglik
)
if(abs(t.upper[["f.root"]]) < froot_tol ){
upper_limit <- t.upper[["root"]]
} else {
warning("failure to compute upper confidence limit")
upper_limit <- NA_real_
}
upper_froot <- t.upper[["f.root"]]
} else {
upper_limit <- upper_froot <- NA_real_
}
### -- prepare and return output
result <- c(lower_limit, upper_limit)
names(result) <- paste(parm, c("lower", "upper"), sep = ".")
attr(result, "prfloglik") <- list(
estimate = param_estimate,
loglik = maximized_loglik,
qtest = qtest,
test = test, level = level, nobs_wrc = nobs_wrc, nobs_hcc = nobs_hcc,
lower_froot = lower_froot, upper_froot = upper_froot
)
result
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.