From 11641062f9b00c3631f92e37002949ad23aa171a Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 19 Jul 2024 20:11:52 -0500 Subject: [PATCH 01/16] try out mwrite_cpp --- NAMESPACE | 1 + R/mwrite.R | 67 +++++++++++++++++++++++++++++++++++++++++++++++---- man/mwrite.Rd | 3 +++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 13cc53e9..6f0e87f6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -150,6 +150,7 @@ export(mrgsim_i) export(mrgsim_q) export(mutate_sims) export(mvgauss) +export(mwrite_cpp) export(mwrite_yaml) export(numerics_only) export(obsaug) diff --git a/R/mwrite.R b/R/mwrite.R index 32f58425..58996ec6 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -9,7 +9,16 @@ tocode <- function(l) { get_upper_tri <- function(x) { x <- as.matrix(x) - x[upper.tri(x, diag = TRUE)] + n <- nrow(x) + x <- x[upper.tri(x, diag = TRUE)] + text <- vector(mode = "character", length = n) + w <- 1 + for(i in seq(n)) { + s <- seq(w, w+i-1) + text[i] <- paste(x[s], collapse = " ") + w <- w + i + } + text } require_yaml <- function() { @@ -186,6 +195,49 @@ mwrite_yaml <- function(x, file, digits = 8) { invisible(l) } +#' Write a model to native mrgsolve format +#' +#' Model code is written to a file in native mrgsolve format. This +#' can be useful for (1) breaking connection to NONMEM modeling outputs that +#' are imported by `$NMXML` or `$NMEXT` and (2) saving model updates (e.g., +#' an updated parameter list). Models can be read back using [mread()]. +#' +#' @inheritParams mwrite_yaml +#' @inheritParams yaml_to_cpp +#' +#' @details +#' See important details in [mwrite_yaml()]. +#' +#' @return +#' A list containing data that was written out to the cpp file, with added +#' item `file`, is returned invisibly. +#' +#' @examples +#' temp <- tempfile(fileext = ".mod") +#' +#' mod <- modlib("pk1", compile = FALSE) +#' +#' x <- mwrite_cpp(mod, file = temp) +#' +#' mod <- mread(x$file, compile = FALSE) +#' +#' mod +#' +#' @seealso [mwrite_yaml()], [yaml_to_cpp()] +#' +#' @md +#' @rdname mwrite +#' @export +mwrite_cpp <- function(x, file, update = TRUE) { + l <- mwrite_model_to_list(x) + code <- parsed_to_cpp_code(l, update = update) + if(is.character(file)) { + writeLines(code, con = file) + } + l$file <- file + invisible(l) +} + #' Read a model from yaml format #' #' Read back models written to file using [mwrite_yaml()]. Function @@ -253,6 +305,14 @@ yaml_to_cpp <- function(file, model = basename(file), project = getwd(), # Take in content parsed from yaml file, clean up, write to cpp file # @return a cleaned-up version of x with `cppfile` slot added parsed_to_cppfile <- function(x, model, project, update = FALSE) { + code <- parsed_to_cpp_code(x, update) + cppfile <- file.path(project, paste0(model, ".mod")) + writeLines(con = cppfile, code) + x$cppfile <- cppfile + x +} + +parsed_to_cpp_code <- function(x, update = FALSE) { prob <- character(0) if(sum(nchar(x$prob))) { prob <- c("$PROB", x$prob, "") @@ -317,10 +377,7 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { code <- c(code, set) } - cppfile <- file.path(project, paste0(model, ".mod")) - writeLines(con = cppfile, code) - x$cppfile <- cppfile - x + code } # Take in parsed content from yaml file diff --git a/man/mwrite.Rd b/man/mwrite.Rd index 59dd7eb6..f15ffe85 100644 --- a/man/mwrite.Rd +++ b/man/mwrite.Rd @@ -3,9 +3,12 @@ \name{mwrite} \alias{mwrite} \alias{mwrite_yaml} +\alias{mwrite_cpp} \title{Write model code to yaml format} \usage{ mwrite_yaml(x, file, digits = 8) + +mwrite_cpp(x, file) } \arguments{ \item{x}{a model object.} From e1953f61fc8665c56dbb8e543986e6415dfe5e8d Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 19 Jul 2024 20:49:03 -0500 Subject: [PATCH 02/16] simpler mwrite_cpp that uses existing function that works on yaml --- R/mwrite.R | 26 ++++++++++++-------------- man/mwrite.Rd | 27 ++++++++++++++++++++++++++- tests/testthat/test-mwrite.R | 5 +++-- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index 58996ec6..916d0a9a 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -202,7 +202,7 @@ mwrite_yaml <- function(x, file, digits = 8) { #' are imported by `$NMXML` or `$NMEXT` and (2) saving model updates (e.g., #' an updated parameter list). Models can be read back using [mread()]. #' -#' @inheritParams mwrite_yaml +#' @inheritParams mwrite #' @inheritParams yaml_to_cpp #' #' @details @@ -230,9 +230,12 @@ mwrite_yaml <- function(x, file, digits = 8) { #' @export mwrite_cpp <- function(x, file, update = TRUE) { l <- mwrite_model_to_list(x) - code <- parsed_to_cpp_code(l, update = update) + temp <- tempfile() + model <- basename(temp) + project <- dirname(temp) + l <- parsed_to_cppfile(l, model = model, project = project, update = update) if(is.character(file)) { - writeLines(code, con = file) + file.copy(temp, file) } l$file <- file invisible(l) @@ -303,16 +306,8 @@ yaml_to_cpp <- function(file, model = basename(file), project = getwd(), } # Take in content parsed from yaml file, clean up, write to cpp file -# @return a cleaned-up version of x with `cppfile` slot added +# @return a cleaned-up version of x with slots for `cppfile` added parsed_to_cppfile <- function(x, model, project, update = FALSE) { - code <- parsed_to_cpp_code(x, update) - cppfile <- file.path(project, paste0(model, ".mod")) - writeLines(con = cppfile, code) - x$cppfile <- cppfile - x -} - -parsed_to_cpp_code <- function(x, update = FALSE) { prob <- character(0) if(sum(nchar(x$prob))) { prob <- c("$PROB", x$prob, "") @@ -376,8 +371,11 @@ parsed_to_cpp_code <- function(x, update = FALSE) { set <- c("$SET", set, "") code <- c(code, set) } - - code + + cppfile <- file.path(project, paste0(model, ".mod")) + writeLines(con = cppfile, code) + x$cppfile <- cppfile + x } # Take in parsed content from yaml file diff --git a/man/mwrite.Rd b/man/mwrite.Rd index f15ffe85..0a716e3c 100644 --- a/man/mwrite.Rd +++ b/man/mwrite.Rd @@ -8,7 +8,7 @@ \usage{ mwrite_yaml(x, file, digits = 8) -mwrite_cpp(x, file) +mwrite_cpp(x, file, update = TRUE) } \arguments{ \item{x}{a model object.} @@ -17,10 +17,16 @@ mwrite_cpp(x, file) will be written to file.} \item{digits}{precision to use when writing outputs.} + +\item{update}{\code{TRUE} if model settings should be written into the cpp file in +a \verb{$SET} block.} } \value{ A list containing data that was written out to the yaml file, with added item \code{file}, is returned invisibly. + +A list containing data that was written out to the cpp file, with added +item \code{file}, is returned invisibly. } \description{ Model code is written to a readable, transport format. This transport format @@ -28,6 +34,11 @@ can be useful for (1) breaking connection to NONMEM modeling outputs that are imported by \verb{$NMXML} or \verb{$NMEXT} and (2) saving model updates (e.g., an updated parameter list). Models can be read back using \code{\link[=mread_yaml]{mread_yaml()}} or converted to mrgsolve cpp format with \code{\link[=yaml_to_cpp]{yaml_to_cpp()}}. + +Model code is written to a file in native mrgsolve format. This +can be useful for (1) breaking connection to NONMEM modeling outputs that +are imported by \verb{$NMXML} or \verb{$NMEXT} and (2) saving model updates (e.g., +an updated parameter list). Models can be read back using \code{\link[=mread]{mread()}}. } \details{ Parameters and omega and sigma matrices that were imported via \verb{$NMXML} @@ -44,6 +55,8 @@ code that might be sourced in by the user when the model is loaded via \code{\link[=mread]{mread()}}. NONMEM xml and ext files imported by \verb{$NMXML} or \verb{$NMEXT} are the \emph{only} external dependencies that are accounted for in the yaml transport file. + +See important details in \code{\link[=mwrite_yaml]{mwrite_yaml()}}. } \examples{ mod <- house() @@ -54,7 +67,19 @@ x <- mwrite_yaml(mod, temp1) readLines(temp1) +temp <- tempfile(fileext = ".mod") + +mod <- modlib("pk1", compile = FALSE) + +x <- mwrite_cpp(mod, file = temp) + +mod <- mread(x$file, compile = FALSE) + +mod + } \seealso{ \code{\link[=mread_yaml]{mread_yaml()}}, \code{\link[=yaml_to_cpp]{yaml_to_cpp()}} + +\code{\link[=mwrite_yaml]{mwrite_yaml()}}, \code{\link[=yaml_to_cpp]{yaml_to_cpp()}} } diff --git a/tests/testthat/test-mwrite.R b/tests/testthat/test-mwrite.R index 55a970bc..27acbdec 100644 --- a/tests/testthat/test-mwrite.R +++ b/tests/testthat/test-mwrite.R @@ -42,13 +42,14 @@ test_that("convert model to list", { expect_equal(l$set$end, 120) expect_equal(l$set$delta, 0.25) + nomega <- nrow(omat(mod)) expect_length(l$omega, 3) expect_equal(names(l$sigma), c("data", "labels", "names")) expect_is(l$omega$data, "list") expect_length(l$omega$data, 1) - expect_length(l$omega$data[[1]], 10) + expect_length(l$omega$data[[1]], nomega) expect_length(l$omega$labels, 1) - expect_length(l$omega$labels[[1]], 4) + expect_length(l$omega$labels[[1]], nomega) expect_is(l$omega$labels, "list") expect_length(l$omega$names, 1) expect_is(l$omega$names, "character") From abfb55a13abe3ddd8cc970b2cdb40ca798fb95c8 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 19 Jul 2024 22:02:20 -0500 Subject: [PATCH 03/16] more naming of omega and sigma objects --- R/mwrite.R | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index 916d0a9a..c24e11af 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -11,14 +11,16 @@ get_upper_tri <- function(x) { x <- as.matrix(x) n <- nrow(x) x <- x[upper.tri(x, diag = TRUE)] - text <- vector(mode = "character", length = n) + x + l <- vector(mode = "list", length = n) + names(l) <- paste0("row",seq(n)) w <- 1 for(i in seq(n)) { s <- seq(w, w+i-1) - text[i] <- paste(x[s], collapse = " ") - w <- w + i + l[[i]] <- x[s] + w <- w + i } - text + l } require_yaml <- function() { @@ -52,12 +54,18 @@ mwrite_model_to_list <- function(x) { l$omega <- list() l$omega$data <- lapply(as.list(omat(x)), get_upper_tri) l$omega$labels <- labels(omat(x)) - names(l$omega$labels) <- seq_along(l$omega$labels) l$omega$names <- names(omat(x)) + if(length(l$omega$data)) { + names(l$omega$data) <- paste0("matrix", seq_along(l$omega$data)) + names(l$omega$labels) <- paste0("matrix",seq_along(l$omega$labels)) + } l$sigma$data <- lapply(as.list(smat(x)), get_upper_tri) l$sigma$labels <- labels(smat(x)) - names(l$sigma$labels) <- seq_along(l$sigma$labels) l$sigma$names <- names(smat(x)) + if(length(l$sigma$data)) { + names(l$sigma$data) <- paste0("matrix", seq_along(l$sigma$data)) + names(l$sigma$labels) <- paste0("matrix",seq_along(l$sigma$labels)) + } # Other l$env <- as.list(x@envir) l$plugin <- x@plugin @@ -334,12 +342,13 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { x$omega$names <- lapply(x$omega$names, as.character) } for(i in seq_along(x$omega$data)) { + datai <- unlist(x$omega$data[[i]], use.names = FALSE) header <- "@block" if(any(x$omega$labels[[i]] != "...")) { o_labels <- paste0(x$omega$labels[[i]], collapse = " ") header <- c(header, paste0("@labels ", o_labels)) - } - omega <- c(omega, "$OMEGA", header, x$omega$data[[i]], "") + } + omega <- c(omega, "$OMEGA", header, datai, "") } sigma <- character(0) @@ -348,12 +357,13 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { x$sigma$names <- lapply(x$sigma$names, as.character) } for(i in seq_along(x$sigma$data)) { + datai <- unlist(x$sigma$data[[i]], use.names = FALSE) header <- "@block" if(any(x$sigma$labels[[i]] != "...")) { s_labels <- paste0(x$sigma$labels[[i]], collapse = " ") header <- c(header, paste0("@labels ", s_labels)) } - sigma <- c(sigma, "$SIGMA", header, x$sigma$data[[i]], "") + sigma <- c(sigma, "$SIGMA", header, datai, "") } code <- c(prob, param, init, omega, sigma, x$code, capture) @@ -371,7 +381,7 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { set <- c("$SET", set, "") code <- c(code, set) } - + cppfile <- file.path(project, paste0(model, ".mod")) writeLines(con = cppfile, code) x$cppfile <- cppfile From efc231fea9105774d54c25027d4d0dda00200033 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 19 Jul 2024 22:07:53 -0500 Subject: [PATCH 04/16] clean up comment --- R/mwrite.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mwrite.R b/R/mwrite.R index c24e11af..feedcfe1 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -314,7 +314,7 @@ yaml_to_cpp <- function(file, model = basename(file), project = getwd(), } # Take in content parsed from yaml file, clean up, write to cpp file -# @return a cleaned-up version of x with slots for `cppfile` added +# @return a cleaned-up version of x with slot for `cppfile` added parsed_to_cppfile <- function(x, model, project, update = FALSE) { prob <- character(0) if(sum(nchar(x$prob))) { From 028c6ee996ceb225f1087b67c6a6b09c5bc96bb8 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 19 Jul 2024 22:59:42 -0500 Subject: [PATCH 05/16] adds tests for missing names for omega and sigma --- R/mwrite.R | 9 ++++++++- tests/testthat/test-mwrite.R | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/R/mwrite.R b/R/mwrite.R index feedcfe1..de842f92 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -13,7 +13,7 @@ get_upper_tri <- function(x) { x <- x[upper.tri(x, diag = TRUE)] x l <- vector(mode = "list", length = n) - names(l) <- paste0("row",seq(n)) + names(l) <- paste0("row", seq(n)) w <- 1 for(i in seq(n)) { s <- seq(w, w+i-1) @@ -59,6 +59,7 @@ mwrite_model_to_list <- function(x) { names(l$omega$data) <- paste0("matrix", seq_along(l$omega$data)) names(l$omega$labels) <- paste0("matrix",seq_along(l$omega$labels)) } + l$sigma <- list() l$sigma$data <- lapply(as.list(smat(x)), get_upper_tri) l$sigma$labels <- labels(smat(x)) l$sigma$names <- names(smat(x)) @@ -344,6 +345,9 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { for(i in seq_along(x$omega$data)) { datai <- unlist(x$omega$data[[i]], use.names = FALSE) header <- "@block" + if(x$omega$names[[i]] != "...") { + header <- c(header, paste0("@name ", x$omega$names[[i]])) + } if(any(x$omega$labels[[i]] != "...")) { o_labels <- paste0(x$omega$labels[[i]], collapse = " ") header <- c(header, paste0("@labels ", o_labels)) @@ -359,6 +363,9 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { for(i in seq_along(x$sigma$data)) { datai <- unlist(x$sigma$data[[i]], use.names = FALSE) header <- "@block" + if(x$sigma$names[[i]] != "...") { + header <- c(header, paste0("@name ", x$sigma$names[[i]])) + } if(any(x$sigma$labels[[i]] != "...")) { s_labels <- paste0(x$sigma$labels[[i]], collapse = " ") header <- c(header, paste0("@labels ", s_labels)) diff --git a/tests/testthat/test-mwrite.R b/tests/testthat/test-mwrite.R index 27acbdec..fca62486 100644 --- a/tests/testthat/test-mwrite.R +++ b/tests/testthat/test-mwrite.R @@ -164,3 +164,36 @@ test_that("captures are handled", { m <- mread_yaml(temp3, compile = FALSE) expect_equivalent(m@capture, c(V = "b", CL = "a")) }) + +test_that("handle multiple unnamed matrices", { + skip_if_not_installed("yaml") + temp <- tempfile() + code <- '$OMEGA 1 2 3\n$OMEGA 3 4 5 6' + mod <- mcode("foo", code, compile = FALSE) + a <- mwrite_yaml(mod, file = temp) + yam <- yaml::yaml.load_file(temp)$omega + expect_equal(names(yam$data), paste0("matrix", 1:2)) + expect_equal(names(yam$labels), paste0("matrix", 1:2)) + expect_equal(names(yam$data$matrix1), paste0("row", 1:3)) + expect_equal(names(yam$data$matrix2), paste0("row", 1:4)) + expect_equal(a$file, temp) + mod2 <- mread_yaml(file = temp, compile = FALSE) + expect_identical(mod@omega, mod2@omega) +}) + +test_that("matrix names are retained", { + skip_if_not_installed("yaml") + code <- '$OMEGA 1 2 3\n@name metrum\n$SIGMA 1 2\n @name rg @labels a b' + mod <- mcode("foo", code, compile = FALSE) + expect_equal(names(omat(mod)), "metrum") + expect_equal(names(smat(mod)), "rg") + temp <- tempfile() + x <- mwrite_yaml(mod, file = temp) + yam <- yaml::yaml.load_file(x$file) + yam <- as.list(yam) + expect_equal(yam$omega$names, "metrum") + expect_equal(yam$sigma$names, "rg") + mod2 <- mread_yaml(temp, compile = FALSE) + expect_equal(names(omat(mod2)), "metrum") + expect_equal(names(smat(mod2)), "rg") +}) From 2d8f9475cf0616fd5faa8dc4af69527410f5dafc Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 08:15:15 -0500 Subject: [PATCH 06/16] fix tests --- R/mwrite.R | 22 +++++++++++++--------- tests/testthat/test-mwrite.R | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index de842f92..e5245134 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -244,7 +244,7 @@ mwrite_cpp <- function(x, file, update = TRUE) { project <- dirname(temp) l <- parsed_to_cppfile(l, model = model, project = project, update = update) if(is.character(file)) { - file.copy(temp, file) + file.copy(l$cppfile, file, overwrite = TRUE) } l$file <- file invisible(l) @@ -344,12 +344,14 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { } for(i in seq_along(x$omega$data)) { datai <- unlist(x$omega$data[[i]], use.names = FALSE) + labelsi <- x$omega$labels[[i]] + namei <- x$omega$names[[i]] header <- "@block" - if(x$omega$names[[i]] != "...") { - header <- c(header, paste0("@name ", x$omega$names[[i]])) + if(namei != "...") { + header <- c(header, paste0("@name ", namei)) } - if(any(x$omega$labels[[i]] != "...")) { - o_labels <- paste0(x$omega$labels[[i]], collapse = " ") + if(any(labelsi != "...")) { + o_labels <- paste0(labelsi, collapse = " ") header <- c(header, paste0("@labels ", o_labels)) } omega <- c(omega, "$OMEGA", header, datai, "") @@ -362,12 +364,14 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { } for(i in seq_along(x$sigma$data)) { datai <- unlist(x$sigma$data[[i]], use.names = FALSE) + labelsi <- x$sigma$labels[[i]] + namei <- x$sigma$names[[i]] header <- "@block" - if(x$sigma$names[[i]] != "...") { - header <- c(header, paste0("@name ", x$sigma$names[[i]])) + if(namei != "...") { + header <- c(header, paste0("@name ", namei)) } - if(any(x$sigma$labels[[i]] != "...")) { - s_labels <- paste0(x$sigma$labels[[i]], collapse = " ") + if(any(labelsi != "...")) { + s_labels <- paste0(labelsi, collapse = " ") header <- c(header, paste0("@labels ", s_labels)) } sigma <- c(sigma, "$SIGMA", header, datai, "") diff --git a/tests/testthat/test-mwrite.R b/tests/testthat/test-mwrite.R index fca62486..c3a45f58 100644 --- a/tests/testthat/test-mwrite.R +++ b/tests/testthat/test-mwrite.R @@ -122,7 +122,7 @@ test_that("imposter code", { yaml <- yaml::as.yaml(x) cat(yaml, file = temp) expect_error( - mread_yaml(temp), + mread_yaml(temp, compile = FALSE), "was not written by `mwrite_yaml()`.", fixed = TRUE ) From 7485b8244ddbe76c2f107bc8c99a07bacc0381b7 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 08:29:10 -0500 Subject: [PATCH 07/16] test for get_upper_tri --- R/mwrite.R | 3 +-- tests/testthat/test-mwrite.R | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index e5245134..09aea75f 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -11,15 +11,14 @@ get_upper_tri <- function(x) { x <- as.matrix(x) n <- nrow(x) x <- x[upper.tri(x, diag = TRUE)] - x l <- vector(mode = "list", length = n) - names(l) <- paste0("row", seq(n)) w <- 1 for(i in seq(n)) { s <- seq(w, w+i-1) l[[i]] <- x[s] w <- w + i } + names(l) <- paste0("row", seq(n)) l } diff --git a/tests/testthat/test-mwrite.R b/tests/testthat/test-mwrite.R index c3a45f58..005e9921 100644 --- a/tests/testthat/test-mwrite.R +++ b/tests/testthat/test-mwrite.R @@ -197,3 +197,12 @@ test_that("matrix names are retained", { expect_equal(names(omat(mod2)), "metrum") expect_equal(names(smat(mod2)), "rg") }) + +test_that("render matrix as list of numeric rows", { + mat <- matrix(rnorm(25), nrow = 5, ncol = 5) + l <- mrgsolve:::get_upper_tri(mat) + expect_equal(names(l), paste0("row", 1:5)) + for(j in 1:5) { + expect_equal(l[[j]], mat[1:j, j]) + } +}) From b7e34385adbb817b236ac6cf79aa1bd6311645d9 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 09:25:21 -0500 Subject: [PATCH 08/16] insert row numbers when writing matrices to cpp file --- R/mwrite.R | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index 09aea75f..f7dcfada 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -342,9 +342,10 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { x$omega$names <- lapply(x$omega$names, as.character) } for(i in seq_along(x$omega$data)) { - datai <- unlist(x$omega$data[[i]], use.names = FALSE) + datai <- x$omega$data[[i]] labelsi <- x$omega$labels[[i]] namei <- x$omega$names[[i]] + omega <- "$OMEGA" header <- "@block" if(namei != "...") { header <- c(header, paste0("@name ", namei)) @@ -353,7 +354,12 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { o_labels <- paste0(labelsi, collapse = " ") header <- c(header, paste0("@labels ", o_labels)) } - omega <- c(omega, "$OMEGA", header, datai, "") + omega <- c(omega, header) + for(i in seq_along(datai)) { + tag <- paste0("// row ", i) + omega <- c(omega, tag, datai[[i]]) + } + omega <- c(omega, "") } sigma <- character(0) @@ -362,9 +368,10 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { x$sigma$names <- lapply(x$sigma$names, as.character) } for(i in seq_along(x$sigma$data)) { - datai <- unlist(x$sigma$data[[i]], use.names = FALSE) + datai <- x$sigma$data[[i]] labelsi <- x$sigma$labels[[i]] namei <- x$sigma$names[[i]] + sigma <- "$SIGMA" header <- "@block" if(namei != "...") { header <- c(header, paste0("@name ", namei)) @@ -373,7 +380,12 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { s_labels <- paste0(labelsi, collapse = " ") header <- c(header, paste0("@labels ", s_labels)) } - sigma <- c(sigma, "$SIGMA", header, datai, "") + sigma <- c(sigma, header) + for(i in seq_along(datai)) { + tag <- paste0("// row ", i) + sigma <- c(sigma, tag, datai[[i]]) + } + sigma <- c(sigma, "") } code <- c(prob, param, init, omega, sigma, x$code, capture) From d620eec88e7e485206ddc3f4d39d9b92b10ae4c0 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 09:50:53 -0500 Subject: [PATCH 09/16] passing check now --- R/mwrite.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index f7dcfada..863fc8ac 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -345,7 +345,7 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { datai <- x$omega$data[[i]] labelsi <- x$omega$labels[[i]] namei <- x$omega$names[[i]] - omega <- "$OMEGA" + omega <- c(omega, "$OMEGA") header <- "@block" if(namei != "...") { header <- c(header, paste0("@name ", namei)) @@ -371,7 +371,7 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { datai <- x$sigma$data[[i]] labelsi <- x$sigma$labels[[i]] namei <- x$sigma$names[[i]] - sigma <- "$SIGMA" + sigma <- c(sigma, "$SIGMA") header <- "@block" if(namei != "...") { header <- c(header, paste0("@name ", namei)) From 533d23aaf454db03f0493a5080d247303d4aa199 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 15:22:34 -0500 Subject: [PATCH 10/16] de-duplicate code for turning matrix into cpp code --- R/mwrite.R | 71 +++++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index 863fc8ac..b30334be 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -7,6 +7,31 @@ tocode <- function(l) { paste0(names(l), " = ", as.character(l)) } +mwrite_matrix <- function(x, block_name) { + code <- character(0) + for(i in seq_along(x$data)) { + datai <- x$data[[i]] + labelsi <- x$labels[[i]] + namei <- x$names[[i]] + code <- c(code, block_name) + header <- "@block" + if(namei != "...") { + header <- c(header, paste0("@name ", namei)) + } + if(any(labelsi != "...")) { + labels_ <- paste0(labelsi, collapse = " ") + header <- c(header, paste0("@labels ", labels_)) + } + code <- c(code, header) + for(i in seq_along(datai)) { + tag <- paste0("// row ", i) + code <- c(code, tag, datai[[i]]) + } + code <- c(code, "") + } + code +} + get_upper_tri <- function(x) { x <- as.matrix(x) n <- nrow(x) @@ -335,58 +360,18 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { } x$update$add <- as.numeric(x$update$add) - - omega <- character(0) + if(length(x$omega$data)) { x$omega$labels <- lapply(x$omega$labels, as.character) x$omega$names <- lapply(x$omega$names, as.character) } - for(i in seq_along(x$omega$data)) { - datai <- x$omega$data[[i]] - labelsi <- x$omega$labels[[i]] - namei <- x$omega$names[[i]] - omega <- c(omega, "$OMEGA") - header <- "@block" - if(namei != "...") { - header <- c(header, paste0("@name ", namei)) - } - if(any(labelsi != "...")) { - o_labels <- paste0(labelsi, collapse = " ") - header <- c(header, paste0("@labels ", o_labels)) - } - omega <- c(omega, header) - for(i in seq_along(datai)) { - tag <- paste0("// row ", i) - omega <- c(omega, tag, datai[[i]]) - } - omega <- c(omega, "") - } + omega <- mwrite_matrix(x$omega, "$OMEGA") - sigma <- character(0) if(length(x$sigma$data)) { x$sigma$labels <- lapply(x$sigma$labels, as.character) x$sigma$names <- lapply(x$sigma$names, as.character) } - for(i in seq_along(x$sigma$data)) { - datai <- x$sigma$data[[i]] - labelsi <- x$sigma$labels[[i]] - namei <- x$sigma$names[[i]] - sigma <- c(sigma, "$SIGMA") - header <- "@block" - if(namei != "...") { - header <- c(header, paste0("@name ", namei)) - } - if(any(labelsi != "...")) { - s_labels <- paste0(labelsi, collapse = " ") - header <- c(header, paste0("@labels ", s_labels)) - } - sigma <- c(sigma, header) - for(i in seq_along(datai)) { - tag <- paste0("// row ", i) - sigma <- c(sigma, tag, datai[[i]]) - } - sigma <- c(sigma, "") - } + sigma <- mwrite_matrix(x$sigma, "$SIGMA") code <- c(prob, param, init, omega, sigma, x$code, capture) From ce407ac031165aac2c3bf82bdff8d45127968a63 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 15:26:15 -0500 Subject: [PATCH 11/16] lint / reorg --- R/mwrite.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index b30334be..e1deac14 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -345,7 +345,6 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { if(sum(nchar(x$prob))) { prob <- c("$PROB", x$prob, "") } - param <- character(0) if(length(x$param)) { param <- c("$PARAM", tocode(x$param), "") @@ -358,8 +357,6 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { if(length(x$capture)) { capture <- c("$CAPTURE", x$capture, "") } - - x$update$add <- as.numeric(x$update$add) if(length(x$omega$data)) { x$omega$labels <- lapply(x$omega$labels, as.character) @@ -377,6 +374,8 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { set <- tocode(x$set) + x$update$add <- as.numeric(x$update$add) + if(isTRUE(update)) { if(length(set)) { set <- c(set, " ") From ad9137922549a795990d4d92f03421fdd2957dc1 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Sat, 20 Jul 2024 21:01:58 -0500 Subject: [PATCH 12/16] move some of the 'cleanup' code into a separate function --- .Rbuildignore | 2 +- R/mwrite.R | 38 ++++++++++++++----------- inst/maintenance/reprex/reprex-mwrite.R | 28 ++++++++++++++++++ 3 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 inst/maintenance/reprex/reprex-mwrite.R diff --git a/.Rbuildignore b/.Rbuildignore index 6a9068d5..85598dc8 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -29,7 +29,7 @@ inst/project/.*[.]so$ inst/models/.*[.]o$ inst/models/.*[.]so$ inst/models/.*[.]check$ -inst/maintenance +inst/maintenance src/.*[.]tar.gz$ .*[.]tar.gz$ img diff --git a/R/mwrite.R b/R/mwrite.R index e1deac14..3f540e97 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -7,6 +7,7 @@ tocode <- function(l) { paste0(names(l), " = ", as.character(l)) } +# block name is $OMEGA or $SIGMA mwrite_matrix <- function(x, block_name) { code <- character(0) for(i in seq_along(x$data)) { @@ -162,9 +163,7 @@ mwrite_model_to_list <- function(x) { code <- Map(code, names(code), f = function(text, name) { c(glue("${name}"), text, " ") }) - l$code <- unlist(code, use.names = FALSE) - l } @@ -270,6 +269,8 @@ mwrite_cpp <- function(x, file, update = TRUE) { if(is.character(file)) { file.copy(l$cppfile, file, overwrite = TRUE) } + unlink(temp) + l$cppfile <- NULL l$file <- file invisible(l) } @@ -323,12 +324,29 @@ mwrite_parse_yaml <- function(file) { require_yaml() text <- readLines(file) l <- yaml::yaml.load(text) + l <- mwrite_read_cleanup(l) if(!identical(l$source, "mrgsolve::mwrite")) { abort("the yaml source file was not written by `mwrite_yaml()`.") } l } +# Right after reading from yaml, there is usually a bunch of little oddities +# that need to be cleaned up so we can keep working in R +mwrite_read_cleanup <- function(x) { + # This usually is rendered as an empty list, but needs to be numeric + x$update$add <- as.numeric(x$update$add) + if(length(x$omega$data)) { + x$omega$labels <- lapply(x$omega$labels, as.character) + x$omega$names <- lapply(x$omega$names, as.character) + } + if(length(x$sigma$data)) { + x$sigma$labels <- lapply(x$sigma$labels, as.character) + x$sigma$names <- lapply(x$sigma$names, as.character) + } + x +} + #' @rdname mread_yaml #' @export yaml_to_cpp <- function(file, model = basename(file), project = getwd(), @@ -357,32 +375,18 @@ parsed_to_cppfile <- function(x, model, project, update = FALSE) { if(length(x$capture)) { capture <- c("$CAPTURE", x$capture, "") } - - if(length(x$omega$data)) { - x$omega$labels <- lapply(x$omega$labels, as.character) - x$omega$names <- lapply(x$omega$names, as.character) - } omega <- mwrite_matrix(x$omega, "$OMEGA") - - if(length(x$sigma$data)) { - x$sigma$labels <- lapply(x$sigma$labels, as.character) - x$sigma$names <- lapply(x$sigma$names, as.character) - } sigma <- mwrite_matrix(x$sigma, "$SIGMA") code <- c(prob, param, init, omega, sigma, x$code, capture) - + set <- tocode(x$set) - - x$update$add <- as.numeric(x$update$add) - if(isTRUE(update)) { if(length(set)) { set <- c(set, " ") } set <- c(set, tocode(x$update)) } - if(length(set)) { set <- c("$SET", set, "") code <- c(code, set) diff --git a/inst/maintenance/reprex/reprex-mwrite.R b/inst/maintenance/reprex/reprex-mwrite.R new file mode 100644 index 00000000..1c33fa23 --- /dev/null +++ b/inst/maintenance/reprex/reprex-mwrite.R @@ -0,0 +1,28 @@ +library(mrgsolve) +library(dplyr) + +mod <- modlib("popex") + +#' Write to yaml +a <- mwrite_yaml(mod, "popex-2.yaml") + +yam <- readLines("popex-2.yaml") + +#' omega rendered like this +cat(yam[21:37], sep = "\n") + +#' Now, write to native mrgsolve format +x <- mwrite_cpp(mod, "popex-2.mod") + +mod2 <- mread(x$file) + +cat(mod2@code[14:26], sep = "\n") + +#' Simulate from this +mrgsim(mod2, ev(amt = 100, ID = 1:10)) %>% plot() + +#' Convert a matrix to something for writing out +omat(mod) + +mrgsolve:::get_upper_tri(omat(mod)) + From a9d1edb0346e33fd17ef85539cec1d9b7636b120 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Mon, 22 Jul 2024 10:06:38 -0500 Subject: [PATCH 13/16] remove as.list from test --- tests/testthat/test-mwrite.R | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/testthat/test-mwrite.R b/tests/testthat/test-mwrite.R index 005e9921..0bf6f6eb 100644 --- a/tests/testthat/test-mwrite.R +++ b/tests/testthat/test-mwrite.R @@ -190,7 +190,6 @@ test_that("matrix names are retained", { temp <- tempfile() x <- mwrite_yaml(mod, file = temp) yam <- yaml::yaml.load_file(x$file) - yam <- as.list(yam) expect_equal(yam$omega$names, "metrum") expect_equal(yam$sigma$names, "rg") mod2 <- mread_yaml(temp, compile = FALSE) From 290b75f57effd267d0a4b1714e96ae17a67cb69f Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Mon, 22 Jul 2024 10:10:36 -0500 Subject: [PATCH 14/16] update labels check from ... to . --- R/mwrite.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mwrite.R b/R/mwrite.R index 3f540e97..34bb60d6 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -19,7 +19,7 @@ mwrite_matrix <- function(x, block_name) { if(namei != "...") { header <- c(header, paste0("@name ", namei)) } - if(any(labelsi != "...")) { + if(any(labelsi != ".")) { labels_ <- paste0(labelsi, collapse = " ") header <- c(header, paste0("@labels ", labels_)) } From ba476718d13481c5419049515d2399bb55fe8a6d Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Mon, 22 Jul 2024 10:12:33 -0500 Subject: [PATCH 15/16] mwrite_cpp is a separate help topic --- R/mwrite.R | 1 - man/mwrite.Rd | 49 +++---------------------------------------------- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/R/mwrite.R b/R/mwrite.R index 34bb60d6..5a7861c6 100644 --- a/R/mwrite.R +++ b/R/mwrite.R @@ -212,7 +212,6 @@ mwrite_model_to_list <- function(x) { #' @seealso [mread_yaml()], [yaml_to_cpp()] #' #' @md -#' @name mwrite #' @export mwrite_yaml <- function(x, file, digits = 8) { require_yaml() diff --git a/man/mwrite.Rd b/man/mwrite.Rd index 0a716e3c..69009df5 100644 --- a/man/mwrite.Rd +++ b/man/mwrite.Rd @@ -1,72 +1,31 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/mwrite.R -\name{mwrite} -\alias{mwrite} -\alias{mwrite_yaml} +\name{mwrite_cpp} \alias{mwrite_cpp} -\title{Write model code to yaml format} +\title{Write a model to native mrgsolve format} \usage{ -mwrite_yaml(x, file, digits = 8) - mwrite_cpp(x, file, update = TRUE) } \arguments{ -\item{x}{a model object.} - -\item{file}{output file name; if non-character (e.g., \code{NULL}), no output -will be written to file.} - -\item{digits}{precision to use when writing outputs.} +\item{file}{the yaml file name.} \item{update}{\code{TRUE} if model settings should be written into the cpp file in a \verb{$SET} block.} } \value{ -A list containing data that was written out to the yaml file, with added -item \code{file}, is returned invisibly. - A list containing data that was written out to the cpp file, with added item \code{file}, is returned invisibly. } \description{ -Model code is written to a readable, transport format. This transport format -can be useful for (1) breaking connection to NONMEM modeling outputs that -are imported by \verb{$NMXML} or \verb{$NMEXT} and (2) saving model updates (e.g., -an updated parameter list). Models can be read back using \code{\link[=mread_yaml]{mread_yaml()}} or -converted to mrgsolve cpp format with \code{\link[=yaml_to_cpp]{yaml_to_cpp()}}. - Model code is written to a file in native mrgsolve format. This can be useful for (1) breaking connection to NONMEM modeling outputs that are imported by \verb{$NMXML} or \verb{$NMEXT} and (2) saving model updates (e.g., an updated parameter list). Models can be read back using \code{\link[=mread]{mread()}}. } \details{ -Parameters and omega and sigma matrices that were imported via \verb{$NMXML} -or \verb{$NMEXT} will be written into the yaml file and the NONMEM import blocks -will be dropped. This allows the user to load a model based on a NONMEM run -without having a connection to that output (e.g., \code{root.xml} or \code{root.ext}). -Given that the connection to the NONMEM modeling outputs is broken when -writing to yaml, any update to the NONMEM run will only be propagated to -the yaml file when \code{mwrite_yaml()} is run again. - -The yaml file does not currently have the ability to track -other external dependencies, such as user-defined header files or other -code that might be sourced in by the user when the model is loaded via -\code{\link[=mread]{mread()}}. NONMEM xml and ext files imported by \verb{$NMXML} or \verb{$NMEXT} are -the \emph{only} external dependencies that are accounted for in the yaml -transport file. - See important details in \code{\link[=mwrite_yaml]{mwrite_yaml()}}. } \examples{ -mod <- house() - -temp1 <- tempfile(fileext = ".yaml") - -x <- mwrite_yaml(mod, temp1) - -readLines(temp1) - temp <- tempfile(fileext = ".mod") mod <- modlib("pk1", compile = FALSE) @@ -79,7 +38,5 @@ mod } \seealso{ -\code{\link[=mread_yaml]{mread_yaml()}}, \code{\link[=yaml_to_cpp]{yaml_to_cpp()}} - \code{\link[=mwrite_yaml]{mwrite_yaml()}}, \code{\link[=yaml_to_cpp]{yaml_to_cpp()}} } From ea9e245eb9552b6e743bb8766ceb23ce335274b8 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Mon, 22 Jul 2024 10:14:41 -0500 Subject: [PATCH 16/16] add new Rd file --- man/mwrite_yaml.Rd | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 man/mwrite_yaml.Rd diff --git a/man/mwrite_yaml.Rd b/man/mwrite_yaml.Rd new file mode 100644 index 00000000..50e3e513 --- /dev/null +++ b/man/mwrite_yaml.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mwrite.R +\name{mwrite_yaml} +\alias{mwrite_yaml} +\title{Write model code to yaml format} +\usage{ +mwrite_yaml(x, file, digits = 8) +} +\arguments{ +\item{x}{a model object.} + +\item{file}{output file name; if non-character (e.g., \code{NULL}), no output +will be written to file.} + +\item{digits}{precision to use when writing outputs.} +} +\value{ +A list containing data that was written out to the yaml file, with added +item \code{file}, is returned invisibly. +} +\description{ +Model code is written to a readable, transport format. This transport format +can be useful for (1) breaking connection to NONMEM modeling outputs that +are imported by \verb{$NMXML} or \verb{$NMEXT} and (2) saving model updates (e.g., +an updated parameter list). Models can be read back using \code{\link[=mread_yaml]{mread_yaml()}} or +converted to mrgsolve cpp format with \code{\link[=yaml_to_cpp]{yaml_to_cpp()}}. +} +\details{ +Parameters and omega and sigma matrices that were imported via \verb{$NMXML} +or \verb{$NMEXT} will be written into the yaml file and the NONMEM import blocks +will be dropped. This allows the user to load a model based on a NONMEM run +without having a connection to that output (e.g., \code{root.xml} or \code{root.ext}). +Given that the connection to the NONMEM modeling outputs is broken when +writing to yaml, any update to the NONMEM run will only be propagated to +the yaml file when \code{mwrite_yaml()} is run again. + +The yaml file does not currently have the ability to track +other external dependencies, such as user-defined header files or other +code that might be sourced in by the user when the model is loaded via +\code{\link[=mread]{mread()}}. NONMEM xml and ext files imported by \verb{$NMXML} or \verb{$NMEXT} are +the \emph{only} external dependencies that are accounted for in the yaml +transport file. +} +\examples{ +mod <- house() + +temp1 <- tempfile(fileext = ".yaml") + +x <- mwrite_yaml(mod, temp1) + +readLines(temp1) + +} +\seealso{ +\code{\link[=mread_yaml]{mread_yaml()}}, \code{\link[=yaml_to_cpp]{yaml_to_cpp()}} +}