Fit CT-VAR Using dynr

Author

Ivan Jacob Agaloos Pesigan

Published

February 2, 2026

Installing Required Packages

Before proceeding, ensure that the necessary packages are installed and loaded. We use dynr for model fitting, expm for matrix exponentiation, and simStateSpace for data generation.

library(dynr)
library(expm)
library(simStateSpace)

Data Generation

We simulate continuous-time data from a three-variable system (\(X\), \(M\), \(Y\)) following an Ornstein–Uhlenbeck (OU) process, which underlies the CT-VAR model.

Each latent variable evolves continuously over time according to a system of stochastic differential equations characterized by:

  • Drift matrix \(\boldsymbol{\Phi}\) (system dynamics),
  • Process noise covariance \(\boldsymbol{\Sigma}\) (process variability), and
  • Measurement noise covariance \(\boldsymbol{\Theta}\) (observation error)

Data Simulation Using simStateSpace

We generate data using the SimSSMOUFixed() function from the simStateSpace package, which simulates trajectories from a continuous-time state-space model with fixed parameters.

n
[1] 20
time
[1] 100
delta_t
[1] 0.1
mu0
[1] 0 0 0
sigma0
     [,1] [,2] [,3]
[1,]  1.0  0.2  0.2
[2,]  0.2  1.0  0.2
[3,]  0.2  0.2  1.0
sigma0_l # sigma0_l <- t(chol(sigma0))
     [,1]      [,2]      [,3]
[1,]  1.0 0.0000000 0.0000000
[2,]  0.2 0.9797959 0.0000000
[3,]  0.2 0.1632993 0.9660918
mu
[1] 0 0 0
phi
       [,1]   [,2]   [,3]
[1,] -0.357  0.000  0.000
[2,]  0.771 -0.511  0.000
[3,] -0.450  0.729 -0.693
sigma
            [,1]       [,2]        [,3]
[1,]  0.24455556 0.02201587 -0.05004762
[2,]  0.02201587 0.07067800  0.01539456
[3,] -0.05004762 0.01539456  0.07553061
sigma_l # sigma_l <- t(chol(sigma))
            [,1]      [,2]     [,3]
[1,]  0.49452559 0.0000000 0.000000
[2,]  0.04451917 0.2620993 0.000000
[3,] -0.10120330 0.0759256 0.243975
nu
[1] 0 0 0
lambda
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
theta
     [,1] [,2] [,3]
[1,]  0.2  0.0  0.0
[2,]  0.0  0.2  0.0
[3,]  0.0  0.0  0.2
theta_l # theta_l <- t(chol(theta))
          [,1]      [,2]      [,3]
[1,] 0.4472136 0.0000000 0.0000000
[2,] 0.0000000 0.4472136 0.0000000
[3,] 0.0000000 0.0000000 0.4472136
sim <- SimSSMOUFixed(
  n = n,
  time = time,
  delta_t = delta_t,
  mu0 = mu0,
  sigma0_l = sigma0_l,
  mu = mu,
  phi = phi,
  sigma_l = sigma_l,
  nu = nu,
  lambda = lambda,
  theta_l = theta_l,
  type = 0
)
plot(sim)

data <- as.data.frame(sim)
colnames(data) <- c("id", "time", "x", "m", "y")
head(data)
  id time          x          m        y
1  1  0.0 -0.3504435 0.41877429 2.611996
2  1  0.1 -0.5920330 1.07433208 1.669272
3  1  0.2 -0.7619855 1.21483834 2.369837
4  1  0.3 -1.6964652 0.21209722 2.128531
5  1  0.4 -1.2282686 0.09950326 1.891140
6  1  0.5  0.1433985 0.66784226 2.036033

Fitting the CT-VAR Model Using dynr

We now use the dynr package to estimate the parameters of the CT-VAR model from the simulated data.

Prepare the Data

dynr_data <- dynr.data(
  dataframe = data,
  id = "id",
  time = "time",
  observed = c("x", "m", "y")
)

Prepare the Initial Condition

