Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function to run NMTRAN on a model #705

Merged
merged 31 commits into from
Aug 7, 2024
Merged

Function to run NMTRAN on a model #705

merged 31 commits into from
Aug 7, 2024

Conversation

barrettk
Copy link
Collaborator

@barrettk barrettk commented Jun 25, 2024

Args

run_nmtran(
    .mod,
    .bbi_args = list(prdefault = FALSE, tprdefault = FALSE, maxlim = 2),
    .nonmem_version = NULL,
    .config_path = NULL,
    run_dir = tempdir(),
    clean = TRUE
)

Example

> run_nmtran(mod)

── NM-TRAN Status ─────────────────────────────────────────────────────────────────────────────────────────────────

── NM-TRAN successful ──

── Absolute Model Path ────────────────────────────────────────────────────────────────────────────────────────────
• /data/Projects/package_dev/bbr/inst/model/nonmem/basic/1.ctl

── NM-TRAN Specifications ─────────────────────────────────────────────────────────────────────────────────────────
• NM-TRAN Executable: /opt/NONMEM/nm75/tr/NMTRAN.exenmtran_presort: TRUENONMEM Version: "nm75"

── Standard Output ────────────────────────────────────────────────────────────────────────────────────────────────
  
 WARNINGS AND ERRORS (IF ANY) FOR PROBLEM    1
             
 (WARNING  2) NM-TRAN INFERS THAT THE DATA ARE POPULATION.

 LIM VALUES MAXLIM ASSESSED BY NMTRAN: 1,2,3,4,5,6,7,8,10,11,13,15,16        
  
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

closes #650

@barrettk
Copy link
Collaborator Author

Previous PR: #652

barrettk added 4 commits June 28, 2024 12:21
 - In testing this feature out in more scenarios surrounding data setup, I noticed a status value of 8 was possible, and pointed to a particular error type. Rather than attempting to categorize each error type, just report a failure for anything other than an exit status of `0`
 - organize documentation and add additional details
 - print method shows how many records have dropped

 - noticed issue with current implementation. The setting of column names will not work if columns were dropped. May also consider take renaming into account (eg., `$INPUT DV=CP`).
Copy link
Collaborator

@kyleam kyleam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few comments prior to main review

tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
barrettk added 2 commits July 15, 2024 15:43
 - opting to separate this into a separate, more focused PR
 - see previous commit message
barrettk added a commit that referenced this pull request Jul 15, 2024
 - taken from #705 (some additional changes)

 - known issues:
   - Need to remove column "C" from columns retrieved from the control stream file. FDATA seems to always start with 'ID'. Will include additional details in a later commit when this behavior is more understood

   - Need to support alternate formats (WIDE, etc)
   - Need to check for additional $INPUT options and add handling for them (unclear which ones at the moment)
@barrettk barrettk marked this pull request as draft July 15, 2024 19:54
barrettk added 3 commits July 16, 2024 12:28
 - I had noticed some weird interaction here in the past when removing this, but seems fine now
 - still need to add more details to the documentation
@barrettk barrettk marked this pull request as ready for review July 19, 2024 18:56
@barrettk barrettk requested a review from kyleam July 19, 2024 18:56
R/print.R Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
barrettk added 5 commits July 23, 2024 15:45
 - make NM-TRAN label consistent across all functions and documentation

 - store NM-TRAN standard error in the same file as standard output. Adjust print method
 - now always returns a character vector signifiying the values of `prdefault`, `tprdefault`, and `maxlim` respectively (i.e. '0' '0' '2' as the default)

 - Unsure if this is set up correctly, as im now receiving different NM-TRAN output messages. Might be a different order than the one highlighted above.
barrettk added 2 commits July 24, 2024 18:22
 - nmtran_presort detection now happens in nmtran_setup()

 - Print whether nmtran_presort was run ahead of `NM-TRAN`

 - Add documentation about nmtran_presort, and make `run_dir` an argument (defaulting to temporary directory). An example illustrates the benefit of changing this to your working directory
@barrettk barrettk requested a review from kyleam July 25, 2024 17:06
@barrettk
Copy link
Collaborator Author

barrettk commented Jul 25, 2024

