Transitions:
On peut donc considérer une marche aléatoire absorbante sur
Pour
puisque l'on doit avoir des probabilités.
Donc on doit avoir
On a:
# Construction de la matrice de transition
T = mat.or.vec(nr = (Pop+1), nc = (Pop+1))
for (row in 2:Pop) {
I = row-1
mv_right = gamma*I*Delta_t # Guérisons
mv_left = beta*I*(Pop-I)*Delta_t # Infections
T[row,(row-1)] = mv_right
T[row,(row+1)] = mv_left
}
# Dernier rang, mouvement vers la gauche uniquement
T[(Pop+1),Pop] = gamma*(Pop)*Delta_t
# On vérifie que les valeurs ne sont pas trop grandes
if (max(rowSums(T))>1) {
T = T/max(rowSums(T))
}
diag(T) = 1-rowSums(T)
library(DTMCPack)
sol = MultDTMC(nchains = 20, tmat = T, io = IC, n = t_f)
donne 20 réalisations d'une CMTD avec matrice de transition T
, conditions initiales IC
(un vecteur de probabilité d'être dans les t_f
Voir le code
DTMCPack
est bien pour obtenir des réalisation d'une CMTD, mais pour l'étudier plus en détail, markovchain
est bien plus complet
library(markovchain)
mcSIS <- new("markovchain",
states = sprintf("I_%d", 0:Pop),
transitionMatrix = T,
name = "SIS")
De façon intéressante, markovchain
redéfinit l'étrange notation de R
pour les produits matriciels où "*
est Hadamard, %*%
est l'habituel", si bien que pour un objet défini comme markovchain
, mcSIS*mcSIS
est le produit matriciel usuel
> summary(mcSIS)
SIS Markov chain that is composed by:
Closed classes:
I_0
Recurrent classes:
{I_0}
Transient classes:
{I_1,I_2,I_3,I_4,I_5,I_6,I_7,I_8,I_9,I_10,I_11,I_12,I_13,I_14,I_15,
I_16,I_17,I_18,I_19,I_20,I_21,I_22,I_23,I_24,I_25,I_26,I_27,I_28,
I_29,I_30,I_31,I_32,I_33,I_34,I_35,I_36,I_37,I_38,I_39,I_40,I_41,
I_42,I_43,I_44,I_45,I_46,I_47,I_48,I_49,I_50,I_51,I_52,I_53,I_54,
I_55,I_56,I_57,I_58,I_59,I_60,I_61,I_62,I_63,I_64,I_65,I_66,I_67,
I_68,I_69,I_70,I_71,I_72,I_73,I_74,I_75,I_76,I_77,I_78,I_79,I_80,
I_81,I_82,I_83,I_84,I_85,I_86,I_87,I_88,I_89,I_90,I_91,I_92,I_93,
I_94,I_95,I_96,I_97,I_98,I_99,I_100}
The Markov chain is not irreducible
The absorbing states are: I_0
Function | Role |
---|---|
absorbingStates |
absorbing states of the transition matrix, if any |
steadyStates |
the vector(s) of steady state(s) in matrix form |
meanFirstPassageTime |
matrix or vector of mean first passage times |
meanRecurrenceTime |
vector of mean number of steps to return to each recurrent state |
hittingProbabilities |
matrix of hitting probabilities for a Markov chain |
meanAbsorptionTime |
expected number of steps for a transient state to be absorbed by any recurrent class |
absorptionProbabilities |
|
canonicForm |
canonic form of transition matrix |
period |
the period of an irreducible DTMC |
summary |
DTMC summary |
Poids | Transition | Effet |
---|---|---|
nouvelle infection d'un susceptible | ||
guérison d'un infectieux |
On utilise
posons
tant que {
R
, il y a plusieurs librairies permettant de faire ça facilementlibrary(GillespieSSA2)
IC <- c(S = (Pop-I_0), I = I_0)
params <- c(gamma = gamma, beta = beta)
reactions <- list(
reaction("beta*S*I", c(S=-1,I=+1), "new_infection"),
reaction("gamma*I", c(S=+1,I=-1), "recovery")
)
set.seed(NULL)
sol <- ssa(
initial_state = IC,
reactions = reactions,
params = params,
method = ssa_exact(),
final_time = t_f,
)
plot(sol$time, sol$state[,"I"], type = "l",
xlab = "Time (days)", ylab = "Number infectious")
posons
tant que {
b = 0.01 # Taux de naissance
d = 0.01 # Taux de mortalité
t_0 = 0 # Temps initial
N_0 = 100 # Population initiale
# Vecteurs pour le temps et l'état, initialisés avec la condition initiale
t = t_0
N = N_0
t_f = 1000 # Temps final
# On garde le temps et l'état actuel en mémoire. On pourrait juste regarder
# la fin des vecteurs t et N, mais ça demande plus d'opérations
t_curr = t_0
N_curr = N_0
while (t_curr<=t_f) {
xi_t = (b+d)*N_curr
# Pour éviter les problèmes lorsque N=0, on termine si c'est le cas
if (N_curr == 0) {
break
}
tau_t = rexp(1, rate = xi_t)
t_curr = t_curr+tau_t
v = c(b*N_curr, xi_t)/xi_t
zeta_t = runif(n = 1)
pos = findInterval(zeta_t, v)+1
switch(pos,
{
# Naissance
N_curr = N_curr+1
},
{
# Mort
N_curr = N_curr-1
})
N = c(N, N_curr)
t = c(t, t_curr)
}
Si vous n'avez pas accès à un générateur de nombre aléatoires exponentiels (pas de problème en R
), vous pouvez utiliser une loi uniforme sur
On veut
On utilise l'inverse de la densité cumulative
qui à ses valeurs dans
> tail(diff(t))
[1] 1.357242e-04 1.291839e-04 5.926044e-05 7.344020e-05 1.401148e-04 4.423529e-04
GillespieSSA2
(qu'on a vu avant) et adaptivetau
implémentent le tau-leapR
, on utilisera parLapply
pour ce genre de simulationsOn écrit une fonction, e.g., run_one_sim
qui .. fait une simulation, puis on l'appelle avec parLapply
après avoir "instancié" un cluster
no_cores <- detectCores()-1
cl <- makeCluster(no_cores)
clusterEvalQ(cl,{
library(GillespieSSA2)
})
clusterExport(cl,
c("params", "run_one_sim"),
envir = .GlobalEnv)
SIMS = parLapply(cl = cl,
X = 1:params$number_sims,
fun = function(x) run_one_sim(params))
stopCluster(cl)
Voir simulate_CTMC_parallel.R
sur Github
À titre d'exemple, pour montrer à quel point on peut gagner en temps
On fait tourner le code parallèle pour 100 simuls entre tictoc::tic()
et tictoc::toc()
, donnant 66.958 sec elapsed
, puis la version séquentielle
tictoc::tic()
SIMS = lapply(X = 1:params$number_sims,
FUN = function(x) run_one_sim(params))
tictoc::toc()
qui donne 318.141 sec elapsed
sur une machine Intel Corei9-8950HK 6C/12T @ 2.90GHz (4.7512.067 sec elapsed
versus 258.985 sec elapsed
sur un processeur AMD Ryzen Threadripper 3970X 32C/64T @ 4.15GHz (21.46
approx
, que l'on utilise sur la solution générée par la fonction ssa