<- function(transition_matrix, state_names = NULL) {
mean_time_to_absorption
= which(diag(transition_matrix) == 1)
absorbing_states
if (length(absorbing_states) == 0) stop("There are no absorbing states.")
= nrow(transition_matrix)
n_states
= setdiff(1:n_states, absorbing_states)
transient_states
= transition_matrix[transient_states, transient_states]
Q
= solve(diag(nrow(Q)) - Q, rep(1, nrow(Q)))
mtta
if (is.null(state_names)) state_names = 1:n_states
data.frame(start_state = state_names[transient_states],
mean_time_to_absorption = mtta)
}
Absorbing States and First Step Analysis
Function to compute mean time to absorption for absorbing chain
This function basically just computes \(\boldsymbol{\mu} = (\textbf{I}-\textbf{Q})^{-1}\textbf{1}\) as solve(I - Q, ones)
, but there’s some rearranging required to put the transition matrix in canonical form.
Function to compute pmf of time to absorption given the initial state.
<- function(transition_matrix, state_names = NULL, start_state) {
pmf_of_time_to_absorption
= which(diag(transition_matrix) == 1)
absorbing_states
if (length(absorbing_states) == 0) stop("There are no absorbing states.")
= nrow(transition_matrix)
n_states
= setdiff(1:n_states, absorbing_states)
transient_states
if (is.null(state_names)) state_names = 1:n_states
if (which(state_names == start_state) %in% absorbing_states) stop("Initial state is an absorbing state; absorption at time 0.")
= 1
n
= sum(transition_matrix[which(state_names == start_state), absorbing_states])
TTA_cdf
while (max(TTA_cdf) < 0.999999) {
= n + 1
n
= c(TTA_cdf, sum((transition_matrix %^% n)[which(state_names == start_state), absorbing_states]))
TTA_cdf
}
= TTA_cdf - c(0, TTA_cdf[-length(TTA_cdf)])
TTA_pmf
data.frame(n = 1:length(TTA_pmf),
prob_absorb_at_time_n = TTA_pmf)
}
Function to compute probabilities of absorption in each absorbing state
This function basically just computes \(\boldsymbol{\Pi} = (\textbf{I}-\textbf{Q})^{-1}\textbf{R}\) as solve(I - Q, R)
, but there’s some rearranging required to put the transition matrix in canonical form.
<- function(transition_matrix, state_names = NULL) {
absorption_probability
= which(diag(transition_matrix) == 1)
absorbing_states
if (length(absorbing_states) == 0) stop("There are no absorbing states.")
= nrow(transition_matrix)
n_states
= setdiff(1:n_states, absorbing_states)
transient_states
= transition_matrix[transient_states, transient_states]
Q
= transition_matrix[transient_states, absorbing_states]
R
= solve(diag(nrow(Q)) - Q, R)
absorption_probability
if (is.null(state_names)) state_names = 1:n_states
colnames(absorption_probability) = paste("prob_absorb_in_state_",
sep = "")
state_names[absorbing_states],
data.frame(start_state = state_names[transient_states],
absorption_probability) }
Ehrenfest urn chain - time to absorption
= 5
M
= 0:M
state_names
= rbind(
P c(5, 0, 0, 0, 0, 0),
c(1, 0, 4, 0, 0, 0),
c(0, 2, 0, 3, 0, 0),
c(0, 0, 3, 0, 2, 0),
c(0, 0, 0, 4, 0, 1),
c(0, 0, 0, 0, 0, 5)
/ 5
)
= c(0, 0, 0, 1, 0, 0) # start in state 3 pi0
plot_state_diagram(P, state_names)
Joining with `by = join_by(prob)`
Simulation-based approximation of time to absorption \(T\) given \(X_0=3\).
The simulate_DTMC_paths
function reshapes the results from wide to long format for easier plotting. But it’s probably easier to leave in wide format - one row for each path - for the purposes of computing path random variables like time to absorption \(T\); you could simulate many paths at once and then add a column for \(T\). I left the simulate_DTMC_paths
function as is, which is why I’m running a loop below to simulate a single sample path, compute \(T\) for the path, and then repeat for many paths.
Also, rather than running a while loop to stop at the first time of absorption, I’m just running a large number of steps (200) for each path and then finding what time absorption occurred.
= which(diag(P) == 1)
absorbing_states
= 10000
n_rep = rep(NA, n_rep)
time_to_absorption
for (i in 1:n_rep) {
= simulate_single_DTMC_path(pi0, P, last_time = 200)
x = min(which(x %in% absorbing_states))
time_to_absorption[i]
}
hist(time_to_absorption)
summary(time_to_absorption)
Min. 1st Qu. Median Mean 3rd Qu. Max.
3.00 7.00 13.00 18.43 25.00 152.00
mean(time_to_absorption)
[1] 18.4339
sd(time_to_absorption)
[1] 16.23996
Mean time to absorption given each initial state
= P[2:5, 2:5]
Q
Q
[,1] [,2] [,3] [,4]
[1,] 0.0 0.8 0.0 0.0
[2,] 0.4 0.0 0.6 0.0
[3,] 0.0 0.6 0.0 0.4
[4,] 0.0 0.0 0.8 0.0
solve(diag(4) - Q, rep(1, 4))
[1] 15.0 17.5 17.5 15.0
Using the function
= mean_time_to_absorption(P, state_names)
mtta
|> kbl() |> kable_styling() mtta
start_state | mean_time_to_absorption |
---|---|
1 | 15.0 |
2 | 17.5 |
3 | 17.5 |
4 | 15.0 |
Molecules distributed at random (assuming it’s possible they could start in an absorbing state).
sum(dbinom(0:M, M, 0.5) * c(0, mtta[, 2], 0))
[1] 15.625
Computation of distribution of time to absorption \(T\) given \(X_0=3\).
= pmf_of_time_to_absorption(P, start_state = 3)
T_pmf
|> head(10) |> kbl() |> kable_styling() T_pmf
n | prob_absorb_at_time_n |
---|---|
1 | 0.0000000 |
2 | 0.0800000 |
3 | 0.0480000 |
4 | 0.0544000 |
5 | 0.0480000 |
6 | 0.0462080 |
7 | 0.0430848 |
8 | 0.0406374 |
9 | 0.0381696 |
10 | 0.0359057 |
ggplot(T_pmf |>
filter(prob_absorb_at_time_n > 0),
aes(x = n,
y = prob_absorb_at_time_n)) +
geom_line(linewidth = 1)
sum(T_pmf[, 1] * T_pmf[, 2])
[1] 17.49977
Rick rolls
States: 0 -> 1 -> 12 -> 123
Mean time to absorption
= c("0", "1", "12", "123")
state_names
= rbind(
P c(5, 1, 0, 0),
c(4, 1, 1, 0),
c(4, 1, 0, 1),
c(0, 0, 0, 6)
/ 6
)
mean_time_to_absorption(P, state_names) |>
kbl() |> kable_styling()
start_state | mean_time_to_absorption |
---|---|
0 | 216 |
1 | 210 |
12 | 180 |
Distribution of time to absorption
= pmf_of_time_to_absorption(P, state_names, start_state = "0")
T_pmf
|> head(10) |> kbl() |> kable_styling() T_pmf
n | prob_absorb_at_time_n |
---|---|
1 | 0.0000000 |
2 | 0.0000000 |
3 | 0.0046296 |
4 | 0.0046296 |
5 | 0.0046296 |
6 | 0.0046082 |
7 | 0.0045868 |
8 | 0.0045653 |
9 | 0.0045440 |
10 | 0.0045228 |
ggplot(T_pmf |>
filter(prob_absorb_at_time_n > 0),
aes(x = n,
y = prob_absorb_at_time_n)) +
geom_line(linewidth = 1)
sum(T_pmf[, 1] * T_pmf[, 2])
[1] 215.9968
Simulation
= 10000
n_rep
= 3000 # fixed number of rolls instead of while loop
n_rolls
= rep(NA, n_rep)
T123
for (i in 1:n_rep){
= sample(1:6, n_rolls, replace = TRUE)
rolls = rolls[-c(n_rolls, n_rolls - 1)]
rolls1 = rolls[-c(1, n_rolls)]
rolls2 = rolls[-c(1, 2)]
rolls3 = min(which((rolls1 == 1) &
T123[i] == 2) &
(rolls2 == 3))) + 2
(rolls3
}
mean(T123)
[1] 217.5725
hist(T123)
Morty rolls
States: 0 -> 6 -> 66 -> 666
= c("0", "6", "66", "666")
state_names
= rbind(
P c(5, 1, 0, 0),
c(5, 0, 1, 0),
c(5, 0, 0, 1),
c(0, 0, 0, 6)
/ 6
)
mean_time_to_absorption(P, state_names) |>
kbl() |> kable_styling()
start_state | mean_time_to_absorption |
---|---|
0 | 258 |
6 | 252 |
66 | 216 |
= pmf_of_time_to_absorption(P, state_names, start_state = "0")
T_pmf
|> head(10) |> kbl() |> kable_styling() T_pmf
n | prob_absorb_at_time_n |
---|---|
1 | 0.0000000 |
2 | 0.0000000 |
3 | 0.0046296 |
4 | 0.0038580 |
5 | 0.0038580 |
6 | 0.0038580 |
7 | 0.0038402 |
8 | 0.0038253 |
9 | 0.0038104 |
10 | 0.0037955 |
ggplot(T_pmf |>
filter(prob_absorb_at_time_n > 0),
aes(x = n,
y = prob_absorb_at_time_n)) +
geom_line(linewidth = 1)
sum(T_pmf[, 1] * T_pmf[, 2])
[1] 257.9962
Simulation
= 10000
n_rep
= 3000 # fixed number of rolls instead of while loop
n_rolls
= rep(NA, n_rep)
T666
for (i in 1:n_rep){
= sample(1:6, n_rolls, replace = TRUE)
rolls = rolls[-c(n_rolls, n_rolls - 1)]
rolls1 = rolls[-c(1, n_rolls)]
rolls2 = rolls[-c(1, 2)]
rolls3 = min(which((rolls1 == 6) &
T666[i] == 6) &
(rolls2 == 6))) + 2
(rolls3
}
mean(T666)
[1] 259.6035
hist(T666)
Cube
= rbind(
Q c(1, 3, 0),
c(1, 1, 2),
c(0, 2, 1)
/4
)
solve(diag(3) - Q, rep(1, 3))
[1] 13.333333 12.000000 9.333333
Ehrenfest urn chain - absorbing state
= 5
M
= 0:M
state_names
= rbind(
P c(5, 0, 0, 0, 0, 0),
c(1, 0, 4, 0, 0, 0),
c(0, 2, 0, 3, 0, 0),
c(0, 0, 3, 0, 2, 0),
c(0, 0, 0, 4, 0, 1),
c(0, 0, 0, 0, 0, 5)
/ 5 )
plot_transition_matrix(P, state_names, n_step = 10)
plot_transition_matrix(P, state_names, n_step = 50)
After many steps, the chain is probably absorbed
plot_transition_matrix(P, state_names, n_step = 200)
Computing probability of absorption in each state using function
absorption_probability(P, state_names) |>
kbl() |> kable_styling()
start_state | prob_absorb_in_state_0 | prob_absorb_in_state_5 |
---|---|---|
1 | 0.62500 | 0.37500 |
2 | 0.53125 | 0.46875 |
3 | 0.46875 | 0.53125 |
4 | 0.37500 | 0.62500 |
= P[2:5, 2:5]
Q Q
[,1] [,2] [,3] [,4]
[1,] 0.0 0.8 0.0 0.0
[2,] 0.4 0.0 0.6 0.0
[3,] 0.0 0.6 0.0 0.4
[4,] 0.0 0.0 0.8 0.0
= P[2:5, c(1, 6)]
R R
[,1] [,2]
[1,] 0.2 0.0
[2,] 0.0 0.0
[3,] 0.0 0.0
[4,] 0.0 0.2
= solve(diag(4) - Q, R)
Pi Pi
[,1] [,2]
[1,] 0.62500 0.37500
[2,] 0.53125 0.46875
[3,] 0.46875 0.53125
[4,] 0.37500 0.62500
Rolling until 456 or 666
= c("0", "4", "45", "456", "6", "66", "666")
state_names
= rbind(
P c(4, 1, 0, 0, 1, 0, 0),
c(3, 1, 1, 0, 1, 0, 0),
c(4, 1, 0, 1, 0, 0, 0),
c(0, 0, 0, 6, 0, 0, 0),
c(4, 1, 0, 0, 0, 1, 0),
c(4, 1, 0, 0, 0, 0, 1),
c(0, 0, 0, 0, 0, 0, 6)
/ 6
)
absorption_probability(P, state_names)
start_state prob_absorb_in_state_456 prob_absorb_in_state_666
1 0 0.5512821 0.4487179
2 4 0.5641026 0.4358974
3 45 0.6282051 0.3717949
4 6 0.5384615 0.4615385
5 66 0.4615385 0.5384615
mean_time_to_absorption(P, state_names)
start_state mean_time_to_absorption
1 0 119.07692
2 4 115.84615
3 45 99.69231
4 6 116.30769
5 66 99.69231
Simulation
= 10000
n_rep
= 3000 # fixed number of rolls instead of while loop
n_rolls
= rep(NA, n_rep)
T_abs = rep(NA, n_rep)
abs_state
for (i in 1:n_rep){
= sample(1:6, n_rolls, replace = TRUE)
rolls = rolls[-c(n_rolls, n_rolls - 1)]
rolls1 = rolls[-c(1, n_rolls)]
rolls2 = rolls[-c(1, 2)]
rolls3 = min(which((rolls1 == 4) &
T456 == 5) &
(rolls2 == 6))) + 2
(rolls3 = min(which((rolls1 == 6) &
T666 == 6) &
(rolls2 == 6))) + 2
(rolls3 = min(c(T456, T666))
T_abs[i] = which.min(c(T456, T666))
abs_state[i]
}
hist(T_abs)
mean(T_abs)
[1] 118.1072
table(abs_state) / n_rep
abs_state
1 2
0.5501 0.4499