@kyleam just requested another review. I wasn't able to notice any difference when passing do2test with nm75. That's not to say it isn't doing anything, but I haven't detected any differences with the models i've tested (one real one outside of bbr). I wasnt able to find any documentation either, though I did look a bit through some of the scripts in /opt/NONMEM/nm75/util/ (like nmfe75original_condor) which confirmed some of the logic you described. Im not convinced it's necessary from that.

I hooked up nmtran_presort and set it to create a temporary tempzzzz1.ctl if executed to mimic how NM-TRAN is expected to work. The remainder of the comments you left (that I didnt mark as resolved) I opted to leave alone for the time being. If you feel strongly about any of them during your next review let me know.

As an aside, let me know what you think about the nmtran_presort documentation. I debated not mentioning it at all, but thought it might give scientists more confidence in the output. The same is true for including it in the print method.

@kyleam
Copy link
Collaborator

kyleam commented Jul 25, 2024

The remainder of the comments you left (that I didnt mark as resolved) I opted to leave alone for the time being. If you feel strongly about any of them during your next review let me know.

I haven't taken a close look at what was addressed or the new changes, but one thing I do feel strongly about is the switch to processx::run. Please do that unless you have a reason to manage the process yourself. (I don't see one.)

 - update print method to only print the run directory if it still exists
@barrettk
Copy link
Collaborator Author

Please do that unless you have a reason to manage the process yourself.

Sounds good just implemented in latest commit

Copy link
Collaborator

@kyleam kyleam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates. Another round of code review. Hopefully after this we can focus on a final phase of interactive testing (especially on our Windows instance).

R/print.R Outdated Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/print.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
tests/testthat/test-workflow-bbi.R Outdated Show resolved Hide resolved
barrettk added 4 commits July 30, 2024 16:17
 - Documentation updates
 - Move tests to their own file and update based on refactors
 - update error messages to use cli::cli_abort with better formatting
@barrettk barrettk requested a review from kyleam July 31, 2024 15:04
R/run-nmtran.R Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
R/run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
 - add parse_nmtran_args tests instead of documentation to help clarify expected results
@barrettk barrettk requested a review from kyleam August 1, 2024 16:37
Copy link
Collaborator

@kyleam kyleam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few minor comments I think would be good to address before merging, but nothing that's a blocker. Holding off on approving until I have a chance to do some interactive testing on Windows.


follow-up on do2test discussion

Quick recap:

  • starting with NONMEM 7.5 nmfe has two calls to NM-TRAN
  • the first call passes a fourth do2test argument
  • bbi doesn't expose nmfe's -do2test argument, so in the bbi/bbr context this call always passes "0" to NM-TRAN for the do2test value
  • if that first call exits with a status of 9, nmfe does a follow-up call that drops the fourth argument (do2test)

