diff --git a/.gitignore b/.gitignore
index bff4a2199..ed5327c28 100755
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
.DS_Store
PmetricsGit.Rproj
+
# Build/check artifacts
/*.tar.gz
/*.Rcheck/
diff --git a/Learn/Rscript/Learn.R b/Learn/Rscript/Learn.R
new file mode 100644
index 000000000..d3c1ec096
--- /dev/null
+++ b/Learn/Rscript/Learn.R
@@ -0,0 +1,11 @@
+# This is an R script you can use to accompany the "Introduction to Pmetrics" online book.
+# You can find the book at https://lapkb.github.io/PM_tutorial/
+
+
+##### EXECUTE THIS CODE
+
+library(Pmetrics) # load the Pmetrics library
+wd <- "/Users/mneely/Library/CloudStorage/OneDrive-CHILDRENSHOSPITALLOSANGELES/Documents/LAPK/Development/Pmetrics_rust/Learn"
+setwd(wd) # all needed files will be here
+
+##### PASTE THE CODE FROM THE BOOK BELOW THIS LINE
diff --git a/Learn/src/ex.csv b/Learn/src/ex.csv
new file mode 100644
index 000000000..6559d3605
--- /dev/null
+++ b/Learn/src/ex.csv
@@ -0,0 +1,260 @@
+id,time,dose,out,wt,africa,age,gender,height
+1,0,600,.,46.7,1,21,1,160
+1,24,600,.,46.7,1,21,1,160
+1,48,600,.,46.7,1,21,1,160
+1,72,600,.,46.7,1,21,1,160
+1,96,600,.,46.7,1,21,1,160
+1,120,.,10.44,46.7,1,21,1,160
+1,120,600,.,46.7,1,21,1,160
+1,121,.,12.89,46.7,1,21,1,160
+1,122,.,14.98,46.7,1,21,1,160
+1,125.99,.,16.69,46.7,1,21,1,160
+1,129,.,20.15,46.7,1,21,1,160
+1,132,.,14.97,46.7,1,21,1,160
+1,143.98,.,12.57,46.7,1,21,1,160
+2,0,600,.,66.5,1,30,1,174
+2,24,600,.,66.5,1,30,1,174
+2,48,600,.,66.5,1,30,1,174
+2,72,600,.,66.5,1,30,1,174
+2,96,600,.,66.5,1,30,1,174
+2,120,.,3.56,66.5,1,30,1,174
+2,120,600,.,66.5,1,30,1,174
+2,120.98,.,5.84,66.5,1,30,1,174
+2,121.98,.,6.54,66.5,1,30,1,174
+2,126,.,6.14,66.5,1,30,1,174
+2,129.02,.,6.56,66.5,1,30,1,174
+2,132.02,.,4.44,66.5,1,30,1,174
+2,144,.,3.76,66.5,1,30,1,174
+3,0,600,.,46.7,1,24,0,164
+3,24,600,.,46.7,1,24,0,164
+3,48,600,.,46.7,1,24,0,164
+3,72,600,.,46.7,1,24,0,164
+3,96,600,.,46.7,1,24,0,164
+3,120,600,.,46.7,1,24,0,164
+3,120.08,.,4.06,46.7,1,24,0,164
+3,121.07,.,3.24,46.7,1,24,0,164
+3,122.08,.,3.09,46.7,1,24,0,164
+3,126.08,.,7.98,46.7,1,24,0,164
+3,129.05,.,7.23,46.7,1,24,0,164
+3,132.1,.,4.71,46.7,1,24,0,164
+3,144.08,.,3.82,46.7,1,24,0,164
+4,0,600,.,50.8,1,25,1,165
+4,24,600,.,50.8,1,25,1,165
+4,48,600,.,50.8,1,25,1,165
+4,72,600,.,50.8,1,25,1,165
+4,96,600,.,50.8,1,25,1,165
+4,120,.,2.1,50.8,1,25,1,165
+4,120,600,.,50.8,1,25,1,165
+4,121,.,3.05,50.8,1,25,1,165
+4,122.02,.,5.21,50.8,1,25,1,165
+4,126,.,5.09,50.8,1,25,1,165
+4,129.03,.,4.24,50.8,1,25,1,165
+4,132,.,3.69,50.8,1,25,1,165
+4,144.02,.,1.96,50.8,1,25,1,165
+5,0,600,.,65.8,1,22,1,181
+5,24,600,.,65.8,1,22,1,181
+5,48,600,.,65.8,1,22,1,181
+5,72,600,.,65.8,1,22,1,181
+5,96,600,.,65.8,1,22,1,181
+5,120,.,2.93,65.8,1,22,1,181
+5,120,600,.,65.8,1,22,1,181
+5,121,.,2.64,65.8,1,22,1,181
+5,122,.,4.8,65.8,1,22,1,181
+5,126,.,3.7,65.8,1,22,1,181
+5,129.02,.,4.13,65.8,1,22,1,181
+5,132,.,2.81,65.8,1,22,1,181
+5,144,.,2.21,65.8,1,22,1,181
+6,0,600,.,65,1,23,1,177
+6,24,600,.,65,1,23,1,177
+6,48,600,.,65,1,23,1,177
+6,72,600,.,65,1,23,1,177
+6,96,600,.,65,1,23,1,177
+6,120,.,6.92,65,1,23,1,177
+6,120,600,.,65,1,23,1,177
+6,121,.,6.89,65,1,23,1,177
+6,121.98,.,6.64,65,1,23,1,177
+6,126,.,13.72,65,1,23,1,177
+6,129,.,12.69,65,1,23,1,177
+6,131.98,.,10.58,65,1,23,1,177
+6,144.98,.,6.62,65,1,23,1,177
+7,0,600,.,51.7,1,27,0,161
+7,24,600,.,51.7,1,27,0,161
+7,48,600,.,51.7,1,27,0,161
+7,72,600,.,51.7,1,27,0,161
+7,96,600,.,51.7,1,27,0,161
+7,120,.,5.41,51.7,1,27,0,161
+7,120,600,.,51.7,1,27,0,161
+7,121.03,.,4.46,51.7,1,27,0,161
+7,122.03,.,4.54,51.7,1,27,0,161
+7,126.02,.,12.19,51.7,1,27,0,161
+7,129.08,.,12.1,51.7,1,27,0,161
+7,132.03,.,8.61,51.7,1,27,0,161
+7,144.03,.,6.37,51.7,1,27,0,161
+8,0,600,.,51.2,1,22,1,163
+8,24,600,.,51.2,1,22,1,163
+8,48,600,.,51.2,1,22,1,163
+8,72,600,.,51.2,1,22,1,163
+8,96,600,.,51.2,1,22,1,163
+8,120,.,6.19,51.2,1,22,1,163
+8,120,600,.,51.2,1,22,1,163
+8,121.03,.,6.33,51.2,1,22,1,163
+8,122,.,6.24,51.2,1,22,1,163
+8,125.98,.,13.03,51.2,1,22,1,163
+8,128.98,.,11.86,51.2,1,22,1,163
+8,132,.,11.45,51.2,1,22,1,163
+8,143.98,.,7.83,51.2,1,22,1,163
+9,0,600,.,55,1,23,1,174
+9,24,600,.,55,1,23,1,174
+9,48,600,.,55,1,23,1,174
+9,72,600,.,55,1,23,1,174
+9,96,600,.,55,1,23,1,174
+9,120,.,2.85,55,1,23,1,174
+9,120,600,.,55,1,23,1,174
+9,120.97,.,3.7,55,1,23,1,174
+9,122,.,6.65,55,1,23,1,174
+9,125.98,.,6.81,55,1,23,1,174
+9,128.98,.,6.51,55,1,23,1,174
+9,132,.,7.48,55,1,23,1,174
+9,143.98,.,4.51,55,1,23,1,174
+10,0,600,.,52.1,1,32,1,163
+10,24,600,.,52.1,1,32,1,163
+10,48,600,.,52.1,1,32,1,163
+10,72,600,.,52.1,1,32,1,163
+10,96,600,.,52.1,1,32,1,163
+10,120,.,2.93,52.1,1,32,1,163
+10,120,600,.,52.1,1,32,1,163
+10,121,.,4.36,52.1,1,32,1,163
+10,122.02,.,7.79,52.1,1,32,1,163
+10,126,.,11.02,52.1,1,32,1,163
+10,129,.,8.86,52.1,1,32,1,163
+10,131.97,.,6.09,52.1,1,32,1,163
+10,144,.,4.15,52.1,1,32,1,163
+11,0,600,.,56.5,1,34,1,165
+11,24,600,.,56.5,1,34,1,165
+11,48,600,.,56.5,1,34,1,165
+11,72,600,.,56.5,1,34,1,165
+11,96,600,.,56.5,1,34,1,165
+11,120,.,2.09,56.5,1,34,1,165
+11,120,600,.,56.5,1,34,1,165
+11,121.03,.,2.68,56.5,1,34,1,165
+11,122,.,4.71,56.5,1,34,1,165
+11,125.98,.,7.71,56.5,1,34,1,165
+11,129,.,6.31,56.5,1,34,1,165
+11,132,.,5.82,56.5,1,34,1,165
+11,144.13,.,2.63,56.5,1,34,1,165
+12,0,600,.,47.9,1,54,0,160
+12,24,600,.,47.9,1,54,0,160
+12,48,600,.,47.9,1,54,0,160
+12,72,600,.,47.9,1,54,0,160
+12,96,600,.,47.9,1,54,0,160
+12,120,.,7.09,47.9,1,54,0,160
+12,120,600,.,47.9,1,54,0,160
+12,121.03,.,6.18,47.9,1,54,0,160
+12,122.13,.,8.66,47.9,1,54,0,160
+12,126,.,11.16,47.9,1,54,0,160
+12,129,.,9.51,47.9,1,54,0,160
+12,132,.,8.14,47.9,1,54,0,160
+12,144,.,7.89,47.9,1,54,0,160
+13,0,600,.,60.5,1,24,1,180
+13,24,600,.,60.5,1,24,1,180
+13,48,600,.,60.5,1,24,1,180
+13,72,600,.,60.5,1,24,1,180
+13,96,600,.,60.5,1,24,1,180
+13,120,.,6.62,60.5,1,24,1,180
+13,120,600,.,60.5,1,24,1,180
+13,121,.,3.18,60.5,1,24,1,180
+13,122,.,5.41,60.5,1,24,1,180
+13,126,.,10.18,60.5,1,24,1,180
+13,129.02,.,12.84,60.5,1,24,1,180
+13,132,.,12.35,60.5,1,24,1,180
+13,144,.,8.06,60.5,1,24,1,180
+14,0,600,.,59.2,1,26,1,174
+14,24,600,.,59.2,1,26,1,174
+14,48,600,.,59.2,1,26,1,174
+14,72,600,.,59.2,1,26,1,174
+14,96,600,.,59.2,1,26,1,174
+14,120,.,3.63,59.2,1,26,1,174
+14,120,600,.,59.2,1,26,1,174
+14,121,.,4.49,59.2,1,26,1,174
+14,122,.,5.5,59.2,1,26,1,174
+14,126,.,7.28,59.2,1,26,1,174
+14,129,.,5.27,59.2,1,26,1,174
+14,132,.,4.89,59.2,1,26,1,174
+14,144,.,2.68,59.2,1,26,1,174
+15,0,450,.,43,1,19,0,150
+15,24,450,.,43,1,19,0,150
+15,48,450,.,43,1,19,0,150
+15,72,450,.,43,1,19,0,150
+15,96,450,.,43,1,19,0,150
+15,120,.,5.53,43,1,19,0,150
+15,120,450,.,43,1,19,0,150
+15,121,.,4.81,43,1,19,0,150
+15,122,.,8.14,43,1,19,0,150
+15,126,.,9.96,43,1,19,0,150
+15,129,.,8.55,43,1,19,0,150
+15,132.05,.,7.54,43,1,19,0,150
+15,144.05,.,5.74,43,1,19,0,150
+16,0,600,.,64.4,1,25,1,173
+16,24,600,.,64.4,1,25,1,173
+16,48,600,.,64.4,1,25,1,173
+16,72,600,.,64.4,1,25,1,173
+16,96,600,.,64.4,1,25,1,173
+16,120,.,5.48,64.4,1,25,1,173
+16,120,600,.,64.4,1,25,1,173
+16,121,.,6.59,64.4,1,25,1,173
+16,122,.,8.91,64.4,1,25,1,173
+16,126,.,10.57,64.4,1,25,1,173
+16,129,.,9.52,64.4,1,25,1,173
+16,132,.,7.83,64.4,1,25,1,173
+16,143.97,.,4.96,64.4,1,25,1,173
+17,0,600,.,54.8,1,23,1,170
+17,24,600,.,54.8,1,23,1,170
+17,48,600,.,54.8,1,23,1,170
+17,72,600,.,54.8,1,23,1,170
+17,96,600,.,54.8,1,23,1,170
+17,120,.,2.11,54.8,1,23,1,170
+17,120,600,.,54.8,1,23,1,170
+17,121.02,.,1.86,54.8,1,23,1,170
+17,122.02,.,6.92,54.8,1,23,1,170
+17,126,.,9.11,54.8,1,23,1,170
+17,129,.,6.96,54.8,1,23,1,170
+17,132,.,5.64,54.8,1,23,1,170
+17,144.08,.,3.59,54.8,1,23,1,170
+18,0,450,.,44.3,1,20,0,164
+18,24,450,.,44.3,1,20,0,164
+18,48,450,.,44.3,1,20,0,164
+18,72,450,.,44.3,1,20,0,164
+18,96,450,.,44.3,1,20,0,164
+18,120,.,7.95,44.3,1,20,0,164
+18,120,450,.,44.3,1,20,0,164
+18,120.98,.,7.47,44.3,1,20,0,164
+18,121.98,.,8.67,44.3,1,20,0,164
+18,126,.,13.83,44.3,1,20,0,164
+18,129.17,.,14.01,44.3,1,20,0,164
+18,132.17,.,8.97,44.3,1,20,0,164
+18,143.97,.,8.4,44.3,1,20,0,164
+19,0,600,.,50,1,36,1,168
+19,24,600,.,50,1,36,1,168
+19,48,600,.,50,1,36,1,168
+19,72,600,.,50,1,36,1,168
+19,96,600,.,50,1,36,1,168
+19,120,.,5.42,50,1,36,1,168
+19,120,600,.,50,1,36,1,168
+19,121,.,7.08,50,1,36,1,168
+19,122,.,7.27,50,1,36,1,168
+19,125.98,.,20.07,50,1,36,1,168
+19,128.98,.,18.24,50,1,36,1,168
+19,132,.,15.36,50,1,36,1,168
+19,144,.,10.92,50,1,36,1,168
+20,0,600,.,59,1,31,1,170
+20,24,600,.,59,1,31,1,170
+20,48,600,.,59,1,31,1,170
+20,72,600,.,59,1,31,1,170
+20,96,600,.,59,1,31,1,170
+20,120,.,4.71,59,1,31,1,170
+20,120,600,.,59,1,31,1,170
+20,120.77,.,4.5,59,1,31,1,170
+20,121.75,.,3.35,59,1,31,1,170
+20,125.67,.,12.35,59,1,31,1,170
+20,128.67,.,11.56,59,1,31,1,170
+20,143.67,.,6.45,59,1,31,1,170
diff --git a/R/DataDescriptions.R b/R/DataDescriptions.R
index dce4f0bf6..bcf73ca83 100755
--- a/R/DataDescriptions.R
+++ b/R/DataDescriptions.R
@@ -195,15 +195,3 @@
"locales"
-
-
-#' @name model
-#' @docType data
-#' @title Pmetrics model
-#' @usage model
-#' @format Sample model text
-#' @author Michael Neely
-#' @keywords datasets
-
-
-"model"
diff --git a/R/PM_model.R b/R/PM_model.R
index bdcf4c6b7..bbb0da2d6 100644
--- a/R/PM_model.R
+++ b/R/PM_model.R
@@ -4,7 +4,6 @@
# Use menu item Code -> Jump To... for rapid navigation
# Keyboard Option+Command+O (Mac) or Alt+O (Windows) to fold all
-
# R6 ----------------------------------------------------------------------
@@ -14,8 +13,8 @@
#' @description
#' `r lifecycle::badge("stable")`
#'
-#' PM_model objects contain the variables, covariates, equations and error models
-#' necessary to run a population analysis.
+#' PM_model objects contain the variables, covariates, equations and
+#' error models necessary to run a population analysis.
#'
#' @details
#' PM_model objects are one of two fundamental objects in Pmetrics, along with
@@ -24,9 +23,9 @@
#' population analysis, i.e. estimating the probability distribution of model equation
#' paramter values in the population. The PM_model object is created using the
#' a model building app (coming soon), by defining a list
-#' directly in R, or by reading a model text file. When reading a model text file,
+#' directly in R, or by reading a model text file. When reading a model text file,
#' the list code is generated and copied to the clipboard for pasting in to scripts.
-#' Model files will be deprecated in future versions of Pmetrics.
+#' Model files will be deprecated in future versions of Pmetrics.
#'
#' **Some notes on the example at the end of this help page:**
#'
@@ -87,7 +86,7 @@ PM_model <- R6::R6Class(
#' @field binary_path The full path and filename of the compiled model
binary_path = NULL,
#' @description
- #' This is the method to create a new `PM_model` object.
+ #' This is the method to create a new `PM_model` object.
#'
#' The first argument allows creation of a model from a variety of pre-existing
#' sources, and if used, all the subsequent arguments will be ignored. If a model
@@ -131,7 +130,7 @@ PM_model <- R6::R6Class(
#' * Quoted name of a model text file in the
#' working directory which will be read and passed to Rust engine. **Note:** Model
#' text files are being deprecated in future versions of Pmetrics.
- #'
+ #'
#' * List that defines the model directly in R. This will be in the same format as if
#' all the subsequent arguments were used. For example:
#' ```
@@ -410,9 +409,8 @@ PM_model <- R6::R6Class(
))
}
self$arg_list <- private$R6fromFile(x) # read file and populate fields
- cli::cli_inform(c("i" = "{.strong Note:} Model files will be deprecated in future versions of Pmetrics."))
+ # cli::cli_inform(c("i" = "{.strong Note:} Model files will be deprecated in future versions of Pmetrics."))
self$copy() # copy to clipboard
-
} else if (is.list(x)) { # x is a list in R
purrr::walk(model_sections, \(s) {
if (s %in% names(x)) {
@@ -479,7 +477,6 @@ PM_model <- R6::R6Class(
}
-
# Get model template name if present (NA if absent) and set type
model_template <- get_found_model(self$arg_list$eqn) # function defined below, returns 0 if not found, -1 if error
@@ -1089,11 +1086,6 @@ PM_model <- R6::R6Class(
}
-
- # if (getPMoptions()$backend != "rust") {
- # cli::cli_abort(c("x" = "Error: unsupported backend.", "i" = "See help for {.fn setPMoptions}"))
- # }
-
#### Include or exclude subjects ####
if (is.null(include)) {
include <- unique(data$standard_data$id)
@@ -1345,17 +1337,15 @@ PM_model <- R6::R6Class(
temp_csv <- tempfile(fileext = ".csv")
data$save(temp_csv, header = FALSE)
- if (getPMoptions()$backend == "rust") {
+
+ if (is.null(self$binary_path)) {
+ self$compile()
if (is.null(self$binary_path)) {
- self$compile()
- if (is.null(self$binary_path)) {
- cli::cli_abort(c("x" = "Model must be compiled before simulating."))
- }
+ cli::cli_abort(c("x" = "Model must be compiled before simulating."))
}
- sim <- simulate_all(temp_csv, self$binary_path, theta, kind = tolower(self$model_list$type))
- } else {
- cli::cli_abort(c("x" = "This function can only be used with the rust backend."))
}
+ sim <- simulate_all(temp_csv, self$binary_path, theta, kind = tolower(self$model_list$type))
+
return(sim)
},
#' @description
@@ -1377,7 +1367,7 @@ PM_model <- R6::R6Class(
output_path <- tempfile(pattern = "model_", fileext = ".pmx")
cli::cli_inform(c("i" = "Compiling model..."))
# path inside Pmetrics package
- template_path <- if (Sys.getenv("env") == "Development") { temporary_path() } else { system.file(package = "Pmetrics")}
+ template_path <- if (Sys.getenv("env") == "Development") { file.path(temporary_path(), "template") } else { system.file(package = "Pmetrics")}
if (file.access(template_path, 0) == -1 | file.access(template_path, 2) == -1){
cli::cli_abort(c("x" = "Template path {.path {template_path}} does not exist or is not writable.",
"i" = "Please set the template path with {.fn setPMoptions} (choose {.emph Compile Options}), to an existing, writable folder."
@@ -1398,19 +1388,33 @@ PM_model <- R6::R6Class(
return(invisible(self))
}, # end compile method
+ #' @description
+ #' Save model to file (deprecated).
+ #' @details
+ #' This method is deprecated. Existing or manually created model files may be read with `PM_model$new(filename)`,
+ #' but including model code in scripts is preferred, as this makes models used in runs transparent and more easily edited.
+ #' Use the `PM_model$copy()` method instead to copy the model code to the clipboard and paste into scripts.
+ save = function(){
+ cli::cli_warn(c("x" = "Saving model files is deprecated.",
+ "i" = "Model list copied to clipboard."))
+ self$copy()
+ return(invisible(self))
+ },
+
+
+
+
#' @description
#' Copy model code to clipboard.
#' @details
#' This method copies the R code to create the model to the clipboard.
- #' This is useful for saving the model code in a script, as model files
- #' will be deprecated in future versions of Pmetrics.
+ #' This is useful for saving the model code in a script.
copy = function() {
arg_list <- self$arg_list
-
+
# pri
pri <- c(
" pri = list(\n",
-
purrr::map_chr(names(arg_list$pri), \(i) {
sprintf(" %s = ab(%.3f, %.3f)", i, arg_list$pri[[i]]$min, arg_list$pri[[i]]$max)
}) %>% paste(collapse = ",\n"),
@@ -1488,37 +1492,37 @@ PM_model <- R6::R6Class(
err <- c(
"\n err = list(\n",
purrr::map_chr((arg_list$err), \(i) {
- sprintf(" %s(%i, c(%.1f, %.1f, %.1f, %.1f)%s)",
- i$type,
- i$initial,
- ifelse(length(i$coeff) >= 1, i$coeff[1], 0),
- ifelse(length(i$coeff) >= 2, i$coeff[2], 0),
- ifelse(length(i$coeff) >= 3, i$coeff[3], 0),
- ifelse(length(i$coeff) >= 4, i$coeff[4], 0),
- ifelse(i$fixed, ", fixed = TRUE", "")
- )
- }) %>% paste(collapse = ",\n"),
- "\n )"
- )
-
- model_copy <- c(
- "mod <- PM_model$new(\n",
- paste0(c(pri, cov, sec, fa, ini, lag, eqn, out, err), collapse = ""),
- "\n)"
- )
- cli::cli_inform(c(
- ">" = "Model code copied to clipboard.",
- ">" = "Paste the code into your script for future use, renaming the assigned variable if needed."))
- if (requireNamespace("clipr", quietly = TRUE)) {
- clipr::write_clip(model_copy)
- } else {
- cli::cli_inform(c("i" = "Please install the {.pkg clipr} package to enable clipboard functionality."))
- cat(model_copy, sep = "\n")
- }
- return(invisible(self))
-
+ sprintf(
+ " %s(%i, c(%.1f, %.1f, %.1f, %.1f)%s)",
+ i$type,
+ i$initial,
+ ifelse(length(i$coeff) >= 1, i$coeff[1], 0),
+ ifelse(length(i$coeff) >= 2, i$coeff[2], 0),
+ ifelse(length(i$coeff) >= 3, i$coeff[3], 0),
+ ifelse(length(i$coeff) >= 4, i$coeff[4], 0),
+ ifelse(i$fixed, ", fixed = TRUE", "")
+ )
+ }) %>% paste(collapse = ",\n"),
+ "\n )"
+ )
+
+ model_copy <- c(
+ "mod <- PM_model$new(\n",
+ paste0(c(pri, cov, sec, fa, ini, lag, eqn, out, err), collapse = ""),
+ "\n)"
+ )
+ cli::cli_inform(c(
+ ">" = "Model code copied to clipboard.",
+ ">" = "Paste the code into your script for future use, renaming the assigned variable if needed."
+ ))
+ if (requireNamespace("clipr", quietly = TRUE)) {
+ clipr::write_clip(model_copy)
+ } else {
+ cli::cli_inform(c("i" = "Please install the {.pkg clipr} package to enable clipboard functionality."))
+ cat(model_copy, sep = "\n")
+ }
+ return(invisible(self))
} # end copy
-
), # end public list
private = list(
# read file
@@ -1686,7 +1690,7 @@ PM_model <- R6::R6Class(
flush.console()
return(arg_list)
}, # end R6fromFile
-
+
write_model_to_rust = function(file_path = "main.rs") {
# Check if model_list is not NULL
if (is.null(self$model_list)) {
@@ -1720,256 +1724,253 @@ PM_model <- R6::R6Class(
},
get_primary = function() {
return(tolower(self$model_list$parameters))
- }
- ) # end private
-) # end R6Class PM_model
-
-##### These functions create various model components
-
-#' @title Additive error model
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Create an additive (lambda) error model
-#' @param initial Initial value for lambda
-#' @param coeff Vector of coefficients defining assay error polynomial
-#' @param fixed Estimate if `FALSE` (default).
-#' @export
-additive <- function(initial, coeff, fixed = FALSE) {
- PM_err$new(type = "additive", initial = initial, coeff = coeff, fixed = fixed)
-}
-
-
-
-#' @title Proportional error model
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Create an proportional (gamma) error model
-#' @param initial Initial value for gamma
-#' @param coeff Vector of coefficients defining assay error polynomial
-#' @param fixed Estimate if `FALSE` (default).
-#' @export
-proportional <- function(initial, coeff, fixed = FALSE) {
- PM_err$new(type = "proportional", initial = initial, coeff = coeff, fixed = fixed)
-}
-
-PM_err <- R6::R6Class(
- "PM_err",
- public = list(
- #' @field type Type of error model, either "additive" or "proportional".
- type = NULL,
- #' @field initial Initial value for the error model.
- initial = NULL,
- #' @field coeff Coefficients for the assay error polynomial.
- coeff = NULL,
- #' @field fixed If `TRUE`, the error model is fixed and not estimated.
- fixed = NULL,
- initialize = function(type, initial, coeff, fixed) {
- self$type <- type
- self$initial <- initial
- self$coeff <- coeff
- self$fixed <- fixed
- },
- print = function() {
- if (self$fixed) {
- cli::cli_text("{.strong {tools::toTitleCase(self$type)}}, with fixed value of {.emph {self$initial}} and coefficients {.emph {paste(self$coeff, collapse = ', ')}}.")
- } else {
- cli::cli_text("{.strong {tools::toTitleCase(self$type)}}, with initial value of {.emph {self$initial}} and coefficients {.emph {paste(self$coeff, collapse = ', ')}}.")
}
- },
- flatten = function() {
- list(initial = self$initial, coeff = self$coeff, type = self$type, fixed = self$fixed)
- }
+ ) # end private
+ ) # end R6Class PM_model
+
+ ##### These functions create various model components
+
+ #' @title Additive error model
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Create an additive (lambda) error model
+ #' @param initial Initial value for lambda
+ #' @param coeff Vector of coefficients defining assay error polynomial
+ #' @param fixed Estimate if `FALSE` (default).
+ #' @export
+ additive <- function(initial, coeff, fixed = FALSE) {
+ PM_err$new(type = "additive", initial = initial, coeff = coeff, fixed = fixed)
+ }
+
+
+ #' @title Proportional error model
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Create an proportional (gamma) error model
+ #' @param initial Initial value for gamma
+ #' @param coeff Vector of coefficients defining assay error polynomial
+ #' @param fixed Estimate if `FALSE` (default).
+ #' @export
+ proportional <- function(initial, coeff, fixed = FALSE) {
+ PM_err$new(type = "proportional", initial = initial, coeff = coeff, fixed = fixed)
+ }
+
+ PM_err <- R6::R6Class(
+ "PM_err",
+ public = list(
+ #' @field type Type of error model, either "additive" or "proportional".
+ type = NULL,
+ #' @field initial Initial value for the error model.
+ initial = NULL,
+ #' @field coeff Coefficients for the assay error polynomial.
+ coeff = NULL,
+ #' @field fixed If `TRUE`, the error model is fixed and not estimated.
+ fixed = NULL,
+ initialize = function(type, initial, coeff, fixed) {
+ self$type <- type
+ self$initial <- initial
+ self$coeff <- coeff
+ self$fixed <- fixed
+ },
+ print = function() {
+ if (self$fixed) {
+ cli::cli_text("{.strong {tools::toTitleCase(self$type)}}, with fixed value of {.emph {self$initial}} and coefficients {.emph {paste(self$coeff, collapse = ', ')}}.")
+ } else {
+ cli::cli_text("{.strong {tools::toTitleCase(self$type)}}, with initial value of {.emph {self$initial}} and coefficients {.emph {paste(self$coeff, collapse = ', ')}}.")
+ }
+ },
+ flatten = function() {
+ list(initial = self$initial, coeff = self$coeff, type = self$type, fixed = self$fixed)
+ }
+ )
)
-)
-
-#' @title Primary parameter values
-#' @description
-#' `r lifecycle::badge("experimental")`
-#' Define primary model parameter object.
-#' This is used internally by the `PM_model` class.
-#' @keywords internal
-PM_pri <- R6::R6Class(
- "PM_pri",
- public = list(
- #' @field min Minimum value of the range.
- min = NULL,
- #' @field max Maximum value of the range.
- max = NULL,
- #' @field mean Mean value of the range, calculated as (min + max) / 2.
- mean = NULL,
- #' @field sd Standard deviation of the range, calculated as (max - min) / 6.
- sd = NULL,
- #' @description
- #' Initialize a new range object.
- #' @param min Minimum value of the range.
- #' @param max Maximum value of the range.
- initialize = function(min, max) {
- self$min <- min
- self$max <- max
- self$mean <- (min + max) / 2
- self$sd <- (max - min) / 6
- },
- #' @description
- #' Print the range.
- print = function() {
- cli::cli_text("[{.strong {self$min}}, {.strong {self$max}}], {.emph ~N({round(self$mean,2)}}, {.emph {round(self$sd,2)})}")
- }
+
+ #' @title Primary parameter values
+ #' @description
+ #' `r lifecycle::badge("experimental")`
+ #' Define primary model parameter object.
+ #' This is used internally by the `PM_model` class.
+ #' @keywords internal
+ PM_pri <- R6::R6Class(
+ "PM_pri",
+ public = list(
+ #' @field min Minimum value of the range.
+ min = NULL,
+ #' @field max Maximum value of the range.
+ max = NULL,
+ #' @field mean Mean value of the range, calculated as (min + max) / 2.
+ mean = NULL,
+ #' @field sd Standard deviation of the range, calculated as (max - min) / 6.
+ sd = NULL,
+ #' @description
+ #' Initialize a new range object.
+ #' @param min Minimum value of the range.
+ #' @param max Maximum value of the range.
+ initialize = function(min, max) {
+ self$min <- min
+ self$max <- max
+ self$mean <- (min + max) / 2
+ self$sd <- (max - min) / 6
+ },
+ #' @description
+ #' Print the range.
+ print = function() {
+ cli::cli_text("[{.strong {self$min}}, {.strong {self$max}}], {.emph ~N({round(self$mean,2)}}, {.emph {round(self$sd,2)})}")
+ }
+ )
)
-)
-
-
-#' @title Initial range for primary parameter values
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Define primary model parameter initial values as range. For nonparametric,
-#' this range will be absolutely respected. For parametric, the range serves
-#' to define the mean (midpoint) and standard deviation (1/6 of the range) of the
-#' initial parameter value distribution.
-#' @param min Minimum value.
-#' @param max Maximum value.
-#' @export
-ab <- function(min, max) {
- PM_pri$new(min, max)
-}
-
-
-
-#' @title Initial mean/SD for primary parameter values
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Define primary model parameter initial values as mean and standard
-#' deviation, which translate to a range. The mean serves as the midpoint
-#' of the range, with 3 standard deviations above and below the mean to define
-#' the min and max of the range. For nonparametric,
-#' this range will be absolutely respected. For parametric,
-#' values can be estimated beyond the range.
-#' @param mean Initial mean.
-#' @param sd Initial standard deviation.
-#' @export
-msd <- function(mean, sd) {
- min <- mean - 3 * sd
- max <- mean + 3 * sd
- if (min < 0) {
- cli::cli_warn(c(
- "i" = "Negative minimum value for primary parameter range.",
- " " = "This may not be appropriate for your model."
- ))
+
+
+ #' @title Initial range for primary parameter values
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Define primary model parameter initial values as range. For nonparametric,
+ #' this range will be absolutely respected. For parametric, the range serves
+ #' to define the mean (midpoint) and standard deviation (1/6 of the range) of the
+ #' initial parameter value distribution.
+ #' @param min Minimum value.
+ #' @param max Maximum value.
+ #' @export
+ ab <- function(min, max) {
+ PM_pri$new(min, max)
}
- PM_pri$new(min, max)
-}
-
-
-
-#' @title Model covariate declaration
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Declare whether covariates in the data are to have
-#' interpolation between values or not.
-#' @param type If `type = "lm"` (the default) or `type = "linear"`,
-#' the covariate value will be
-#' linearly interpolated between values when fitting the model to the data.
-#' in a model list `cov` item. To fix covariate values to the value at the
-#' last time point, set `type = "none"`.
-#' @return A value of 1 for "lm" and 0 for "none", which will be passed to Rust.
-#' @examples
-#' \dontrun{
-#' cov <- c(
-#' wt = interp(), # same as interp("lm") or interp("linear")
-#' visit = interp("none")
-#' )
-#' }
-#' @export
-interp <- function(type = "lm") {
- if (!type %in% c("lm", "linear", "none")) {
- cli::cli_abort(c(
- "x" = "{type} is not a valid covariate interpolation type.",
- "i" = "See help for {.help PM_model()}."
- ))
+
+
+ #' @title Initial mean/SD for primary parameter values
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Define primary model parameter initial values as mean and standard
+ #' deviation, which translate to a range. The mean serves as the midpoint
+ #' of the range, with 3 standard deviations above and below the mean to define
+ #' the min and max of the range. For nonparametric,
+ #' this range will be absolutely respected. For parametric,
+ #' values can be estimated beyond the range.
+ #' @param mean Initial mean.
+ #' @param sd Initial standard deviation.
+ #' @export
+ msd <- function(mean, sd) {
+ min <- mean - 3 * sd
+ max <- mean + 3 * sd
+ if (min < 0) {
+ cli::cli_warn(c(
+ "i" = "Negative minimum value for primary parameter range.",
+ " " = "This may not be appropriate for your model."
+ ))
+ }
+ PM_pri$new(min, max)
}
- if (type %in% c("lm", "linear")) {
- return(1)
- } else {
- return(0)
+
+
+ #' @title Model covariate declaration
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Declare whether covariates in the data are to have
+ #' interpolation between values or not.
+ #' @param type If `type = "lm"` (the default) or `type = "linear"`,
+ #' the covariate value will be
+ #' linearly interpolated between values when fitting the model to the data.
+ #' in a model list `cov` item. To fix covariate values to the value at the
+ #' last time point, set `type = "none"`.
+ #' @return A value of 1 for "lm" and 0 for "none", which will be passed to Rust.
+ #' @examples
+ #' \dontrun{
+ #' cov <- c(
+ #' wt = interp(), # same as interp("lm") or interp("linear")
+ #' visit = interp("none")
+ #' )
+ #' }
+ #' @export
+ interp <- function(type = "lm") {
+ if (!type %in% c("lm", "linear", "none")) {
+ cli::cli_abort(c(
+ "x" = "{type} is not a valid covariate interpolation type.",
+ "i" = "See help for {.help PM_model()}."
+ ))
+ }
+ if (type %in% c("lm", "linear")) {
+ return(1)
+ } else {
+ return(0)
+ }
}
-}
-
-
-
-
-# PLOT --------------------------------------------------------------------
-
-#' @title Plot PM_model objects
-#' @description
-#' `r lifecycle::badge("stable")`
-#'
-#' Plots a [PM_model] based on differential equations using network plots from tidygraph and ggraph packages.
-#'
-#' @details
-#' This accepts a [PM_model] object and creates a network plot where nodes are compartments
-#' and edges are arrows connecting compartments.
-#' @method plot PM_model
-#' @param x The name of an [PM_model] object.
-#' @param marker Controls the characteristics of the compartments (nodes).
-#' It can be boolean or a list.
-#' `TRUE` will plot the compartments with default characteristics.
-#' `FALSE` will suppress compartment plotting.
-#' If a list, can control some marker characteristics, including overriding defaults.
-#' These include:
-#' \itemize{
-#' \item{`color`} Marker color (default: dodgerblue).
-#' \item{`opacity`} Ranging between 0 (fully transparent) to 1 (fully opaque). Default is 0.5.
-#' \item{`size`} Relative size of boxes, ranging from 0 to 1. Default is 0.25.
-#' \item{`line`} A list of additional attributes governing the outline for filled shapes, most commonly
-#' color (default: black) and width (default: 0.5).
-#' }
-#'
-#'
-#' Example: `marker = list(color = "red", opacity = 0.8, line = list(color = "black", width = 1))`
-#' @param line Controls characteristics of arrows (edges).
-#' `TRUE` will plot default lines. `FALSE` will suppress lines.
-#' If a list, can control some line characteristics, including overriding defaults.
-#' These include:
-#' \itemize{
-#' \item{`color`} Line color (default: black)
-#' \item{`width`} Thickness in points (default: 1).
-#' }
-#'
-#'
-#' Example: `line = list(color = "red", width = 2)`
-#' @param explicit A data frame or tibble containing two columns named `from` and `to`
-#' to add additional connecting arrows to the plot indicating transfer between
-#' compartments. For each row, the `from` column contains the compartment number of the arrow origin, and the
-#' `to` column contains the compartment number of the arrow destination. Use 0 to indicate
-#' a destination to the external sink. e.g., `explicit = data.frame(from = 3, to = 0)`
-#' @param implicit Similar to `explicit`, used to add dashed connecting arrows
-#' to the plot indicating implicit transfer between
-#' compartments. For each row, the `from` column contains the compartment number of the arrow origin, and the
-#' `to` column contains the compartment number of the arrow destination. Use 0 to indicate
-#' a destination to the external sink. e.g., `implicit = data.frame(from = 2, to = 4)`
-#' @param print If `TRUE`, will print the object and return it. If `FALSE`, will only return the object.
-#' @param ... Not used.
-#' @return A plot object of the model.
-#' @author Markus Hovd, Julian Otalvaro, Michael Neely
-#' @seealso [PM_model], [ggraph::ggraph()], [ggplot2::ggplot()]
-#' @export
-#' @examples
-#' \dontrun{
-#' NPex$model$plot()
-#' }
-#' @family PMplots
-
-plot.PM_model <- function(x,
- marker = TRUE,
- line = TRUE,
- explicit,
- implicit,
- print = TRUE,
- ...) {
+
+
+ # PLOT --------------------------------------------------------------------
+
+ #' @title Plot PM_model objects
+ #' @description
+ #' `r lifecycle::badge("stable")`
+ #'
+ #' Plots a [PM_model] based on differential equations using network plots from tidygraph and ggraph packages.
+ #'
+ #' @details
+ #' This accepts a [PM_model] object and creates a network plot where nodes are compartments
+ #' and edges are arrows connecting compartments.
+ #' @method plot PM_model
+ #' @param x The name of an [PM_model] object.
+ #' @param marker Controls the characteristics of the compartments (nodes).
+ #' It can be boolean or a list.
+ #' `TRUE` will plot the compartments with default characteristics.
+ #' `FALSE` will suppress compartment plotting.
+ #' If a list, can control some marker characteristics, including overriding defaults.
+ #' These include:
+ #' \itemize{
+ #' \item{`color`} Marker color (default: dodgerblue).
+ #' \item{`opacity`} Ranging between 0 (fully transparent) to 1 (fully opaque). Default is 0.5.
+ #' \item{`size`} Relative size of boxes, ranging from 0 to 1. Default is 0.25.
+ #' \item{`line`} A list of additional attributes governing the outline for filled shapes, most commonly
+ #' color (default: black) and width (default: 0.5).
+ #' }
+ #'
+ #'
+ #' Example: `marker = list(color = "red", opacity = 0.8, line = list(color = "black", width = 1))`
+ #' @param line Controls characteristics of arrows (edges).
+ #' `TRUE` will plot default lines. `FALSE` will suppress lines.
+ #' If a list, can control some line characteristics, including overriding defaults.
+ #' These include:
+ #' \itemize{
+ #' \item{`color`} Line color (default: black)
+ #' \item{`width`} Thickness in points (default: 1).
+ #' }
+ #'
+ #'
+ #' Example: `line = list(color = "red", width = 2)`
+ #' @param explicit A data frame or tibble containing two columns named `from` and `to`
+ #' to add additional connecting arrows to the plot indicating transfer between
+ #' compartments. For each row, the `from` column contains the compartment number of the arrow origin, and the
+ #' `to` column contains the compartment number of the arrow destination. Use 0 to indicate
+ #' a destination to the external sink. e.g., `explicit = data.frame(from = 3, to = 0)`
+ #' @param implicit Similar to `explicit`, used to add dashed connecting arrows
+ #' to the plot indicating implicit transfer between
+ #' compartments. For each row, the `from` column contains the compartment number of the arrow origin, and the
+ #' `to` column contains the compartment number of the arrow destination. Use 0 to indicate
+ #' a destination to the external sink. e.g., `implicit = data.frame(from = 2, to = 4)`
+ #' @param print If `TRUE`, will print the object and return it. If `FALSE`, will only return the object.
+ #' @param ... Not used.
+ #' @return A plot object of the model.
+ #' @author Markus Hovd, Julian Otalvaro, Michael Neely
+ #' @seealso [PM_model], [ggraph::ggraph()], [ggplot2::ggplot()]
+ #' @export
+ #' @examples
+ #' \dontrun{
+ #' NPex$model$plot()
+ #' }
+ #' @family PMplots
+
+ plot.PM_model <- function(
+ x,
+ marker = TRUE,
+ line = TRUE,
+ explicit,
+ implicit,
+ print = TRUE,
+ ...
+ ) {
model <- x
marker <- if (is.list(marker) || marker) {
amendMarker(marker,
@@ -2028,9 +2029,6 @@ plot.PM_model <- function(x,
unlist()
-
-
-
#### INTERNAL FUNCTIONS
# Parse the function body
parse_equations <- function(func) {
@@ -2387,9 +2385,8 @@ plot.PM_model <- function(x,
}
-
# Modify layout logic to use circular positioning
- create_plot <- function(connections, compartments, outputs) {
+ create_plot <- function(connections, compartments, outputs) {
box_width <- 1.2
box_height <- 0.8
@@ -2546,12 +2543,14 @@ plot.PM_model <- function(x,
if (length(outputs) > 0) {
out_df <- bind_rows(lapply(outputs, function(out) {
comp <- out$compartment
- if (is.null(comp)) return(data.frame(x = NA, y = NA, label = NA))
+ if (is.null(comp)) {
+ return(data.frame(x = NA, y = NA, label = NA))
+ }
txt <- paste0("y[", out$output_num, "]")
pos <- layout_df %>% filter(compartment == comp)
data.frame(x = pos$x, y = pos$y - 0.2, label = txt)
}))
- if (any(is.na(out_df$x))){
+ if (any(is.na(out_df$x))) {
missing_out <- as.character(which(is.na(out_df$x)))
cli::cli_warn(c("!" = "{?This/These} output equation{?s} did not contain a parsable compartment number on the right side of the equation and {?was/were} not plotted: {missing_out}."))
out_df <- out_df %>% filter(!is.na(x))
@@ -2587,7 +2586,13 @@ plot.PM_model <- function(x,
expanded_equations <- purrr::map(parse(text = tolower(eqns)), expand_distribute)
outputs <- parse_output_equations(as.list(parse(text = tolower(outs))))
- out_comp <- map_chr(outputs, function(o) if (!is.null(o$compartment)) {as.character(o$compartment)} else {"unknown"})
+ out_comp <- map_chr(outputs, function(o) {
+ if (!is.null(o$compartment)) {
+ as.character(o$compartment)
+ } else {
+ "unknown"
+ }
+ })
result <- extract_connections(expanded_equations)
elim_count <- sum(sapply(result$connections, function(c) c$to == 0))
elim_coeff <- map_chr(result$connections, function(c) if (c$to == 0) c$coeff else NA) %>% keep(~ !is.na(.))
diff --git a/R/PM_sim.R b/R/PM_sim.R
index 70fa3eeb2..7a6b3e1dd 100755
--- a/R/PM_sim.R
+++ b/R/PM_sim.R
@@ -571,6 +571,8 @@ PM_sim <- R6::R6Class(
))
}
+ useTheta <- FALSE # default, unless poppar is theta.csv format
+
# CASE 1 - poppar is PM_result
if (inherits(poppar, "PM_result")) {
@@ -666,8 +668,13 @@ PM_sim <- R6::R6Class(
# not returning because going on to simulate below
### This is for loading a saved simulation from file
+ } else if (inherits(poppar, "data.frame")){ # poppar is in the form of theta.csv
+ poppar$prob <- 1/nrow(poppar)
+ final <- list(popPoints = poppar)
+ useTheta <- TRUE
+ case <- 8
- # CASE 8 - last option, poppar is filename
+ # CASE 9 - last option, poppar is filename
} else {
if (file.exists(poppar)) {
if (grepl("rds$", poppar, perl = TRUE)) { # poppar is rds filename
@@ -695,9 +702,9 @@ PM_sim <- R6::R6Class(
} # end if poppar is filename
# If we reach this point, we are creating a new simulation
-
+
# check model and data
- if(case %in% c(2, 3, 7)) { # need model and data if not from PM_result
+ if(case %in% c(2, 3, 7, 8)) { # need model and data if not from PM_result
if (missing(model)) { model <- "model.txt" } # try the default
if (!inherits(model, "PM_model")) {model <- PM_model$new(model, compile = FALSE)} # compile later
@@ -791,7 +798,7 @@ PM_sim <- R6::R6Class(
seed = seed, ode = ode,
noise = noise,
makecsv = makecsv, outname = outname, clean = clean,
- quiet = quiet,
+ quiet = quiet, useTheta = useTheta,
nocheck = nocheck, overwrite = overwrite, msg = msg
)
@@ -899,14 +906,14 @@ PM_sim <- R6::R6Class(
covariate, usePost,
seed, ode,
noise,
- makecsv, outname, clean, quiet,
+ makecsv, outname, clean, quiet, useTheta,
nocheck, overwrite, msg) {
# DATA PROCESSING AND VALIDATION ------------------------------------------
###### POPPAR
- npar <- nrow(poppar$popCov)
+ npar <- ncol(poppar$popPoints) - 1
###### MODEL
@@ -1182,13 +1189,13 @@ PM_sim <- R6::R6Class(
"i" = "E.g. {.code limits = list(wt = c(40, 80), age = c(10, 50))}. See {.fn PM_sim} for help."
))
}
-
+
# figure out which covariates have different limits and change them
covUpdates <- tibble::enframe(covariate$limits, name = "par", value = "rng") %>%
tidyr::unnest_wider(rng, names_sep = "") %>%
dplyr::rename(min = rng1, max = rng2)
-
+
covLimits <- dplyr::rows_update(covLimits, covUpdates, by = "par")
# goodNames <- which(names(covMean) %in% names(covariate$limits))
@@ -1350,17 +1357,21 @@ PM_sim <- R6::R6Class(
} else { # postToUse is false
# set theta as nsim rows drawn from prior
- thisPrior <- private$getSimPrior(
- i = 1,
- poppar = poppar,
- split = split,
- postToUse = NULL,
- limits = limits,
- seed = seed[1],
- nsim = nsim,
- toInclude = toInclude,
- msg = msg
- )
+ if(!useTheta){
+ thisPrior <- private$getSimPrior(
+ i = 1,
+ poppar = poppar,
+ split = split,
+ postToUse = NULL,
+ limits = limits,
+ seed = seed[1],
+ nsim = nsim,
+ toInclude = toInclude,
+ msg = msg
+ )
+ } else {
+ thisPrior <- list(thetas = poppar$popPoints)
+ }
self$data <- private$getSim(thisPrior, template, mod, noise2, msg = msg)
}
@@ -1506,7 +1517,6 @@ PM_sim <- R6::R6Class(
# call simulator and process results
getSim = function(thisPrior, template, mod, noise2, msg = NULL) {
-
thetas <- thisPrior$thetas %>%
select(-prob) %>%
as.matrix()
diff --git a/R/PM_tutorial.R b/R/PM_tutorial.R
index b9a599f68..b2a54b734 100755
--- a/R/PM_tutorial.R
+++ b/R/PM_tutorial.R
@@ -61,9 +61,9 @@ PM_tutorial <- function() {
cli::cli_text("Click {.file {file.path(ans, 'Learn/Rscript/Learn.R')}} to proceed with the tutorial.")
# create the sample data files
- data(modEx, dataEx)
+ data(dataEx)
+ # example data
readr::write_csv(dataEx$data, file = file.path(ans, "Learn", "src", "ex.csv" ), na = ".")
- modEx$save(file = file.path(ans, "Learn", "src", "model.txt" ))
# simulation template
PM_data$new()$
addEvent(id = 1, time = 0, dose = 500, addl = 5, ii = 24, wt = 46.7, africa = 1, age = 21, gender = 1, height = 180)$
diff --git a/R/PMbuild.R b/R/PMbuild.R
index e397be881..30fd0bc40 100755
--- a/R/PMbuild.R
+++ b/R/PMbuild.R
@@ -16,10 +16,11 @@
PM_build <- function() {
- clear_build() # clean prior template/artifacts
+
if (is_rustup_installed()) {
cli::cli_text("Rust was detected in your system, Fetching dependencies and building base project.")
- dummy_compile(template_path = getPMoptions("model_template_path"))
+ template_path <- if (Sys.getenv("env") == "Development") { file.path(temporary_path(), "template") } else { system.file(package = "Pmetrics")}
+ dummy_compile(template_path = template_path)
} else {
cli::cli_text("Rust was not detected in your system, this can be caused by multiple reasons:")
ul <- cli::cli_ul()
diff --git a/R/PMoptions.R b/R/PMoptions.R
index d2d8ea235..13e96cbbf 100755
--- a/R/PMoptions.R
+++ b/R/PMoptions.R
@@ -100,208 +100,452 @@ setPMoptions <- function(launch.app = TRUE) {
# --- UI ---
ui = bslib::page_fluid(
- theme = bslib::bs_theme(bootswatch = "flatly"),
+ theme = bslib::bs_theme(
+ bootswatch = "flatly",
+ primary = "#2c3e50",
+ "card-border-radius" = "0.5rem"
+ ),
title = "Pmetrics Options",
- tags$details(
- tags$summary("Data File Reading"),
- selectInput("sep", "Field separator",
- choices = c(Comma = ",", Semicolon = ";", Tab = "\t"),
- selected = ","),
+ shiny::tags$div(
+ class = "container-fluid p-4",
- selectInput("dec", "Decimal mark",
- choices = c(Period = ".", Comma = ","),
- selected = ".")
- ),
- # Formatting options
- tags$details(
- tags$summary("Formatting Options"),
- numericInput("digits", "Number of digits to display",
- value = 3, min = 0, max = 10, step = 1)
- ),
-
-
- #Compile options
- # tags$details(
- # tags$summary("Compile Options"),
- # markdown("Default Rust model template path is in Pmetrics package installation folder. Change if you have write permission errors."),
- # tags$div(
- # style = "display: flex; align-items: flex-start; gap: 8px;",
- # textAreaInput("model_template_path", NULL, value = system.file(package = "Pmetrics"), autoresize = TRUE),
- # actionButton("reset_model_template", "Reset to default", class = "btn-secondary")
- # ),
- # conditionalPanel(
- # condition = "input.show == false",selectInput("backend", "Default backend",
- # choices = c("Rust" = "rust"),
- # selected = "rust"),
- # markdown("*Rust is the only backend currently supported by Pmetrics.*")
- # )
- # ),
-
- tags$details(
- tags$summary("Prediction Error Metrics"),
- br(),
- checkboxInput("show_metrics", "Display error metrics on obs-pred plots with linear regression", TRUE),
- selectInput("bias_method", "Bias Method",
- choices = c(
- "Mean absolute error (MAE)" = "mae",
- "Mean weighted error (MWE)" = "mwe"
+ # Header
+ shiny::tags$div(
+ class = "mb-4",
+ shiny::tags$h2(
+ class = "mb-1",
+ shiny::icon("cog", class = "me-2"),
+ "Pmetrics Options"
+ ),
+ shiny::tags$p(class = "text-muted mb-0", "Configure your Pmetrics preferences")
),
- selected = "mwe"),
- selectInput("imp_method", "Imprecision Method",
- choices = c(
+ # Main content layout - use fluidRow for proper spacing
+ shiny::fluidRow(
+ # Left column
+ shiny::column(
+ width = 6,
+ # Data File Reading Card
+ bslib::card(
+ class = "mb-3",
+ bslib::card_header(
+ class = "bg-primary text-white",
+ shiny::icon("file-csv", class = "me-2"),
+ "Data File Reading"
+ ),
+ bslib::card_body(
+ shiny::fluidRow(
+ shiny::column(
+ width = 6,
+ shiny::selectInput(
+ "sep",
+ bslib::tooltip(
+ shiny::tags$span("Field separator", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Character used to separate fields in data files"
+ ),
+ choices = c(Comma = ",", Semicolon = ";", Tab = "\t"),
+ selected = ","
+ )
+ ),
+ shiny::column(
+ width = 6,
+ shiny::selectInput(
+ "dec",
+ bslib::tooltip(
+ shiny::tags$span("Decimal mark", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Character used as decimal point in numbers"
+ ),
+ choices = c(Period = ".", Comma = ","),
+ selected = "."
+ )
+ )
+ )
+ )
+ ),
+
+ # Formatting Options Card
+ bslib::card(
+ class = "mb-3",
+ bslib::card_header(
+ class = "bg-primary text-white",
+ shiny::icon("hashtag", class = "me-2"),
+ "Display Formatting"
+ ),
+ bslib::card_body(
+ shiny::numericInput(
+ "digits",
+ bslib::tooltip(
+ shiny::tags$span("Decimal places", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Number of decimal places to show in output"
+ ),
+ value = 3, min = 0, max = 10, step = 1
+ )
+ )
+ ),
+
+ # Report Generation Card
+ bslib::card(
+ class = "mb-3",
+ bslib::card_header(
+ class = "bg-primary text-white",
+ shiny::icon("file-lines", class = "me-2"),
+ "Fit Report Template"
+ ),
+ bslib::card_body(
+ shiny::selectInput(
+ "report_template",
+ bslib::tooltip(
+ shiny::tags$span("Plot library", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "HTML summary of model fit to open in browser"
+ ),
+ choices = c(
+ "Interactive (plotly)" = "plotly",
+ "Static (ggplot2)" = "ggplot2"
+ ),
+ selected = "plotly"
+ )
+ )
+ )
+ ), # end left column
- "Mean squared error (MSE)" = "mse",
- "Mean weighted squared error (MWSE)" = "mwse",
- "Root mean squared error (RMSE)" = "rmse",
- "Mean, bias-adjusted, squared error (MBASE)" = "mbase",
- "Mean, bias-adjusted, weighted, squared error (MBAWSE)" = "mbawse",
- "Root mean, bias-adjusted, weighted, squared error (RMBAWSE)" = "rmbawse"
- ),
- selected = "rmbawse"),
-
- checkboxInput("use_percent", "Use percent for error metrics", value = TRUE),
-
- selectInput("ic_method", "Information Criterion Method",
- choices = c(
- "Akaike Information Criterion (AIC)" = "aic",
- "Bayesian Information Criterion (BIC)" = "bic"
- ),
- selected = "aic")
+ # Right column - Prediction Error Metrics Card
+ shiny::column(
+ width = 6,
+ bslib::card(
+ class = "mb-3",
+ bslib::card_header(
+ class = "bg-primary text-white",
+ shiny::icon("chart-line", class = "me-2"),
+ "Prediction Error Metrics"
+ ),
+ bslib::card_body(
+ shiny::tags$div(
+ class = "mb-3",
+ bslib::input_switch(
+ "show_metrics",
+ shiny::tags$span(
+ "Show metrics on plots",
+ bslib::tooltip(
+ shiny::icon("circle-question", class = "ms-1 text-muted"),
+ "Display error metrics on observed vs. predicted plots"
+ )
+ ),
+ value = TRUE
+ )
+ ),
+
+ shiny::tags$hr(class = "my-3"),
+
+ shiny::selectInput(
+ "bias_method",
+ bslib::tooltip(
+ shiny::tags$span("Bias method", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Method to calculate prediction bias (accuracy)"
+ ),
+ choices = c(
+ "Mean Absolute Error (MAE)" = "mae",
+ "Mean Weighted Error (MWE)" = "mwe"
+ ),
+ selected = "mwe"
+ ),
+
+ shiny::selectInput(
+ "imp_method",
+ bslib::tooltip(
+ shiny::tags$span("Imprecision method", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Method to calculate prediction imprecision (scatter)"
+ ),
+ choices = c(
+ "Mean Squared Error (MSE)" = "mse",
+ "Mean Weighted Squared Error (MWSE)" = "mwse",
+ "Root Mean Squared Error (RMSE)" = "rmse",
+ "Mean Bias-Adjusted Squared Error (MBASE)" = "mbase",
+ "Mean Bias-Adjusted Weighted Squared Error (MBAWSE)" = "mbawse",
+ "Root Mean Bias-Adjusted Weighted Squared Error (RMBAWSE)" = "rmbawse"
+ ),
+ selected = "rmbawse"
+ ),
+
+ shiny::tags$div(
+ class = "mb-3",
+ bslib::input_switch(
+ "use_percent",
+ shiny::tags$span(
+ "Report as percentages",
+ bslib::tooltip(
+ shiny::icon("circle-question", class = "ms-1 text-muted"),
+ "Express error metrics as percentages"
+ )
+ ),
+ value = TRUE
+ )
+ ),
+
+ shiny::tags$hr(class = "my-3"),
+
+ shiny::selectInput(
+ "ic_method",
+ bslib::tooltip(
+ shiny::tags$span("Information criterion", shiny::icon("circle-question", class = "ms-1 text-muted")),
+ "Method for model comparison"
+ ),
+ choices = c(
+ "Akaike Information Criterion (AIC)" = "aic",
+ "Bayesian Information Criterion (BIC)" = "bic"
+ ),
+ selected = "aic"
+ )
+ )
+ )
+ ) # end right column
+ ), # end fluidRow
- ),
-
- tags$details(
- tags$summary("Report Generation"),
- selectInput("report_template", "Default report template",
- choices = c("plotly", "ggplot2"),
- selected = "plotly")
- ),
- br(),
- div(
- class = "d-flex gap-2",
- actionButton("save", "Save"),
- actionButton("exit", "Exit"),
- ),
-
- br(),
- br(),
- shiny::verbatimTextOutput("settings_location"),
- br(),
-
- actionButton("open_file", "Open Options File",
- icon = icon("folder-open"), class = "btn-primary")
+ # Footer with buttons and file location
+ shiny::fluidRow(
+ shiny::column(
+ width = 12,
+ bslib::card(
+ class = "mt-3",
+ bslib::card_body(
+ class = "py-3",
+ shiny::fluidRow(
+ # Left side: action buttons with unsaved indicator below
+ shiny::column(
+ width = 6,
+ shiny::tags$div(
+ id = "button-container",
+ style = "display: inline-block;",
+ shiny::tags$div(
+ class = "d-flex gap-2",
+ shiny::actionButton(
+ "save",
+ shiny::tags$span(shiny::icon("floppy-disk", class = "me-1"), "Save"),
+ class = "btn-success"
+ ),
+ shiny::actionButton(
+ "exit",
+ shiny::tags$span(shiny::icon("xmark", class = "me-1"), "Close"),
+ class = "btn-secondary"
+ )
+ ),
+ shiny::uiOutput("save_status")
+ )
+ ),
+ # Right side: file location
+ shiny::column(
+ width = 6,
+ shiny::tags$div(
+ class = "d-flex gap-2 align-items-center justify-content-end",
+ shiny::tags$small(
+ class = "text-muted me-2",
+ shiny::tags$span(
+ shiny::icon("folder", class = "me-1"),
+ "Options file: ",
+ shiny::textOutput("settings_path", inline = TRUE)
+ )
+ ),
+ shiny::actionButton(
+ "open_file",
+ shiny::tags$span(shiny::icon("external-link-alt", class = "me-1"), "Open"),
+ class = "btn-outline-primary btn-sm"
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ ) # end footer fluidRow
+ ) # end container div
),
# --- Server ---
server = function(input, output, session) {
+ # Track if there are unsaved changes
+ unsaved_changes <- shiny::reactiveVal(FALSE)
+
# Load settings from external file
settings <- tryCatch({
jsonlite::fromJSON(PMoptionsUserFile)
}, error = function(e) NULL)
- # update this list every time a new option is added
- input_types <- list(
- sep = updateSelectInput,
- dec = updateSelectInput,
- show_metrics = updateCheckboxInput,
- digits = updateNumericInput,
- bias_method = updateSelectInput,
- imp_method = updateSelectInput,
- use_percent = updateCheckboxInput,
- ic_method = updateSelectInput,
- report_template = updateSelectInput,
- backend = updateSelectInput,
- model_template_path = updateTextAreaInput
- )
+ # Apply saved settings to inputs
+ if (!is.null(settings)) {
+ # Select inputs
+ if (!is.null(settings$sep)) shiny::updateSelectInput(session, "sep", selected = settings$sep)
+ if (!is.null(settings$dec)) shiny::updateSelectInput(session, "dec", selected = settings$dec)
+ if (!is.null(settings$digits)) shiny::updateNumericInput(session, "digits", value = settings$digits)
+ if (!is.null(settings$report_template)) shiny::updateSelectInput(session, "report_template", selected = settings$report_template)
+ if (!is.null(settings$ic_method)) shiny::updateSelectInput(session, "ic_method", selected = settings$ic_method)
+
+ # Bias/imprecision methods - strip percent_ prefix for display
+ if (!is.null(settings$bias_method)) {
+ shiny::updateSelectInput(session, "bias_method", selected = stringr::str_remove(settings$bias_method, "^percent_"))
+ }
+ if (!is.null(settings$imp_method)) {
+ shiny::updateSelectInput(session, "imp_method", selected = stringr::str_remove(settings$imp_method, "^percent_"))
+ }
+
+ # Switch inputs - bslib::update_switch uses 'id' not 'inputId'
+ if (!is.null(settings$show_metrics)) bslib::update_switch(id = "show_metrics", value = settings$show_metrics, session = session)
+ if (!is.null(settings$use_percent)) {
+ # Determine use_percent from the bias_method prefix
+ use_pct <- grepl("^percent_", settings$bias_method)
+ bslib::update_switch(id = "use_percent", value = use_pct, session = session)
+ }
+ }
+ # Flag to track if initial load is complete
+ # We use a timer to wait for the async update cycle to complete:
+ # 1) Server sends update messages to client
+ # 2) Client updates inputs and sends new values back
+ # 3) Server receives the updated values
+ # This round-trip needs time to complete before we start tracking changes
+ initialized <- shiny::reactiveVal(FALSE)
+ init_timer <- shiny::reactiveTimer(1000, session)
- # Apply updates
- purrr::imap(settings, function(val, name) {
- updater <- input_types[[name]]
- arg_name <- input_types[[name]] %>% formals() %>% names() %>% keep(~ .x %in% c("value", "selected"))
-
- if (!is.null(updater) && !is.null(arg_name)) {
- args <- list(session = session, inputId = name)
- args[[arg_name]] <- val %>% stringr::str_remove("^percent_") # remove 'percent_' prefix if present
- do.call(updater, args)
- }
- })
+ shiny::observeEvent(init_timer(), {
+ initialized(TRUE)
+ }, once = TRUE, ignoreInit = TRUE)
+
+ # Mark changes when any input changes (only after initialization)
+ shiny::observe({
+ # Only mark changes after initial load is complete
+ if (initialized()) {
+ unsaved_changes(TRUE)
+ }
+ }) |> shiny::bindEvent(
+ input$sep, input$dec, input$digits, input$show_metrics,
+ input$bias_method, input$imp_method, input$use_percent,
+ input$ic_method, input$report_template,
+ ignoreInit = TRUE
+ )
- # Display path to user settings file
- output$settings_location <- renderText({
- glue::glue("Options file path:\n{PMoptionsUserFile}")
+ # Display path to user settings file (truncated for display)
+ output$settings_path <- shiny::renderText({
+ # Truncate path for display if too long
+ path <- PMoptionsUserFile
+ if (nchar(path) > 50) {
+ path <- paste0("...", substr(path, nchar(path) - 47, nchar(path)))
+ }
+ path
})
+ # Show save status indicator below the buttons (full width of button container)
+ output$save_status <- shiny::renderUI({
+ if (unsaved_changes()) {
+ shiny::tags$div(
+ class = "mt-2 badge bg-warning text-dark d-flex align-items-center justify-content-center py-2",
+ style = "width: 100%;",
+ shiny::icon("exclamation-triangle", class = "me-1"),
+ "Unsaved changes"
+ )
+ } else {
+ NULL
+ }
+ })
### Action button handlers
# Save updated settings
- observeEvent(input$save, {
- settings <- list(sep = input$sep, dec = input$dec, digits = input$digits, show_metrics = input$show_metrics,
- bias_method = glue::glue(c("","percent_")[1+as.numeric(input$use_percent)], input$bias_method),
- imp_method = glue::glue(c("","percent_")[1+as.numeric(input$use_percent)], input$imp_method),
+ shiny::observeEvent(input$save, {
+ settings <- list(
+ sep = input$sep,
+ dec = input$dec,
+ digits = input$digits,
+ show_metrics = input$show_metrics,
+ bias_method = glue::glue(c("", "percent_")[1 + as.numeric(input$use_percent)], input$bias_method),
+ imp_method = glue::glue(c("", "percent_")[1 + as.numeric(input$use_percent)], input$imp_method),
ic_method = input$ic_method,
- report_template = input$report_template, backend = input$backend,
- model_template_path = input$model_template_path)
-
- save_status <- tryCatch(jsonlite::write_json(settings, PMoptionsUserFile, pretty = TRUE, auto_unbox = TRUE),
- error = function(e) {
- shiny::showNotification(
- paste("Error saving settings:", e$message),
- type = "error", duration = 5
- )
- return(FALSE)
- })
+ report_template = input$report_template
+ # backend = input$backend,
+ # model_template_path = input$model_template_path
+ )
+
+ tryCatch({
+ jsonlite::write_json(settings, PMoptionsUserFile, pretty = TRUE, auto_unbox = TRUE)
+ unsaved_changes(FALSE)
shiny::showNotification(
- "Settings saved", type = "message", duration = 3
+ shiny::tags$span(shiny::icon("check", class = "me-1"), "Settings saved successfully!"),
+ type = "message",
+ duration = 3
)
- })
-
- # Reset model template path to default
- observeEvent(input$reset_model_template, {
- updateTextAreaInput(
- session,
- inputId = "model_template_path",
- value = system.file(package = "Pmetrics")
+ }, error = function(e) {
+ shiny::showNotification(
+ shiny::tags$span(shiny::icon("times-circle", class = "me-1"), paste("Error saving:", e$message)),
+ type = "error",
+ duration = 5
)
})
-
-
- # Exit the app
- observeEvent(input$exit, {
+ })
+
+ # # Reset model template path to default
+ # shiny::observeEvent(input$reset_model_template, {
+ # shiny::updateTextAreaInput(
+ # session,
+ # inputId = "model_template_path",
+ # value = system.file(package = "Pmetrics")
+ # )
+ # })
+
+ # Exit the app with confirmation if unsaved changes
+ shiny::observeEvent(input$exit, {
+ if (unsaved_changes()) {
+ shiny::showModal(shiny::modalDialog(
+ title = shiny::tags$span(shiny::icon("exclamation-triangle", class = "me-2 text-warning"), "Unsaved Changes"),
+ "You have unsaved changes. Are you sure you want to exit?",
+ footer = shiny::tagList(
+ shiny::actionButton("confirm_exit", "Exit without saving", class = "btn-danger"),
+ shiny::modalButton("Cancel")
+ ),
+ easyClose = TRUE
+ ))
+ } else {
shiny::stopApp()
- # if (file.access(input$model_template_path, 0) == 0 & file.access(input$model_template_path, 2) == 0){
- # shiny::stopApp()
- # } else {
- # shiny::showModal(shiny::modalDialog(
- # title = "Permission Error",
- # "The specified model template path is not writable. Please choose a different path with write permissions before exiting.",
- # easyClose = TRUE,
- # footer = NULL
- # ))
- # }
- })
-
- # Open the options file in the default application
- observeEvent(input$open_file, {
- system(glue::glue("open {PMoptionsUserFile}"))
- })
- } #end server
- ) #end shinyApp
-
+ }
+ })
+
+ # Confirm exit without saving
+ shiny::observeEvent(input$confirm_exit, {
+ shiny::removeModal()
+ shiny::stopApp()
+ })
+
+ # Open the options file in the default application (cross-platform)
+ shiny::observeEvent(input$open_file, {
+ os <- getOS()
+ if (os == 1) {
+ # macOS
+ system2("open", PMoptionsUserFile)
+ } else if (os == 2) {
+ # Windows - use shell command
+ system2("cmd", c("/c", "start", "", shQuote(PMoptionsUserFile)))
+ } else if (os == 3) {
+ # Linux
+ system2("xdg-open", PMoptionsUserFile)
+ } else {
+ shiny::showNotification(
+ "Unable to open file on this operating system.",
+ type = "warning",
+ duration = 3
+ )
+ }
+ })
+ } #end server
+ ) #end shinyApp
- # Launch the app without trying to launch another browser
- if(launch.app){
- shiny::runApp(app, launch.browser = TRUE)
- }
- return(invisible(NULL))
+ # Launch the app without trying to launch another browser
+ if(launch.app){
+ shiny::runApp(app, launch.browser = TRUE)
+ }
+
+ return(invisible(NULL))
- } # end of PM_options function
+} # end of PM_options function
diff --git a/data-raw/data-raw.R b/data-raw/data-raw.R
index e620122b9..f772af6e7 100755
--- a/data-raw/data-raw.R
+++ b/data-raw/data-raw.R
@@ -35,11 +35,44 @@ usethis::use_data(mic1, overwrite = TRUE)
# Run Files -------------------------------------------------------------------
-model <- readLines(file.path(wd, "model.txt"))
-usethis::use_data(model, overwrite = TRUE)
+# model <- readLines(file.path(wd, "model.txt"))
+# usethis::use_data(model, overwrite = TRUE)
+
+# # model file
+#modEx <- PM_model$new(file.path(wd, "model.txt"))
+modEx <- PM_model$new(
+
+ pri = list(
+ ka = ab(0.100, 0.900),
+ ke = ab(0.001, 0.100),
+ v = ab(30.000, 120.000),
+ tlag1 = ab(0.000, 4.000)
+ ),
+ cov = list(
+ wt = interp(),
+ africa = interp(),
+ age = interp(),
+ gender = interp(),
+ height = interp()
+ ),
+ lag = function ()
+ {
+ lag[1] = tlag1
+ },
+ eqn = function ()
+ {
+ dx[1] = b[1] - ka * x[1]
+ dx[2] = rateiv[1] + ka * x[1] - ke * x[2]
+ },
+ out = function ()
+ {
+ y[1] = x[2]/v
+ },
+ err = list(
+ proportional(5, c(0.0, 0.1, -0.0, 0.0))
+ )
-# model file
-modEx <- PM_model$new(file.path(wd, "model.txt"))
+)
usethis::use_data(modEx, overwrite = TRUE)
# data
diff --git a/data/modEx.rda b/data/modEx.rda
index 663006338..014ac8a24 100755
Binary files a/data/modEx.rda and b/data/modEx.rda differ
diff --git a/data/model.rda b/data/model.rda
deleted file mode 100755
index 6f5895d4d..000000000
Binary files a/data/model.rda and /dev/null differ
diff --git a/man/PM_model.Rd b/man/PM_model.Rd
index 148e63831..7695c6de1 100755
--- a/man/PM_model.Rd
+++ b/man/PM_model.Rd
@@ -6,8 +6,8 @@
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}}
-PM_model objects contain the variables, covariates, equations and error models
-necessary to run a population analysis.
+PM_model objects contain the variables, covariates, equations and
+error models necessary to run a population analysis.
}
\details{
PM_model objects are one of two fundamental objects in Pmetrics, along with
@@ -95,6 +95,7 @@ the original arguments into Rust}
\item \href{#method-PM_model-map}{\code{PM_model$map()}}
\item \href{#method-PM_model-sim}{\code{PM_model$sim()}}
\item \href{#method-PM_model-compile}{\code{PM_model$compile()}}
+\item \href{#method-PM_model-save}{\code{PM_model$save()}}
\item \href{#method-PM_model-copy}{\code{PM_model$copy()}}
\item \href{#method-PM_model-clone}{\code{PM_model$clone()}}
}
@@ -697,6 +698,22 @@ file to a binary file that can be used for fitting or simulation.
If the model is already compiled, the method does nothing.
}
+}
+\if{html}{\out{