dynr_initial <- prep.initial(
  values.inistate = c(
    0, 0, 0
  ),
  params.inistate = c(
    "mu0_1_1", "mu0_2_1", "mu0_3_1"
  ),
  values.inicov = matrix(
    data = c(
      1.0, 0.2, 0.2,
      0.2, 1.0, 0.2,
      0.2, 0.2, 1.0
    ),
    nrow = 3
  ),
  params.inicov = matrix(
    data = c(
      "sigma0_1_1", "sigma0_2_1", "sigma0_3_1",
      "sigma0_2_1", "sigma0_2_2", "sigma0_3_2",
      "sigma0_3_1", "sigma0_3_2", "sigma0_3_3"
    ),
    nrow = 3
  )
)

Prepare the Measurement Model

dynr_measurement <- prep.measurement(
  values.load = diag(3),
  params.load = matrix(
    data = "fixed",
    nrow = 3,
    ncol = 3
  ),
  state.names = c("eta_x", "eta_m", "eta_y"),
  obs.names = c("x", "m", "y")
)

Prepare the Dynamic Model

dynr_dynamics <- prep.formulaDynamics(
  formula = list(
    eta_x ~ phi_1_1 * eta_x + phi_1_2 * eta_m + phi_1_3 * eta_y,
    eta_m ~ phi_2_1 * eta_x + phi_2_2 * eta_m + phi_2_3 * eta_y,
    eta_y ~ phi_3_1 * eta_x + phi_3_2 * eta_m + phi_3_3 * eta_y
  ),
  startval = c(
    phi_1_1 = -0.2, phi_2_1 = 0.0, phi_3_1 = 0.0,
    phi_1_2 = 0.0, phi_2_2 = -0.2, phi_3_2 = 0.0,
    phi_1_3 = 0.0, phi_2_3 = 0.0, phi_3_3 = -0.2
  ),
  isContinuousTime = TRUE
)

Prepare the Noise Matrices

dynr_noise <- prep.noise(
  values.latent = matrix(
    data = 0.2 * diag(3),
    nrow = 3
  ),
  params.latent = matrix(
    data = c(
      "sigma_1_1", "sigma_2_1", "sigma_3_1",
      "sigma_2_1", "sigma_2_2", "sigma_3_2",
      "sigma_3_1", "sigma_3_2", "sigma_3_3"
    ),
    nrow = 3
  ),
  values.observed = 0.2 * diag(3),
  params.observed = matrix(
    data = c(
      "theta_1_1", "fixed", "fixed",
      "fixed", "theta_2_2", "fixed",
      "fixed", "fixed", "theta_3_3"
    ),
    nrow = 3
  )
)

Prepare the Model

In this example, we increase the maximum evaluations in the optimization process and constrain the lower and upper bounds of the parameters for the drift matrix.

dynr_model <- dynr.model(
  data = dynr_data,
  initial = dynr_initial,
  measurement = dynr_measurement,
  dynamics = dynr_dynamics,
  noise = dynr_noise,
  outfile = "fit-ct-var-dynr.c"
)
dynr_model@options$maxeval <- 100000
lb <- ub <- rep(NA, times = length(dynr_model$xstart))
names(ub) <- names(lb) <- names(dynr_model$xstart)
lb[
  c(
    "phi_1_1", "phi_2_1", "phi_3_1",
    "phi_1_2", "phi_2_2", "phi_3_2",
    "phi_1_3", "phi_2_3", "phi_3_3"
  )
] <- -1.5
ub[
  c(
    "phi_1_1", "phi_2_1", "phi_3_1",
    "phi_1_2", "phi_2_2", "phi_3_2",
    "phi_1_3", "phi_2_3", "phi_3_3"
  )
] <- 1.5
dynr_model$lb <- lb
dynr_model$ub <- ub

Model Estimation

We estimate the parameters using dynr.cook().

