diff --git a/content/blog/2024-04-04-steady-state/featured.png b/content/blog/2024-04-04-steady-state/featured.png new file mode 100644 index 0000000..f611d8f Binary files /dev/null and b/content/blog/2024-04-04-steady-state/featured.png differ diff --git a/content/blog/2024-04-04-steady-state/index.Rmd b/content/blog/2024-04-04-steady-state/index.Rmd new file mode 100644 index 0000000..5d92349 --- /dev/null +++ b/content/blog/2024-04-04-steady-state/index.Rmd @@ -0,0 +1,350 @@ +--- +title: nlmixr2/rxode2 steady state changes +author: 'Matthew Fidler' +date: '2024-04-04' +slug: [] +categories: [nlmixr2, rxode2, steady state] +tags: [steady state] +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` +One of the things that I changed in the last release was steady state. + +Once I created the `nonmem2rx` package, I searched for NONMEM control +streams that we could test the import from, especially those with +attached data. I ran across +[nmtests](https://github.com/mrgsolve/nmtests) that uses NONMEM to +test against `mrgsolve` and how it behaves. + +During that test I noticed that `rxode2` did not follow the convention +of NONMEM in lagged steady state doses. This blog post discusses the +prior and current differences between the steady state of NONMEM and +`rxode2`/`NONMEM`. + +To reproduce the [nmtests](https://github.com/nlmixr2/nmtests/) in the +current version of `rxode2`, we will first define the model to compare +with what NONMEM does: + +```{r model} + +library(rxode2) + +fl <- rxode2({ + cl <- 1.1 + v <- 20 + ka <- 1.5 + d/dt(depot) <- -ka*depot + d/dt(central) <- ka*depot - (cl/v)*central + lag(central) <- lagt + f(central) <- bioav + if (mode == 1) rate(central) <- rat2 + if (mode == 2) dur(central) <- dur2 + cp <- central/(v/1000) +}) + +``` + +Then we import the test data from `nlmixr2data` (note this dataset has +been modified from the original +[nmtests](https://github.com/nlmixr2/nmtests/) with a few more tests +cases). + +## Steady State with Lag time + +We will show the first difference we saw in the [nmtests](https://github.com/nlmixr2/nmtests/): + +```{r} +dfull <- nlmixr2data::nmtest +d <- dfull[dfull$id ==9, ] + +print(d[d$evid != 0, ]) +``` +In this case, there is a lag time for a steady state dose. In the previous version of `rxode2` we saw: + +```{r} +library(ggtext) +library(ggplot2) + +rxSolve(fl, d, ssAtDoseTime=FALSE) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 ssAtDoseTime=FALSE", + subtitle="show a difference at dose time until the lag time") + + theme(plot.title = element_markdown()) + + annotate("rect", ymin=c(0, 0), ymax=c(1200, 1200),xmin=c(0, 0), xmax=c(4, 4), + fill="blue", color="blue", + alpha=0.2) +``` + +At first I was not concerned with the difference because we do not +have to match NONMEM. The purpose of having the steady state in the +first place was to simulate steady state. In my opinion, simulating +steady state and simulating a few extra doses was the way to make sure +you had the correct steady state. (I still think there are situations +in NONMEM where this is best practice as I will mention later). + +However some in the nlmixr2 team suggested that it should not wait for +another cycle, it is more convenient to have the steady state work as +expected immediately. I eventually conceded. Hence the new default +is to solve back-calculating the steady state of the lag time: + +```{r ssAtDoseTime} +rxSolve(fl, d) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 ssAtDoseTime=TRUE", + subtitle="show no differences (default as of rxode2 2.1.0)") + + theme(plot.title = element_markdown()) +``` + + + +## More extreme steady state example + +One of the new features of `rxode2` is handling steady state where +steady state interval is less than the infusion interval. For +example, from the [nmtests](https://github.com/nlmixr2/nmtests/) +dataset: + +```{r ssid10print} +dfull <- nlmixr2data::nmtest +d <- dfull[dfull$id ==10, ] + +print(d[d$evid != 0, ]) +``` + +In this case, the infusion lasts well over 12 hours +(`100/2*0.812`=`40.2`) even though the steady state dosing is every 12 +hours. If this was performed it would mean dosing every 12 hours and +not removing the previous zero order infusion dose. It seems a bit +extreme, but perhaps this is a zero order release tablet that lasts in +the body for a few days. In this case we match NONMEM: + +```{r ssid10plot} +rxSolve(fl, d) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 for interdose interval < infusion duration", + subtitle="show no differences (as of rxode2 2.1.0)") + + theme(plot.title = element_markdown()) +``` + +If you look carefully, there are additional times where each infusion +is turned off as the remaining doses wear off. + +## Another extreme steady state support + +In addition to supporting steady state where the inter-dose interval +is smaller than the infusion time, we also now support cases where the +lag-time is greater than the inter-dose interval. This is an +additional test case that is found in our fork of +[nmtests](https://github.com/nlmixr2/nmtests/). + +```{r ssid409print} +dfull <- nlmixr2data::nmtest +d <- dfull[dfull$id ==409, ] + +print(d[d$evid != 0, ]) +``` + +This gives the following: + +```{r ssid409plot} +rxSolve(fl, d) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 for lag time > interdose interval", + subtitle="show large at beginning (as of rxode2 2.1.0 and NONMEM 7.4)") + + theme(plot.title = element_markdown()) +``` + +In this case, there are large differences between what NONMEM has and +what `rxode2`/`nlmixr2`, especially at the beginning of the profile. +Here NONMEM has negative concentrations. They become positive because +NONMEM changes the steady state doses to non-steady state doses with +the `addl` flag (as does `rxode2`). In a few steady state doses after +the initial dose is administered, steady state is achieved in NONMEM +as well. You can see in this plot that the steady state is achieved +immediately in the `rxode2` case. + +This is one of the reasons why I suggest a few extra doses +"just in case". + +I did reach out to Bob Bauer about what I thought was a bug. His +reply is as follows: + +> Rules about steady-state feature are documented as follows: +> +> According to guide VIII, the manual reads, under the section ABSORPTION LAG PARAMETER: +> +> An absorption lag time for a dose is computed by the PK routine using, if needed, information in the dose record. When additional doses are specified on a dose event record, the absorption lag time applies to the dose and to all the additional doses. With a steady-state multiple dose the absorption lag time applies not only to this dose, but also to all the preceding implied doses. With a steady-state dose, the lag time should be less than the interdose interval + +> In guide VIII, section ADDL DATA ITEM: +> +> For non-steady-state doses, ADDL should be a positive number if and only if the II data item is a positive number. II giv es the time between additional doses. For steady-state infusions, ADDL must be 0. For other steady-state doses, ADDL is optional. If it is a positive number, it continues the pattern of implied doses beyond the steady-state dose. The additional doses of the pattern are non-steady state doses. +> +> And in intro7.pdf: +> +> With NM75 there is a new way of computing SS, the Empirical method, in which there is no SS data item, and a negative value of ADDL requests the computation. This is described separately. (See empirical_SS). (See Guide Introduction_7 "An Empirical Method of Achieving Steady State") + + +So my impression is this is not supported by NONMEM (as documented in +the manual). The key statement for me is "With a steady-state dose, +the lag time should be less than the interdose interval". However, a +run-time error does not raise when this condition occurs (which would +be helpful I think). + +Note that `rxode2` does not currently support the empirical method of +steady state supported in NONMEM 7.5. + +## Steady state and additional doses + +NONMEM supports steady state with additional doses, and as of `rxode2` +2.1.0, we do too. At first I thought simply duplicating the steady +state records is what was done, but it is not the case. + +Lets look at an example where the additional doses take along the +steady state flag: + +```{r ssid25print} +dfull <- nlmixr2data::nmtest +d <- dfull[dfull$id ==25, ] + +print(d[d$evid != 0, ]) +``` + +In this example there is a steady-state 1 followed by a steady state 2 +dose. If this solved by duplicated the steady state information you would get: + +```{r ssid25plot1} +rxSolve(fl, d, addlDropSs=FALSE) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 when addlDropSs=FALSE", + subtitle="or the steady-state flag is preserved does not match") + + theme(plot.title = element_markdown()) +``` + +In this case: + +- The first ODE system is reset and the dose is solved to steady +state. + +- On the second ODE dose, the system is reset and solved to steady +state, the old concentration is added to the steady state dose to +produce the overall PK curve. + +Since the system **keeps** the steady-state flag with additional +doses, this behavior is repeated, which is what you are instructing +the system to do, after all. + +Note that NONMEM does not keep the steady state flag with additional +doses, it simply keeps dosing without the steady state flag. In this +case, you would want to keep dosing without the steady-state flag to +arrive at the true steady state in about 1 full dosing interval. + +This is why the default for `rxode2` is to not to keep the steady +state flag either: + +```{r ssid25plot2} +rxSolve(fl, d) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 when addlDropSs=TRUE", + subtitle="or the steady-state flag is NOT preserved matches NONMEM") + + theme(plot.title = element_markdown(), + plot.subtitle=element_markdown()) +``` + +This rolling out of doses without the steady-state flag is also why +NONMEM recovers in the prior unsupported steady state scenario. + + +## Other things retained in addl doses in NONMEM vs rxode2 + +One way that steady state is handled that is different than NONMEM is in covariate definitions: + +```{r ss102print} +dfull <- nlmixr2data::nmtest +d <- dfull[dfull$id ==102, ] + +print(d[d$evid != 0, ]) +``` + +In this case it is also helpful to see how the covariates are defined: + +```{r ss102print2} +print(head(d)) +``` + +Here you can see that the covariate `lagt` starts at `12.13` and then +drops to `0` after the first dose. When comparing NONMEM and rxode2 +solves we have the following plot: + +```{r ss102plot1} +rxSolve(fl, d) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 when addlKeepsCov=FALSE", + subtitle="shows a mismatch between NONMEM and rxode2") + + theme(plot.title = element_markdown()) +``` + +For the `rxode2` solve, the covariate `lagt` is imputed from the +surrounding times instead of carrying forward the value `12.13`, which +we at the nlmixr2 team thinks is the right behavior. In contrast, +NONMEM carries the covariate value forward for all of the additional +doses. You can force `rxode2` to duplicate the NONMEM way of solving +by `addlKeepsCov=TRUE`. + +```{r ss102plot2} +rxSolve(fl, d, addlKeepsCov=TRUE) %>% + plot(cp) + + geom_point(data=d, aes(x=time, y=cp), color="red") + + labs(x=NULL, + y=NULL, + title = "NONMEM solve vs rxode2 when addlKeepsCov=TRUE", + subtitle="shows an exact match between NONMEM and rxode2") + + theme(plot.title = element_markdown()) +``` + +## Looking forward + +We are working on implementing these same steady state features in the +solved systems of rxode2 (and giving the solved systems a general +overall by changing the method to get the solved solutions). We +decided to push this a bit early because we had to fix some things for +CRAN. The old steady state systems still apply for solved linear +models (and all the linear model bugs still apply too). We hope to +clean these up as soon as we can (though it took us quite a bit of +time to implement these steady state features in rxode2 ODE systems). + +One of the other things this enables is the ability to do adaptive +dosing inside of `rxode2`/`nlmixr2` which opens up interesting ways of +coding adaptive dosing simulations and maybe dual peak absorption +models. + +## Conclusion + +- With this new release of `rxode2`, `NONMEM` and `rxode2` steady state + handling are now closer, but still not the same. + +- We have made some different choices about steady state support (for + example the covariate handling, and have not included empirical + steady state) diff --git a/content/blog/2024-04-04-steady-state/index.html b/content/blog/2024-04-04-steady-state/index.html new file mode 100644 index 0000000..c8138de --- /dev/null +++ b/content/blog/2024-04-04-steady-state/index.html @@ -0,0 +1,307 @@ +--- +title: nlmixr2/rxode2 steady state changes +author: 'Matthew Fidler' +date: '2024-04-04' +slug: [] +categories: [nlmixr2, rxode2, steady state] +tags: [steady state] +--- + + + +

