Open barrettk opened 5 months ago
FYI I finalized a working prototype. Can open up a scratch branch/draft PR, though I imagine there might be some discussion around A) supporting windows, and B) whether or not we need to copy the model to a temporary location like I have. It's worth noting I took quite a bit from NMProject
(here & here). The main differences are A) how we determine the NMTRAN
executable path (using bbi.yaml
) and B) using bbr
and nmrec
for getting model paths and modifying/fetching data paths.
processx
, but system
seems to work pretty well, and gave me no issues when trying to generate the necessary system call. See comments below, but I had issues generating the system call via processx
. > run_nmtran(MOD1)
Running NMTRAN with NONMEM version `nm75`
WARNINGS AND ERRORS (IF ANY) FOR PROBLEM 1
(WARNING 2) NM-TRAN INFERS THAT THE DATA ARE POPULATION.
Note: Analytical 2nd Derivatives are constructed in FSUBS but are never used.
You may insert $ABBR DERIV2=NO after the first $PROB to save FSUBS construction and compilation time
One unfortunate thing is that NMTRAN
does require a valid data path to properly execute. While this makes sense, and is there for good reason, this does prevent us from testing this out on a few of our 'complex' models (within bbr
), as we do not have the necessary dataset as part of this package.
Something to think about/look into:
NMTRAN
? If so, should we allow those commands? NMProject
allows users to specify a full command via nm_tran_command().Though I dont imagine we would include this function in bbr
, I wrote this helper function for comparing two models (similar to @kyleam's bash script):
# Starting model - set a reference
open_model_file(MOD1)
# Make new model
MOD_COMPARE <- copy_model_from(MOD1)
# Make a change
open_model_file(MOD_COMPARE)
# compare NMTRAN evaluation
compare_nmtran(MOD1, MOD_COMPARE)
# delete new model at the end
delete_models(MOD_COMPARE, .tags = NULL, .force = TRUE)
> compare_nmtran(MOD1, MOD_COMPARE)
Running NMTRAN with NONMEM version `nm75`
Models are not equivalent
[1] "nmtran_1_RtmpdmX0QB/FCON nmtran_2_RtmpdmX0QB/FCON differ: byte 3595, line 46"
attr(,"status")
[1] 1
I wanted to test whether diagonal matrices could specify standard deviation for one value, and variance for another
The reference model had this block:
$OMEGA
0.05 STANDARD ; iiv CL
0.2 ; iiv V2
The new model had this block:
$OMEGA
0.05 STANDARD ; iiv CL
0.2 VAR ; iiv V2
I found no differences here, meaning that adding VAR
to the second ETA value had no impact. This was an important observation, as it means I can assume one flag each for diagonal and off-diagonal values, per diagonal matrix-type record.
> compare_nmtran(MOD1, MOD_COMPARE)
Running NMTRAN with NONMEM version `nm75`
No differences found
character(0)
[ just a drive-by comment ... ]
I know we initially discussed using
processx
, butsystem
seems to work pretty well, and allowed me to port over more code fromNMProject
.
Please use processx
. system()
and friends invoke the command through the shell, which means you need to worry about things like path quoting and shell injection. Even if the latter isn't a realistic security concern for the given context, it's better to just avoid it entirely.
[ another quick comment ]
Based on how nmfe*
invokes NMTRAN.exe
...
$ grep tr/NMTRAN /opt/NONMEM/nm75/run/nmfe75
# $dir/tr/NMTRAN.exe $prdefault $tprdefault $maxlim < $1 >& FMSG
$dir/util/nmtran_presort < tempzzzz1 | $dir/tr/NMTRAN.exe $prdefault $tprdefault $maxlim $do2test >& FMSG
$dir/util/nmtran_presort < tempzzzz1 | $dir/tr/NMTRAN.exe $prdefault $tprdefault $maxlim >& FMSG
... I think it's worth considering these questions:
What does nmtran_presort
do? Should we be processing the input with that too to follow nmfe*
?
nmfe*
relays some arguments to NMTRAN.exe
. Is that something that something this check should do too? Are there cases where this new NM-TRAN check would fail but the actual run would pass due to this check not passing the same args to the NMTRAN.exe
call?
The answer may be that none of that matters in the context of this check, but that'd still be good to document here.
Please use processx. system() and friends invoke the command through the shell, which means you need to worry about things like path quoting and shell injection. Even if the latter isn't a realistic security concern for the given context, it's better to just avoid it entirely
FWIW I had a lot of trouble trying to use processx
. An example command could be:
/opt/NONMEM/nm75/tr/NMTRAN.exe < 1.ctl
When experimenting with various processx::run()
configurations, I couldn't get it to not quote the arguments. Here are a couple examples I would return:
/opt/NONMEM/nm75/tr/NMTRAN.exe '< 1.ctl' # when treating it as a single arg (args = '< 1.ctl')
/opt/NONMEM/nm75/tr/NMTRAN.exe '<' 1.ctl # when making '<' and '1.ctl' two separate args (args = c('<', '1.ctl'))
'/opt/NONMEM/nm75/tr/NMTRAN.exe <' 1.ctl # when making '<' part of the command
/opt/NONMEM/nm75/tr/NMTRAN.exe < '1.ctl' # I think I produced this one as well, but am blanking on the configuration
In this case, <
isn't really an argument, so I think that's part of the problem (though I couldnt make it part of the command either).
<
call, but that call seems to be a bit different than just readLines()
, or even coercing it to a single string with newline separators. I agree with the motivation to use processx
, but do you have any suggestions for how I could either A) reproduce the command /opt/NONMEM/nm75/tr/NMTRAN.exe < 1.ctl
(with no quotes), or B) find an alternative to the <
command?
prog <foo
is shell syntax that sends the contents of foo
as standard input to prog
. processx doesn't go through a shell. See processx's stdin
arg.
See processx's stdin arg.
I had looked into that, but will give it another look. Thanks
This idea has been brought up numerous times, but the core idea is to provide a method for validating a
bbr
model object viaNMTRAN
before submitting the model. There are other applications for this as well, such as validating a recently tweaked model, or just ensuringNONMEM
syntax is correct in general.Limitations
NMTRAN
,bbr
needs to know where theNONMEM
executable is. We can determine this via abbi.yaml
, as well as via a user-provided.config_path
(see?submit_model
arguments), thought this doesn't necessarily match up with the version of NONMEM used when actually submitting the model. This becausebbi
determines the path a little differently.bbi_init()
be called before validating the model. This requirement may limit what functions we can nest arun_nmtran()
call into (such as the new (in-flight)tweak_initial_estimates
function).Below is a prototype scaffold of how we can determine the
NMTRAN
executable path:Prototype functions
```r locate_nmtran <- function(.mod, .config_path = NULL, nmtran_exe = NULL){ if(is.null(nmtran_exe)){ model_dir <- get_model_working_directory(.mod) config_path <- .config_path %||% file.path(model_dir, "bbi.yaml") if(!file_exists(config_path)){ stop(paste("No bbi configuration was found in the execution directory.", "Please run `bbi_init()` with the appropriate directory to continue.")) } if (!is.null(.config_path)) { config_path <- normalizePath(.config_path) } bbi_config <- yaml::read_yaml(config_path) nm_config <- bbi_config$nonmem # look for default nonmem installation default_nm <- purrr::keep(nm_config, function(nm_ver){ !is.null(nm_ver$default) }) # Set nonmem path if(length(default_nm) > 0){ default_nm <- default_nm[[1]] }else{ # If no default, use the last one (likely higher version) default_nm <- nm_config[[length(nm_config)]] } # Set NMTRAN executable path nm_path <- default_nm$home nmtran_exe <- file.path(nm_path, "tr", "NMTRAN.exe") } if(!file_exists(nmtran_exe)){ stop(glue("Could not find an NMTRAN executable at `{nmtran_exe}`")) } return(nmtran_exe) } run_nmtran <- function(.mod, .config_path = NULL, nmtran_exe = NULL){ nmtran_exe <- locate_nmtran(.mod, .config_path, nmtran_exe) } ```