The main question is whether passing 0 for do2test is equivalent to not passing a fourth argument. If it is, then the current run_nmtran approach is functionally equivalent to what nmfe does. If not, then there is divergence (though it's hard to say for which cases, if any, it matters).

The only way to definitively answer that question would be to analyze the NM-TRAN code.

To follow nmfe here, we could

  • determine if the NONMEM version passes do2test (e.g., by some version check or a grep of nmfe script)

  • follow its logic (i.e. two calls, check status of first)

But at this point, I think we can leave that be, especially given run_nmtran is a standalone helper. We should revisit the idea if submit_model starts using it as a default preflight check.

I would like if there was a code comment about this. Something like:

# Note: run_nmtran considers only the first three NM-TRAN arguments (prdefault,
# tprdefault, and maxlim), but, starting with NONMEM 7.5, nmfe passes a fourth
# argument (do2test) to NM-TRAN. See discussion at
# https://github.com/metrumresearchgroup/bbr/pull/705

I wasn't able to notice any difference when passing do2test with nm75. That's not to say it isn't doing anything, but I haven't detected any differences with the models i've tested (one real one outside of bbr). I wasnt able to find any documentation either, [...] Im not convinced it's necessary from that.

Thanks for testing. The details get tricky here, so, if you still have it around, for posterity it'd be nice to document what specifically you tested.

R/run-nmtran.R Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
tests/testthat/test-run-nmtran.R Outdated Show resolved Hide resolved
@kyleam
Copy link
Collaborator

kyleam commented Aug 2, 2024

[...] interactive testing on Windows.

The first run with nonmem/basic/1 looked okay. But then I noticed nmtran_presort was false in the output. That's because on Windows nmtran_presort has an .exe extension. So I was hoping it'd just be a matter of

diff --git a/R/run-nmtran.R b/R/run-nmtran.R
index 45ece442..6869357b 100644
--- a/R/run-nmtran.R
+++ b/R/run-nmtran.R
@@ -178,6 +178,9 @@ nmtran_setup <- function(
 
   # Check if nmtran_presort exists
   nmtran_presort_exe <- file.path(nm_path, "util", "nmtran_presort")
+  if (ON_WINDOWS) {
+    nmtran_presort_exe <- paste0(nmtran_presort_exe, ".exe")
+  }
   run_presort <- unname(fs::file_exists(nmtran_presort_exe))
 
   return(

But then the presort subprocess produces empty output for the control stream.

Looking at run/nmfe75.bat, presort is called on Windows, so it's not just that presort isn't appropriate to call on Windows.

$ grep presort run/nmfe75.bat
rem %dir%\util\nmtran_presort < %1 | %dir%\tr\nmtran.exe %prdefault% %tprdefault% %maxlim% %1 >FMSG
%dir%\util\nmtran_presort < tempzzzz1 >tempzzzz

If I call it from bash, it doesn't produce empty output:

$ /c/nm75g/util/nmtran_presort.exe <inst/model/nonmem/basic/1/1.ctl | wc -c
1186

$ /c/nm75g/util/nmtran_presort <inst/model/nonmem/basic/1/1.ctl | wc -c
1186

Not sure yet what's going on here, but I won't have more time to look at it today. @barrettk, this is under the shared Windows VM, if you want to take a look.

 - add details about additional `do2test` argument for NONMEM 7.5 and later

 - adjust test to not look for a specific status value on failure. Any non-zero value is a failure, so we dont need to check the specific value
@barrettk
Copy link
Collaborator Author

barrettk commented Aug 2, 2024

Still investigating your latest comment/deciding on an approach, but I did notice that calling nmtran_presort via processx::run() seems to save the output to a fort.6 file regardless of stdout:

image

I have a fix for this, though at the moment im not too confident this would be a reliable approach:

    processx::run(
      command = nmtran_presort_exe,
      stdin = mod_path_new,
      stdout = nmtran_input,
      wd = run_dir,
      error_on_status = TRUE
    )

    if (ON_WINDOWS) {
      presort_ctl <- readLines(file.path(run_dir, "fort.6"))
      writeLines(presort_ctl, nmtran_input)
    }

Upon doing this, nmtran_presort behaved as expected. However, this led the NM-TRAN call to have the same issue..not saving the output to the specified location, and instead overwriting fort.6, which now includes this content:
image

I could do the same thing as above to fix this issue (save the results from fort.6 to nmtran_input, though I imagine there has to be a better way. I looked at some of the windows specific arguments in processx::process$new() and process::run(), though none of them seem related to this issue. Will keep digging, but somewhat reassuring that we may have a suitable fallback approach.

Looking through nm75g/util/nmfe75original.bat, I did see instructions for temporarily writing to a fort.6 file. All mentions of fort.6 only show up in batch files, and im curious why these results get copied over to tempzzzz1 in linux but not Windows, especially given that we dont execute any batch files.

echo Starting NMTRAN
if "%tprdefault%"=="1" set prdefault=1
del fort.6 2>trash.tmp
copy %1 tempzzzz1 >trash.tmp
if "%dde%"=="0" goto lddeskip
%dir%\util\ddexpand %1 tempzzzz1 
if exist fort.6 copy fort.6 tempzzzz1 >trash.tmp
if %errorlevel%==0 copy %1 tempzzzz1 >trash.tmp
copy tempzzzz1 %1_dde >trash.tmp
del fort.6 2>trash.tmp
:lddeskip
%dir%\util\nmtran_presort < tempzzzz1 >tempzzzz
if exist fort.6 copy fort.6 tempzzzz >trash.tmp
del fort.6 2>trash.tmp
%dir%\tr\nmtran.exe <tempzzzz %prdefault% %tprdefault% %maxlim% %do2test% >FMSG
if not %errorlevel%==9 goto do2testdone
type FMSG
del FMSG
%dir%\tr\nmtran.exe <tempzzzz %prdefault% %tprdefault% %maxlim% >FMSG

I also look at running batch files in Windows via processx::process. I know we're not actually executing that file currently, but im just thinking out loud as to whether we rethink how we execute NM-TRAN/nmtran_presort
image

I have code set up to copy over the output from fort.6 if on windows, but im wondering how best to determine if this is a suitable approach, versus if we need to rethink how we execute NM-TRAN/nmtran_presort on Windows

 - This is not an ideal solution, though it has worked in practice. On Windows the output ends up being saved to a `fort.6` file instead of the intended stdout location. stdout instead gets created with no contents.

 - This approach copies the `nmtran_presort` results from `fort.6` to stdout, and reads in the `NM-TRAN` results from `fort.6` instead of stdout
@kyleam
Copy link
Collaborator

kyleam commented Aug 7, 2024

@barrettk Thanks for looking into the Windows issue and proposing a fix.

Capturing some links and info from our internal discussion:

  • https://stackoverflow.com/a/51783320

    For many (but not all) compilers, unit 6 is the standard output.

    For those compilers, and only those, the statements write(*, *) and write(6, ) would be identical. Consequently, on those compilers, closing unit 6 would cause further output of the form write(, *) to be send to a file called fort.6.

  • https://stat.ethz.ch/pipermail/r-devel/2017-June/074499.html

    A user has reported an issue that appears when a fortran executable is
    called via R on Windows. I am unsure if this expected behavior or a
    bug in Fortran or in how R calls Windows executables.

    The problem is that when the fortran program is called from R, stdout
    gets written to a file "fort.6" instead of stdout. When the same
    executable is called from the terminal it works fine and prints to
    stdout. This unexpected behavior is unfortunate for R wrappers that
    rely on captured output.

    A minimal example is available from github [1] and can be installed with

    [...]

    Interestingly if we open a command line terminal and run the same
    executable it prints output to stdout. So perhaps it has to do with
    the way R invokes CreateProcess() on Windows?

  • WRITE(6,*) calls in nmtran_presort.f90

    $ grep IW /c/nm75g/util/nmtran_presort.f90
          INTEGER IR,IW,I1,I2,I3,J,IOERR,IDONE,IFIRST,MFIRST,MMAIN,IMAIN,MLAST,ILAST
          IW=6
            WRITE(IW,99) TRIM(S)
              WRITE(IW,99) TRIM(S2)
              WRITE(IW,99) TRIM(S2)
              WRITE(IW,99) TRIM(S2)
            WRITE(IW,99) TRIM(S)
    
  • On our Windows VM, MINGW64 Git Bash terminal and the Rtools terminal works as expected (i.e. output is written to stdout)

I'm still fuzzy on what's going on our end. Why do these FORTRAN executables work in some environment (terminals mentioned above and, given lst output of success test models, shell scripts executed bbi)?

But the above info about stdout in the FORTRAN world, including an interaction with R, is enough to make me comfortable with the workaround you propose. I don't think we need to hold up this PR to do a deeper dive into this.

Copy link
Collaborator

@kyleam kyleam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a minor suggestion for the Windows comment, but this is ready to go in my view. Thanks.

R/run-nmtran.R Outdated Show resolved Hide resolved
@barrettk barrettk removed the request for review from andersone1 August 7, 2024 16:39
@barrettk barrettk merged commit 0faf7b7 into main Aug 7, 2024
6 checks passed
@barrettk barrettk deleted the feat/run_nmtran branch August 7, 2024 16:45
barrettk added a commit that referenced this pull request Aug 7, 2024
 - taken from #705 (some additional changes)

 - known issues:
   - Need to remove column "C" from columns retrieved from the control stream file. FDATA seems to always start with 'ID'. Will include additional details in a later commit when this behavior is more understood

   - Need to support alternate formats (WIDE, etc)
   - Need to check for additional $INPUT options and add handling for them (unclear which ones at the moment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Function to run NMTRAN on a model object
2 participants