One of the things that I changed in the last release was steady state.

+

Once I created the nonmem2rx package, I searched for NONMEM control +streams that we could test the import from, especially those with +attached data. I ran across +nmtests that uses NONMEM to +test against mrgsolve and how it behaves.

+

During that test I noticed that rxode2 did not follow the convention +of NONMEM in lagged steady state doses. This blog post discusses the +prior and current differences between the steady state of NONMEM and +rxode2/NONMEM.

+

To reproduce the nmtests in the +current version of rxode2, we will first define the model to compare +with what NONMEM does:

+
library(rxode2)
+
## rxode2 2.1.2.9000 using 8 threads (see ?getRxThreads)
+##   no cache: create with `rxCreateCache()`
+
fl <- rxode2({
+  cl <- 1.1
+  v <- 20
+  ka <- 1.5
+  d/dt(depot) <- -ka*depot
+  d/dt(central) <- ka*depot - (cl/v)*central
+  lag(central) <- lagt
+  f(central) <- bioav
+  if (mode == 1) rate(central) <- rat2
+  if (mode == 2) dur(central) <- dur2
+  cp <- central/(v/1000)
+})
+
## using C compiler: ‘gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0’
+## In file included from /usr/share/R/include/R.h:71,
+##                  from /home/matt/R/x86_64-pc-linux-gnu-library/4.3/rxode2parse/include/rxode2parse.h:33,
+##                  from /home/matt/R/x86_64-pc-linux-gnu-library/4.3/rxode2/include/rxode2.h:9,
+##                  from /home/matt/R/x86_64-pc-linux-gnu-library/4.3/rxode2parse/include/rxode2_model_shared.h:3,
+##                  from rx_54ce7be6c68b8b7855a25655c9d2a337_.c:117:
+## /usr/share/R/include/R_ext/Complex.h:80:6: warning: ISO C99 doesn’t support unnamed structs/unions [-Wpedantic]
+##    80 |     };
+##       |      ^
+

