From 6bab7f66cdb3bdbd6a06304ab08c92ef6fce2cde Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 12 Feb 2026 21:13:36 -0600 Subject: [PATCH 1/9] add macros for FINAL rows --- inst/base/modelheader.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inst/base/modelheader.h b/inst/base/modelheader.h index ced854e5..21b529e3 100644 --- a/inst/base/modelheader.h +++ b/inst/base/modelheader.h @@ -92,6 +92,10 @@ typedef double capture; #define THETA(a) THETA##a // Should modeled infusion parameters get checked #define CHECK_MODELED_INFUSIONS _check_modeled_infusions +// Check for final record of the simulation +#define FINAL_IROW (self.rown == (self.nrow-1)) +// Check for final record of current individual +#define FINAL_ROW (self.irown == (self.inrow-1)) // NMVARS #ifdef _MRGSOLVE_USING_NM_VARS_ From e25c7117f7d106c8fe3e4bea8ed744cea84254c8 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 12 Feb 2026 21:15:49 -0600 Subject: [PATCH 2/9] make macros reserved --- R/Aaaa.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/Aaaa.R b/R/Aaaa.R index 8821d8b8..f3886224 100644 --- a/R/Aaaa.R +++ b/R/Aaaa.R @@ -106,7 +106,8 @@ Reserved_cvar <- c("SOLVERTIME","table","ETA","EPS", "AMT", "CMT", "NEWIND", "DONE", "CFONSTOP", "DXDTZERO", "CFONSTOP","INITSOLV","_F", "_R","_ALAG", "SETINIT", "report", "_VARS_", "VARS", - "SS_ADVANCE", "END_OF_INFUSION", "CHECK_MODELED_INFUSIONS") + "SS_ADVANCE", "END_OF_INFUSION", "CHECK_MODELED_INFUSIONS", + "FINAL_ROW", "FINAL_IROW") Reserved <- c("ID", "amt", "cmt", "ii", "ss", "evid", "addl", "rate","time", Reserved_cvar, From 3c01d76c46c74f6744e91e4b143713096aafcfa5 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 12 Feb 2026 22:02:37 -0600 Subject: [PATCH 3/9] FINAL_ROW macro tests; no modeled events after last time --- inst/base/modelheader.h | 4 +- inst/maintenance/unit/test-rown-nrow.R | 56 ++++++++++++++++++++++++-- src/devtran.cpp | 1 + 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/inst/base/modelheader.h b/inst/base/modelheader.h index 21b529e3..ce7ffb63 100644 --- a/inst/base/modelheader.h +++ b/inst/base/modelheader.h @@ -93,9 +93,9 @@ typedef double capture; // Should modeled infusion parameters get checked #define CHECK_MODELED_INFUSIONS _check_modeled_infusions // Check for final record of the simulation -#define FINAL_IROW (self.rown == (self.nrow-1)) +#define FINAL_ROW (self.rown == (self.nrow-1)) // Check for final record of current individual -#define FINAL_ROW (self.irown == (self.inrow-1)) +#define FINAL_IROW (self.irown == (self.inrow-1)) // NMVARS #ifdef _MRGSOLVE_USING_NM_VARS_ diff --git a/inst/maintenance/unit/test-rown-nrow.R b/inst/maintenance/unit/test-rown-nrow.R index ba13507f..3aaeab67 100644 --- a/inst/maintenance/unit/test-rown-nrow.R +++ b/inst/maintenance/unit/test-rown-nrow.R @@ -234,19 +234,29 @@ test_that("individual row counters work with PRED model", { # https://github.com/metrumresearchgroup/mrgsolve/pull/1323 code_counter_update_on_output <- ' -$preamble capture total1 = 0; capture total2 = 0; -$main self.mtime(12); +$preamble +capture total1 = 0; capture total2 = 0; +capture total3 = 0; capture total4 = 0; +capture total5 = 0; +$main +self.mtime(1.2); self.mtime(2.9); +self.mtime(11); self.mtime(12); self.mtime(24); $table if(self.rown+1==self.nrow) ++total1; if(self.nid==self.idn+1 && self.irown+1 == self.inrow) ++total2; +if(self.irown+1==self.inrow) ++total5; +if(FINAL_ROW) ++total3; +if(FINAL_IROW) ++total4; + capture rown = self.rown; capture nrow = self.nrow; capture irown = self.irown; capture inrow = self.inrow; ' +mod <- mcode("counter-update-on-output", code_counter_update_on_output) + test_that("row counters are only updated on output records gh-1323", { - mod <- mcode("counter-update-on-output", code_counter_update_on_output) data <- expand.ev(amt = 0, cmt = 0, ID = 1:2) out <- mrgsim(mod, data = data, end = 24, delta = 24) expect_equal(nrow(out), 6) @@ -263,7 +273,47 @@ test_that("row counters are only updated on output records gh-1323", { expect_equal(out$total1[6], 1) expect_equal(out$total2[6], 1); + # Macros + expect_equal(out$total1, out$total3) + expect_equal(out$total4, out$total4) +}) + +test_that("more row counter macros gh-132X", { + data1 <- ev(amt = 0, cmt = 1, ID = 1) + data2 <- ev(amt = 0, cmt = 1, ID = 2) + + data1 <- expand_observations(data1, times = 1:5) + data2 <- expand_observations(data2, times = 1:8) + + data <- rbind(data1, data2) + data$cmt <- 0 + + out <- mrgsim_df(mod, data = data) + expect_equal(nrow(out), nrow(data)) + + out1 <- subset(out, ID==1) + + expect_equal(nrow(out1), 6) + expect_true(all(out1$inrow==6)) + expect_equal(out1$rown, out1$irown) + expect_equal(out1$total4[6], 1) + expect_equal(out1$total4, out1$total5) + expect_equal(out1$irown, seq(6)-1) + + expect_true(all(out1$total1==0)) + expect_true(all(out1$total3==0)) + + out2 <- subset(out, ID==2) + expect_equal(nrow(out2), 9) + expect_true(all(out2$inrow==9)) + expect_equal(out2$rown-6, out2$irown) + expect_equal(out2$total3[9], 1) + expect_equal(out2$total4, out2$total5) + expect_equal(out2$irown, seq(9)-1) + + expect_true(all(out2$total4[1:8]==1)) }) +rm(mod) rm(code_test_rown_nrow, code_test_rown_nrow_pred) rm(code_counter_update_on_output) diff --git a/src/devtran.cpp b/src/devtran.cpp index cb861e35..bd5f5c5d 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -669,6 +669,7 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, for(size_t mti = 0; mti < mt.size(); ++mti) { // Unpack and check double this_time = (mt[mti]).time; + if(this_time > maxtime) continue; unsigned int this_evid = (mt[mti]).evid; if(this_time < tto && !mt[mti].now) { throw Rcpp::exception( From d4c5f2a26edb4b39341a3554886589a2bdaf1bfa Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 12 Feb 2026 22:28:25 -0600 Subject: [PATCH 4/9] increment row counter past number of data rows --- inst/maintenance/unit/test-rown-nrow.R | 2 +- src/devtran.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/inst/maintenance/unit/test-rown-nrow.R b/inst/maintenance/unit/test-rown-nrow.R index 3aaeab67..296385be 100644 --- a/inst/maintenance/unit/test-rown-nrow.R +++ b/inst/maintenance/unit/test-rown-nrow.R @@ -307,7 +307,7 @@ test_that("more row counter macros gh-132X", { expect_equal(nrow(out2), 9) expect_true(all(out2$inrow==9)) expect_equal(out2$rown-6, out2$irown) - expect_equal(out2$total3[9], 1) + expect_equal(out2$total3[9], 2) expect_equal(out2$total4, out2$total5) expect_equal(out2$irown, seq(9)-1) diff --git a/src/devtran.cpp b/src/devtran.cpp index bd5f5c5d..106e26de 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -463,12 +463,17 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, if(crow == NN) continue; rec_ptr this_rec = a[i][j]; - + // Only update row counters on output records if(this_rec->output()) { prob.irown(icrow); prob.rown(crow); - } + } + + if(this_rec->time() > maxtime) { + prob.irown(icrow + j); + prob.rown(crow + j); + } this_rec->id(id); @@ -669,7 +674,6 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, for(size_t mti = 0; mti < mt.size(); ++mti) { // Unpack and check double this_time = (mt[mti]).time; - if(this_time > maxtime) continue; unsigned int this_evid = (mt[mti]).evid; if(this_time < tto && !mt[mti].now) { throw Rcpp::exception( From 693ae4f46266792b53bf7999ab456f5ce95f75d8 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 12 Feb 2026 23:31:09 -0600 Subject: [PATCH 5/9] refactor to continue for any record beyond maxtime --- inst/maintenance/unit/test-rown-nrow.R | 2 +- src/devtran.cpp | 28 ++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/inst/maintenance/unit/test-rown-nrow.R b/inst/maintenance/unit/test-rown-nrow.R index 296385be..3aaeab67 100644 --- a/inst/maintenance/unit/test-rown-nrow.R +++ b/inst/maintenance/unit/test-rown-nrow.R @@ -307,7 +307,7 @@ test_that("more row counter macros gh-132X", { expect_equal(nrow(out2), 9) expect_true(all(out2$inrow==9)) expect_equal(out2$rown-6, out2$irown) - expect_equal(out2$total3[9], 2) + expect_equal(out2$total3[9], 1) expect_equal(out2$total4, out2$total5) expect_equal(out2$irown, seq(9)-1) diff --git a/src/devtran.cpp b/src/devtran.cpp index 106e26de..f97868f6 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -427,7 +427,9 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, double tfrom = a[i].front()->time(); double tto = tfrom; - double maxtime = a[i].back()->time(); + + const double maxtime = a[i].back()->time(); + const int NNI = a[i].size(); for(int k=0; k < neta; ++k) prob.eta(k,eta(i,k)); for(int k=0; k < neps; ++k) prob.eps(k,eps(crow,k)); @@ -460,23 +462,21 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, ic = prob.interrupt; } - if(crow == NN) continue; - rec_ptr this_rec = a[i][j]; + this_rec->id(id); + tto = this_rec->time(); + + if(icrow==NNI || crow==NN || tto > maxtime) { + continue; + } + // Only update row counters on output records if(this_rec->output()) { prob.irown(icrow); prob.rown(crow); } - if(this_rec->time() > maxtime) { - prob.irown(icrow + j); - prob.rown(crow + j); - } - - this_rec->id(id); - if(prob.systemoff()) { // This starts a loop that will finish the remaining records // for an individual; no other calls to any model functions will @@ -488,8 +488,8 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, if(status==999) CRUMP("999 sent from the model."); if(this_rec->output()) { if(status==1) { - ans(crow,0) = this_rec->id(); - ans(crow,1) = this_rec->time(); + ans(crow,0) = id; + ans(crow,1) = tto; for(unsigned int k=0; k < n_capture; ++k) { ans(crow,(k+capture_start)) = prob.capture(capture[k]); } @@ -516,9 +516,7 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, &prob ); } - - tto = this_rec->time(); - + double dt = (tto-tfrom)/(tfrom == 0.0 ? 1.0 : tfrom); if((dt > 0.0) && (dt < mindt)) { From a6e75615209d7edcd198b667e49fa7a8e8dc47e4 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 13 Feb 2026 16:07:43 -0600 Subject: [PATCH 6/9] finish tests --- inst/maintenance/unit/test-rown-nrow.R | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/inst/maintenance/unit/test-rown-nrow.R b/inst/maintenance/unit/test-rown-nrow.R index 3aaeab67..c50f171b 100644 --- a/inst/maintenance/unit/test-rown-nrow.R +++ b/inst/maintenance/unit/test-rown-nrow.R @@ -236,18 +236,22 @@ test_that("individual row counters work with PRED model", { code_counter_update_on_output <- ' $preamble capture total1 = 0; capture total2 = 0; -capture total3 = 0; capture total4 = 0; +capture total3 = 0; capture total4 = 0; capture total5 = 0; + $main self.mtime(1.2); self.mtime(2.9); self.mtime(11); self.mtime(12); self.mtime(24); + $table if(self.rown+1==self.nrow) ++total1; if(self.nid==self.idn+1 && self.irown+1 == self.inrow) ++total2; -if(self.irown+1==self.inrow) ++total5; -if(FINAL_ROW) ++total3; + +if(FINAL_ROW) ++total3; if(FINAL_IROW) ++total4; +if(self.irown+1==self.inrow) ++total5; + capture rown = self.rown; capture nrow = self.nrow; capture irown = self.irown; From 4d765d356afa9678cb4d5ca338155312730ac093 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Fri, 13 Feb 2026 20:22:17 -0600 Subject: [PATCH 7/9] refactor NNI --- src/devtran.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devtran.cpp b/src/devtran.cpp index f97868f6..a05bf1ff 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -429,7 +429,7 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, double tto = tfrom; const double maxtime = a[i].back()->time(); - const int NNI = a[i].size(); + const int NNI = dat.inrow(i); for(int k=0; k < neta; ++k) prob.eta(k,eta(i,k)); for(int k=0; k < neps; ++k) prob.eps(k,eps(crow,k)); From 3f2c68c3dbd567db9d69b0a0f783eeaed41571f3 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Tue, 17 Feb 2026 15:24:02 -0600 Subject: [PATCH 8/9] update gh issue/pr number in test header --- inst/maintenance/unit/test-rown-nrow.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/maintenance/unit/test-rown-nrow.R b/inst/maintenance/unit/test-rown-nrow.R index c50f171b..275d1d69 100644 --- a/inst/maintenance/unit/test-rown-nrow.R +++ b/inst/maintenance/unit/test-rown-nrow.R @@ -282,7 +282,7 @@ test_that("row counters are only updated on output records gh-1323", { expect_equal(out$total4, out$total4) }) -test_that("more row counter macros gh-132X", { +test_that("more row counter macros gh-1327", { data1 <- ev(amt = 0, cmt = 1, ID = 1) data2 <- ev(amt = 0, cmt = 1, ID = 2) From 3068c9e549912ba03edc91843ba6265e2e7b0728 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Tue, 17 Feb 2026 16:47:52 -0600 Subject: [PATCH 9/9] revert --- src/devtran.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/devtran.cpp b/src/devtran.cpp index a05bf1ff..11a61861 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -461,16 +461,17 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, Rcpp::checkUserInterrupt(); ic = prob.interrupt; } - + rec_ptr this_rec = a[i][j]; this_rec->id(id); tto = this_rec->time(); + // TODO: simplify if(icrow==NNI || crow==NN || tto > maxtime) { continue; } - + // Only update row counters on output records if(this_rec->output()) { prob.irown(icrow);