fn <- "fit-ct-var-dynr.Rds"
if (file.exists(fn)) {
  fit <- readRDS(fn)
} else {
  fit <- dynr.cook(
    dynr_model,
    verbose = FALSE
  )
  saveRDS(fit, file = fn)
}
[1] "Get ready!!!!"
using C compiler: ‘gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0’
Optimization function called.
Starting Hessian calculation ...
Finished Hessian calculation.
Original exit flag:  3 
Modified exit flag:  3 
Optimization terminated successfully: ftol_rel or ftol_abs was reached. 
Original fitted parameters:  -0.2150421 0.7992531 -0.5372629 -0.1590345 
-0.527781 0.7714035 0.09440367 0.0700308 -0.7375808 -1.531834 0.06888895 
-0.2670268 -2.826667 0.1710893 -2.686909 -1.630224 -1.604964 -1.643162 
-0.1764606 -0.2433819 0.331227 -0.3120474 -0.1145515 0.140546 0.1462391 
0.01082431 0.268019 

Transformed fitted parameters:  -0.2150421 0.7992531 -0.5372629 -0.1590345 
-0.527781 0.7714035 0.09440367 0.0700308 -0.7375808 0.2161389 0.01488958 
-0.05771489 0.06023562 0.006154261 0.08523567 0.1958856 0.2008968 0.1933677 
-0.1764606 -0.2433819 0.331227 0.7319468 -0.08384563 0.1028722 1.167078 
0.0007446744 1.321966 

Doing end processing
Successful trial
Total Time: 2.488782 
Backend Time: 2.470807 
summary(fit)
Coefficients:
             Estimate Std. Error t value   ci.lower   ci.upper Pr(>|t|)    
phi_1_1    -0.2150421  0.1195297  -1.799 -0.4493160  0.0192318   0.0361 *  
phi_2_1     0.7992531  0.0799146  10.001  0.6426233  0.9558830   <2e-16 ***
phi_3_1    -0.5372629  0.0900443  -5.967 -0.7137466 -0.3607793   <2e-16 ***
phi_1_2    -0.1590345  0.0980611  -1.622 -0.3512306  0.0331617   0.0525 .  
phi_2_2    -0.5277810  0.0669206  -7.887 -0.6589430 -0.3966190   <2e-16 ***
phi_3_2     0.7714035  0.0748155  10.311  0.6247679  0.9180392   <2e-16 ***
phi_1_3     0.0944037  0.0879781   1.073 -0.0780302  0.2668375   0.1417    
phi_2_3     0.0700308  0.0592130   1.183 -0.0460246  0.1860862   0.1185    
phi_3_3    -0.7375808  0.0671581 -10.983 -0.8692084 -0.6059533   <2e-16 ***
sigma_1_1   0.2161389  0.0333941   6.472  0.1506876  0.2815902   <2e-16 ***
sigma_2_1   0.0148896  0.0142932   1.042 -0.0131246  0.0429038   0.1488    
sigma_3_1  -0.0577149  0.0166399  -3.468 -0.0903285 -0.0251013   0.0003 ***
sigma_2_2   0.0602356  0.0123083   4.894  0.0361118  0.0843594   <2e-16 ***
sigma_3_2   0.0061543  0.0098537   0.625 -0.0131586  0.0254671   0.2662    
sigma_3_3   0.0852357  0.0167118   5.100  0.0524810  0.1179903   <2e-16 ***
theta_1_1   0.1958856  0.0078438  24.973  0.1805121  0.2112592   <2e-16 ***
theta_2_2   0.2008968  0.0070651  28.435  0.1870494  0.2147442   <2e-16 ***
theta_3_3   0.1933677  0.0070266  27.519  0.1795958  0.2071396   <2e-16 ***
mu0_1_1    -0.1764606  0.1990029  -0.887 -0.5664990  0.2135778   0.1877    
mu0_2_1    -0.2433819  0.2454784  -0.991 -0.7245107  0.2377469   0.1608    
mu0_3_1     0.3312270  0.2619670   1.264 -0.1822190  0.8446729   0.1031    
sigma0_1_1  0.7319468  0.2506898   2.920  0.2406039  1.2232897   0.0018 ** 
sigma0_2_1 -0.0838456  0.2213391  -0.379 -0.5176622  0.3499710   0.3524    
sigma0_3_1  0.1028722  0.2350566   0.438 -0.3578302  0.5635746   0.3308    
sigma0_2_2  1.1670776  0.3852839   3.029  0.4119350  1.9222202   0.0012 ** 
sigma0_3_2  0.0007447  0.2904693   0.003 -0.5685648  0.5700541   0.4990    
sigma0_3_3  1.3219659  0.4366714   3.027  0.4661057  2.1778261   0.0012 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

