Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
.Rproj.user
.DS_Store
docs
data-raw/
.claude
*-fit.rds
.luna.cache.*
6 changes: 4 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ Imports:
stats,
job,
diffr
Suggests:
Suggests:
httr,
ellmer,
nlmixr2,
ferx,
ggplot2,
xpose,
vpc,
Expand All @@ -41,7 +42,8 @@ Suggests:
rstudioapi
Remotes:
InsightRX/irxutils,
InsightRX/pharmr.extra
InsightRX/pharmr.extra,
ferx=FeRx-NLME/ferx-r
License: MIT + file LICENSE
URL: https://github.com/InsightRX/uno, https://insightrx.github.io/uno/, https://insightrx.github.io/luna/
LazyData: TRUE
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export(luna_dataset)
export(luna_diff)
export(luna_edit)
export(luna_edit_project)
export(luna_ferx_info)
export(luna_gof)
export(luna_help)
export(luna_ind)
Expand Down
11 changes: 5 additions & 6 deletions R/create_cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ create_cache <- function(
"project", project
)

## read pharmpy configuration
if(verbose)
cli::cli_alert_info("Reading Pharmpy settings into cache")
.luna_cache$set(
"pharmpy_conf", get_pharmpy_conf()
)
## Note: pharmpy configuration (previously read here via get_pharmpy_conf())
## is intentionally not read at cache creation. Reading it eagerly required
## pharmpy to be installed for all users, causing luna_load_project() to fail
## for PSN and nlmixr2 users. Pharmpy config is now read lazily inside
## pharmpy-specific functions (luna_run(), luna_check(), etc.).