Then we import the test data from nlmixr2data (note this dataset has +been modified from the original +nmtests with a few more tests +cases).

+
+

Steady State with Lag time

+

We will show the first difference we saw in the nmtests:

+
dfull <- nlmixr2data::nmtest
+d <- dfull[dfull$id ==9, ]
+
+print(d[d$evid != 0, ])
+
##      id time     cp cmt amt evid ii ss addl rate lagt bioav rat2 dur2 mode
+## 1057  9    0 1002.7   2 100    1 24  1    3   10 3.16 0.412   10    2    0
+

In this case, there is a lag time for a steady state dose. In the previous version of rxode2 we saw:

+
library(ggtext)
+library(ggplot2)
+
+rxSolve(fl, d, ssAtDoseTime=FALSE) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> <span style='font-family: monospace;'>ssAtDoseTime=FALSE</span>",
+       subtitle="show a difference at dose time until the lag time") +
+  theme(plot.title = element_markdown()) +
+  annotate("rect", ymin=c(0, 0), ymax=c(1200, 1200),xmin=c(0, 0), xmax=c(4, 4),
+           fill="blue", color="blue",
+           alpha=0.2)
+

+

At first I was not concerned with the difference because we do not +have to match NONMEM. The purpose of having the steady state in the +first place was to simulate steady state. In my opinion, simulating +steady state and simulating a few extra doses was the way to make sure +you had the correct steady state. (I still think there are situations +in NONMEM where this is best practice as I will mention later).

