| SOCR ≫ | DSPA ≫ | DSPA2 Topics ≫ |
This DSPA Appendix shows examples of dynamic stochastic differential equation (SDE) models of cancer-treatment. The main objective is to develop an advanced generative stochastic differential equation (SDE) model proposing a new approach of cancer-treatment prediction. Ideally, the model should balance the relations between the primary pair of clinical outcome measures - intensity of the cancer-treatment intervention (by surgery, chemotherapy and radiation, manifesting as tumor local-control, \(LC\)) and side-effects (\(RP2\)), radiation pneumonitis grade 2 or higher, a measure of significant side effect in radiation therapy, see DOI: 10.1259/bjr.20220239.
Review the earlier Volterra-Lotka preditor-pray model and Hodgkin-Huxley (HH) model of neuronal neuron action potential propagation, which provide an oversimplified motivation for building a more advanced SDE cancer-model, which includes many controllable parameters accounting for the univariate distributions of dozens of observable clinical variables (biomarkers) and multivariate relations. The model (hyper)parameters can be tuned by Bayesian optimization using very sparsely sampled longitudinal clinical cancer-treatment data that includes the temporal dynamics of \(LC\) and \(RP2\) clinical outcomes from the point of initial diagnosis, through cancer-treatment intervention using alternative treatment strategies, to the final outcome stages. We’ll include a simulation of the proposed model and visualize SDE model instantiations that shows the time dynamics of \(LC\) and \(RP2\).
This advanced stochastic differential equation (SDE) model to predict and simulate cancer-treatment outcomes will track
Such a generative SDE framework can be viewed either as a Lotka-Volterra extension, where \(LC\) and \(RP2\) interact like predator-prey, or as an Hodgkin-Huxley-like system (with gating-type variables controlling “firing” or “flare-up” episodes of toxicity vs. tumor response). The key is to incorporate both deterministic dynamics (e.g., standard dose-response models) and stochastic components, e.g., random fluctuations reflecting inter-patient heterogeneity, measurement noise, or temporal variability in a patient’s biology.
The following R chunk includes
knitr-specific parallel configuration specification.
library(knitr)
knitr::opts_chunk$set(
cache = FALSE, # Disable caching for parallel code
autodep = FALSE, # Disable automatic dependency detection
message = FALSE, # Suppress messages
warning = FALSE # Suppress warnings
)We will start with an oversimplified mathematical model of tumor dynamics without utilizing advanced biophysical principles. A more sophisticated biophysics SDE model will be presented later.
Let’s denote the state variables by \[\mathbf{X}(t) = \begin{pmatrix} LC(t) \\ RP2(t) \\ \mathbf{B}(t) \end{pmatrix},\] where \(LC(t)\) measures local tumor control (e.g., size reduction, imaging readouts), \(RP2(t)\) measures radiation- or drug-related side effects or toxicity, and \(\mathbf{B}(t)\) is a vector of relevant biomarkers or other continuous clinical variables (e.g., lab values, immunological markers), potentially of dimension 10-30 depending on data availability.
The treatment inputs can be denoted by \(\mathbf{u}(t)\) the treatment inputs (e.g., dose schedules for surgery/chemotherapy/radiation, or immunotherapy cycles). These external controls can drive the tumor and/or side-effect trajectories.
Next, we need to formulate the time evolution of the SDE model
\[d \mathbf{X}(t) = \mathbf{f}\bigl(\mathbf{X}(t), \mathbf{u}(t), \boldsymbol{\theta}\bigr)\,dt \;+\; \mathbf{g}\bigl(\mathbf{X}(t), \mathbf{u}(t), \boldsymbol{\theta}\bigr)\,d\mathbf{W}(t),\] where \(\mathbf{f}(\cdot)\) is the drift (deterministic) part, modeling how tumor control and toxicity progress on average, \(\mathbf{g}(\cdot)\) encodes the diffusion (noise) part, allowing for random fluctuations in responses and side effects, \(\boldsymbol{\theta}\) is a parameter set controlling tumor growth rates, clearance rates, synergy between multiple therapies, and so on, and \(\mathbf{W}(t)\) is a vector Wiener process (Brownian motion).
Using intuition from the Lotka-Volterra model, we may treat \(LC\) as a “prey” variable (grows unless suppressed) and \(RP2\) as a “predator” (side effects escalate and feed off continuing treatment). Let’s consider a possible drift in the form
\[\begin{aligned} dLC &= \Bigl[\alpha\,LC - \beta\,LC \cdot RP2 \;-\;\phi_{\text{decay}}\,LC \Bigr] dt \;+\; \sigma_{LC}(LC,RP2)\, dW_1,\\[6pt] dRP2 &= \Bigl[\gamma\,LC \cdot RP2 - \delta\,RP2 \;-\;\psi_{\text{treatment}}\, \mathbf{u}(t)\,RP2 \Bigr] dt \;+\; \sigma_{RP2}(LC,RP2)\, dW_2,\\[6pt] d\mathbf{B} &= \mathbf{F}_{\mathbf{B}}(\mathbf{B}, LC, RP2, \mathbf{u})\,dt \;+\; \boldsymbol{\Sigma}_{\mathbf{B}}(\mathbf{B},LC,RP2)\,d\mathbf{W}_{\mathbf{B}}, \end{aligned}\] where \(\alpha, \beta, \gamma, \delta, \dots\) are parameters, and \(\sigma_{LC}, \sigma_{RP2}, \boldsymbol{\Sigma}_{\mathbf{B}}\) are noise intensities (possibly state-dependent).
Similarly to the Hodgkin-Huxley model, \(LC\) is analogous to a “membrane voltage,” while \(RP2\) (and possibly some gating variables) evolves more slowly, controlling when toxicity “fires.” The drift terms would mimic the classic HH model formulation:
\[\begin{aligned} d LC &= \text{(nonlinear function of LC, gating vars, u(t))}\,dt \;+\; \ldots,\\ d RP2 &= \text{(slower gating dynamics, driven by LC transitions)}\,dt \;+\; \ldots \end{aligned}.\]
Such a SDE model captures bursts of toxicity or short-lived flare-ups.
The (hyper)parameter vector estimation can be achieved by using Bayesian optimization. Because clinical data are often limited and noisy, we can use Bayesian methods (e.g., approximate Bayesian computation in Julia, Stan, or specialized packages) or utilize Bayesian optimization to tune the parameter vector \(\boldsymbol{\theta}\).
In clinical setting, naturally sparse sampling (few time points per patient) is handled by fitting population-level priors, then refining posteriors for subgroups or individuals. We can define a likelihood linking the SDE outputs to observed data (LC, toxicities, biomarkers). Markov chain Monte Carlo (MCMC) or gradient-based Bayesian approaches can then infer the posterior distribution for \(\boldsymbol{\theta}\).
R
ImplementationWe’ll start with a simpler first-generation SDE 2D model, using the
snssde2d() function for the \(LC\)-\(RP2\) subsystem (as higher-dimensional SDEs
require specialized handling in R). We’ll focus just on
\(LC\) and \(RP2\) dynamics, with treatment as an
external parameter.
The state variables are \[\mathbf{X}(t) = \begin{pmatrix} LC(t) \\ RP2(t) \end{pmatrix}. \]
The 2D SDE model system is \[\begin{aligned} dLC &= \left[\alpha LC \left(1 - \frac{LC}{K}\right) - \beta LC \cdot RP2 - \phi_{\text{treat}} u(t) \right] dt + \sigma_{LC} LC \, dW_1, \\ dRP2 &= \left[\gamma LC \cdot RP2 - \delta RP2 + \psi_{\text{treat}} u(t) \right] dt + \sigma_{RP2} \sqrt{RP2} \, dW_2. \end{aligned}\]
library(Sim.DiffProc)
library(plotly)
# Parameters
alphaP <- 0.3 # Tumor growth rate
KP <- 1.5 # Tumor carrying capacity
betaP <- 0.02 # Toxicity suppression rate
gammaP <- 0.015 # Toxicity activation rate
deltaP <- 0.4 # Toxicity clearance rate
phi_treat <- 0.1 # Treatment efficacy on LC
psi_treat <- 0.05 # Treatment-induced toxicity
sigma_LC <- 0.05 # Noise for LC
sigma_RP2 <- 0.1 # Noise for RP2
# Treatment schedule (example: weekly cycles)
u_t <- function(t) {
ifelse(t %% 7 < 2, 1.0, 0.0) # Treatment active 2 days/week
}
# Define drift and diffusion for snssde2d()
fx <- expression(
alphaP * x * (1 - x/KP) - betaP * x * y - phi_treat * 1.0, # u(t) simplified to 1 when active
gammaP * x * y - deltaP * y + psi_treat * 1.0
)
gx <- expression(
sigma_LC * x,
sigma_RP2 * sqrt(y)
)
# Simulate
set.seed(123)
mod <- snssde2d(
drift = fx,
diffusion = gx,
x0 = c(x = 0.8, y = 0.1),
t0 = 0,
T = 60,
N = 500,
M = 50
)
# Visualize
fig <- plot_ly()
for (i in 1:5) { # Plot first 5 paths
fig <- fig %>%
add_trace(x = mod$time, y = mod$X[, i], name = paste("LC", i),
type = "scatter", mode = "lines", line = list(color = "blue")) %>%
add_trace(x = mod$time, y = mod$Y[, i], name = paste("RP2", i),
line = list(color = "red"), yaxis = "y2")
}
fig <- fig %>% layout(
title = "LC (Tumor Control) vs. RP2 (Toxicity)",
yaxis2 = list(overlaying = "y", side = "right", title = "RP2"),
xaxis = list(title = "Time (Days)")
)
figTo match the existing R package capabilities, in this
simplified 2D model the state space only covers \(LC\) and \(RP2\), but excludes the vector of
biomarkers \(B\). We’ll revisit this
later. The treatment schedule uses treatment cycles directly in
the drift terms; phi_treat * 1.0 assumes full dose
when active. For time-dependent u(t), we should precompute
its values over the time grid and reference them in the drift
expressions. An extension accounting for observable vectors of
biomarkers, which are omitted in the 2D SDE model, may require
simulating each biomarker as a 1D SDE or use alternative packages like
pomp.
Below we show the protocol for R-based parameter
estimation using rBayesianOptimization.
library(rBayesianOptimization)
# Simulate observed data (ground truth)
observed_data <- data.frame(
time = seq(from=mod$t0, to=mod$T, by=mod$Dt),
LC = rowMeans(mod$X),
RP2 = rowMeans(mod$Y)
)
# Objective function to minimize MSE
objective_func <- function(alphaP, betaP, gammaP) {
sim_mod <- snssde2d(
drift = expression(
alphaP * x * (1 - x/1.5) - betaP * x * y - 0.1,
gammaP * x * y - 0.4 * y + 0.05
),
diffusion = expression(0.05*x, 0.1*sqrt(y)),
x0 = c(0.8, 0.1),
t0 = 0,
T = 60,
N = 500
)
mse <- mean((sim_mod$X - observed_data$LC)^2 + (sim_mod$Y - observed_data$RP2)^2)
return(list(Score = -mse))
}
# Bayesian optimization
opt_result <- BayesianOptimization(
objective_func,
bounds = list(
alphaP = c(0.1, 0.5),
betaP = c(0.01, 0.05),
gammaP = c(0.01, 0.03)
),
init_points = 5,
n_iter = 20
)## elapsed = 0.02 Round = 1 alphaP = 0.340964 betaP = 0.04482155 gammaP = 0.02975993 Value = -0.01000155
## elapsed = 0.00 Round = 2 alphaP = 0.1091407 betaP = 0.01837854 gammaP = 0.02581208 Value = -0.01493705
## elapsed = 0.02 Round = 3 alphaP = 0.428225 betaP = 0.03315608 gammaP = 0.01017334 Value = -0.005088571
## elapsed = 0.00 Round = 4 alphaP = 0.1146278 betaP = 0.02775184 gammaP = 0.02165458 Value = -0.005338672
## elapsed = 0.00 Round = 5 alphaP = 0.1940198 betaP = 0.0246884 gammaP = 0.01649646 Value = -0.007238786
## elapsed = 0.02 Round = 6 alphaP = 0.4813248 betaP = 0.03072383 gammaP = 0.02149576 Value = -0.1544111
## elapsed = 0.01 Round = 7 alphaP = 0.1550263 betaP = 0.03071254 gammaP = 0.02586016 Value = -0.009795527
## elapsed = 0.00 Round = 8 alphaP = 0.1000 betaP = 0.0500 gammaP = 0.0100 Value = -0.04291667
## elapsed = 0.00 Round = 9 alphaP = 0.3880017 betaP = 0.02188004 gammaP = 0.01164873 Value = -0.01688399
## elapsed = 0.01 Round = 10 alphaP = 0.1963528 betaP = 0.01123368 gammaP = 0.0300 Value = -0.007565522
## elapsed = 0.02 Round = 11 alphaP = 0.282257 betaP = 0.04799122 gammaP = 0.01532491 Value = -0.009646454
## elapsed = 0.00 Round = 12 alphaP = 0.245288 betaP = 0.04974854 gammaP = 0.02067502 Value = -0.02056674
## elapsed = 0.00 Round = 13 alphaP = 0.2989052 betaP = 0.0500 gammaP = 0.0100 Value = -0.01580561
## elapsed = 0.00 Round = 14 alphaP = 0.1000 betaP = 0.0500 gammaP = 0.0300 Value = -0.02294954
## elapsed = 0.00 Round = 15 alphaP = 0.1000 betaP = 0.0500 gammaP = 0.01689369 Value = -0.02679663
## elapsed = 0.00 Round = 16 alphaP = 0.5000 betaP = 0.0100 gammaP = 0.0300 Value = -0.006871026
## elapsed = 0.00 Round = 17 alphaP = 0.5000 betaP = 0.03723532 gammaP = 0.0100 Value = -0.02225222
## elapsed = 0.00 Round = 18 alphaP = 0.1854054 betaP = 0.02100875 gammaP = 0.01888987 Value = -0.009016901
## elapsed = 0.00 Round = 19 alphaP = 0.2725012 betaP = 0.02431722 gammaP = 0.0274043 Value = -0.01257695
## elapsed = 0.00 Round = 20 alphaP = 0.2398215 betaP = 0.03820745 gammaP = 0.01359014 Value = -0.01843583
## elapsed = 0.00 Round = 21 alphaP = 0.5000 betaP = 0.03124543 gammaP = 0.02835901 Value = -0.01552359
## elapsed = 0.00 Round = 22 alphaP = 0.1661688 betaP = 0.03703741 gammaP = 0.02313721 Value = -0.01509109
## elapsed = 0.00 Round = 23 alphaP = 0.4314345 betaP = 0.01653852 gammaP = 0.02955281 Value = -0.009896026
## elapsed = 0.00 Round = 24 alphaP = 0.1721756 betaP = 0.02145591 gammaP = 0.02817582 Value = -0.01390439
## elapsed = 0.00 Round = 25 alphaP = 0.1000 betaP = 0.02164099 gammaP = 0.01373757 Value = -0.01346684
##
## Best Parameters Found:
## Round = 3 alphaP = 0.428225 betaP = 0.03315608 gammaP = 0.01017334 Value = -0.005088571
## alphaP betaP gammaP
## 0.42822499 0.03315608 0.01017334
# Set the 3 parameters to the BO estimates and plot 50 SDE Model solutions
alphaP <- opt_result$Best_Par[[1]] # Tumor growth rate
betaP <- opt_result$Best_Par[[2]] # Toxicity suppression rate
gammaP <- opt_result$Best_Par[[3]] # Toxicity activation rate
# Define drift and diffusion for snssde2d()
fx <- expression(
alphaP * x * (1 - x/KP) - betaP * x * y - phi_treat * 1.0, # u(t) simplified to 1 when active
gammaP * x * y - deltaP * y + psi_treat * 1.0
)
gx <- expression(
sigma_LC * x,
sigma_RP2 * sqrt(y)
)
# Simulate
set.seed(123)
mod <- snssde2d(
drift = fx,
diffusion = gx,
x0 = c(x = 0.8, y = 0.1),
t0 = 0,
T = 60,
N = 500,
M = 50
)
# Visualize
fig <- plot_ly()
for (i in 6:10){ # 1:mod$M) { # Plot just 5 paths or all of them
fig <- fig %>%
add_trace(x = mod$time, y = mod$X[, i], name = paste("LC", i),
type = "scatter", mode = "lines", line = list(color = "blue")) %>%
add_trace(x = mod$time, y = mod$Y[, i], name = paste("RP2", i),
line = list(color = "red"), yaxis = "y2")
}
fig <- fig %>% layout(
title = "LC (Tumor Control) vs. RP2 (Toxicity)",
yaxis2 = list(overlaying = "y", side = "right", title = "RP2"),
xaxis = list(title = "Time (Days)")
)
fig