Mathematical and statistical software often relies on sequential
computations. ordinary differential equations where
numerical approximations are based on looping over the evolving time.
When using high-level languages such as R
calculations can
be very slow unless the algorithms can be vectorized
There are various excellent (O)DE solvers (R: deSolve) Here I will
illustrate the above techniques using the targeted
R
-package based on the target C++
library.
The ODE is specified using the specify_ode
function
args(targeted::specify_ode)
#> function (code, fname = NULL, pname = c("dy", "x", "y", "p"))
#> NULL
The differential equations are here specified as a string containing
the C++
code defining the differential equation via the
code
argument. The variable names are defined through the
pname
argument which defaults to
All variables are treated as armadillo vectors/matrices,
arma::mat
.
As an example, we can specify the simple differential equation \[y'(t) = y(t)-1\]
This compiles the function and stores the pointer in the variable
dy
.
To solve the ODE we must then use the function
solve_ode
The first argument is the external pointer, the second argument
input
is the input matrix (\(x(t)\) above), and the init
argument is the vector of initial boundary conditions \(y(0)\). The argument par
is
the vector of parameters defining the ODE (\(p\)).
In this example the input variable does not depend on any exogenous variables so we only need to supply the time points, and the defined ODE does not depend on any parameters. To approximate the solution with initial condition \(y(0)=0\), we therefore run the following code
As a more interesting example consider the Lorenz Equations \[\frac{dx_{t}}{dt} = \sigma(y_{t}-x_{t})\] \[\frac{dy_{t}}{dt} = x_{t}(\rho-z_{t})-y_{t}\] \[\frac{dz_{t}}{dt} = x_{t}y_{t}-\beta z_{t}\]
we may define them as
library(targeted)
ode <- 'dy(0) = p(0)*(y(1)-y(0));
dy(1) = y(0)*(p(1)-y(2));
dy(2) = y(0)*y(1)-p(2)*y(2);'
f <- specify_ode(ode)
With the choice of parameters given by \(\sigma=10, \rho=28, \beta=8/3\) and initial conditions \((x_0,y_0,z_0)=(1,1,1)\), we can calculate the solution
tt <- seq(0, 100, length.out=2e4)
y <- solve_ode(f, input=tt, init=c(1, 1, 1), par=c(10, 28, 8/3))
head(y)
#> [,1] [,2] [,3]
#> [1,] 1.000000 1.000000 1.0000000
#> [2,] 1.003322 1.135177 0.9920639
#> [3,] 1.013094 1.271279 0.9849468
#> [4,] 1.029068 1.409156 0.9786954
#> [5,] 1.051048 1.549630 0.9733721
#> [6,] 1.078888 1.693496 0.9690541
colnames(y) <- c("x","y","z")
scatterplot3d::scatterplot3d(y, cex.symbols=0.1, type='b',
color=viridisLite::viridis(nrow(y)))
To illustrate the use of exogenous inputs, consider the following simulated data
n <- 1e4
tt <- seq(0, 10, length.out=n) # Time
xx <- rep(0, n); xx[(n/3):(2*n/3)] <- 1 # Exogenous input, x(t)
input <- cbind(tt, xx)
and the following ODE
\[y'(t) = \beta_{0} + \beta_{1}y(t) + \beta_{2}y(t)x(t) + \beta_{3}x(t)\cdot t\]
With \(y(0)=100\) and \(\beta_0=0, \beta_{1}=0.4, \beta_{2}=-0.5, \beta_{3}=-5\) we obtain the following solution
sessionInfo()
#> R version 4.3.2 (2023-10-31)
#> Platform: aarch64-apple-darwin22.6.0 (64-bit)
#> Running under: macOS Sonoma 14.3.1
#>
#> Matrix products: default
#> BLAS: /Users/kkzh/.asdf/installs/R/4.3.2/lib/R/lib/libRblas.dylib
#> LAPACK: /Users/kkzh/.asdf/installs/R/4.3.2/lib/R/lib/libRlapack.dylib; LAPACK version 3.11.0
#>
#> locale:
#> [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#>
#> time zone: Europe/Copenhagen
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] targeted_0.5 lava_1.7.4
#>
#> loaded via a namespace (and not attached):
#> [1] Matrix_1.6-5 future.apply_1.11.1 jsonlite_1.8.8
#> [4] highr_0.10 futile.logger_1.4.3 compiler_4.3.2
#> [7] Rcpp_1.0.12 parallel_4.3.2 jquerylib_0.1.4
#> [10] globals_0.16.2 splines_4.3.2 yaml_2.3.7
#> [13] fastmap_1.1.1 lattice_0.22-5 R6_2.5.1
#> [16] knitr_1.45 future_1.33.1 nloptr_2.0.3
#> [19] bslib_0.5.1 rlang_1.1.3 cachem_1.0.8
#> [22] xfun_0.41 sass_0.4.7 viridisLite_0.4.2
#> [25] cli_3.6.2 formatR_1.14 futile.options_1.0.1
#> [28] digest_0.6.34 grid_4.3.2 mvtnorm_1.2-4
#> [31] RcppArmadillo_0.12.8.0.0 timereg_2.0.5 scatterplot3d_0.3-44
#> [34] evaluate_0.23 pracma_2.4.4 data.table_1.15.0
#> [37] lambda.r_1.2.4 numDeriv_2016.8-1.1 listenv_0.9.1
#> [40] codetools_0.2-19 survival_3.5-7 optimx_2023-10.21
#> [43] parallelly_1.37.0 rmarkdown_2.25 tools_4.3.2
#> [46] htmltools_0.5.6.1 mets_1.3.4