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)
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
)
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)")
)
fig
To 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.02 Round = 2 alphaP = 0.1091407 betaP = 0.01837854 gammaP = 0.02581208 Value = -0.01493705
## elapsed = 0.00 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.02 Round = 9 alphaP = 0.3880017 betaP = 0.02188004 gammaP = 0.01164873 Value = -0.01688399
## elapsed = 0.00 Round = 10 alphaP = 0.1963528 betaP = 0.01123368 gammaP = 0.0300 Value = -0.007565522
## elapsed = 0.00 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.02 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.01 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.02 Round = 20 alphaP = 0.2398215 betaP = 0.03820745 gammaP = 0.01359014 Value = -0.01843583
## elapsed = 0.02 Round = 21 alphaP = 0.5000 betaP = 0.03124543 gammaP = 0.02835901 Value = -0.01552359
## elapsed = 0.02 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.01 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
For higher dimensional SDE models with biomarkers, use
sde.sim()
from the sde
package or switch to
Julia
’s DifferentialEquations.jl
. In these
situations, the time-dependent parameters should precompute the
treatment schedules as vectors and reference them in
drift/diffusion terms using approxfun()
.
The parameter estimate validation may require holding-out data or
synthetic benchmarking.
Let’s build a stochastic differential equation (SDE) model that captures the dynamic interplay between tumor local control (LC), treatment-related side effects (RP2), and biomarkers (\(B\)). The model extends the Lotka-Volterra framework with treatment inputs and biomarker dynamics. Consider the triple of state variables (outcomes + inputs)
\[\mathbf{X}(t) = \begin{pmatrix} LC(t) \\ RP2(t) \\ \mathbf{B}(t) \end{pmatrix}, \quad \text{where the hyperparameter vector } \mathbf{B}(t) \in \mathbb{R}^k \text{ (e.g., } k=10\text{ biomarkers)}.\]
The analytical representation of the SDE system is \[\begin{aligned} dLC &= \left[\alpha LC \left(1 - \frac{LC}{K}\right) - \beta LC \cdot RP2 - \phi_{\text{treat}} u(t) LC \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, \\ dB_i &= \left[\mu_i(B_i) + \eta_i LC - \nu_i RP2 \right] dt + \sigma_{B_i} \, dW_{B_i}, \quad i=1,\dots,k, \end{aligned}\] which depends on the following parameters
Here’s a comprehensive solution using Julia
’s
DifferentialEquations.jl
package for high-dimensional SDE
modeling. For specificity, we’ll implement a \(5D\) system (\(LC\), \(RP2\) and a \(3\)-component biomarkers vector).
This workflow requires Julia
\(V\ge 1.9\) from julialang.org.
# R-block of code
# The first time - install these libs
#
# install.packages("JuliaCall")
# install.packages("JuliaConnectoR")
# library(JuliaCall)
# install_julia() # This is needed just once to install Julia on the system!!!
# install.packages("XRJulia") # Install the package if not already installed
# Install required R packages
# install.packages(c("JuliaCall", "plotly", "reticulate"))
library(JuliaCall)
Sys.setenv(JULIA_NUM_THREADS = 11)
julia <- julia_setup() # This will start Julia with 10 threads
# findJulia() # This will locate the Julia installation
# julia <- JuliaSession() # Start Julia and assign the session to the `julia` object
# Check that the julia object is properly initialized by running a simple Julia command:
julia_command("1 + 1") # # Should return 2
## 2
message(paste0("The (Julia) result of this command, \'julia_command(\"1 + 1\")\', is ", julia_command("1 + 1"), "!\n"))
# Verify the number of threads in Julia:
julia_command("using Base.Threads; Threads.nthreads() = 10") # Manually set threads
# Should return 10
message(paste0("The Number of Cores Julia starts with is: ", julia_eval("Threads.nthreads()"), "!\n"))
# julia_command("using Base.Threads; Threads.nthreads()") # Should return 10
# julia_command("ENV[\"JULIA_NUM_THREADS\"]") # Should return "10"
# JuliaConnectoR::stopJulia() # Terminate the Julia session
#### JUST ONE TIME!!!!
## **1.3 Julia Packages (Run in Julia REPL)**
# using Pkg
# Pkg.add(["DifferentialEquations", "ModelingToolkit", "SciMLBase", "Plots", "CSV", "HDF5"])
Side-note on Computational Inefficiencies: In this case, the \(5\) state variables include \(2\) outcomes and \(3\) biomarkers. Of course, there could be additional observables included in a more complex SDE model, which depends on a mixture of biospecimen, demographic, genetic, phenotypic and imaging descriptors. In principle, we should consider pre-processing the high-dimensional biomarkers including
Such pre-processing will boost the energy of the vital (ensemble) biomarkers within each type, and will have the added benefit of ensuring the computational feasibility of solving the complex SDE modeling in reasonable time (working with \(\approx 5\) to \(25\) salient biomarkers). In this simulation example, we use a state space composed of
\[\mathbf{X}(t) = \begin{pmatrix} LC(t) \\ RP2(t) \\ B_1(t) \\ B_2(t) \\ B_3(t) \end{pmatrix}\]
anddefine the 5D SDE model
\[\begin{aligned} dLC &= \left[\alpha LC \left(1 - \frac{LC}{K}\right) - \beta LC \cdot RP2 - \phi u(t) \right]dt + \sigma_{LC} LC \, dW_1 \\ dRP2 &= \left[\gamma LC \cdot RP2 - \delta RP2 + \psi u(t) \right]dt + \sigma_{RP2} \sqrt{RP2} \, dW_2 \\ dB_1 &= \left[\mu_1(B_{1,\text{eq}} - B_1) + \eta_1 LC \right]dt + \sigma_{B1} \, dW_3 \\ dB_2 &= \left[\mu_2(B_{2,\text{eq}} - B_2) - \eta_2 RP2 \right]dt + \sigma_{B2} \, dW_4 \\ dB_3 &= \left[\mu_3(B_{3,\text{eq}} - B_3) + \eta_3 LC \cdot RP2 \right]dt + \sigma_{B3} \, dW_5 \end{aligned} .\]
# sde_analysis.R
library(JuliaCall)
library(plotly)
# # For HDF5 file handling
# if (!require("BiocManager", quietly = TRUE))
# install.packages("BiocManager")
# BiocManager::install("rhdf5")
library(rhdf5)
# Initialize Julia
julia_setup(installJulia = TRUE)
julia_library("HDF5")
# Set the working directory
setwd("C:/Users/IvoD/Desktop/Ivo.dir/Research/UMichigan/Publications_Books/2023/DSPA_Springer_2nd_Edition_2023/Rmd_HTML/appendix/")
# Run Julia simulation
julia_source("./DSPA_Appendix19_SDE_JuliaChunk1/DSPA_Appendix19_SDE_JuliaChunk1.jl")
# Load HDF5 with explicit dimensions
h5_data <- h5read("./DSPA_Appendix19_SDE_JuliaChunk1/cancer_SDE_trajectories.h5", "/")
time_points <- h5_data$time_points
trajectory_ids <- 1:(dim(h5_data$trajectories)[3]) # 100 trajectories
dims <- as.integer(h5_data$dims) # [variables=5, time=601, trajectories=100]
# Create individual surfaces
fig <- plot_ly(showscale = FALSE) %>%
add_surface(
x = time_points, y = trajectory_ids, z = h5_data$trajectories[1,,],
name = "LC", colorscale = "Viridis", visible = TRUE
) %>%
add_surface(
x = time_points, y = trajectory_ids, z = h5_data$trajectories[2,,],
name = "RP2", colorscale = "Hot", visible = FALSE
) %>%
add_surface(
x = time_points, y = trajectory_ids, z = h5_data$trajectories[3,,],
name = "B1", colorscale = "Greens", visible = FALSE
) %>%
add_surface(
x = time_points, y = trajectory_ids, z = h5_data$trajectories[4,,],
name = "B2", colorscale = "Reds", visible = FALSE
) %>%
add_surface(
x = time_points, y = trajectory_ids, z = h5_data$trajectories[5,,],
name = "B3", colorscale = "Blues", visible = FALSE
)
# Create dropdown menu with PROPERLY STRUCTURED BUTTONS
fig <- fig %>% layout(
title = "Cancer Treatment Dynamics - 3D Trajectory Analysis\n [variables=5, timepoints=601, trajectories=100]",
scene = list(
xaxis = list(title = "Time (days)"),
yaxis = list(title = "Trajectory ID"),
zaxis = list(title = "Value"),
camera = list(eye = list(x = 1.8, y = -1.8, z = 0.8))
),
updatemenus = list(
list(
type = "dropdown",
x = 1.2,
y = 0.8,
buttons = list(
list(method = "restyle",
args = list("visible", list(TRUE, FALSE, FALSE, FALSE, FALSE)),
label = "LC"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE, FALSE, FALSE, FALSE)),
label = "RP2"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, TRUE, FALSE, FALSE)),
label = "B1"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, FALSE, TRUE, FALSE)),
label = "B2"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, FALSE, FALSE, TRUE)),
label = "B3"),
list(method = "restyle",
args = list("visible", list(TRUE, TRUE, TRUE, TRUE, TRUE)),
label = "All")
)
)
)
)
# Show composite 3D plot with all SDE-generated trajectories -- across time and instantiations
fig