+

However some in the nlmixr2 team suggested that it should not wait for +another cycle, it is more convenient to have the steady state work as +expected immediately. I eventually conceded. Hence the new default +is to solve back-calculating the steady state of the lag time:

+
rxSolve(fl, d) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> <span style='font-family: monospace;'>ssAtDoseTime=TRUE</span>",
+       subtitle="show no differences (default as of rxode2 2.1.0)") +
+  theme(plot.title = element_markdown())
+

+
+
+

More extreme steady state example

+

One of the new features of rxode2 is handling steady state where +steady state interval is less than the infusion interval. For +example, from the nmtests +dataset:

+
dfull <- nlmixr2data::nmtest
+d <- dfull[dfull$id ==10, ]
+
+print(d[d$evid != 0, ])
+
##      id time     cp cmt amt evid ii ss addl rate lagt bioav rat2 dur2 mode
+## 1189 10    0 6014.5   2 100    1 12  1    4    2    0 0.812   10    2    0
+

In this case, the infusion lasts well over 12 hours +(100/2*0.812=40.2) even though the steady state dosing is every 12 +hours. If this was performed it would mean dosing every 12 hours and +not removing the previous zero order infusion dose. It seems a bit +extreme, but perhaps this is a zero order release tablet that lasts in +the body for a few days. In this case we match NONMEM:

+
rxSolve(fl, d) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> for interdose interval < infusion duration",
+       subtitle="show no differences (as of rxode2 2.1.0)") +
+  theme(plot.title = element_markdown())
+

+

If you look carefully, there are additional times where each infusion +is turned off as the remaining doses wear off.

+
+
+

Another extreme steady state support

+

In addition to supporting steady state where the inter-dose interval +is smaller than the infusion time, we also now support cases where the +lag-time is greater than the inter-dose interval. This is an +additional test case that is found in our fork of +nmtests.

+
dfull <- nlmixr2data::nmtest
+d <- dfull[dfull$id ==409, ]
+
+print(d[d$evid != 0, ])
+
##       id time     cp cmt amt evid ii ss addl rate lagt bioav rat2 dur2 mode
+## 4899 409    0 -18569   2 100    1 24  1    1   10   46 0.412   10    2    0
+

