diff --git a/NAMESPACE b/NAMESPACE index 3b18f56dc..12fe2e300 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,6 +25,7 @@ S3method(handle_spec_block,specCMTN) S3method(handle_spec_block,specFIXED) S3method(handle_spec_block,specINCLUDE) S3method(handle_spec_block,specINIT) +S3method(handle_spec_block,specINPUT) S3method(handle_spec_block,specNAMESPACE) S3method(handle_spec_block,specNMEXT) S3method(handle_spec_block,specNMXML) @@ -85,6 +86,7 @@ export(bmat) export(cama) export(carry.out) export(carry_out) +export(check_data_names) export(cmat) export(cmtn) export(collapse_matrix) @@ -152,6 +154,7 @@ export(obsaug) export(obsonly) export(omat) export(outvars) +export(param_tags) export(parse_rx) export(plot_sims) export(qsim) @@ -259,6 +262,7 @@ importFrom(rlang,as_label) importFrom(rlang,enquo) importFrom(rlang,enquos) importFrom(rlang,eval_tidy) +importFrom(rlang,inform) importFrom(rlang,is_named) importFrom(rlang,quo_name) importFrom(rlang,quos) diff --git a/R/Aaaa.R b/R/Aaaa.R index e0c16a8c1..125c4bd83 100644 --- a/R/Aaaa.R +++ b/R/Aaaa.R @@ -29,7 +29,7 @@ #' @importFrom magrittr %>% #' @importFrom tibble tibble as_tibble #' @importFrom rlang quos enquo enquos quo_name syms !!! !! eval_tidy as_label -#' @importFrom rlang is_named .data abort warn +#' @importFrom rlang is_named .data abort warn inform #' @importFrom lifecycle deprecate_soft deprecate_warn #' @importFrom glue glue #' @importFrom Rcpp evalCpp @@ -79,7 +79,8 @@ block_list <- c("ENV", "PROB", "PARAM", "INIT", "FIXED", "CMTN", "THETA", "NMXML", "VCMT", "PKMODEL", "PLUGIN", "INCLUDE", "NAMESPACE", "OMEGA", "SIGMA", "SET","GLOBAL", "CAPTURE", - "PREAMBLE", "PRED", "BLOCK", "TRANSIT", "YAML", "NMEXT") + "PREAMBLE", "PRED", "BLOCK", "TRANSIT", "YAML", "NMEXT", + "INPUT") Reserved_cvar <- c("SOLVERTIME","table","ETA","EPS", "AMT", "CMT", "ID", "TIME", "EVID","simeps", "self", "simeta", diff --git a/R/class_mrgmod.R b/R/class_mrgmod.R index 7c726d1db..a856a2f94 100644 --- a/R/class_mrgmod.R +++ b/R/class_mrgmod.R @@ -559,6 +559,7 @@ setMethod("as.list", "mrgmod", function(x, deep = FALSE, ...) { init <- as.list(init(x)) param <- as.list(param(x)) cmt <- cmt(x) + param_tag <- shlib(x)[["param_tag"]] covariates <- as.character(shlib(x)[["covariates"]]) pars <- pars(x) neq <- neq(x) diff --git a/R/handle_spec_block.R b/R/handle_spec_block.R index 49afb91af..e8ef39ab3 100644 --- a/R/handle_spec_block.R +++ b/R/handle_spec_block.R @@ -43,7 +43,10 @@ handle_spec_block.default <- function(x, ...) { #' @param annotated logical #' @param object the name of an object in `ENV` #' @param as_object indicates that object code is being provided -#' @param covariates logical +#' @param covariates logical; mark as covariates and potentially required +#' data input +#' @param input logical; mark as potentially required data input +#' @param tag space or comma-separated user-defined tags for the parameter block #' @param name block name #' @param labels aliases to use for simulated ETA values #' @param prefix a prefix to add to the label @@ -77,7 +80,7 @@ handle_spec_block.default <- function(x, ...) { #' @seealso [PKMODEL()] NULL -# PARAM / FIXED ---------------------------------------------------------------- +# PARAM ---------------------------------------------------------------- #' @export handle_spec_block.specPARAM <- function(x, ...) { @@ -91,7 +94,9 @@ PARAM <- function(x, annotated = FALSE, object = NULL, as_object = FALSE, - covariates = FALSE, ...) { + covariates = FALSE, + input = FALSE, + tag = NULL, ...) { if(is.character(object)) { if(isTRUE(as_object)) { @@ -114,6 +119,7 @@ PARAM <- function(x, named = TRUE ) env[["param"]][[pos]] <- x + save_param_tag(env, names(x), covariates, input, tag) return(NULL) } @@ -121,7 +127,7 @@ PARAM <- function(x, context <- env[["incoming_names"]][pos] context <- as.character(glue("parse annotated parameter block ({context})")) l <- parse_annot(x, block = "PARAM", envir = env$ENV, context = context) - env[["param"]][[pos]] <- l[["v"]] + env[["param"]][[pos]] <- x <- l[["v"]] env[["annot"]][[pos]] <- l[["an"]] } else { x <- tolist(x,envir=env$ENV) @@ -129,22 +135,52 @@ PARAM <- function(x, msg <- " invalid model specification Block no: {pos} Block type: {env$incoming_names[pos]} - Some parameters are missing names + Some parameters are missing names. " stop(glue(msg), call. = FALSE) } env[["param"]][[pos]] <- x } - if(covariates) { - env[["covariates"]] <- c( - env[["covariates"]], names(env[["param"]][[pos]]) - ) - } + save_param_tag(env, names(x), covariates, input, tag) return(NULL) } +save_param_tag <- function(env, pars, covariates, input, tag) { + if(isTRUE(covariates)) { + env[["covariates"]] <- c(env[["covariates"]], pars) + tagdf <- data.frame( + name = pars, + tag = "covariates", + stringsAsFactors = FALSE + ) + tagdf <- rbind(env[["param_tag"]], tagdf) + env[["param_tag"]] <- unique(tagdf) + } + if(isTRUE(input)) { + tag <- c("input", tag) + } + if(is.character(tag) && length(tag) > 0) { + tag <- cvec_cs(tag) + tagdf <- expand.grid(name = pars, tag = tag, stringsAsFactors = FALSE) + tagdf <- rbind(env[["param_tag"]], tagdf) + env[["param_tag"]] <- unique(tagdf) + } +} + +# INPUT ---------------------------------------------------------------- + +#' @export +handle_spec_block.specINPUT <- function(x, env, ...) { + o <- scrape_opts(x, envir = env$ENV, ...) + o$pos <- o$env <- o$class <- o$input <- NULL + o <- c(o, attributes(x), list(env = env, input = TRUE)) + do.call(PARAM, o) +} + +# FIXED ---------------------------------------------------------------- + #' @export handle_spec_block.specFIXED <- function(x, ...) { scrape_and_call(x, pass = "FIXED", ...) diff --git a/R/inven.R b/R/inven.R index 31f5ed78f..aa9fc59a9 100644 --- a/R/inven.R +++ b/R/inven.R @@ -37,7 +37,7 @@ #' stated, \code{.strict} will be set to \code{TRUE} if a value \code{.strict} was not #' passed in the call. #' @export -inventory <- function(x,obj,..., .strict = FALSE) { +inventory <- function(x, obj, ..., .strict = FALSE) { oname <- as.character(as.list(match.call())$obj) @@ -51,7 +51,7 @@ inventory <- function(x,obj,..., .strict = FALSE) { missing <- setdiff(need,names(obj)) miss <- length(missing) - + if(!miss) { message("Found all required parameters in ", sQuote(oname),".") return(invisible(x)) @@ -67,3 +67,224 @@ inventory <- function(x,obj,..., .strict = FALSE) { return(invisible(x)) } + +#' Check input data set names against model parameters +#' +#' Use this function to check names of input data sets against parameters that +#' have been assigned different tags. Assignment is made in the model +#' specification file. This is useful to alert the user to misspelled or +#' otherwise misspecified parameter names in input data sets. See [param_tags()] +#' for information on associating tags with parameters. +#' +#' @param data a data frame or other object with names to check. +#' @param x a model object. +#' @param check_covariates logical; if `TRUE`, check `data` for parameter names +#' carrying the `covariates` tag. +#' @param check_inputs logical; if `TRUE`, check `data` for parameter names +#' carrying the `input` tag. +#' @param tags a character vector of user-defined parameter tags to require +#' in `data`; this may be a comma- or space-separated string (e.g. +#' `"tag1,tag2"`). +#' @param mode the default is to `"warn"` the user when `data` is missing +#' some expected column names; alternatively, use `"error"` to issue an +#' error or `"inform"` to generate a message when `data` is missing some +#' expected column names. +#' @param silent silences message on successful check. +#' +#' @details +#' By default, `data` will be checked for parameters with the `covariates` or +#' `input` tags; these checks can be bypassed with the `check_covariates` +#' and `check_inputs` arguments. When a parameter name is missing from `data` +#' the user will be warned by default. Use `mode = "error"` to generate an +#' error instead of a warning and use `mode = "inform"` to simply be informed. +#' When the user has not tagged any parameters for checking, there will +#' either be a warning (default) or an error (when `mode = "error"`). +#' +#' It is an error to request a parameter tag via the `tags` argument when that +#' tag is not found in the model. +#' +#' It is an error to call `check_data_names` when no parameters have been tagged +#' in the model specification file (see [param_tags()]). +#' +#' +#' @examples +#' +#' mod <- mcode("ex-cdn", "$PARAM @input \n CL = 1, KA = 2", compile = FALSE) +#' +#' param(mod) +#' +#' # Coding mistake! +#' data <- expand.evd(amt = 100, cl = 2, KA = 5) +#' +#' check_data_names(data, mod) +#' +#' try(check_data_names(data, mod, mode = "error")) +#' +#' check_data_names(data, mod, mode = "inform") +#' +#' @return +#' A logical value is returned; `TRUE` if all expected parameters were found +#' and `FALSE` otherwise. +#' +#' @seealso [param_tags()] +#' +#' @md +#' @export +check_data_names <- function(data, x, check_covariates = TRUE, + check_inputs = TRUE, tags = NULL, + mode = c("warn", "error", "inform"), + silent = FALSE) { + if(!is_named(data)) { + abort("`data` must be a named object.") + } + + if(!is.mrgmod(x)) { + abort("`x` must be a model object.") + } + + if(!is.null(tags)) { + if(!is.character(tags)) { + abort("`tags` must have type 'character'.") + } + } + + mode <- match.arg(mode) + check_covariates <- isTRUE(check_covariates) + check_inputs <- isTRUE(check_inputs) + silent <- isTRUE(silent) + err <- mode=="error" + inform <- mode=="inform" + + tg <- param_tags(x) + + need_name <- character(0) + need_type <- character(0) + + # Check that user-defined tags exist + if(length(tags) > 0) { + tags <- cvec_cs(tags) + if(!all(tags %in% tg$tag)) { + bad_tag <- setdiff(tags, tg$tag) + names(bad_tag) <- rep("x", length(bad_tag)) + msg <- c("Unrecognized user tag(s):", bad_tag) + abort(msg, use_cli_format = TRUE) + } + } + + if(check_covariates) { + tags <- c("covariates", tags) + } + + if(check_inputs) { + tags <- c("input", tags) + } + + # Check the list of what we're looking for against the list of tagged + # parameters _in the model_ + tg <- tg[tg$tag %in% tags,] + + if(nrow(tg)==0) { + msg <- "Did not find any inputs, covariates, or user tags to check." + if(err) { + abort(msg, use_cli_format = TRUE) + } else { + warn(msg, use_cli_format = TRUE) + } + return(invisible(FALSE)) + } + + # Now, start checking against what is in the data + need_name <- tg$name + need_type <- tg$tag + + # This associates parameter with all of their tags + need_type <- tapply( + X = need_type, + INDEX = need_name, + FUN = paste0, collapse = ", ", + simplify = TRUE + ) + + # tapply will reorder things; restore order with this: + need_name <- unique(need_name) + need_type <- need_type[need_name] + + found <- need_name %in% names(data) + + if(!(status <- all(found))) { + miss <- need_name[!found] + miss <- paste0(miss, " (", need_type[!found], ")") + names(miss) <- rep("*", length(miss)) + msg <- c("Could not find the following parameter names in `data`:", miss) + foot <- "Please check names in `data` against names in the parameter list." + if(err) { + abort(msg, footer = c(x = foot), use_cli_format = TRUE) + } else if(!inform) { + warn(msg, footer = c(i = foot), use_cli_format = TRUE) + } else { + inform(msg, use_cli_format = TRUE) + } + } else { + if(!silent) { + msg <- "Found all expected parameter names in `data`." + message(msg) + } + } + return(invisible(status)) +} + +#' Return parameter tags +#' +#' Use this function if you added the `@covariates` or `@input` attributes or +#' specified a user-defined tag (via `@tag`) in one or more parameter blocks +#' and need to extract that information. Also, using the `$INPUT` block to +#' declare parameters will automatically add the `input` tag (via `@input`). +#' Once these attributes / tags are added, you can use [check_data_names()] to +#' reconcile names of input data sets against tagged model parameters. +#' +#' @param x mrgsolve model object. +#' +#' @return +#' A data frame listing parameter names and their tags. +#' +#' @section Model specification: +#' +#' Note: it is good practice to tag parameters where appropriate with `input` +#' or `covariates` as these will automatically be expected on input data when +#' you call [check_data_names()]. User-defined tags are also possible, but you +#' will need to alert [check_data_names()] to look for them. +#' +#' **Examples** +#' +#' You can use the `$INPUT` block to add the `input` tag on these parameters +#' +#' ``` +#' $INPUT +#' STUDY = 101, WT = 70, DVID = 1 +#' ``` +#' Tag some covariates in the model +#' +#' ``` +#' $PARAM @covariates +#' WT = 70, SEX = 1, EGFR = 110 +#' ``` +#' +#' A user-defined tag +#' +#' ``` +#' $PARAM @tag flags +#' FFLAG = 1, DFLAG = 0 +#' ``` +#' +#' @examples +#' mod <- house() +#' +#' param_tags(mod) +#' +#' @seealso [check_data_names()] +#' +#' @md +#' @export +param_tags <- function(x) { + return(x@shlib$param_tag) +} diff --git a/R/modspec.R b/R/modspec.R index 710fd78d0..8a19227f4 100644 --- a/R/modspec.R +++ b/R/modspec.R @@ -730,6 +730,7 @@ parse_env <- function(spec, incoming_names = names(spec),build,ENV=new.env()) { mread.env$capture <- vector("list", n) mread.env$error <- character(0) mread.env$covariates <- character(0) + mread.env$param_tag <- data.frame(name=0, tag=0)[0,] mread.env$nm_import <- character(0) mread.env$ENV <- ENV mread.env$blocks <- names(spec) diff --git a/R/mread.R b/R/mread.R index 9d1994c6e..ac131e68e 100644 --- a/R/mread.R +++ b/R/mread.R @@ -449,6 +449,7 @@ mread <- function(model, project = getOption("mrgsolve.project", getwd()), # lock some of this down so we can check order later x@code <- readLines(build[["modfile"]], warn=FALSE) x@shlib[["covariates"]] <- mread.env[["covariates"]] + x@shlib[["param_tag"]] <- mread.env[["param_tag"]] x@shlib[["cpp_variables"]] <- build$cpp_variables inc <- spec[["INCLUDE"]] if(is.null(inc)) inc <- character(0) diff --git a/inst/WORDLIST b/inst/WORDLIST index 2a81d743b..c797d7c68 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -148,3 +148,5 @@ Rcpp df MPN Makevars +misspecified + diff --git a/inst/models/1005.cpp b/inst/models/1005.cpp index 1b1deef0c..cf9c56450 100644 --- a/inst/models/1005.cpp +++ b/inst/models/1005.cpp @@ -7,7 +7,7 @@ for equivalent NONMEM control stream. [ PKMODEL ] cmt = "GUT CENT PERIPH", depot = TRUE -[ PARAM ] SEX = 0, WT = 70 +[ INPUT ] SEX = 0, WT = 70 [ NMXML ] project = system.file("nonmem", package = "mrgsolve") diff --git a/inst/project/housemodel.cpp b/inst/project/housemodel.cpp index a8a96bfcb..725e3b76e 100644 --- a/inst/project/housemodel.cpp +++ b/inst/project/housemodel.cpp @@ -20,8 +20,6 @@ VC : 20 : Volume of distribution (L) KA : 1.2 : Absorption rate constant (1/hr) F1 : 1.0 : Bioavailability fraction (.) D1 : 2.0 : Infusion duration (hr) -WT : 70 : Weight (kg) -SEX : 0 : Covariate female sex WTCL : 0.75 : Exponent WT on CL WTVC : 1.00 : Exponent WT on VC SEXCL: 0.7 : Prop cov effect on CL @@ -30,6 +28,10 @@ KIN : 100 : Resp prod rate constant (1/hr) KOUT : 2 : Resp elim rate constant (1/hr) IC50 : 10 : Conc giving 50% max resp (ng/ml) +$PARAM @annotated @covariates +WT : 70 : Weight (kg) +SEX : 0 : Covariate female sex + $CMT @annotated GUT : Dosing compartment (mg) CENT : Central compartment (mg) @@ -43,7 +45,6 @@ RESP : Response (unitless) $SET end=120, delta=0.25 - $GLOBAL #define CP (CENT/VCi) #define INH (CP/(IC50+CP)) @@ -72,4 +73,3 @@ double DV = CP*exp(EXPO); $CAPTURE @annotated DV: Dependent variable (ng/ml) CP: Plasma concentration (ng/ml) - diff --git a/man/BLOCK_PARSE.Rd b/man/BLOCK_PARSE.Rd index 01b930af2..0c6746e62 100644 --- a/man/BLOCK_PARSE.Rd +++ b/man/BLOCK_PARSE.Rd @@ -19,6 +19,8 @@ PARAM( object = NULL, as_object = FALSE, covariates = FALSE, + input = FALSE, + tag = NULL, ... ) @@ -81,7 +83,12 @@ HANDLEMATRIX( \item{as_object}{indicates that object code is being provided} -\item{covariates}{logical} +\item{covariates}{logical; mark as covariates and potentially required +data input} + +\item{input}{logical; mark as potentially required data input} + +\item{tag}{space or comma-separated user-defined tags for the parameter block} \item{...}{passed} diff --git a/man/check_data_names.Rd b/man/check_data_names.Rd new file mode 100644 index 000000000..eae3e283a --- /dev/null +++ b/man/check_data_names.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/inven.R +\name{check_data_names} +\alias{check_data_names} +\title{Check input data set names against model parameters} +\usage{ +check_data_names( + data, + x, + check_covariates = TRUE, + check_inputs = TRUE, + tags = NULL, + mode = c("warn", "error", "inform"), + silent = FALSE +) +} +\arguments{ +\item{data}{a data frame or other object with names to check.} + +\item{x}{a model object.} + +\item{check_covariates}{logical; if \code{TRUE}, check \code{data} for parameter names +carrying the \code{covariates} tag.} + +\item{check_inputs}{logical; if \code{TRUE}, check \code{data} for parameter names +carrying the \code{input} tag.} + +\item{tags}{a character vector of user-defined parameter tags to require +in \code{data}; this may be a comma- or space-separated string (e.g. +\code{"tag1,tag2"}).} + +\item{mode}{the default is to \code{"warn"} the user when \code{data} is missing +some expected column names; alternatively, use \code{"error"} to issue an +error or \code{"inform"} to generate a message when \code{data} is missing some +expected column names.} + +\item{silent}{silences message on successful check.} +} +\value{ +A logical value is returned; \code{TRUE} if all expected parameters were found +and \code{FALSE} otherwise. +} +\description{ +Use this function to check names of input data sets against parameters that +have been assigned different tags. Assignment is made in the model +specification file. This is useful to alert the user to misspelled or +otherwise misspecified parameter names in input data sets. See \code{\link[=param_tags]{param_tags()}} +for information on associating tags with parameters. +} +\details{ +By default, \code{data} will be checked for parameters with the \code{covariates} or +\code{input} tags; these checks can be bypassed with the \code{check_covariates} +and \code{check_inputs} arguments. When a parameter name is missing from \code{data} +the user will be warned by default. Use \code{mode = "error"} to generate an +error instead of a warning and use \code{mode = "inform"} to simply be informed. +When the user has not tagged any parameters for checking, there will +either be a warning (default) or an error (when \code{mode = "error"}). + +It is an error to request a parameter tag via the \code{tags} argument when that +tag is not found in the model. + +It is an error to call \code{check_data_names} when no parameters have been tagged +in the model specification file (see \code{\link[=param_tags]{param_tags()}}). +} +\examples{ + +mod <- mcode("ex-cdn", "$PARAM @input \n CL = 1, KA = 2", compile = FALSE) + +param(mod) + +# Coding mistake! +data <- expand.evd(amt = 100, cl = 2, KA = 5) + +check_data_names(data, mod) + +try(check_data_names(data, mod, mode = "error")) + +check_data_names(data, mod, mode = "inform") + +} +\seealso{ +\code{\link[=param_tags]{param_tags()}} +} diff --git a/man/param_tags.Rd b/man/param_tags.Rd new file mode 100644 index 000000000..bb359f68d --- /dev/null +++ b/man/param_tags.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/inven.R +\name{param_tags} +\alias{param_tags} +\title{Return parameter tags} +\usage{ +param_tags(x) +} +\arguments{ +\item{x}{mrgsolve model object.} +} +\value{ +A data frame listing parameter names and their tags. +} +\description{ +Use this function if you added the \verb{@covariates} or \verb{@input} attributes or +specified a user-defined tag (via \verb{@tag}) in one or more parameter blocks +and need to extract that information. Also, using the \verb{$INPUT} block to +declare parameters will automatically add the \code{input} tag (via \verb{@input}). +Once these attributes / tags are added, you can use \code{\link[=check_data_names]{check_data_names()}} to +reconcile names of input data sets against tagged model parameters. +} +\section{Model specification}{ + + +Note: it is good practice to tag parameters where appropriate with \code{input} +or \code{covariates} as these will automatically be expected on input data when +you call \code{\link[=check_data_names]{check_data_names()}}. User-defined tags are also possible, but you +will need to alert \code{\link[=check_data_names]{check_data_names()}} to look for them. + +\strong{Examples} + +You can use the \verb{$INPUT} block to add the \code{input} tag on these parameters + +\if{html}{\out{
}}\preformatted{$INPUT +STUDY = 101, WT = 70, DVID = 1 +}\if{html}{\out{
}} + +Tag some covariates in the model + +\if{html}{\out{
}}\preformatted{$PARAM @covariates +WT = 70, SEX = 1, EGFR = 110 +}\if{html}{\out{
}} + +A user-defined tag + +\if{html}{\out{
}}\preformatted{$PARAM @tag flags +FFLAG = 1, DFLAG = 0 +}\if{html}{\out{
}} +} + +\examples{ +mod <- house() + +param_tags(mod) + +} +\seealso{ +\code{\link[=check_data_names]{check_data_names()}} +} diff --git a/src/housemodel-mread-header.h b/src/housemodel-mread-header.h index 43d445965..768c3d39c 100644 --- a/src/housemodel-mread-header.h +++ b/src/housemodel-mread-header.h @@ -1,4 +1,4 @@ -// Source MD5: 509e24de6401c4c7d8c72c3487b55e52 +// Source MD5: a7088afe8d21493d4c368f3b74d3e6a8 // PLUGINS: @@ -69,15 +69,15 @@ typedef double localdouble; #define KA _THETA_[2] #define F1 _THETA_[3] #define D1 _THETA_[4] -#define WT _THETA_[5] -#define SEX _THETA_[6] -#define WTCL _THETA_[7] -#define WTVC _THETA_[8] -#define SEXCL _THETA_[9] -#define SEXVC _THETA_[10] -#define KIN _THETA_[11] -#define KOUT _THETA_[12] -#define IC50 _THETA_[13] +#define WTCL _THETA_[5] +#define WTVC _THETA_[6] +#define SEXCL _THETA_[7] +#define SEXVC _THETA_[8] +#define KIN _THETA_[9] +#define KOUT _THETA_[10] +#define IC50 _THETA_[11] +#define WT _THETA_[12] +#define SEX _THETA_[13] #define ECL _xETA(1) #define EVC _xETA(2) #define EKA _xETA(3) diff --git a/src/housemodel-mread-source.cpp b/src/housemodel-mread-source.cpp index 7525d6c16..f7e5a130a 100644 --- a/src/housemodel-mread-source.cpp +++ b/src/housemodel-mread-source.cpp @@ -1,4 +1,4 @@ -// Source MD5: 509e24de6401c4c7d8c72c3487b55e52 +// Source MD5: a7088afe8d21493d4c368f3b74d3e6a8 #include "housemodel-mread-header.h" diff --git a/tests/testthat/test-as_list_mrgmod.R b/tests/testthat/test-as_list_mrgmod.R index 4f22bbec6..9c0551169 100644 --- a/tests/testthat/test-as_list_mrgmod.R +++ b/tests/testthat/test-as_list_mrgmod.R @@ -61,6 +61,7 @@ test_that("check items in as.list output", { expect_true("sodll" %in% x_names) expect_true("soloc" %in% x_names) expect_true("covariates" %in% x_names) + expect_true("param_tag" %in% x_names) expect_true("fixed" %in% x_names) expect_true("plugins" %in% x_names) expect_true("random" %in% x_names) diff --git a/tests/testthat/test-inventory-too.R b/tests/testthat/test-inventory-too.R index 559f58bf2..7dfa16fe5 100644 --- a/tests/testthat/test-inventory-too.R +++ b/tests/testthat/test-inventory-too.R @@ -25,7 +25,7 @@ options("mrgsolve_mread_quiet"=TRUE) context("test-inventory-too") test_that("inventory conditions", { - + mod <- mrgsolve::house() data <- expand.ev(amt=100,CL=1, SEX=0) @@ -40,3 +40,89 @@ test_that("inventory conditions", { expect_error(mod %>% idata_set(data,need=c("CL", "WTCL"))) expect_is(mod %>% data_set(data,need="CL") %>% mrgsim(end=1),"mrgsims") }) + +test_that("check_data_names", { + code <- c("$PARAM A=1", + "$PARAM @input \n B=2", + "$PARAM @covariates \n C=3", + "$PARAM @tag foo \n D = 4") + + # There must be something to check against + mod <- mcode("cdn-1", code[1], compile = FALSE) + data <- data.frame(A = 1) + expect_warning(check_data_names(data, mod), + "Did not find any inputs, covariates, or user") + expect_error(check_data_names(data, mod, mode = "error"), + "Did not find any inputs, covariates, or user") + + # missing parameters in data + mod <- mcode("cdn-2", paste0(code[1:2], collapse = "\n"), compile = FALSE) + data <- data.frame(A = 1) + expect_warning(check_data_names(data, mod), "Could not find the following") + expect_warning(check_data_names(data, mod, silent = TRUE), + "Could not find the following") + expect_warning(check_data_names(data, mod, check_input = FALSE), + "Did not find any inputs, covariates, or user") + expect_error(check_data_names(data, mod, mode = "error"), + "Could not find the following") + expect_message(check_data_names(data, mod, mode = "inform"), + "Could not find the following") + + mod <- mcode("cdn-3", paste0(code[1:3], collapse = "\n"), compile = FALSE) + data <- data.frame(A = 1, B = 2, C = 3) + expect_message(check_data_names(data, mod), "Found all") + expect_silent(check_data_names(data, mod, silent = TRUE)) + + data$C <- NULL + expect_warning(check_data_names(data, mod), "C (covariates)", fixed = TRUE) + expect_message( + check_data_names(data, mod, check_covariates = FALSE), + "Found all expected" + ) + + mod <- mcode("cdn-4", paste0(code[4], collapse = "\n"), compile = FALSE) + data <- data.frame(D = 4) + expect_warning(check_data_names(data, mod), + "Did not find any inputs, covariates, or user") + expect_message(check_data_names(data, mod, tags = "foo"), "Found all") + + mod <- mcode("cdn-5", paste0(code[c(3,4)], collapse = "\n"), compile = FALSE) + data <- data.frame(C=3) + expect_message(check_data_names(data, mod), "Found all expected") + expect_warning(check_data_names(data, mod, tags = "foo"), + "D (foo)", fixed = TRUE) + expect_error(check_data_names(data, mod, tags = "kyle"), + "Unrecognized user tag") + + mod <- house() + data <- data.frame(WGT = 70, SEX = 10) + expect_warning(check_data_names(data, mod), "WT (covariates)", fixed = TRUE) + expect_warning( + check_data_names(data, mod, check_covariates = FALSE), + "Did not find any inputs, covariates, or user" + ) + + mod <- modlib("1005", compile = FALSE) + tags <- param_tags(mod) + expect_true(all(tags$tag=="input")) +}) + +test_that("param_tags returns tags", { + ans <- param_tags(house()) + expect_is(ans, "data.frame") + expect_equal(nrow(ans), 2) + expect_identical(names(ans), c("name", "tag")) + expect_identical(ans$tag[1], "covariates") + + code <- "$PARAM @tag foo\n A = 1\n$PARAM @input\nB = 2" + mod <- mcode("cdn-6", code, compile = FALSE) + ans <- param_tags(mod) + expect_equal(ans$name, c("A", "B")) + expect_equal(ans$tag, c("foo", "input")) + + mod <- mread("pk1", project = modlib(), compile = FALSE) + ans <- param_tags(mod) + expect_is(ans, "data.frame") + expect_equal(nrow(ans), 0) + expect_identical(names(ans), c("name", "tag")) +}) diff --git a/tests/testthat/test-modspec.R b/tests/testthat/test-modspec.R index c1414034d..4ed6e6050 100644 --- a/tests/testthat/test-modspec.R +++ b/tests/testthat/test-modspec.R @@ -547,3 +547,43 @@ test_that("autodec variables can be skipped", { cpp <- as.list(mod)$cpp_variables expect_equal(cpp$var, c("e", "b", "d")) }) + +test_that("tagged parameter blocks", { + code <- "$PARAM @input \n CL = 5" + x <- mcode("tag-1", code, compile = FALSE) + expect_equal(names(param(x)), "CL") + tagdf <- x@shlib$param_tag + expect_is(tagdf, "data.frame") + expect_equal(names(tagdf), c("name", "tag")) + expect_equal(tagdf$name, "CL") + expect_equal(tagdf$tag, "input") + + code <- "$PARAM @tag foo, bar par @input \n V2 = 5" + x <- mcode("tag-2", code, compile = FALSE) + expect_equal(names(param(x)), "V2") + tagdf <- x@shlib$param_tag + expect_equal(nrow(tagdf), 4) + expect_equal(tagdf$name, rep("V2", 4)) + expect_equal(tagdf$tag, c("input", "foo", "bar", "par")) + + code <- "$PARAM @tag foo, bar \n V2 = 5, CL = 3" + x <- mcode("tag-3", code, compile = FALSE) + tagdf <- x@shlib$param_tag + check <- expand.grid( + name = c("V2", "CL"), + tag = c("foo", "bar"), + stringsAsFactors = FALSE + ) + expect_equal(tagdf, check) +}) + +test_that("INPUT block", { + code <- "$INPUT CL = 1, V2 = 2" + x <- mcode("input-1", code, compile = FALSE) + expect_equal(names(param(x)), c("CL", "V2")) + tagdf <- x@shlib$param_tag + expect_is(tagdf, "data.frame") + expect_equal(names(tagdf), c("name", "tag")) + expect_equal(tagdf$name, c("CL", "V2")) + expect_equal(tagdf$tag, rep("input", 2)) +})