## read / update models and results
update_cache(project)
Expand Down
4 changes: 2 additions & 2 deletions R/get_luna_config.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ get_luna_config <- function(
}
conf <- read_yaml_safe(global_conf_file)
if(is_luna_cache_available(abort = FALSE)) {
proj_yaml <- .luna_cache$get("yaml")
proj_yaml <- .luna_cache$get("project")$yaml
proj_conf <- proj_yaml$project$config
## use any specified project conf to overwrite env conf
for(key in names(proj_conf)) {
if(is.null(conf[[key]]) || proj_conf[[key]] != conf[[key]]) {
if(is.null(conf[[key]]) || !identical(proj_conf[[key]], conf[[key]])) {
if(verbose)
cli::cli_alert_info("Overriding global luna setting for `{key}` with project-specific setting")
conf[[key]] <- proj_conf[[key]]
Expand Down
7 changes: 6 additions & 1 deletion R/get_status.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
#'
get_status <- function(id, folder = ".") {
status <- "not run"
# NONMEM: check for .lst output file
tmp <- find_file_with_fallback(
folder,
file.path(id, paste0("run", ".lst")),
fallback = file.path(paste0(id, ".lst")),
verbose = FALSE,
abort = FALSE
)
if(!is.null(tmp)) {
if (!is.null(tmp)) {
status <- "finished"
}
# ferx: check for -fit.rds result file
if (file.exists(file.path(folder, paste0(id, "-fit.rds")))) {
status <- "finished"
}
status
Expand Down
79 changes: 51 additions & 28 deletions R/luna_check.R
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#' Syntax-check a NONMEM model
#' Syntax-check a model
#'
#' For NONMEM models (`method = "pharmpy"`, `"psn"`, `"nmfe"`): uses pharmpy
#' to parse the model file. For ferx models (`method = "ferx"`): verifies the
#' `.ferx` file exists.
#'
#' @inheritParams luna_run
#'
#' @export
luna_check <- function(
id,
folder = NULL,
verbose = FALSE,
...
id,
folder = NULL,
verbose = FALSE,
...
) {

id <- unlist(lapply(id, validate_id))
Expand All @@ -30,42 +34,61 @@ luna_check <- function(
verbose = FALSE
)

# Transform folder path to absolute path
folder <- normalizePath(folder, mustWork = TRUE)

# read the model file with nm_read_model()
method <- ifelse0(config$tools$modelfit$method, "pharmpy")

## ferx: check file existence only
if (method == "ferx") {
model_file <- file.path(folder, paste0(id, ".ferx"))
if (!file.exists(model_file)) {
cli::cli_alert_warning("ferx model file not found: {.file {model_file}}")
return(invisible(FALSE))
}
cli::cli_alert_success("ferx model file found: {.file {model_file}}")
return(invisible(TRUE))
}

## NONMEM path
model_file <- file.path(folder, paste0(id, ".mod"))
if(! file.exists(model_file)) {
if (!file.exists(model_file)) {
cli::cli_abort("Model file for run {id} not found!")
}
model <- pharmr::read_model(model_file)

# Some integrity checksa
if(! inherits(model, "pharmpy.model.model.Model")) {
cli::cli_abort("Model is not a pharmpy model. Please check the model file.")
model <- tryCatch(
pharmr::read_model(model_file),
error = function(e) {
cli::cli_alert_warning("Model has a syntax error:")
message(conditionMessage(e))
return(invisible(FALSE))
}
)

if (isFALSE(model)) return(invisible(FALSE))

if (!inherits(model, "pharmpy.model.model.Model")) {
cli::cli_alert_warning("Model could not be parsed as a pharmpy model.")
return(invisible(FALSE))
}
if(is.null(model$dataset)) {
cli::cli_abort("Model has no dataset. Please check the model and dataset files.")

if (is.null(model$dataset)) {
cli::cli_alert_warning("Model parsed but dataset could not be loaded. Check the {.field $DATA} path.")
return(invisible(FALSE))
}

cli::cli_alert_success("Model loaded successfully.")

model_ok <- pharmr.extra::run_nlme(
model = model,
id = id,
path = folder,
method = "nmfe",
as_job = as_job,
check_only = TRUE, # !! don't run the model, only check it using NM-TRAN
console = FALSE,
verbose = verbose,
...
model,
check_only = TRUE,
verbose = verbose
)

if(model_ok) {
cli::cli_alert_success("Model syntax OK!")
} else {
cli::cli_alert_warning("Model seems to have syntax error")
cat(attr(model_ok, "message"))
if (isFALSE(model_ok)) {
cli::cli_alert_warning("Model failed NONMEM compilation check.")
return(invisible(FALSE))
}

cli::cli_alert_success("Model syntax OK!")
invisible(TRUE)
}
44 changes: 44 additions & 0 deletions R/luna_ferx_info.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#' Show fit results for a ferx model run
#'
#' Reads the `{id}-fit.rds` file saved by [luna_run()] (when
#' `method = "ferx"`) and prints a formatted parameter summary.
#'
#' @inheritParams luna_run
#'
#' @return The `ferx_fit` result object, invisibly.
#'
#' @export
luna_ferx_info <- function(id, folder = NULL) {
id <- validate_id(id)
if (is.null(folder)) {
folder <- .luna_cache$get("project")$metadata$folder
}
folder <- normalizePath(folder, mustWork = TRUE)

result_file <- file.path(folder, paste0(id, "-fit.rds"))
if (!file.exists(result_file)) {
cli::cli_abort(
c(
"No ferx results found for run {.val {id}}.",
"i" = "Run {.fn luna_run} first, or check that {.file {result_file}} exists."
)
)
}

result <- readRDS(result_file)

cli::cli_h1("ferx fit: {id}")
cli::cli_alert_info("Method: {result$method}")
cli::cli_alert_info("Converged: {result$converged}")
cli::cli_alert_info("OFV: {round(result$ofv, 4)}")
if (!is.null(result$aic))
cli::cli_alert_info("AIC: {round(result$aic, 4)}")
if (!is.null(result$bic))
cli::cli_alert_info("BIC: {round(result$bic, 4)}")
if (!is.null(result$n_iterations))
cli::cli_alert_info("Iterations: {result$n_iterations}")
if (!is.null(result$wall_time_secs))
cli::cli_alert_info("Wall time: {round(result$wall_time_secs, 1)} s")

invisible(result)
}
16 changes: 13 additions & 3 deletions R/luna_load_project.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,21 @@ luna_load_project <- function(
)
class(project) <- c("luna.project", class(project))

if(dir.exists(paste0(".luna.cache.", name))) {
cache_folder <- file.path(folder, paste0(".luna.cache.", name))
if(dir.exists(cache_folder)) {
is_cache_available <- is_luna_cache_available(abort = FALSE)
if(!is_cache_available) {
cached_folder <- if(
is_cache_available && .luna_cache$exists("folder")
) .luna_cache$get("folder") else NULL
cached_name <- if(
is_cache_available && .luna_cache$exists("name")
) .luna_cache$get("name") else NULL
if(
!is_cache_available ||
!identical(cached_folder, folder) ||
!identical(cached_name, name)
) {
if(verbose) cli::cli_alert_info("Reloading luna project cache")
cache_folder <- file.path(folder, paste0(".luna.cache.", name))
.luna_cache <<- cachem::cache_disk(dir = cache_folder)
}
if(verbose) cli::cli_alert_info("Updating luna project cache")
Expand Down
Loading
Loading