This gives the following:

+
rxSolve(fl, d) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> for lag time > interdose interval",
+       subtitle="show large at beginning (as of rxode2 2.1.0 and NONMEM 7.4)") +
+  theme(plot.title = element_markdown())
+

+

In this case, there are large differences between what NONMEM has and +what rxode2/nlmixr2, especially at the beginning of the profile. +Here NONMEM has negative concentrations. They become positive because +NONMEM changes the steady state doses to non-steady state doses with +the addl flag (as does rxode2). In a few steady state doses after +the initial dose is administered, steady state is achieved in NONMEM +as well. You can see in this plot that the steady state is achieved +immediately in the rxode2 case.

+

This is one of the reasons why I suggest a few extra doses +“just in case”.

+

I did reach out to Bob Bauer about what I thought was a bug. His +reply is as follows:

+
+

Rules about steady-state feature are documented as follows:

+

According to guide VIII, the manual reads, under the section ABSORPTION LAG PARAMETER:

+

An absorption lag time for a dose is computed by the PK routine using, if needed, information in the dose record. When additional doses are specified on a dose event record, the absorption lag time applies to the dose and to all the additional doses. With a steady-state multiple dose the absorption lag time applies not only to this dose, but also to all the preceding implied doses. With a steady-state dose, the lag time should be less than the interdose interval

+
+
+

In guide VIII, section ADDL DATA ITEM:

+

For non-steady-state doses, ADDL should be a positive number if and only if the II data item is a positive number. II giv es the time between additional doses. For steady-state infusions, ADDL must be 0. For other steady-state doses, ADDL is optional. If it is a positive number, it continues the pattern of implied doses beyond the steady-state dose. The additional doses of the pattern are non-steady state doses.

+

And in intro7.pdf:

+

With NM75 there is a new way of computing SS, the Empirical method, in which there is no SS data item, and a negative value of ADDL requests the computation. This is described separately. (See empirical_SS). (See Guide Introduction_7 “An Empirical Method of Achieving Steady State”)

+
+

So my impression is this is not supported by NONMEM (as documented in +the manual). The key statement for me is “With a steady-state dose, +the lag time should be less than the interdose interval”. However, a +run-time error does not raise when this condition occurs (which would +be helpful I think).

+

Note that rxode2 does not currently support the empirical method of +steady state supported in NONMEM 7.5.

+
+
+

Steady state and additional doses

+

NONMEM supports steady state with additional doses, and as of rxode2 +2.1.0, we do too. At first I thought simply duplicating the steady +state records is what was done, but it is not the case.

+

Lets look at an example where the additional doses take along the +steady state flag:

+
dfull <- nlmixr2data::nmtest
+d <- dfull[dfull$id ==25, ]
+
+print(d[d$evid != 0, ])
+
##      id time     cp cmt amt evid ii ss addl rate lagt bioav rat2 dur2 mode
+## 3175 25    0 1891.9   1 100    1 24  1    3    0    0     1   10    2    0
+## 3188 25   12 4606.4   1  50    1 24  2    3    0    0     1   10    2    0
+

In this example there is a steady-state 1 followed by a steady state 2 +dose. If this solved by duplicated the steady state information you would get:

+
rxSolve(fl, d, addlDropSs=FALSE) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> when <span style='font-family: monospace'>addlDropSs=FALSE</span>",
+       subtitle="or the steady-state flag is preserved does not match") +
+  theme(plot.title = element_markdown())
+

+

In this case:

+ +

Since the system keeps the steady-state flag with additional +doses, this behavior is repeated, which is what you are instructing +the system to do, after all.

+

Note that NONMEM does not keep the steady state flag with additional +doses, it simply keeps dosing without the steady state flag. In this +case, you would want to keep dosing without the steady-state flag to +arrive at the true steady state in about 1 full dosing interval.

+

This is why the default for rxode2 is to not to keep the steady +state flag either:

+
rxSolve(fl, d) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> when <span style='font-family: monospace'>addlDropSs=TRUE</span>",
+       subtitle="or the steady-state flag is <span style='color: black'>NOT</span> preserved matches NONMEM") +
+  theme(plot.title = element_markdown(),
+        plot.subtitle=element_markdown())
+

+

This rolling out of doses without the steady-state flag is also why +NONMEM recovers in the prior unsupported steady state scenario.

