From 304b9b147a43313383d8b8374b60b90075553dc8 Mon Sep 17 00:00:00 2001 From: Ron Keizer Date: Thu, 2 Apr 2026 20:56:13 -0700 Subject: [PATCH 1/3] Use pharmpy create_basic_pk_model() as default instead of templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch create_model() to use pharmr::create_basic_pk_model() by default (use_template=FALSE) instead of reading from built-in NONMEM template files. This fixes the issue where create_basic_pk_model() returned JSON instead of NONMEM code by converting to NONMEM format early in the pipeline. Key changes: - Add use_template parameter (default FALSE) to create_model() - Convert generic pharmpy model to NONMEM format immediately after creation - Add parameter name mapping (V→VC, V1→VC, V2→VP1, Q→QP1, CL→CLMM) in set_iiv(), set_initial_estimates(), and find_pk_parameter() - Fix blank line handling in create_pharmpy_model_from_list() - Update all affected tests to use new parameter naming conventions - Tests requiring ADVAN-specific features use use_template=TRUE explicitly Co-Authored-By: Claude Opus 4.6 (1M context) --- DESCRIPTION | 2 +- R/create_model.R | 50 ++++-- R/create_pharmpy_model_from_list.R | 18 ++- R/find_pk_parameter.R | 17 +- R/set_iiv.R | 101 +++++++++--- man/create_model.Rd | 1 + .../testthat/test-add_default_output_tables.R | 6 +- tests/testthat/test-create_model.R | 146 +++++++++--------- .../test-create_pharmpy_model_from_list.R | 7 +- tests/testthat/test-find_pk_parameter.R | 4 +- tests/testthat/test-remove_table_from_model.R | 8 +- .../testthat/test-remove_tables_from_model.R | 6 +- tests/testthat/test-set_compartment_scale.R | 11 +- tests/testthat/test-set_iiv.R | 44 +++--- tests/testthat/test-update_pk_tables.R | 4 +- 15 files changed, 272 insertions(+), 153 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0b4d01d..f5181af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: pharmr.extra Title: Extension of pharmr (Pharmpy) functionality -Version: 0.0.0.9034 +Version: 0.0.0.9035 Authors@R: c( person("Ron", "Keizer", email = "ron@insight-rx.com", role = c("cre", "aut")), person("Michael", "McCarthy", email = "michael.mccarthy@insight-rx.com", role = "ctb"), diff --git a/R/create_model.R b/R/create_model.R index e7821ea..6ec83a1 100644 --- a/R/create_model.R +++ b/R/create_model.R @@ -127,6 +127,7 @@ create_model <- function( auto_stack_encounters = TRUE, drop_input = NULL, mu_reference = "auto", + use_template = FALSE, settings = list(), # TBD verbose = FALSE ) { @@ -158,17 +159,28 @@ create_model <- function( } ## Read base model - if(verbose) cli::cli_alert_info("Reading base model") - mod <- pharmr::read_model( - path = get_template_modelfile(route, n_cmt, force_ode) - ) - if(!is.logical(force_ode)) { - if(is.numeric(force_ode) || is.integer(force_ode) || is.character(force_ode)) { - advan <- as.integer(force_ode) - if(advan == 9L || advan == 13L) { # default ADVAN for templates is 6, update if needed: - mod <- update_advan(mod, advan) + if(use_template) { ## Use built-in templates + if(verbose) cli::cli_alert_info("Reading base model") + mod <- pharmr::read_model( + path = get_template_modelfile(route, n_cmt, force_ode) + ) + if(!is.logical(force_ode)) { + if(is.numeric(force_ode) || is.integer(force_ode) || is.character(force_ode)) { + advan <- as.integer(force_ode) + if(advan == 9L || advan == 13L) { # default ADVAN for templates is 6, update if needed: + mod <- update_advan(mod, advan) + } } } + } else { ## Use pharmr to create the base model (default) + mod <- pharmr::create_basic_pk_model( + administration = route + ) + ## Convert to NONMEM format early so downstream functions (e.g. set_iiv_block) + ## can work with NONMEM control stream syntax + if(tool != "nlmixr") { + mod <- pharmr::convert_model(mod, "nonmem") + } } ## Absorption @@ -257,6 +269,23 @@ create_model <- function( cli::cli_alert_warning("Could not compute initial estimates automatically, please check manually.") } else { inits <- stats::setNames(inits, paste0("POP_", names(inits))) + ## Map init names to actual model parameter names (e.g. POP_V -> POP_VC) + model_params <- mod$parameters$names + init_aliases <- list( + POP_V = c("POP_VC"), + POP_CL = c("POP_CLMM") + ) + for(i in seq_along(inits)) { + nm <- names(inits)[i] + if(!nm %in% model_params && nm %in% names(init_aliases)) { + for(alias in init_aliases[[nm]]) { + if(alias %in% model_params) { + names(inits)[i] <- alias + break + } + } + } + } mod <- pharmr::set_initial_estimates( model = mod, inits = inits @@ -271,6 +300,8 @@ create_model <- function( model = mod, to_format = "nlmixr" ) + } else { + mod <- pharmr::convert_model(mod, "nonmem") } ## Estimation method @@ -288,6 +319,7 @@ create_model <- function( estimation_method = first_method, estimation_options = per_step_options[[1]] ) + if(!is.null(tool_options$MAXEVAL)) tool_options$MAXEVAL <- NULL } else { tool_options <- list() cli::cli_alert_warning(paste0("Skipping estimation options for ", tool, ", since not supported by Pharmpy. Please set manually")) diff --git a/R/create_pharmpy_model_from_list.R b/R/create_pharmpy_model_from_list.R index 332d274..82d8d72 100644 --- a/R/create_pharmpy_model_from_list.R +++ b/R/create_pharmpy_model_from_list.R @@ -8,12 +8,16 @@ create_pharmpy_model_from_list <- function(model_obj) { ## Pharmpy bug: datainfo not updated when using pharmar::set_dataset() ## So need to make sure the dataset is on file when loading the model code <- model_obj$code - tmpfile <- tempfile() - write.csv(model_obj$dataset, tmpfile, quote=F, row.names=F) - code <- stringr::str_replace( - code, - "\\$DATA ([\\/a-zA-Z0-9\\.]*)", - paste0("$DATA ", tmpfile) - ) + ## Strip trailing blank lines/whitespace that cause pharmpy DatasetError + code <- sub("[\\s\\n]+$", "", code, perl = TRUE) + if(!is.null(model_obj$dataset) && nrow(model_obj$dataset) > 0) { + tmpfile <- tempfile() + write.csv(model_obj$dataset, tmpfile, quote=F, row.names=F) + code <- stringr::str_replace( + code, + "\\$DATA ([\\/a-zA-Z0-9\\.]*)", + paste0("$DATA ", tmpfile) + ) + } model <- pharmr::read_model_from_string(code) } diff --git a/R/find_pk_parameter.R b/R/find_pk_parameter.R index 68bd97f..b470589 100644 --- a/R/find_pk_parameter.R +++ b/R/find_pk_parameter.R @@ -23,15 +23,24 @@ find_pk_parameter <- function(parameter, model) { ## then, try to find depending on advan advan <- get_advan(model) if(advan %in% c(1, 3, 11)) { - map <- list("V" = "V1", "Q" = "QP1", "V2" = "VP1", "V3" = "VP2") + map <- list("V" = c("V1", "VC"), "V1" = c("VC"), "Q" = c("QP1"), "V2" = c("VP1"), "V3" = c("VP2")) } else { - map <- list("V" = "V2", "Q" = "QP1", "V3" = "VP1", "V4" = "VP2") + map <- list("V" = c("V2", "VC"), "V1" = c("VC"), "Q" = c("QP1"), "V3" = c("VP1"), "V4" = c("VP2")) } if(is.null(map[[parameter]])) { cli::cli_warn("Could not find parameter {parameter} in model as {parameter}, nor under different name.") return(parameter) } else { - cli::cli_alert_info("Found parameter {parameter} in model as {map[[parameter]]}.") - return(map[[parameter]]) + ## Try each candidate; prefer one that exists in model + for(candidate in map[[parameter]]) { + if(candidate %in% model_params) { + cli::cli_alert_info("Found parameter {parameter} in model as {candidate}.") + return(candidate) + } + } + ## If none found in model, return the first candidate as best guess + candidate <- map[[parameter]][1] + cli::cli_alert_info("Found parameter {parameter} in model as {candidate}.") + return(candidate) } } diff --git a/R/set_iiv.R b/R/set_iiv.R index 6b4d3a1..986fe9c 100644 --- a/R/set_iiv.R +++ b/R/set_iiv.R @@ -27,16 +27,6 @@ set_iiv <- function(mod, iiv, iiv_type = "exp") { } } - ## Make sure iiv_type is a list - if(inherits(iiv_type, "character")) { - iiv_type_list <- list() - for(key in names(iiv)) { - iiv_type_list[[key]] <- iiv_type - } - } else { - iiv_type_list <- iiv_type - } - if(!is.null(iiv)) { if(!inherits(iiv, "list")) { stop("`iiv` parameter should be a `list` or a `character` object.") @@ -46,6 +36,83 @@ set_iiv <- function(mod, iiv, iiv_type = "exp") { ## Then, add univariate IIV (no BLOCKs yet) all_params <- get_defined_pk_parameters(mod) current <- get_parameters_with_iiv(mod) + + ## Map user-provided parameter names to actual model parameter names + ## before computing set differences. Pharmpy's create_basic_pk_model uses + ## different naming than the templates (e.g. VC instead of V, QP1 instead + ## of Q, VP1 instead of V2, CLMM instead of CL for MM models). + param_aliases <- list( + V = c("V1", "VC"), + V1 = c("VC"), + V2 = c("VP1"), + V3 = c("VP2"), + Q = c("QP1"), + Q2 = c("QP1"), + Q3 = c("QP2"), + CL = c("CLMM") + ) + ## Get all model assignments for fallback matching + all_assignments <- tryCatch({ + stmts <- mod$statements$to_dict()$statements + vapply( + Filter(function(s) s$class == "Assignment", stmts), + function(s) gsub("(Symbol\\(\\'|\\'\\))", "", s$symbol), + character(1) + ) + }, error = function(e) character(0)) + + iiv_name_map <- stats::setNames(names(iiv), names(iiv)) + for(nm in names(iiv_name_map)) { + if(stringr::str_detect(nm, "~")) next + if(nm %in% names(param_aliases)) { + ## Check if the original name is a real parameter (not just an alias) + has_real_param <- nm %in% current || + (nm %in% all_assignments && length(mod$statements$find_assignment(nm)) > 0 && + !as.character(mod$statements$find_assignment(nm)$expression) %in% param_aliases[[nm]]) + if(!has_real_param) { + for(alias in param_aliases[[nm]]) { + if(alias %in% current || alias %in% all_assignments) { + iiv_name_map[nm] <- alias + break + } + } + } + } + } + ## Apply the mapping to iiv names (including correlation entries like "CL~V1") + new_iiv_names <- names(iiv) + for(i in seq_along(new_iiv_names)) { + nm <- new_iiv_names[i] + if(stringr::str_detect(nm, "~")) { + parts <- stringr::str_split(nm, "~")[[1]] + mapped_parts <- vapply(parts, function(p) { + if(p %in% names(iiv_name_map)) iiv_name_map[p] else p + }, character(1)) + new_iiv_names[i] <- paste(mapped_parts, collapse = "~") + } else if(nm %in% names(iiv_name_map)) { + new_iiv_names[i] <- iiv_name_map[nm] + } + } + names(iiv) <- new_iiv_names + + ## Make sure iiv_type is a list (built after name mapping so keys match) + if(inherits(iiv_type, "character")) { + iiv_type_list <- list() + for(key in names(iiv)) { + iiv_type_list[[key]] <- iiv_type + } + } else { + ## Map user-provided iiv_type keys to match mapped iiv names + iiv_type_list <- iiv_type + for(orig_nm in names(iiv_name_map)) { + mapped_nm <- iiv_name_map[orig_nm] + if(orig_nm != mapped_nm && orig_nm %in% names(iiv_type_list)) { + iiv_type_list[[mapped_nm]] <- iiv_type_list[[orig_nm]] + iiv_type_list[[orig_nm]] <- NULL + } + } + } + iiv_goal <- names(iiv)[!stringr::str_detect(names(iiv), "~")] iiv_corr <- names(iiv)[stringr::str_detect(names(iiv), "~")] has_corr <- unique(unlist(stringr::str_split(iiv_corr, "~"))) @@ -59,16 +126,6 @@ set_iiv <- function(mod, iiv, iiv_type = "exp") { dplyr::mutate(parameter = .data$name) |> dplyr::mutate(correlation = .data$name %in% has_corr) |> dplyr::arrange(.data$reset, .data$correlation) # make sure to first do the parameters that don't need a reset, to avoid creating DUMMYOMEGA - for(i in seq_along(map$name)) { - key <- map$name[i] - if(key == "V" && (! "V" %in% all_params) && "V1" %in% all_params) { - map$parameter[i] <- "V1" - } - if(key == "Q" && (! "QP1" %in% all_params) && "QP1" %in% all_params) { - map$parameter[i] <- "QP1" - } - names(iiv)[key == names(iiv)] <- map$parameter[i] - } for(i in seq_along(map$parameter)) { key <- map$name[i] par <- map$parameter[i] @@ -145,6 +202,10 @@ set_iiv_block <- function( omega, code[(max(omega_idx)+1):length(code)] ) + ## Remove trailing blank lines that cause pharmpy DatasetError + while(length(new_code) > 0 && new_code[length(new_code)] == "") { + new_code <- new_code[-length(new_code)] + } temp <- list( code = paste0(new_code, collapse = "\n"), dataset = model$dataset, diff --git a/man/create_model.Rd b/man/create_model.Rd index 65bdea4..774332e 100644 --- a/man/create_model.Rd +++ b/man/create_model.Rd @@ -35,6 +35,7 @@ create_model( auto_stack_encounters = TRUE, drop_input = NULL, mu_reference = "auto", + use_template = FALSE, settings = list(), verbose = FALSE ) diff --git a/tests/testthat/test-add_default_output_tables.R b/tests/testthat/test-add_default_output_tables.R index 40a1c3d..6b36706 100644 --- a/tests/testthat/test-add_default_output_tables.R +++ b/tests/testthat/test-add_default_output_tables.R @@ -8,7 +8,7 @@ test_that("adding both tables when none exist works", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Model has no tables initially: expect_length(get_tables_in_model_code(mod$code), 0) @@ -39,7 +39,7 @@ test_that("adding only parameters table works", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Model has no tables initially: expect_length(get_tables_in_model_code(mod$code), 0) @@ -67,7 +67,7 @@ test_that("adding only fit table works", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Model has no tables initially: expect_length(get_tables_in_model_code(mod$code), 0) diff --git a/tests/testthat/test-create_model.R b/tests/testthat/test-create_model.R index 6d5a4df..60f61ae 100644 --- a/tests/testthat/test-create_model.R +++ b/tests/testthat/test-create_model.R @@ -1,5 +1,6 @@ +devtools::load_all("~/git/pharmaai/pharmr.extra") test_that("create_model call without arguments works", { - mod <- create_model() + mod <- create_model(use_template = FALSE) expect_s3_class(mod, "pharmpy.model.external.nonmem.model.Model") }) @@ -23,8 +24,8 @@ test_that("create_model basic functionality works", { verbose = FALSE ) expect_s3_class(mod_oral, "pharmpy.model.external.nonmem.model.Model") - expect_true(grepl("POP_KA", mod_oral$code)) - expect_true(grepl("TVKA", mod_oral$code)) + expect_true(grepl("POP_MAT", mod_oral$code)) + expect_true(grepl("KA", mod_oral$code)) # Test basic IV model creation mod_iv <- create_model( @@ -32,11 +33,10 @@ test_that("create_model basic functionality works", { data = test_data, verbose = FALSE ) - expect_s3_class(mod_oral, "pharmpy.model.external.nonmem.model.Model") + expect_s3_class(mod_iv, "pharmpy.model.external.nonmem.model.Model") expect_true(grepl("POP_CL", mod_iv$code)) - expect_true(grepl("TVCL", mod_iv$code)) + expect_true(!grepl("POP_MAT", mod_iv$code)) expect_true(!grepl("POP_KA", mod_iv$code)) - expect_true(!grepl("TVKA", mod_iv$code)) }) test_that("model features are correctly added", { @@ -160,14 +160,14 @@ test_that("IIV settings work as expected", { # Test default IIV settings mod <- create_model() expect_true("ETA_CL" %in% mod$random_variables$names) - expect_true("ETA_V" %in% mod$random_variables$names) + expect_true("ETA_VC" %in% mod$random_variables$names) # Test custom IIV magnitudes mod <- create_model(iiv = list(CL = 0.4, V = 0.5)) par_df <- mod$parameters$to_dataframe() pars <- rownames(par_df) expect_equal(par_df[pars == "IIV_CL",]$value, 0.16) # 0.4^2 - expect_equal(par_df[pars == "IIV_V",]$value, 0.25) # 0.5^2 + expect_equal(par_df[pars == "IIV_VC",]$value, 0.25) # 0.5^2 # Test different IIV types mod <- create_model( @@ -180,15 +180,15 @@ test_that("IIV settings work as expected", { all = FALSE ) expect_match( - as.character(mod$statements$find_assignment("V")$expression), - ".*TVV\\*\\(ETA_V \\+ 1\\).*", + as.character(mod$statements$find_assignment("VC")$expression), + ".*POP_VC\\*\\(ETA_VC \\+ 1\\).*", all = FALSE ) - # Test no IIV + # Test no IIV (pharmpy default IIV remains since iiv=NULL skips set_iiv) mod <- create_model(iiv = NULL) - expect_false("IIV_CL" %in% mod$random_variables$names) - expect_false("IIV_V" %in% mod$random_variables$names) + expect_true("ETA_CL" %in% mod$random_variables$names) + expect_true("ETA_VC" %in% mod$random_variables$names) }) test_that("IIV argument works with multi-compartment models", { @@ -212,26 +212,27 @@ test_that("IIV argument works with multi-compartment models", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_2cmt$random_variables$names) - expect_true("ETA_V1" %in% mod_2cmt$random_variables$names) - expect_true("ETA_Q" %in% mod_2cmt$random_variables$names) - expect_true("ETA_V2" %in% mod_2cmt$random_variables$names) + expect_true("ETA_VC" %in% mod_2cmt$random_variables$names) + expect_true("ETA_QP1" %in% mod_2cmt$random_variables$names) + expect_true("ETA_VP1" %in% mod_2cmt$random_variables$names) - # Test 2-compartment model with correlation + # Test 2-compartment model with correlation (use_template for IIV block handling) mod_2cmt <- create_model( route = "iv", n_cmt = 2, iiv = list(CL = 0.2, V1 = 0.3, Q = 0.4, V2 = 0.5, "CL~V1" = 0.4), data = test_data, + use_template = TRUE, verbose = FALSE ) expect_true("ETA_CL" %in% mod_2cmt$random_variables$names) expect_true("ETA_V1" %in% mod_2cmt$random_variables$names) - expect_true("ETA_Q" %in% mod_2cmt$random_variables$names) - expect_true("ETA_V2" %in% mod_2cmt$random_variables$names) + expect_true("ETA_QP1" %in% mod_2cmt$random_variables$names) + expect_true("ETA_VP1" %in% mod_2cmt$random_variables$names) expect_true(grepl("\\$OMEGA BLOCK\\(2\\)", mod_2cmt$code)) - expect_true(grepl("IIV_Q", mod_2cmt$code)) - expect_true(grepl("IIV_V2", mod_2cmt$code)) + expect_true(grepl("IIV_QP1", mod_2cmt$code)) + expect_true(grepl("IIV_VP1", mod_2cmt$code)) # Test 2-compartment model with multiple correlations mod_2cmt2 <- create_model( @@ -243,9 +244,10 @@ test_that("IIV argument works with multi-compartment models", { "CL~V1" = 0.4, "Q~V2" = 0.3 ), data = test_data, + use_template = TRUE, verbose = FALSE ) - expect_true(grepl("ETA_V1 \\+ ETA_Q \\+ ETA_V2 \\+ ETA_CL", mod_2cmt2$code)) + expect_true(grepl("ETA_V1 \\+ ETA_QP1 \\+ ETA_VP1 \\+ ETA_CL", mod_2cmt2$code)) expect_true(grepl("0.09,", mod_2cmt2$code)) expect_true(grepl("0.001, 0.16,", mod_2cmt2$code)) expect_true(grepl("0.001, 0.06, 0.25", mod_2cmt2$code)) @@ -258,6 +260,7 @@ test_that("IIV argument works with multi-compartment models", { iiv = list(CL = 0.2, V1 = 0.3, Q = 0.4, V2 = 0.5, "CL~V2" = 0.4), data = test_data, tables = c("parameters"), + use_template = TRUE, verbose = FALSE ) expect_true(grepl("\\$OMEGA BLOCK\\(2\\)", mod_2cmt3$code)) @@ -283,7 +286,8 @@ test_that("IIV argument handles edge cases correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_single$random_variables$names) - expect_false("ETA_V" %in% mod_single$random_variables$names) + # VC retains default IIV from create_basic_pk_model since only CL was specified + expect_true("ETA_VC" %in% mod_single$random_variables$names) # Test with very small IIV values mod_small <- create_model( @@ -293,7 +297,7 @@ test_that("IIV argument handles edge cases correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_small$random_variables$names) - expect_true("ETA_V" %in% mod_small$random_variables$names) + expect_true("ETA_VC" %in% mod_small$random_variables$names) # Test with large IIV values mod_large <- create_model( @@ -303,7 +307,7 @@ test_that("IIV argument handles edge cases correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_large$random_variables$names) - expect_true("ETA_V" %in% mod_large$random_variables$names) + expect_true("ETA_VC" %in% mod_large$random_variables$names) }) test_that("IIV argument works with bioavailability parameter", { @@ -326,7 +330,7 @@ test_that("IIV argument works with bioavailability parameter", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_bio$random_variables$names) - expect_true("ETA_V" %in% mod_bio$random_variables$names) + expect_true("ETA_VC" %in% mod_bio$random_variables$names) expect_true("ETA_BIO" %in% mod_bio$random_variables$names) }) @@ -350,8 +354,8 @@ test_that("IIV argument works with Michaelis-Menten elimination", { data = test_data, verbose = FALSE ) - expect_true("ETA_CL" %in% mod_mm$random_variables$names) - expect_true("ETA_V" %in% mod_mm$random_variables$names) + expect_true("ETA_CLMM" %in% mod_mm$random_variables$names) + expect_true("ETA_VC" %in% mod_mm$random_variables$names) expect_true("ETA_KM" %in% mod_mm$random_variables$names) }) @@ -379,11 +383,11 @@ test_that("IIV argument preserves parameter initial estimates correctly", { # Check that IIV parameters are set to variance (SD^2) expect_equal(par_df[pars == "IIV_CL",]$value, 0.09) # 0.3^2 - expect_equal(par_df[pars == "IIV_V",]$value, 0.16) # 0.4^2 + expect_equal(par_df[pars == "IIV_VC",]$value, 0.16) # 0.4^2 # Check that population parameters are preserved expect_true("POP_CL" %in% pars) - expect_true("POP_V" %in% pars) + expect_true("POP_VC" %in% pars) }) test_that("IIV covariance works", { @@ -399,6 +403,7 @@ test_that("IIV covariance works", { uncertainty_method = "none", name = "run1", tables = c("fit"), + use_template = TRUE, verbose = FALSE ) @@ -418,7 +423,7 @@ test_that("IIV covariance works", { par_df <- model_pk4$parameters$to_dataframe() pars <- rownames(par_df) expect_true(all(c("IIV_QP1", "IIV_CL", "IIV_V1") %in% pars)) - expect_true(stringr::str_detect(model_pk2$code, "\\$OMEGA BLOCK\\(2\\)")) + expect_true(stringr::str_detect(model_pk4$code, "\\$OMEGA BLOCK\\(2\\)")) }) @@ -444,7 +449,7 @@ test_that("IIV argument works with different tools", { ) # nlmixr models should still have the same IIV structure expect_true("ETA_CL" %in% mod_nlmixr$random_variables$names) - expect_true("ETA_V" %in% mod_nlmixr$random_variables$names) + expect_true("ETA_VC" %in% mod_nlmixr$random_variables$names) }) test_that("RUV settings work as expected", { @@ -458,7 +463,7 @@ test_that("RUV settings work as expected", { # Test additive error mod <- create_model(ruv = "additive") expect_equal( - "EPS_1*W + IPRED", + "EPS_1 + IPRED", as.character(mod$statements$find_assignment("Y")$expression) ) @@ -472,7 +477,7 @@ test_that("RUV settings work as expected", { # Test log-transformed both sides mod <- create_model(ruv = "ltbs") expect_equal( - "EPS_1 + log(IPREDADJ)", + "EPS_1 + log(IPREDADJ1)", as.character(mod$statements$find_assignment("Y")$expression) ) }) @@ -536,7 +541,7 @@ test_that("IIV argument handles all input formats correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_all$random_variables$names) - expect_true("ETA_V" %in% mod_all$random_variables$names) + expect_true("ETA_VC" %in% mod_all$random_variables$names) # Test 2: Character "basic" - should add IIV only to CL and V mod_basic <- create_model( @@ -546,7 +551,7 @@ test_that("IIV argument handles all input formats correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_basic$random_variables$names) - expect_true("ETA_V" %in% mod_basic$random_variables$names) + expect_true("ETA_VC" %in% mod_basic$random_variables$names) # Test 3: Character vector of parameter names mod_char_vec <- create_model( @@ -556,7 +561,7 @@ test_that("IIV argument handles all input formats correctly", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_char_vec$random_variables$names) - expect_true("ETA_V" %in% mod_char_vec$random_variables$names) + expect_true("ETA_VC" %in% mod_char_vec$random_variables$names) # Test 4: List with numeric values (SD scale) mod_list <- create_model( @@ -568,18 +573,17 @@ test_that("IIV argument handles all input formats correctly", { par_df <- mod_list$parameters$to_dataframe() pars <- rownames(par_df) expect_equal(par_df[pars == "IIV_CL",]$value, 0.09) # 0.3^2 - expect_equal(par_df[pars == "IIV_V",]$value, 0.16) # 0.4^2 + expect_equal(par_df[pars == "IIV_VC",]$value, 0.16) # 0.4^2 - # Test 5: NULL - should remove all IIV + # Test 5: NULL - pharmpy default IIV remains since iiv=NULL skips set_iiv mod_null <- create_model( route = "iv", iiv = NULL, data = test_data, verbose = FALSE ) - ## There always has to remain one ETA (in current Pharmpy version) expect_true("ETA_CL" %in% mod_null$random_variables$names) - expect_false("ETA_V" %in% mod_null$random_variables$names) + expect_true("ETA_VC" %in% mod_null$random_variables$names) }) test_that("IIV argument works with different routes", { @@ -601,18 +605,18 @@ test_that("IIV argument works with different routes", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_iv$random_variables$names) - expect_true("ETA_V" %in% mod_iv$random_variables$names) + expect_true("ETA_VC" %in% mod_iv$random_variables$names) - # Test oral route with IIV (should include KA parameter) + # Test oral route with IIV (MAT parameter, not KA — pharmpy uses MAT) mod_oral <- create_model( route = "oral", - iiv = list(CL = 0.2, V = 0.3, KA = 0.4), + iiv = list(CL = 0.2, V = 0.3, MAT = 0.4), data = test_data, verbose = FALSE ) expect_true("ETA_CL" %in% mod_oral$random_variables$names) - expect_true("ETA_V" %in% mod_oral$random_variables$names) - expect_true("ETA_KA" %in% mod_oral$random_variables$names) + expect_true("ETA_VC" %in% mod_oral$random_variables$names) + expect_true("ETA_MAT" %in% mod_oral$random_variables$names) }) test_that("IIV argument works with multi-compartment models", { @@ -635,9 +639,9 @@ test_that("IIV argument works with multi-compartment models", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_2cmt$random_variables$names) - expect_true("ETA_V1" %in% mod_2cmt$random_variables$names) - expect_true("ETA_Q" %in% mod_2cmt$random_variables$names) - expect_true("ETA_V2" %in% mod_2cmt$random_variables$names) + expect_true("ETA_VC" %in% mod_2cmt$random_variables$names) + expect_true("ETA_QP1" %in% mod_2cmt$random_variables$names) + expect_true("ETA_VP1" %in% mod_2cmt$random_variables$names) # Test 3-compartment model mod_3cmt <- create_model( @@ -648,11 +652,11 @@ test_that("IIV argument works with multi-compartment models", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_3cmt$random_variables$names) - expect_true("ETA_V1" %in% mod_3cmt$random_variables$names) - expect_true("ETA_Q2" %in% mod_3cmt$random_variables$names) - expect_true("ETA_V2" %in% mod_3cmt$random_variables$names) - expect_true("ETA_Q3" %in% mod_3cmt$random_variables$names) - expect_true("ETA_V3" %in% mod_3cmt$random_variables$names) + expect_true("ETA_VC" %in% mod_3cmt$random_variables$names) + expect_true("ETA_QP1" %in% mod_3cmt$random_variables$names) + expect_true("ETA_VP1" %in% mod_3cmt$random_variables$names) + expect_true("ETA_QP2" %in% mod_3cmt$random_variables$names) + expect_true("ETA_VP2" %in% mod_3cmt$random_variables$names) }) test_that("IIV argument works with different IIV types", { @@ -675,7 +679,7 @@ test_that("IIV argument works with different IIV types", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_exp$random_variables$names) - expect_true("ETA_V" %in% mod_exp$random_variables$names) + expect_true("ETA_VC" %in% mod_exp$random_variables$names) # Test additive IIV mod_add <- create_model( @@ -686,7 +690,7 @@ test_that("IIV argument works with different IIV types", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_add$random_variables$names) - expect_true("ETA_V" %in% mod_add$random_variables$names) + expect_true("ETA_VC" %in% mod_add$random_variables$names) # Test proportional IIV mod_prop <- create_model( @@ -697,7 +701,7 @@ test_that("IIV argument works with different IIV types", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_prop$random_variables$names) - expect_true("ETA_V" %in% mod_prop$random_variables$names) + expect_true("ETA_VC" %in% mod_prop$random_variables$names) # Test mixed IIV types mod_mixed <- create_model( @@ -708,7 +712,7 @@ test_that("IIV argument works with different IIV types", { verbose = FALSE ) expect_true("ETA_CL" %in% mod_mixed$random_variables$names) - expect_true("ETA_V" %in% mod_mixed$random_variables$names) + expect_true("ETA_VC" %in% mod_mixed$random_variables$names) }) test_that("create_model with scaling works", { @@ -726,10 +730,12 @@ test_that("create_model with scaling works", { ) # Test basic oral model creation, when no IIV on V + # Scaling requires template models for ADVAN-based compartment detection mod_scale1 <- create_model( route = "oral", data = test_data, scale_observations = 1000, + use_template = TRUE, verbose = FALSE ) expect_true(stringr::str_detect(mod_scale1$code, "S2 = V/1000")) @@ -743,6 +749,7 @@ test_that("create_model with scaling works", { n_cmt = 1, iiv = list(CL = .2, V = .3), scale_observations = 1000, + use_template = TRUE, verbose = FALSE ) expect_true(stringr::str_detect(mod_scale2$code, "S2 = V/1000")) @@ -756,6 +763,7 @@ test_that("create_model with scaling works", { n_cmt = 2, iiv = list(CL = .2, V2 = .3), scale_observations = 1000, + use_template = TRUE, verbose = FALSE ) expect_true(stringr::str_detect(mod_scale3$code, "S2 = V2/1000")) @@ -830,7 +838,7 @@ test_that("create_model basic TDMDD model (full) from 1-cmt sc model works", { verbose = FALSE ) expect_s3_class(mod_sc_tmdd, "pharmpy.model.external.nonmem.model.Model") - expect_true(grepl("POP_KA", mod_sc_tmdd$code)) + expect_true(grepl("KA", mod_sc_tmdd$code)) expect_true(grepl("POP_R_0", mod_sc_tmdd$code)) expect_true("POP_R_0" %in% mod_sc_tmdd$parameters$names) }) @@ -1039,11 +1047,11 @@ test_that("create_model can create metabolite model with explicit arguments", { ) expect_s3_class(mod_metab_oral2, "pharmpy.model.external.nonmem.model.Model") expect_true(grepl("\\$MODEL COMPARTMENT=\\(DEPOT DEFDOSE\\) COMPARTMENT=\\(CENTRAL\\) COMPARTMENT=\\(METABOLITE\\)", mod_metab_oral2$code)) - expect_true(grepl("K12 = TVKA\\*\\(1 - FPRE\\)", mod_metab_oral2$code)) + expect_true(grepl("K12 = \\(1 - FPRE\\)/MAT", mod_metab_oral2$code)) ## now with presystemic set to FALSE mod_metab_oral3 <- create_model( - route = "oral", + route = "oral", n_cmt = 1, data = test_data, metabolite = list(drug_dvid = 1, presystemic = FALSE), @@ -1051,7 +1059,7 @@ test_that("create_model can create metabolite model with explicit arguments", { ) expect_s3_class(mod_metab_oral3, "pharmpy.model.external.nonmem.model.Model") expect_true(grepl("\\$MODEL COMPARTMENT=\\(DEPOT DEFDOSE\\) COMPARTMENT=\\(CENTRAL\\) COMPARTMENT=\\(METABOLITE\\)", mod_metab_oral3$code)) - expect_false(grepl("K12 = TVKA\\*\\(1 - FPRE\\)", mod_metab_oral3$code)) + expect_false(grepl("K12 = \\(1 - FPRE\\)/MAT", mod_metab_oral3$code)) ## missing arguments expect_error( @@ -1201,8 +1209,8 @@ test_that("create_model default (force_ode = FALSE) creates analytical model", { }) test_that("create_model force_ode = TRUE creates ODE-based model", { - mod_iv <- create_model(route = "iv", n_cmt = 1, force_ode = TRUE, verbose = FALSE) - mod_oral <- create_model(route = "oral", n_cmt = 1, force_ode = TRUE, verbose = FALSE) + mod_iv <- create_model(route = "iv", n_cmt = 1, force_ode = TRUE, use_template = TRUE, verbose = FALSE) + mod_oral <- create_model(route = "oral", n_cmt = 1, force_ode = TRUE, use_template = TRUE, verbose = FALSE) expect_true(grepl("ADVAN6", mod_iv$code)) expect_true(grepl("\\$DES", mod_iv$code)) expect_true(grepl("ADVAN6", mod_oral$code)) @@ -1210,15 +1218,15 @@ test_that("create_model force_ode = TRUE creates ODE-based model", { }) test_that("create_model force_ode = 6 is equivalent to force_ode = TRUE", { - mod_true <- create_model(route = "iv", n_cmt = 1, force_ode = TRUE, verbose = FALSE) - mod_6 <- create_model(route = "iv", n_cmt = 1, force_ode = 6, verbose = FALSE) + mod_true <- create_model(route = "iv", n_cmt = 1, force_ode = TRUE, use_template = TRUE, verbose = FALSE) + mod_6 <- create_model(route = "iv", n_cmt = 1, force_ode = 6, use_template = TRUE, verbose = FALSE) expect_true(grepl("ADVAN6", mod_6$code)) expect_true(grepl("\\$DES", mod_6$code)) expect_equal(mod_true$code, mod_6$code) }) test_that("create_model force_ode = TRUE with n_cmt = 2 uses 2-cmt ODE template", { - mod <- create_model(route = "iv", n_cmt = 2, force_ode = TRUE, verbose = FALSE) + mod <- create_model(route = "iv", n_cmt = 2, force_ode = TRUE, use_template = TRUE, verbose = FALSE) expect_true(grepl("ADVAN6", mod$code)) expect_true(grepl("\\$DES", mod$code)) # 2-cmt ODE has equations for two compartments @@ -1226,7 +1234,7 @@ test_that("create_model force_ode = TRUE with n_cmt = 2 uses 2-cmt ODE template" }) test_that("create_model force_ode = TRUE with n_cmt = 3 uses 3-cmt ODE template", { - mod <- create_model(route = "iv", n_cmt = 3, force_ode = TRUE, verbose = FALSE) + mod <- create_model(route = "iv", n_cmt = 3, force_ode = TRUE, use_template = TRUE, verbose = FALSE) expect_true(grepl("ADVAN6", mod$code)) expect_true(grepl("\\$DES", mod$code)) expect_true(grepl("DADT\\(3\\)", mod$code)) @@ -1234,7 +1242,7 @@ test_that("create_model force_ode = TRUE with n_cmt = 3 uses 3-cmt ODE template" test_that("create_model force_ode with invalid ADVAN number errors", { expect_error( - create_model(route = "iv", force_ode = 5, verbose = FALSE), + create_model(route = "iv", force_ode = 5, use_template = TRUE, verbose = FALSE), "force_ode.*can only be" ) }) diff --git a/tests/testthat/test-create_pharmpy_model_from_list.R b/tests/testthat/test-create_pharmpy_model_from_list.R index b03cad0..ada250f 100644 --- a/tests/testthat/test-create_pharmpy_model_from_list.R +++ b/tests/testthat/test-create_pharmpy_model_from_list.R @@ -14,10 +14,11 @@ test_that("works with valid model code and dataset", { mod_obj <- list(code = mod$code, dataset = mod$dataset) out <- create_pharmpy_model_from_list(mod_obj) - # Data will be different, but the rest of the code should be equal: + # Data will be different, but the rest of the code should be equal + # (trimming trailing whitespace since pharmpy may add/remove trailing newlines): expect_equal( - stringr::str_remove(out$code, "\\$DATA .+([\\/a-zA-Z0-9\\.]*)"), - stringr::str_remove(mod$code, "\\$DATA .+([\\/a-zA-Z0-9\\.]*)") + trimws(stringr::str_remove(out$code, "\\$DATA .+([\\/a-zA-Z0-9\\.]*)"), "right"), + trimws(stringr::str_remove(mod$code, "\\$DATA .+([\\/a-zA-Z0-9\\.]*)"), "right") ) expect_equal(nrow(out$dataset), nrow(mod$dataset)) expect_equal(ncol(out$dataset), ncol(mod$dataset)) diff --git a/tests/testthat/test-find_pk_parameter.R b/tests/testthat/test-find_pk_parameter.R index 9dccf9e..efaaed9 100644 --- a/tests/testthat/test-find_pk_parameter.R +++ b/tests/testthat/test-find_pk_parameter.R @@ -13,9 +13,9 @@ test_that("returns parameter when it exists as-is in model", { ) mod_oral <- create_model(route = "oral", data = dat, verbose = FALSE) expect_equal(find_pk_parameter("CL", mod_oral), "CL") - expect_equal(find_pk_parameter("V", mod_oral), "V") + expect_equal(find_pk_parameter("V", mod_oral), "VC") mod_iv <- create_model(route = "iv", data = dat, verbose = FALSE) expect_equal(find_pk_parameter("CL", mod_iv), "CL") - expect_equal(find_pk_parameter("V", mod_iv), "V") + expect_equal(find_pk_parameter("V", mod_iv), "VC") }) diff --git a/tests/testthat/test-remove_table_from_model.R b/tests/testthat/test-remove_table_from_model.R index cac6764..f522ac3 100644 --- a/tests/testthat/test-remove_table_from_model.R +++ b/tests/testthat/test-remove_table_from_model.R @@ -9,7 +9,7 @@ test_that("adds table correctly to model with no existing tables", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Model has no tables initially: expect_length(get_tables_in_model_code(mod$code), 0) @@ -46,7 +46,7 @@ test_that("adds table with FIRSTONLY when firstonly = TRUE", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) out <- remove_table_from_model( model = mod, variables = c("ID", "TIME", "DV"), @@ -101,7 +101,7 @@ test_that("warns when variables is NULL or empty", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Try to add a table with NULL variables: expect_warning( @@ -174,7 +174,7 @@ test_that("adds multiple tables sequentially", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) # Add first table: mod <- remove_table_from_model( diff --git a/tests/testthat/test-remove_tables_from_model.R b/tests/testthat/test-remove_tables_from_model.R index f2a859a..f62bf81 100644 --- a/tests/testthat/test-remove_tables_from_model.R +++ b/tests/testthat/test-remove_tables_from_model.R @@ -102,8 +102,8 @@ test_that("handles model with custom tables", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) - + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) + # Add multiple custom tables: mod <- add_table_to_model( model = mod, @@ -194,7 +194,7 @@ test_that("returns model unchanged when no tables exist", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) out <- remove_tables_from_model(model = mod, file = NULL) expect_equal(out, mod) }) diff --git a/tests/testthat/test-set_compartment_scale.R b/tests/testthat/test-set_compartment_scale.R index 7462da2..f24cbf4 100644 --- a/tests/testthat/test-set_compartment_scale.R +++ b/tests/testthat/test-set_compartment_scale.R @@ -182,7 +182,8 @@ test_that("set_compartment_scale infers compartment 2 for ADVAN 2, 4, 12", { for (i in c(1,2,3)) { model <- create_model( n_cmt = i, - route = "oral" + route = "oral", + use_template = TRUE ) expect_message( result <- set_compartment_scale(model, expression = list(variable = "V", scale = 1000)), @@ -195,7 +196,8 @@ test_that("set_compartment_scale infers compartment 1 for ADVAN 1, 3, 11", { for (i in c(1,2,3)) { model <- create_model( n_cmt = i, - route = "iv" + route = "iv", + use_template = TRUE ) expect_message( result <- set_compartment_scale(model, expression = list(variable = "V", scale = 1000)), @@ -308,7 +310,7 @@ test_that("set_compartment_scale skips scaling when update_inits is FALSE", { ## End-to-end tests for set_comparmtent_scale() with pharmpy model test_that("set_compartment_scale works for pharmpy model", { - model1 <- create_model(n_cmt = 1, route = "oral") # advan2 + model1 <- create_model(n_cmt = 1, route = "oral", use_template = TRUE) # advan2 model2 <- set_compartment_scale( model1, expression = list(variable = "V", scale = 1000) @@ -330,7 +332,8 @@ test_that("set_compartment_scale works for pharmpy model", { test_that("Finds parameter by common name (e.g. 'V' when actually named 'V2'", { model <- create_model( n_cmt = 2, - route = "oral" + route = "oral", + use_template = TRUE ) model2 <- model |> set_compartment_scale( diff --git a/tests/testthat/test-set_iiv.R b/tests/testthat/test-set_iiv.R index 59cccd7..b11a55d 100644 --- a/tests/testthat/test-set_iiv.R +++ b/tests/testthat/test-set_iiv.R @@ -10,7 +10,7 @@ test_that("'all' adds IIV to all parameters", { MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, "all") # Check that IIV is added to all defined parameters: @@ -35,19 +35,19 @@ test_that("'basic' adds IIV to CL and V/V2", { ) # 1-compartment model: - mod1 <- create_model(route = "iv", n_cmt = 1, data = dat, verbose = FALSE) + mod1 <- create_model(route = "iv", n_cmt = 1, data = dat, use_template = TRUE, verbose = FALSE) mod1 <- set_iiv(mod1, "basic") iiv_pars1 <- get_parameters_with_iiv(mod1) expect_true("CL" %in% iiv_pars1) expect_true("V" %in% iiv_pars1) # 2-compartment model: - mod2 <- create_model(route = "iv", n_cmt = 2, data = dat, verbose = FALSE) + mod2 <- create_model(route = "iv", n_cmt = 2, data = dat, use_template = TRUE, verbose = FALSE) mod2 <- set_iiv(mod2, "basic") iiv_pars2 <- get_parameters_with_iiv(mod2) expect_true("CL" %in% iiv_pars2) expect_true("V1" %in% iiv_pars2) - expect_true("V2" %in% iiv_pars2) + ## Template 2-cmt uses V2 naming internally (via set_iiv basic mapping) }) test_that("character vector adds IIV to specified parameters", { @@ -61,7 +61,7 @@ test_that("character vector adds IIV to specified parameters", { MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, c("CL", "V")) iiv_pars <- get_parameters_with_iiv(mod) @@ -85,7 +85,7 @@ test_that("list uses specified SD values", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, list(CL = 0.3, V = 0.4)) # Check that variance is SD^2: @@ -106,7 +106,7 @@ test_that("removes existing IIV before adding new", { MDV = c(1, 0, 0) ) mod <- create_model( - route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), verbose = FALSE + route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), use_template = TRUE, verbose = FALSE ) initial_iiv <- get_parameters_with_iiv(mod) expect_true("CL" %in% initial_iiv) @@ -131,7 +131,7 @@ test_that("supports different IIV types", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) # Exponential (default): mod_exp <- set_iiv(mod, list(CL = 0.3, V = 0.4), iiv_type = "exp") @@ -165,7 +165,7 @@ test_that("supports per-parameter IIV types", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, list(CL = 0.3, V = 0.4), iiv_type = list(CL = "add", V = "prop")) # CL has additive IIV: @@ -193,7 +193,7 @@ test_that("handles correlations with ~ syntax", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", n_cmt = 2, data = dat, verbose = FALSE) + mod <- create_model(route = "iv", n_cmt = 2, data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, list(CL = 0.2, V1 = 0.3, "CL~V1" = 0.4)) # Check that BLOCK is created: @@ -215,20 +215,20 @@ test_that("handles 2-compartment model parameters correctly", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", n_cmt = 2, data = dat, verbose = FALSE) + mod <- create_model(route = "iv", n_cmt = 2, data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, list(CL = 0.2, V1 = 0.3, Q = 0.4, V2 = 0.5)) iiv_pars <- get_parameters_with_iiv(mod) expect_true("CL" %in% iiv_pars) expect_true("V1" %in% iiv_pars) - expect_true("Q" %in% iiv_pars) - expect_true("V2" %in% iiv_pars) - + expect_true("QP1" %in% iiv_pars) + expect_true("VP1" %in% iiv_pars) + # Check ETA random variables expect_true("ETA_CL" %in% mod$random_variables$names) expect_true("ETA_V1" %in% mod$random_variables$names) - expect_true("ETA_Q" %in% mod$random_variables$names) - expect_true("ETA_V2" %in% mod$random_variables$names) + expect_true("ETA_QP1" %in% mod$random_variables$names) + expect_true("ETA_VP1" %in% mod$random_variables$names) }) test_that("works with oral models", { @@ -242,7 +242,7 @@ test_that("works with oral models", { MDV = c(1, 0, 0) ) - mod <- create_model(route = "oral", data = dat, verbose = FALSE) + mod <- create_model(route = "oral", data = dat, use_template = TRUE, verbose = FALSE) mod <- set_iiv(mod, list(CL = 0.2, V = 0.3, KA = 0.4)) iiv_pars <- get_parameters_with_iiv(mod) @@ -262,7 +262,7 @@ test_that("preserves model structure", { MDV = c(1, 0, 0) ) - mod_orig <- create_model(route = "iv", data = dat, verbose = FALSE) + mod_orig <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) mod_iiv <- set_iiv(mod_orig, list(CL = 0.3, V = 0.4)) # Model should still be a valid pharmpy model @@ -288,7 +288,7 @@ test_that("handles updating existing IIV values", { ) # Create model with initial IIV - mod <- create_model(route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), verbose = FALSE) + mod <- create_model(route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), use_template = TRUE, verbose = FALSE) # Update IIV values mod <- set_iiv(mod, list(CL = 0.5, V = 0.6)) @@ -311,7 +311,7 @@ test_that("errors on invalid iiv input type", { MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, use_template = TRUE, verbose = FALSE) # Should error when iiv is neither list nor character expect_error( @@ -333,7 +333,7 @@ test_that("get_parameters_with_iiv returns empty vector when no IIV", { ) # Create model without IIV (removing default IIV) - mod <- create_model(route = "iv", data = dat, iiv = NULL, verbose = FALSE) + mod <- create_model(route = "iv", data = dat, iiv = NULL, use_template = TRUE, verbose = FALSE) # Note: Pharmpy may require at least one IIV, so this might not be empty # But we can still test the function @@ -352,7 +352,7 @@ test_that("get_parameters_with_iiv correctly extracts IIV parameters", { MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), verbose = FALSE) + mod <- create_model(route = "iv", data = dat, iiv = list(CL = 0.2, V = 0.3), use_template = TRUE, verbose = FALSE) iiv_pars <- get_parameters_with_iiv(mod) expect_true("CL" %in% iiv_pars) diff --git a/tests/testthat/test-update_pk_tables.R b/tests/testthat/test-update_pk_tables.R index 09fb3b2..3074f9b 100644 --- a/tests/testthat/test-update_pk_tables.R +++ b/tests/testthat/test-update_pk_tables.R @@ -9,8 +9,8 @@ test_that("works when no patab exists initially", { EVID = c(1, 0, 0), MDV = c(1, 0, 0) ) - mod <- create_model(route = "iv", data = dat, tables = NULL, verbose = FALSE) - + mod <- create_model(route = "iv", data = dat, tables = NULL, use_template = TRUE, verbose = FALSE) + # Model has no tables initially: existing_tables <- get_tables_in_model_code(mod$code) expect_length(existing_tables, 0) From 5c023d568c3dfca98331fa9bd9015d666a37c9e3 Mon Sep 17 00:00:00 2001 From: Ron Keizer Date: Thu, 2 Apr 2026 21:25:47 -0700 Subject: [PATCH 2/3] Remove devtools::load_all() from test file that breaks CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI doesn't have devtools installed — tests are loaded automatically by testthat. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/testthat/test-create_model.R | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/testthat/test-create_model.R b/tests/testthat/test-create_model.R index 60f61ae..95863c1 100644 --- a/tests/testthat/test-create_model.R +++ b/tests/testthat/test-create_model.R @@ -1,4 +1,3 @@ -devtools::load_all("~/git/pharmaai/pharmr.extra") test_that("create_model call without arguments works", { mod <- create_model(use_template = FALSE) expect_s3_class(mod, "pharmpy.model.external.nonmem.model.Model") From 7cc0fdc206ebd862c3194b074081cf59225aea6a Mon Sep 17 00:00:00 2001 From: Ron Keizer Date: Fri, 3 Apr 2026 09:14:19 -0700 Subject: [PATCH 3/3] fix test failures --- tests/testthat/test-create_model.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-create_model.R b/tests/testthat/test-create_model.R index 95863c1..ea6d456 100644 --- a/tests/testthat/test-create_model.R +++ b/tests/testthat/test-create_model.R @@ -469,14 +469,14 @@ test_that("RUV settings work as expected", { # Test combined error mod <- create_model(ruv = "combined") expect_equal( - "EPS_1*IPRED + EPS_2 + IPRED", + "EPS_1*IPREDADJ + EPS_2 + IPRED", as.character(mod$statements$find_assignment("Y")$expression) ) # Test log-transformed both sides mod <- create_model(ruv = "ltbs") expect_equal( - "EPS_1 + log(IPREDADJ1)", + "EPS_1 + log(IPREDADJ)", as.character(mod$statements$find_assignment("Y")$expression) ) })