-2 log-likelihood value at convergence = 8608.96
AIC = 8662.96
BIC = 8814.18

Extracting Matrices for Use in cTMed

After fitting, we extract the estimated drift matrix (\(\boldsymbol{\Phi}\)) and process noise covariance (\(\boldsymbol{\Sigma}\)), along with their sampling covariance matrices.

coefs <- coef(fit)
vcovs <- vcov(fit)
phi_names <- c(
  "phi_1_1", "phi_2_1", "phi_3_1",
  "phi_1_2", "phi_2_2", "phi_3_2",
  "phi_1_3", "phi_2_3", "phi_3_3"
)
sigma_names <- c(
  "sigma_1_1", "sigma_2_1", "sigma_3_1",
  "sigma_2_1", "sigma_2_2", "sigma_3_2",
  "sigma_3_1", "sigma_3_2", "sigma_3_3"
)
sigma_vech_names <- c(
  "sigma_1_1", "sigma_2_1", "sigma_3_1",
  "sigma_2_2", "sigma_3_2",
  "sigma_3_3"
)
theta_names <- c(
  "phi_1_1", "phi_2_1", "phi_3_1",
  "phi_1_2", "phi_2_2", "phi_3_2",
  "phi_1_3", "phi_2_3", "phi_3_3",
  "sigma_1_1", "sigma_2_1", "sigma_3_1",
  "sigma_2_2", "sigma_3_2",
  "sigma_3_3"
)
phi <- matrix(
  data = coefs[phi_names],
  nrow = 3,
  ncol = 3
)
colnames(phi) <- rownames(phi) <- c("x", "m", "y")
sigma <- matrix(
  data = coefs[sigma_names],
  nrow = 3,
  ncol = 3
)
theta <- coefs[theta_names]
vcov_phi_vec <- vcovs[phi_names, phi_names]
vcov_sigma_vech <- vcovs[sigma_vech_names, sigma_vech_names]
vcov_theta <- vcovs[theta_names, theta_names]

Estimated Drift Matrix with Corresponding Sampling Covariance Matrix

phi
           x          m           y
x -0.2150421 -0.1590345  0.09440367
m  0.7992531 -0.5277810  0.07003080
y -0.5372629  0.7714035 -0.73758084
vcov_phi_vec
              phi_1_1       phi_2_1       phi_3_1       phi_1_2       phi_2_2
phi_1_1  0.0142873522  0.0006079799 -0.0030038849 -0.0097797943 -4.790491e-04
phi_2_1  0.0006079799  0.0063863490 -0.0004996187 -0.0001104026 -4.577211e-03
phi_3_1 -0.0030038849 -0.0004996187  0.0081079793  0.0018766011  4.121789e-04
phi_1_2 -0.0097797943 -0.0001104026  0.0018766011  0.0096159753  2.870124e-04
phi_2_2 -0.0004790491 -0.0045772109  0.0004121789  0.0002870124  4.478369e-03
phi_3_2  0.0020607367  0.0002166850 -0.0056919613 -0.0020116829 -3.373929e-04
phi_1_3  0.0066272153 -0.0000384865 -0.0010966857 -0.0065404706 -5.471564e-05
phi_2_3  0.0003941462  0.0031845277 -0.0003412820 -0.0003010944 -3.150796e-03
phi_3_3 -0.0014572097 -0.0001184132  0.0039009020  0.0014237474  1.646252e-04
              phi_3_2       phi_1_3       phi_2_3       phi_3_3
phi_1_1  0.0020607367  6.627215e-03  0.0003941462 -0.0014572097
phi_2_1  0.0002166850 -3.848650e-05  0.0031845277 -0.0001184132
phi_3_1 -0.0056919613 -1.096686e-03 -0.0003412820  0.0039009020
phi_1_2 -0.0020116829 -6.540471e-03 -0.0003010944  0.0014237474
phi_2_2 -0.0003373929 -5.471564e-05 -0.0031507962  0.0001646252
phi_3_2  0.0055973556  1.189036e-03  0.0003301834 -0.0038753551
phi_1_3  0.0011890355  7.740141e-03  0.0002868850 -0.0016934695
phi_2_3  0.0003301834  2.868850e-04  0.0035061835 -0.0003437688
phi_3_3 -0.0038753551 -1.693470e-03 -0.0003437688  0.0045102156

