Go back to the Contents page.
Press Show to reveal the code chunks.
# Set seed for reproducibility
set.seed(1982)
# Set global options for all code chunks
knitr::opts_chunk$set(
# Disable messages printed by R code chunks
message = FALSE,
# Disable warnings printed by R code chunks
warning = FALSE,
# Show R code within code chunks in output
echo = TRUE,
# Include both R code and its results in output
include = TRUE,
# Evaluate R code chunks
eval = TRUE,
# Enable caching of R code chunks for faster rendering
cache = FALSE,
# Align figures in the center of the output
fig.align = "center",
# Enable retina display for high-resolution figures
retina = 2,
# Show errors in the output instead of stopping rendering
error = TRUE,
# Do not collapse code and output into a single block
collapse = FALSE
)
# Start the figure counter
fig_count <- 0
# Define the captioner function
captioner <- function(caption) {
fig_count <<- fig_count + 1
paste0("Figure ", fig_count, ": ", caption)
}
capture.output(
knitr::purl(here::here("functionality.Rmd"), output = here::here("functionality.R")),
file = here::here("purl_log.txt")
)
source(here::here("functionality.R"))
Let \(\Gamma = (\mathcal{V},\mathcal{E})\) be a metric graph. Let \(u_d: \Gamma \times(0, T) \rightarrow \mathbb{R}\) be the desired state and \(\mu>0\) a regularization parameter. We define the cost functional
\[\begin{equation} \label{eq:costfun} \tag{1} J(u, z)=\frac{1}{2} \int_0^T\left(\left\|u-u_d\right\|_{L_2(\Gamma)}^2+\mu\|z\|_{L_2(\Gamma)}^2\right) dt \end{equation}\]
Let \(f:\Gamma\times (0,T)\rightarrow\mathbb{R}\) and \(u_0: \Gamma \rightarrow \mathbb{R}\) be fixed functions. We will call them right-hand side and initial datum, respectively. Let \(\alpha\in(0,2]\) and \(z: \Gamma \times(0, T) \rightarrow \mathbb{R}\) denote the control variable. We shall be concerned with the following PDE-constrained optimization problem: Find
\[\begin{equation} \label{eq:min_pro} \tag{2} \min\; J(u, z) \end{equation}\] subject to the fractional diffusion equation \[\begin{equation} \label{eq:maineq} \tag{3} \left\{ \begin{aligned} \partial_t u(s,t) + (\kappa^2 - \Delta_\Gamma)^{\alpha/2} u(s,t) &= f(s,t)+z(s,t), && \quad (s,t) \in \Gamma \times (0, T), \\ u(s,0) &= u_0(s), && \quad s \in \Gamma, \end{aligned} \right. \end{equation}\] with \(u(\cdot,t)\) satisfying the Kirchhoff vertex conditions \[\begin{equation} \label{eq:Kcond} \tag{4} \mathcal{K} = \left\{\phi\in C(\Gamma)\;\middle|\; \forall v\in \mathcal{V}:\; \sum_{e\in\mathcal{E}_v}\partial_e \phi(v)=0 \right\} \end{equation}\] and the control constraints \[\begin{align} \label{control_constraints} \tag{5} a(s,t)\leq z(s,t)\leq b(s,t)\;\mathrm{a.e.}\;(s,t)\in\Gamma \times(0, T). \end{align}\]
The optimal variables \((\bar{u}, \bar{p}, \bar{z})\) satisfy
\[\begin{equation} \label{eq:maineqoptimal} \tag{6} \left\{ \begin{aligned} \partial_t \bar{u}(s,t) + (\kappa^2 - \Delta_\Gamma)^{\alpha/2} \bar{u}(s,t) &= f(s,t)+\bar{z}(s,t), && \quad (s,t) \in \Gamma \times (0, T), \\ \bar{u}(s,0) &= u_0(s), && \quad s \in \Gamma, \end{aligned} \right. \end{equation}\] and \[\begin{equation} \label{eq:adjointeq} \tag{7} \left\{ \begin{aligned} -\partial_t \bar{p}(s,t) + (\kappa^2 - \Delta_\Gamma)^{\alpha/2} \bar{p}(s,t) &= \bar{u}(s,t)-u_d(s,t), && \quad (s,t) \in \Gamma \times (0, T), \\ \bar{p}(s,T) &= 0, && \quad s \in \Gamma, \end{aligned} \right. \end{equation}\] with \[\begin{align} \label{zz} \tag{8} \bar{z}(s,t) = \max\left\{a(s,t),\min\left\{b(s,t),-\dfrac{1}{\mu}\bar{p}(s,t)\right\}\right\}. \end{align}\] By considering the change of variable \(\bar{q}(s,t) = \bar{p}(s,T-t)\), the fractional adjoint problem \(\eqref{eq:adjointeq}\) becomes a forward-in-time problem where the transformed adjoint state \(\bar{q}\) solves \[\begin{equation} \label{transformed_adjoint_state} \tag{9} \left\{ \begin{aligned} \partial_t \bar{q}(s,t) + (\kappa^2 - \Delta_\Gamma)^{\alpha/2} \bar{q}(s,t) &= \bar{v}(s,t)-v_d(s,t), && \quad (s,t) \in \Gamma \times (0, T), \\ \bar{q}(s,0) &= 0, && \quad s \in \Gamma, \end{aligned} \right. \end{equation}\] since \(\partial_t\bar{q}(s,t) = -\partial_t\bar{p}(s,T-t)\) and \(\bar{q}(s, 0) =\bar{p}(s,T-0)= \bar{p}(s,T)=0\). Here, \(\bar{v}(s,t) = \bar{u}(s,T-t)\) and \(v_d(s,t) = u_d(s,T-t)\).
Given \(\bar{u}\) and \(u_d\), we can time-reverse them to obtain \(\bar{v}\) and \(v_d\) and then use the same numerical scheme we use for the forward problem \(\eqref{eq:maineqoptimal}\) to solve the adjoint problem \(\eqref{transformed_adjoint_state}\). The control variable \(\bar{z}\) is then computed using \(\eqref{zz}\).
The numerical scheme for \(\eqref{eq:maineqoptimal}\) and \(\eqref{transformed_adjoint_state}\) are given by (see the Functionality page) \[\begin{align} \label{numericalscheme1} \tag{10} \bar{\boldsymbol{U}}^{k+1} = \boldsymbol{C}^{-1}\left(\sum_{k=1}^{m+1} a_k\left(\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}-p_k\boldsymbol{I}\right)^{-1} + r\boldsymbol{I}\right) (\boldsymbol{C}\bar{\boldsymbol{U}}^k+\tau (\boldsymbol{F}^{k+1}+\bar{\boldsymbol{Z}}^{k+1})), \end{align}\] and \[\begin{align} \label{thenumericalscheme2} \tag{11} \bar{\boldsymbol{Q}}^{k+1} = \boldsymbol{C}^{-1}\left(\sum_{k=1}^{m+1} a_k\left(\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}-p_k\boldsymbol{I}\right)^{-1} + r\boldsymbol{I}\right) (\boldsymbol{C}\bar{\boldsymbol{Q}}^k+\tau (\bar{\boldsymbol{V}}^{k+1}-\boldsymbol{V_d}^{k+1})), \end{align}\] with initial conditions \(\bar{\boldsymbol{U}}^0 = \boldsymbol{U}_0\) and \(\bar{\boldsymbol{Q}}^0 = \boldsymbol{0}\). Observe that \(\eqref{numericalscheme1}\)-\(\eqref{thenumericalscheme2}\) is a coupled problem.
For notation convenience, let
Observe that the entries of \(\boldsymbol{F}\) are given by \(\boldsymbol{F}_{ik}=(\psi^i_h, f(\cdot,t_k))_{L_2(\Gamma)}\) where \(\psi^i_h\) is the \(i\)-th basis function on a mesh with \(N_h\) nodes. By introducing a finer integration mesh with \(N_{h_{\text{ok}}}\) nodes, we can approximate \(\boldsymbol{F}_{ik}\) as \(\boldsymbol{\psi}_i^\top\boldsymbol{C}^{\text{ok}}\boldsymbol{f}_k\), where \(\boldsymbol{\psi}_i=[\psi^i_h(s_1)\dots \psi^i_h(s_{N_{h_{\text{ok}}}})]^\top\) for \(i=1\dots, N_h\), \(\boldsymbol{f}_k = [f(s_1,t_k)\dots f(s_{N_{h_{\text{ok}}}},t_k)]\top\) for \(k= 0,\dots,N\), and matrix \(\boldsymbol{C}^{\text{ok}}\) has entries \(\boldsymbol{C}^{\text{ok}}_{nm} = (\psi_{h_\text{ok}}^n,\psi_{h_\text{ok}}^m)_{L_2(\Gamma)}\) for \(n,m = 1,\dots, N_{h_\text{ok}}\). Let \(\boldsymbol{f}\) denote the matrix with columns \(\boldsymbol{f}_k\) and \(\boldsymbol{\Psi}\) denote a matrix with entries \(\boldsymbol{\Psi}_{ji}=\psi^i_h(s_j)\) for \(i=1\dots, N_h\) and \(j = 1,\dots N_{h_{\text{ok}}}\). Then \[\begin{equation} \boldsymbol{F} \approx \boldsymbol{\Psi}^\top \boldsymbol{C}^{\text{ok}} \boldsymbol{f}. \end{equation}\] Similarly, if \(\boldsymbol{U_d}\) is the matrix with entries \(\boldsymbol{U_d}^{jk} = u_d(s_j,t_k)\) for \(j = 1,\dots, N_{h_{\text{ok}}}\) and \(k = 0,\dots, N\), then the matrix \(\boldsymbol{V_d}\) can be approximated as \[\begin{equation} \boldsymbol{V_d} \approx \text{reversecolumns}(\boldsymbol{\Psi}^\top \boldsymbol{C}^{\text{ok}} \boldsymbol{U_d}). \end{equation}\]
To approximate \(\boldsymbol{F}\) and \(\boldsymbol{V_d}\), we use the values of \(f\) and \(u_d\) on the integration mesh with \(N_{h_{\text{ok}}}\) nodes. However, to approximate \(\bar{\boldsymbol{Z}}\) and \(\bar{\boldsymbol{V}}\), we do not even have access to the values of \(\bar{z}\) and \(\bar{v}\) on the coarse mesh, let alone on the fine mesh. Instead, we use their approximations on the coarse mesh, given by the matrices \(\bar{\boldsymbol{z}} = \max\{\boldsymbol{A},\min\{\boldsymbol{B},-\bar{\boldsymbol{P}}/\mu\}\}\) and \(\text{reversecolumns}(\bar{\boldsymbol{U}})\), respectively. With these at hand, we can approximate \(\bar{\boldsymbol{Z}}\) and \(\bar{\boldsymbol{V}}\) by first projecting \(\bar{\boldsymbol{z}} = \max\{\boldsymbol{A},\min\{\boldsymbol{B},-\bar{\boldsymbol{P}}/\mu\}\}\) and \(\text{reversecolumns}(\bar{\boldsymbol{U}})\) onto the fine mesh and then approximating the inner products \((\psi^i_h,\bar{z}(\cdot,t_k))_{L_2(\Gamma)}\) and \((\psi^i_h,\bar{u}(\cdot,T-t_k))_{L_2(\Gamma)}\) as \[\begin{equation} \bar{\boldsymbol{Z}} \approx \boldsymbol{\Psi}^\top \boldsymbol{C}^{\text{ok}} \boldsymbol{\Psi} \bar{\boldsymbol{z}}\quad \text{ and }\quad \bar{\boldsymbol{V}} \approx \text{reversecolumns}(\boldsymbol{\Psi}^\top \boldsymbol{C}^{\text{ok}} \boldsymbol{\Psi} \bar{\boldsymbol{U}}). \end{equation}\]
We follow the approach in Glusa and Otárola (2021) to construct an exact solution to the optimal control problem \(\eqref{eq:costfun}\)-\(\eqref{control_constraints}\). The solution to the elliptic problem \[\begin{align} \label{eq:elliptic_problem} \tag{12} (\kappa^2 - \Delta_\Gamma)^{\alpha/2} u(s)=f(s),\quad s\in\Gamma, \end{align}\] where \(u(\cdot)\) satisfies the Kirchhoff vertex conditions \(\eqref{eq:Kcond}\), is given by \[\begin{align*} u(s) = \sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}(f,e_j)_{L_2(\Gamma)}e_j(s),\quad s\in\Gamma. \end{align*}\] We choose coefficients \(\{x_j\}_{j=1}^{\infty}\) and \(\{y_j\}_{j=1}^{\infty}\) and set \[\begin{align} \label{eq:rhsfunctions} \tag{13} f(s) = \sum_{j=1}^{\infty}x_je_j(s)\quad \text{ and } \quad g(s) = \sum_{j=1}^{\infty}y_je_j(s). \end{align}\] Hence the corresponding solutions are given by \[\begin{align} \label{eq:sols} \tag{14} u(s) = \sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}x_je_j(s)\quad \text{ and } \quad v(s) = \sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}y_je_j(s). \end{align}\] We also define \(\psi(t) = \cos(t)\) and \(\phi(t) =\sin(T-t)\). Observe that \(\psi(0)=1\) and \(\phi(T)=0\). We also choose \(\mu =0.1\), \(a(s,t) = -0.5\) and \(b(s,t)=0.5\). Now we set \[\begin{align*} f(s,t) &= \psi'(t)u(s)+\psi(t)f(s) - \bar{z}(s,t) = -\sin(t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}x_je_j(s)+ \cos(t)\sum_{j=1}^{\infty}x_je_j(s)- \max\{-0.5,\min\{0.5, \sin(T-t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}y_je_j(s)\}\}\\ u_d(s,t) & = \psi(t)u(s)-\mu\phi'(t)v(s)+\mu\phi(t)g(s) = \cos(t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}x_je_j(s)+\mu\cos(T-t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}y_je_j(s)+\mu\sin(T-t)\sum_{j=1}^{\infty}y_je_j(s)\\ u_0(s) & = u(s)= \sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}x_je_j(s) \end{align*}\] The exact solution to the optimal control problem is given by \[\begin{align} \bar{u}(s,t)&=\psi(t)u(s) = \cos(t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}x_je_j(s)\\ \bar{p}(s,t)&=-\mu\phi(t)v(s)= - \mu\sin(T-t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}y_je_j(s)\\ \bar{z}(s,t)&=\max\{a(s,t),\min\{b(s,t), \phi(t)v(s)\}\}= \max\{-0.5,\min\{0.5, \sin(T-t)\sum_{j=1}^{\infty}\lambda_j^{-\alpha/2}y_je_j(s)\}\} \end{align}\]
Observe that the choice of \(f\) decouples the systems \(\eqref{numericalscheme1}\)-\(\eqref{thenumericalscheme2}\).
At \(t=0\), we get \[\begin{align*} \bar{u}(s,0) = \psi(0)u(s) = u(s) = u_0(s), \end{align*}\] while for \((s,t) \in \Gamma \times (0, T)\), we obtain \[\begin{align*} \partial_t \bar{u}(s,t) +(\kappa^2 - \Delta_\Gamma)^{\alpha/2} \bar{u}(s,t) = \psi'(t)u(s) + \psi(t) (\kappa^2 - \Delta_\Gamma)^{\alpha/2} u(s) = \psi'(t)u(s) + \psi(t)f(s) = f(s,t) + \bar{z}(s,t). \end{align*}\] Similarly, at \(t=T\), we get \[\begin{align*} \bar{p}(s,T) = -\mu\phi(T)v(s) = 0, \end{align*}\] while for \((s,t) \in \Gamma \times (0, T)\), we obtain \[\begin{align*} -\partial_t \bar{p}(s,t) +(\kappa^2 - \Delta_\Gamma)^{\alpha/2} \bar{p}(s,t) = \mu\phi'(t)v(s) -\mu \phi(t) (\kappa^2 - \Delta_\Gamma)^{\alpha/2} v(s) = \mu\phi'(t)v(s) -\mu \phi(t)g(s) \end{align*}\] and \[\begin{align*} \bar{u}(s,t) - u_d(s,t) = \psi(t)u(s) - \psi(t)u(s) + \mu\phi'(t)v(s) - \mu\phi(t)g(s) = \mu\phi'(t)v(s) -\mu \phi(t)g(s). \end{align*}\] This shows that \(\bar{u}\) and \(\bar{p}\) satisfy \(\eqref{eq:maineqoptimal}\) and \(\eqref{eq:adjointeq}\).
In this section, we implement the numerical scheme for the optimal control problem \(\eqref{eq:costfun}\)-\(\eqref{control_constraints}\) on the tadpole graph. We start by defining the parameters and constructing the exact solution. We them use \(\eqref{numericalscheme1}\)-\(\eqref{thenumericalscheme2}\) to solve the forward and adjoint problems. Finally, we compute the control variable \(\bar{z}\) using \(\eqref{zz}\).
# Parameters
T_final <- 2
time_step <- 0.02
h <- 0.02
kappa <- 15
alpha <- 1.8
m = 4
beta <- alpha/2
mu <- 0.1
a <- - 0.5
b <- 0.5
N_finite = 4 # choose even
adjusted_N_finite <- N_finite + N_finite/2 + 1
# Coefficients for f and g
coeff_elliptic_g <- rep(0, adjusted_N_finite)
coeff_elliptic_g[7] <- 100
coeff_elliptic_f <- rep(0, adjusted_N_finite)
coeff_elliptic_f[7] <- -10
# time and spatial mesh
time_seq <- seq(0, T_final, length.out = ((T_final - 0) / time_step + 1))
graph <- gets.graph.tadpole(h = h)
graph$compute_fem()
G <- graph$mesh$G
C <- graph$mesh$C
L <- kappa^2*C + G
I <- Matrix::Diagonal(nrow(C))
# Compute the eigenvalues and eigenfunctions
eigen_params <- gets.eigen.params(N_finite = N_finite,
kappa = kappa,
alpha = alpha,
graph = graph)
EIGENVAL_MINUS_ALPHA <- eigen_params$EIGENVAL_MINUS_ALPHA
EIGENFUN <- eigen_params$EIGENFUN
# Construct the right hand side functions f and g for the elliptic problem
elliptic_f <- as.vector(EIGENFUN %*% coeff_elliptic_f)
elliptic_g <- as.vector(EIGENFUN %*% coeff_elliptic_g)
# Construct the corresponding elliptic solution u and v
elliptic_u <- as.vector(EIGENFUN %*% (coeff_elliptic_f * EIGENVAL_MINUS_ALPHA))
elliptic_v <- as.vector(EIGENFUN %*% (coeff_elliptic_g * EIGENVAL_MINUS_ALPHA))
A <- matrix(a, nrow = nrow(C), ncol = length(time_seq))
B <- matrix(b, nrow = nrow(C), ncol = length(time_seq))
# Construct the optimal variables
psi <- cos(time_seq)
psi_prime <- - sin(time_seq)
phi <- sin(T_final - time_seq)
phi_prime <- - cos(T_final - time_seq)
# psi_rate <- 1
# phi_rate <- 1
# phi_coefficient <- 1
# psi <- exp(psi_rate*time_seq)
# psi_prime <- psi_rate * exp(psi_rate*time_seq)
# phi <- phi_coefficient * exp(phi_rate*time_seq) - phi_coefficient * exp(phi_rate*T_final)
# phi_prime <- phi_coefficient * phi_rate * exp(phi_rate*time_seq)
u_bar <- elliptic_u %*% t(psi)
p_bar <- - mu * (elliptic_v %*% t(phi))
z_bar <- pmax(A, pmin(B, - p_bar / mu))
# Construct the data for the problem
f <- elliptic_u %*% t(psi_prime) + elliptic_f %*% t(psi) - z_bar
f_plus_z_bar <- elliptic_u %*% t(psi_prime) + elliptic_f %*% t(psi)
u_d <- elliptic_u %*% t(psi) -
mu * (elliptic_v %*% t(phi_prime)) +
mu * (elliptic_g %*% t(phi))
u_0 <- elliptic_u
# Construct the fractional operator, which is shared for the forward and adjoint problems
my_op_frac <- my.fractional.operators.frac(L, beta, C, scale.factor = kappa^2, m = m, time_step)
# Construct the integration mesh
overkill_graph <- gets.graph.tadpole(h = 0.0001)
overkill_graph$compute_fem()
overkill_EIGENFUN <- gets.eigen.params(N_finite = N_finite,
kappa = kappa,
alpha = alpha,
graph = overkill_graph)$EIGENFUN
# Construct the right hand side functions f and g for the elliptic problem on the integration mesh
overkill_elliptic_f <- as.vector(overkill_EIGENFUN %*% coeff_elliptic_f)
overkill_elliptic_g <- as.vector(overkill_EIGENFUN %*% coeff_elliptic_g)
# Construct the corresponding elliptic solution u and v on the integration mesh
overkill_elliptic_u <- as.vector(overkill_EIGENFUN %*% (coeff_elliptic_f * EIGENVAL_MINUS_ALPHA))
overkill_elliptic_v <- as.vector(overkill_EIGENFUN %*% (coeff_elliptic_g * EIGENVAL_MINUS_ALPHA))
# Construct the projection matrix
Psi <- graph$fem_basis(overkill_graph$get_mesh_locations())
R <- t(Psi) %*% overkill_graph$mesh$C
# Construct the right hand side term for the forward problem
F_plus_Z_bar <- overkill_elliptic_u %*% t(psi_prime) + overkill_elliptic_f %*% t(psi)
F_plus_Z_bar_innerproduct <- R %*% F_plus_Z_bar
# Solve the forward problem
U_bar <- solve_fractional_evolution(my_op_frac,
time_step,
time_seq,
val_at_0 = u_0,
RHST = F_plus_Z_bar_innerproduct)
# Construct the right hand side term for the adjoint problem
U_d <- overkill_elliptic_u %*% t(psi) -
mu * (overkill_elliptic_v %*% t(phi_prime)) +
mu * (overkill_elliptic_g %*% t(phi))
V_d <- reversecolumns(R %*% U_d)
V_bar <- reversecolumns(R %*% Psi %*% U_bar)
# Solve the adjoint problem
Q_bar <- solve_fractional_evolution(my_op_frac,
time_step,
time_seq,
val_at_0 = u_0*0,
RHST = V_bar - V_d)
P_bar <- reversecolumns(Q_bar)
# Compute the control variable
Z_bar <- pmax(A, pmin(B, - P_bar / mu))
start_idx <- which.min(abs(time_seq - 0))
end_idx <- which.min(abs(time_seq - T_final))
idx <- seq(start_idx, end_idx, by = 1)
Figure 1: Right-hand side functions \(f\) and \(g\) for the elliptic problem.
Figure 2: Solutions \(u\) and \(v\) to the elliptic problem.
Figure 3: Time evolution of the right-hand side function \(f+\bar{z}\) of the state equation.
v_bar_minus_v_d <- reversecolumns(u_bar - u_d)
graph.plotter.3d.single(graph, v_bar_minus_v_d[, idx], time_seq[idx])
Figure 4: Time evolution of the right-hand side function \(\bar{v}-v_d\) of the adjoint equation.
Figure 5: Exact (in red) and numerical (in blue) optimal state variable.
Figure 6: Exact (in red) and numerical (in blue) optimal adjoint variable.
Figure 7: Exact (in red) and numerical (in blue) optimal control variable.
Figure 8: Desired state (in red) and optimal state (in blue).
We used R version 4.5.0 (R Core Team 2025) and the following R packages: gsignal v. 0.3.7 (Van Boxtel, G.J.M., et al. 2021), here v. 1.0.1 (Müller 2020), htmltools v. 0.5.8.1 (Cheng et al. 2024), knitr v. 1.50 (Xie 2014, 2015, 2025), Matrix v. 1.7.3 (Bates, Maechler, and Jagan 2025), MetricGraph v. 1.5.0.9000 (Bolin, Simas, and Wallin 2023a, 2023b, 2024, 2025; Bolin et al. 2024), patchwork v. 1.3.1 (Pedersen 2025), plotly v. 4.10.4 (Sievert 2020), RColorBrewer v. 1.1.3 (Neuwirth 2022), renv v. 1.0.7 (Ushey and Wickham 2024), reshape2 v. 1.4.4 (Wickham 2007), rmarkdown v. 2.29 (Xie, Allaire, and Grolemund 2018; Xie, Dervieux, and Riederer 2020; Allaire et al. 2024), rSPDE v. 2.5.1.9000 (Bolin and Kirchner 2020; Bolin and Simas 2023; Bolin, Simas, and Xiong 2024), scales v. 1.4.0 (Wickham, Pedersen, and Seidel 2025), slackr v. 3.4.0 (Kaye et al. 2025), tidyverse v. 2.0.0 (Wickham et al. 2019), viridisLite v. 0.4.2 (Garnier et al. 2023), xaringanExtra v. 0.8.0 (Aden-Buie and Warkentin 2024).