+
+
+

Other things retained in addl doses in NONMEM vs rxode2

+

One way that steady state is handled that is different than NONMEM is in covariate definitions:

+
dfull <- nlmixr2data::nmtest
+d <- dfull[dfull$id ==102, ]
+
+print(d[d$evid != 0, ])
+
##       id time cp cmt amt evid ii ss addl rate  lagt bioav rat2 dur2 mode
+## 3440 102    0  0   2 100    1 24  0    3    0 12.13  2.23   10    2    0
+

In this case it is also helpful to see how the covariates are defined:

+
print(head(d))
+
##       id time cp cmt amt evid ii ss addl rate  lagt bioav rat2 dur2 mode
+## 3440 102    0  0   2 100    1 24  0    3    0 12.13  2.23   10    2    0
+## 3441 102    0  0   2   0    0  0  0    0    0  0.00  2.23   10    2    0
+## 3442 102    1  0   2   0    0  0  0    0    0  0.00  2.23   10    2    0
+## 3443 102    2  0   2   0    0  0  0    0    0  0.00  2.23   10    2    0
+## 3444 102    3  0   2   0    0  0  0    0    0  0.00  2.23   10    2    0
+## 3445 102    4  0   2   0    0  0  0    0    0  0.00  2.23   10    2    0
+

Here you can see that the covariate lagt starts at 12.13 and then +drops to 0 after the first dose. When comparing NONMEM and rxode2 +solves we have the following plot:

+
rxSolve(fl, d) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> when <span style='font-family: monospace'>addlKeepsCov=FALSE</span>",
+       subtitle="shows a mismatch between NONMEM and rxode2") +
+  theme(plot.title = element_markdown())
+

+

For the rxode2 solve, the covariate lagt is imputed from the +surrounding times instead of carrying forward the value 12.13, which +we at the nlmixr2 team thinks is the right behavior. In contrast, +NONMEM carries the covariate value forward for all of the additional +doses. You can force rxode2 to duplicate the NONMEM way of solving +by addlKeepsCov=TRUE.

+
rxSolve(fl, d, addlKeepsCov=TRUE) %>%
+  plot(cp) +
+  geom_point(data=d, aes(x=time, y=cp), color="red") +
+  labs(x=NULL,
+       y=NULL,
+       title = "<span style = 'color: red;'>NONMEM</span> solve vs <span style='color: black;'>rxode2</span> when <span style='font-family: monospace'>addlKeepsCov=TRUE</span>",
+       subtitle="shows an exact match between NONMEM and rxode2") +
+  theme(plot.title = element_markdown())
+

+
+
+

Looking forward

+

We are working on implementing these same steady state features in the +solved systems of rxode2 (and giving the solved systems a general +overall by changing the method to get the solved solutions). We +decided to push this a bit early because we had to fix some things for +CRAN. The old steady state systems still apply for solved linear +models (and all the linear model bugs still apply too). We hope to +clean these up as soon as we can (though it took us quite a bit of +time to implement these steady state features in rxode2 ODE systems).

+

One of the other things this enables is the ability to do adaptive +dosing inside of rxode2/nlmixr2 which opens up interesting ways of +coding adaptive dosing simulations and maybe dual peak absorption +models.

+
+
+

Conclusion

+ +
diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot1-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot1-1.png new file mode 100644 index 0000000..5fbac1b Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot1-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot2-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot2-1.png new file mode 100644 index 0000000..5139dbb Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ss102plot2-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ssAtDoseTime-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssAtDoseTime-1.png new file mode 100644 index 0000000..8c2a38a Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssAtDoseTime-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid10plot-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid10plot-1.png new file mode 100644 index 0000000..dd4bb1c Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid10plot-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot1-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot1-1.png new file mode 100644 index 0000000..b83a76d Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot1-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot2-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot2-1.png new file mode 100644 index 0000000..2aa24a7 Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid25plot2-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid409plot-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid409plot-1.png new file mode 100644 index 0000000..f683af6 Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/ssid409plot-1.png differ diff --git a/content/blog/2024-04-04-steady-state/index_files/figure-html/unnamed-chunk-2-1.png b/content/blog/2024-04-04-steady-state/index_files/figure-html/unnamed-chunk-2-1.png new file mode 100644 index 0000000..ec26054 Binary files /dev/null and b/content/blog/2024-04-04-steady-state/index_files/figure-html/unnamed-chunk-2-1.png differ