Process Noise Covariance Matrix with Corresponding Sampling Covariance Matrix

sigma
            [,1]        [,2]         [,3]
[1,]  0.21613889 0.014889581 -0.057714887
[2,]  0.01488958 0.060235625  0.006154261
[3,] -0.05771489 0.006154261  0.085235668
vcov_sigma_vech
              sigma_1_1     sigma_2_1     sigma_3_1     sigma_2_2     sigma_3_2
sigma_1_1  1.115167e-03  4.004670e-05 -1.981839e-04  1.254314e-05 -1.364776e-05
sigma_2_1  4.004670e-05  2.042965e-04 -1.184950e-05  1.534016e-05 -4.509985e-05
sigma_3_1 -1.981839e-04 -1.184950e-05  2.768867e-04 -4.248350e-06  1.226255e-05
sigma_2_2  1.254314e-05  1.534016e-05 -4.248350e-06  1.514936e-04 -8.509441e-06
sigma_3_2 -1.364776e-05 -4.509985e-05  1.226255e-05 -8.509441e-06  9.709484e-05
sigma_3_3  3.967862e-05  7.242787e-06 -1.124499e-04  5.840479e-07 -1.087946e-05
              sigma_3_3
sigma_1_1  3.967862e-05
sigma_2_1  7.242787e-06
sigma_3_1 -1.124499e-04
sigma_2_2  5.840479e-07
sigma_3_2 -1.087946e-05
sigma_3_3  2.792858e-04

Estimated Drift Matrix and Process Noise Covariance Matrix with Corresponding Sampling Covariance Matrix

theta
     phi_1_1      phi_2_1      phi_3_1      phi_1_2      phi_2_2      phi_3_2 
-0.215042116  0.799253150 -0.537262944 -0.159034458 -0.527781028  0.771403519 
     phi_1_3      phi_2_3      phi_3_3    sigma_1_1    sigma_2_1    sigma_3_1 
 0.094403674  0.070030797 -0.737580843  0.216138890  0.014889581 -0.057714887 
   sigma_2_2    sigma_3_2    sigma_3_3 
 0.060235625  0.006154261  0.085235668 
vcov_theta
                phi_1_1       phi_2_1       phi_3_1       phi_1_2       phi_2_2
phi_1_1    1.428735e-02  6.079799e-04 -3.003885e-03 -9.779794e-03 -4.790491e-04
phi_2_1    6.079799e-04  6.386349e-03 -4.996187e-04 -1.104026e-04 -4.577211e-03
phi_3_1   -3.003885e-03 -4.996187e-04  8.107979e-03  1.876601e-03  4.121789e-04
phi_1_2   -9.779794e-03 -1.104026e-04  1.876601e-03  9.615975e-03  2.870124e-04
phi_2_2   -4.790491e-04 -4.577211e-03  4.121789e-04  2.870124e-04  4.478369e-03
phi_3_2    2.060737e-03  2.166850e-04 -5.691961e-03 -2.011683e-03 -3.373929e-04
phi_1_3    6.627215e-03 -3.848650e-05 -1.096686e-03 -6.540471e-03 -5.471564e-05
phi_2_3    3.941462e-04  3.184528e-03 -3.412820e-04 -3.010944e-04 -3.150796e-03
phi_3_3   -1.457210e-03 -1.184132e-04  3.900902e-03  1.423747e-03  1.646252e-04
sigma_1_1 -2.333538e-03 -3.147503e-04  5.590589e-04  1.229505e-03  1.558647e-04
sigma_2_1  1.860527e-04 -4.867247e-04 -7.184730e-05 -2.533846e-04  2.364259e-04
sigma_3_1  2.316315e-04  7.797280e-05 -6.592973e-04 -3.085935e-05 -2.232206e-05
sigma_2_2 -3.241493e-05  1.690887e-04  3.090526e-05  2.853755e-05 -2.312286e-04
sigma_3_2 -5.186949e-06  3.832628e-05  9.180382e-05  2.027968e-05  3.420616e-05
sigma_3_3 -3.757908e-05 -4.591419e-05  8.377694e-05 -7.921819e-07  1.697090e-05
                phi_3_2       phi_1_3       phi_2_3       phi_3_3     sigma_1_1
phi_1_1    2.060737e-03  6.627215e-03  3.941462e-04 -1.457210e-03 -2.333538e-03
phi_2_1    2.166850e-04 -3.848650e-05  3.184528e-03 -1.184132e-04 -3.147503e-04
phi_3_1   -5.691961e-03 -1.096686e-03 -3.412820e-04  3.900902e-03  5.590589e-04
phi_1_2   -2.011683e-03 -6.540471e-03 -3.010944e-04  1.423747e-03  1.229505e-03
phi_2_2   -3.373929e-04 -5.471564e-05 -3.150796e-03  1.646252e-04  1.558647e-04
phi_3_2    5.597356e-03  1.189036e-03  3.301834e-04 -3.875355e-03 -2.910012e-04
phi_1_3    1.189036e-03  7.740141e-03  2.868850e-04 -1.693470e-03 -5.863538e-04
phi_2_3    3.301834e-04  2.868850e-04  3.506184e-03 -3.437688e-04 -7.760723e-05
phi_3_3   -3.875355e-03 -1.693470e-03 -3.437688e-04  4.510216e-03  1.448105e-04
sigma_1_1 -2.910012e-04 -5.863538e-04 -7.760723e-05  1.448105e-04  1.115167e-03
sigma_2_1  7.670478e-05  1.414938e-04 -1.119311e-04 -3.768986e-05  4.004670e-05
sigma_3_1  3.251735e-04 -1.831107e-04 -2.155920e-05 -9.609252e-05 -1.981839e-04
sigma_2_2 -3.096602e-05 -1.275493e-05  1.359967e-04  1.458799e-05  1.254314e-05
sigma_3_2 -1.288004e-04 -1.380574e-06 -1.015506e-04  6.378117e-05 -1.364776e-05
sigma_3_3  7.878627e-05  5.620888e-05  1.958059e-05 -2.839006e-04  3.967862e-05
              sigma_2_1     sigma_3_1     sigma_2_2     sigma_3_2     sigma_3_3
phi_1_1    1.860527e-04  2.316315e-04 -3.241493e-05 -5.186949e-06 -3.757908e-05
phi_2_1   -4.867247e-04  7.797280e-05  1.690887e-04  3.832628e-05 -4.591419e-05
phi_3_1   -7.184730e-05 -6.592973e-04  3.090526e-05  9.180382e-05  8.377694e-05
phi_1_2   -2.533846e-04 -3.085935e-05  2.853755e-05  2.027968e-05 -7.921819e-07
phi_2_2    2.364259e-04 -2.232206e-05 -2.312286e-04  3.420616e-05  1.697090e-05
phi_3_2    7.670478e-05  3.251735e-04 -3.096602e-05 -1.288004e-04  7.878627e-05
phi_1_3    1.414938e-04 -1.831107e-04 -1.275493e-05 -1.380574e-06  5.620888e-05
phi_2_3   -1.119311e-04 -2.155920e-05  1.359967e-04 -1.015506e-04  1.958059e-05
phi_3_3   -3.768986e-05 -9.609252e-05  1.458799e-05  6.378117e-05 -2.839006e-04
sigma_1_1  4.004670e-05 -1.981839e-04  1.254314e-05 -1.364776e-05  3.967862e-05
sigma_2_1  2.042965e-04 -1.184950e-05  1.534016e-05 -4.509985e-05  7.242787e-06
sigma_3_1 -1.184950e-05  2.768867e-04 -4.248350e-06  1.226255e-05 -1.124499e-04
sigma_2_2  1.534016e-05 -4.248350e-06  1.514936e-04 -8.509441e-06  5.840479e-07
sigma_3_2 -4.509985e-05  1.226255e-05 -8.509441e-06  9.709484e-05 -1.087946e-05
sigma_3_3  7.242787e-06 -1.124499e-04  5.840479e-07 -1.087946e-05  2.792858e-04