Binder

Differential Equations

CRAN Task View

Le CRAN propose une Task View sur les équations différentielles.

Une équation différentielle est une équation entre une ou plusieurs fonctions inconnues et leurs dérivées. Elle décrit comment une fonction varie par rapport à une ou plusieurs variables (souvent le temps et/ou l’espace) et par rapport à ses dérivées.

Il y a différentes facon de classifier les équations différentielles :

  • les équations peuvent être stochastique (la quantité inconnue est aléatoire) ou déterministe (la quantité inconnue est déterministe).

  • les équations peuvent porter sur des fonctions à une seule variable (équation différentielle ordinaire) ou à plusieurs variables (équation aux dérivées partielles).

  • les équations peuvent inclure des fonctions dont la dérivée à un certain pas de temps dépend de la dérivée à un pas de temps précédent (équation différentielle à retard ou differential equations delay). Elles peuvent aussi inclure des relations algébriques entre les variables (équation différentielle algébrique).

Il existe plusieurs packages R permettant de résoudre ces équations et d’ajuster ces modèles à de la donnée. Ici, seuls les package deSolve et diffeqr sont utilisés pour résoudre des ED.

Résolution numérique d’EDO et d’EDP

Le package deSolve permet de résoudre numériquement des équations simples.

Installation et chargement

install.packages("deSolve")
library(tidyverse)
library(GGally)
library(deSolve)
library(fda)
theme_set(theme_bw())

EDO simple: la désintégration atomique

On cherche à résoudre \(y' = ay\) avec condition initiale \(y(0) = y_0\). On commence par coder l’équation différentielle: - t représente le temps courant - Y représente l’état courant du système - parameters stocke les paramètres du modèle (ici \(a\))

model <- function(t, Y, parameters) {
  with(as.list(parameters), {
    dy = -a * Y
    list(dy)
  })
}

On renseigne ensuite la jacobienne \(\frac{\partial y'}{\partial y}\)

jac <- function(t, Y, parameters) {
  with(as.list(parameters), {
    PD[1, 1] <- a
    return(PD)
  })
}

On peut ensuite résoudre l’EDO pour \(a = 1\) et \(y_0 = 1\) sur l’intervalle \([0, 1]\) des pas de temps de longeur \(0.01\) comme suit:

params <- c(a = 1)
y0     <- c(1)
times  <- seq(0, 1, by = 0.01)
PD     <- matrix(0, nrow = 1, ncol = 1)
out_atome <- ode(y0, times, model, parms = params, jacfun = jac)

Le résultat est une matrice: - une colonne pour le temps (reprend les valeurs de times) - une colonne par dimension dans le système d’équations différentielles

On peut vérifier que la solution numérique (en bleu) est confondue avec la solution analytique (en rouge).

plot_data <-
  data.frame(out_atome) %>%
  rename(numeric = X1) %>%
  mutate(analytic = exp(-time)) %>%
  pivot_longer(cols = -time,
               names_to = "type",
               values_to = "y")
ggplot(plot_data, aes(x = time, y = y, color = type)) +
  geom_line() +
  ylim(0, 1) +
  theme(legend.position = c(0.95, 0.95),
        legend.justification = c(1, 1),
        legend.background = element_rect(fill = NA))

ode utilise la méthode de Runge-Kutta pour calculer \(y(t)\) et renvoie uniquement les valeurs \(y(0), y(0.01), \dots, y(1)\) mais fait néanmoins appel à des points intérmédiaires lors du calcul. On peut s’en convaincre en comparant les valeurs finales (\(y(1)\)) obtenues avec times = seq(0, 1, by = 0.01) et times = seq(0, 1, by = 1).

On peut diagnostiquer la réussite de l’intégration numérique via la commande diagnostics

diagnostics(out_atome)
## 
## --------------------
## lsoda return code
## --------------------
## 
##   return code (idid) =  2 
##   Integration was successful.
## 
## --------------------
## INTEGER values
## --------------------
## 
##   1 The return code : 2 
##   2 The number of steps taken for the problem so far: 102 
##   3 The number of function evaluations for the problem so far: 133 
##   5 The method order last used (successfully): 6 
##   6 The order of the method to be attempted on the next step: 6 
##   7 If return flag =-4,-5: the largest component in error vector 0 
##   8 The length of the real work array actually required: 36 
##   9 The length of the integer work array actually required: 21 
##  14 The number of Jacobian evaluations and LU decompositions so far: 0 
##  15 The method indicator for the last succesful step,
##            1=adams (nonstiff), 2= bdf (stiff): 1 
##  16 The current method indicator to be attempted on the next step,
##            1=adams (nonstiff), 2= bdf (stiff): 1 
##  
## --------------------
## RSTATE values
## --------------------
## 
##   1 The step size in t last used (successfully): 0.01 
##   2 The step size to be attempted on the next step: 0.01 
##   3 The current value of the independent variable which the solver has reached: 1.00002 
##   4 Tolerance scale factor > 1.0 computed when requesting too much accuracy: 0 
##   5 The value of t at the time of the last method switch, if any: 0 
## 

EDO classique: le modèle SIR

Source https://kinglab.eeb.lsa.umich.edu/480/nls/de.html Nous considérons le modèle différentiel classique en épidémiologie.

# Define differential System
closed.sir.model <- function (t, state, parameters) {
  ## first extract the state variables
  S <- state[1]
  I <- state[2]
  R <- state[3]
  ## now extract the parameters
  beta <- parameters[1]
  gamma <- parameters[2]
  N <- S + I + R
  ## now code the model equations
  dSdt <- -beta * S * I/N
  dIdt <- beta * S * I/N - gamma * I
  dRdt <- gamma * I
  ## combine results into a single vector
  dxdt <- c(dSdt,dIdt,dRdt)
  ## return result as a list!
  list(dxdt)
}

Define parameters and times of evaluation

parms <- c(beta = 400,gamma =  365/13)
# times stamps
times <- seq(from = 0,to = 60/365,by = 1/365/4)
# initial conditions
xstart <- c(S = 999,I = 1,R = 0)

Solving with ODE

out.SIR <-
  ode(func = closed.sir.model,
      y = xstart,
      times = times,
      parms = parms,
      method = 'lsodar') %>%
  as.data.frame()

And plot

out.SIR %>%
  gather(variable,value,-time) %>%
  ggplot(aes(x = time,y = value,color = variable)) +
  geom_line(size = 2) +
  labs(x = 'time (yr)',y = 'number of individuals')

EDO classique: Modèle de Lorenz

Il s’agit d’une modélisation idéalisée de l’atmosphère. X, Y et Z représentent respectivement les variations verticale et horizontale de la température et le flux de convection.

\[ \left\{ \begin{align} X' &= aX + YZ \\ Y' &= b(Y-Z) \\ Z' &= -XY + cY -Z \\ \end{align} \right. \]

times <- seq(0, 100, by = 0.01)

a <- -8/3
b <- -10
c <- 28

lorenz <- function(t, y, parms) {
  with(as.list(y), {
    dX <- a * X + Y * Z
    dY <- b * (Y - Z)
    dZ <- -X * Y + c * Y - Z
    list(c(dX, dY, dZ))
  })
}

out_lorenz <- ode(y = c(X = 1, Y = 1, Z = 1), times = times, func = lorenz, parms = NULL)

df_lorenz <-
  out_lorenz %>%
  as_tibble() %>%
  mutate_all(as.numeric)

df_lorenz
## # A tibble: 10,001 x 4
##     time     X     Y     Z
##    <dbl> <dbl> <dbl> <dbl>
##  1  0    1      1     1   
##  2  0.01 0.985  1.01  1.26
##  3  0.02 0.973  1.05  1.52
##  4  0.03 0.965  1.11  1.80
##  5  0.04 0.962  1.19  2.09
##  6  0.05 0.964  1.29  2.40
##  7  0.06 0.973  1.41  2.74
##  8  0.07 0.990  1.55  3.11
##  9  0.08 1.02   1.72  3.52
## 10  0.09 1.06   1.91  3.97
## # … with 9,991 more rows
ggpairs(df_lorenz,
        lower = list(continuous = wrap("points", size = 0.1)))

EDO classique: Réaction de Belousov-Zhabotinskii

Il s’agit d’une récation chimique exhibant des motifs périodiques.

\[ \begin{align} A+B \xrightarrow{k_A} 2A \\ B+C \xrightarrow{k_B} 2B \\ C+A \xrightarrow{k_C} 2C \\ \end{align} \]

\[ \left\{ \begin{align} \partial_t n_A = n_A (k_A n_B - k_C n_C) \\ \partial_t n_B = n_B (k_B n_C - k_A n_A) \\ \partial_t n_C = n_C (k_C n_A - k_B n_B) \\ \end{align} \right. \]

times <- seq(0, 30, by = 0.01)

k_A <- 1
k_B <- 1
k_C <- 2

bz <- function(t, y, params) {
  with(as.list(y), {
    dA <- A * (k_A * B - k_C * C)
    dB <- B * (k_B * C - k_A * A)
    dC <- C * (k_C * A - k_B * B)
    list(c(dA, dB, dC))
  })
}

out_bz <- ode(y = c(A = 0.6, B = 0.6, C = 0.3), times = times, func = bz, parms = NULL)

out_bz %>%
  as_tibble() %>%
  mutate_all(as.numeric) %>%
  pivot_longer(-time, names_to = "variable", values_to = "value") %>%
  ggplot() +
  aes(x = time, y = value, color = variable) +
  geom_line()

EDP classique

La résolution d’EDP est plus complexe et consiste à transformer les EDPs en EDOs en utilisant la méthode des différences finies.

On illustre la démarche sur un modèle de diffusion de pestes (des aphides) sur une rangée de plantes positionnées entre \(x = 0\) et \(x = 60\).

\[ \frac{\partial N}{\partial t} = - \frac{\partial F}{\partial x} + g \times N \]

où le flux diffusif est donné par \(F = -D \frac{\partial N}{\partial x}\) et les contraintes stipulent que la densité d’aphides tombe à \(0\) aux deux bouts de la rangée de plantes: \(\forall t \geq 0, N(x=0, t) = N(x=60, t) = 0\).

Au temps initial, les aphides ne sont présentes qu’au milieu de la rangée de plantes: \[ N(x, t = 0) = \begin{cases} 1 & \text{if} \quad x = 30 \\ 0 & \text{else} \end{cases} \]

La méthodes des différences finies consiste à diviser le domaine en boîtes et à discrétiser l’équation comme suit \[ \frac{dN_i}{dt} = - \frac{F_{i, i+1} - F_{i-1, i}}{\Delta x_i} + g\times N_i \]\(N_i\) représente la densité d’aphide au milieu de la boîte tandis que les flux sont définies aux interfaces entre boîtes: \[ F_{i-1, i} = -D_{i, i-1} \times \frac{N_i - N_{i-1}}{\Delta x_{i-1, i}} \] pour se ramener à un système d’EDOs.

On commence par définir les équations du modèle

Aphid <- function(t, APHIDS, parameters) {
  with(as.list(parameters), {
    ## taille des boîtes
    deltax  <- c(0.5, rep(1, numboxes - 1), 0.5)
    ## valeurs du flux aux interfaces
    Flux    <- -D * diff(c(0, APHIDS, 0)) / deltax
    ## valeurs des dérivées
    dAPHIDS <- -diff(Flux) / delx + APHIDS * r
    # the return value
    list(dAPHIDS)
  })
}

puis les paramètres du modèle et de la grille de discretisation:

params <- list(
    D        <- 0.3,    # m2/day  diffusion rate
    r        <- 0.01,   # /day    net growth rate
    delx     <- 1,      # m       thickness of boxes
    numboxes <- 60,
    # distance of boxes on plant, m, 1 m intervals
    Distance <- seq(from = 0.5, by = delx, length.out = numboxes)
)

et enfin les conditions intiales

# Initial conditions:  # ind/m2
APHIDS        <- rep(0, times = numboxes)
APHIDS[30:31] <- 1
# initialise state variables
state         <- c(APHIDS = APHIDS)

on peut alors laisser la densité évoluer sur 200 jours avec un rendu par jour:

out_aphide <- ode.1D(state, times = 0:200, Aphid, parms = params,
              nspec = 1, names = "Aphid")

Le résultat est comme auparavant une matrice avec la première colonne pour le temps et une colonne par EDO dans la discrétisation.

On peut utiliser ce résultat pour voir la diffusion des aphides dans les plantes au cours du temps:

out_aphide %>%
  data.frame %>%
  pivot_longer(cols = -time,
               names_to = "location",
               names_pattern = "APHIDS(.*)",
               values_to = "density") %>%
  mutate(location = as.integer(location)) %>%
  ggplot(aes(x = time, y = location, fill = density)) +
  geom_tile() +
  scale_fill_viridis_c()

Equation différentielle à retard

Cet exemple est tiré du document : https://cran.r-project.org/web/packages/deSolve/vignettes/deSolve.pdf .

Le modèle logistique est un modèle de dynamique de population simple dans lequel une population donnée évolue jusqu’à atteindre un plateau (la capacité biotique du milieu noté M). La dynamique de la population peut être représentée par l’ED suivante : \(x'(t)=r.x(t).[\frac{1-x(t)}{M}]\). Le terme densité-dépendant \(\frac{1-x(t)}{M}\) peut avoir un effet sur la population avec un temps de retard. L’ED peut alors être réécrite : \(x'(t)=r.x(t).[\frac{1-x(t - \tau)}{M}]\). Sous cette forme l’ED est une équation différentielle à retard (EDR).

Dans le cas d’une population de lemming, le lag est fixé à 9 mois (0.74 année), soit le temps de croissance d’un lemming jusqu’au stade adulte. Le paramètre r est fixé sur la base de données expérimentales et la capacité biotique du milieu (M) est fixée arbitrairement à 19.

Les lignes de codes qui suivent permettent de simuler et de visualiser l’évolution d’une population de lemming (\(1^{ère}\) figure). La deuxième figure représente l’évolution de \(x(t)\) en fonction de \(x(t-\tau)\).

# DDE function
derivs <- function(t, y, parms) {
  if (t < 0) {
    lag <- 19
  } else {
    lag <- lagvalue(t - 0.74)
  }
  dy <- r * y * (1 - lag / m)
  list(dy, dy = dy)
}

# parameters
r <- 3.5; m <- 19

# initial values and times
yinit <- c(y = 19.001)
times <- seq(-0.74, 40, by = 0.01)

yout <- dede(y = yinit, times = times, func = derivs,
             parms = NULL, atol = 1e-10)

df_out <-
  yout %>%
  as_tibble() %>%
  mutate_all(as.numeric) %>%
  mutate(yend = lead(y), dy.yend = lead(dy.y))

ggplot(df_out) +
  aes(x = time, y = y) +
  geom_line()

ggplot(df_out) +
  aes(x = y, y = dy.y, xend = yend, yend = dy.yend) +
  geom_segment() +
  labs(x = "y", y = "y'")
## Warning: Removed 1 rows containing missing values (geom_segment).

diffeqr

Il s’agit d’une interface R pour utiliser le package Julia DifferentialEquations.jl. Le package est disponible sur le CRAN et sur GitHub.

Installation

Pour l’utiliser il faut installer Julia, en le téléchargeant ici et le package DifferentialEquations.jl. Attention le package R JuliaCall qui permet d’appeler des fonctions julia depuis R ne fonctionne pas avec la version 4.0 de R au moment du test donc utilisez la version 3.6.

Installation des packages Julia

Je conseille de tester le package Julia dans un premier temps. L’instruction qui suit permet d’exécuter du code julia dans un fichier Rmarkdown:

julia <- JuliaCall::julia_markdown_setup()
## Julia version 1.5.1 at location /usr/local/julia1.5.1/bin will be used.
## Loading setup script for JuliaCall...
## Finish loading setup script for JuliaCall.
  • Installation de DifferentialEquations.jl et Plots.jl
using Pkg
Pkg.add(["Plots", "DifferentialEquations"])
  • Résolution du système de Lorenz
using DifferentialEquations, Plots

function lorenz(du,u,p,t)
     du[1] = p[1]*(u[2]-u[1])
     du[2] = u[1]*(p[2]-u[3]) - u[2]
     du[3] = u[1]*u[2] - p[3]*u[3]
end
## lorenz (generic function with 1 method)

u0 = [1.0;0.0;0.0]
## 3-element Array{Float64,1}:
##  1.0
##  0.0
##  0.0
tspan = (0.0,100.0)
## (0.0, 100.0)
p = [10.0,28.0,8/3]
## 3-element Array{Float64,1}:
##  10.0
##  28.0
##   2.6666666666666665
prob = ODEProblem(lorenz,u0,tspan,p)
## ODEProblem with uType Array{Float64,1} and tType Float64. In-place: true
## timespan: (0.0, 100.0)
## u0: [1.0, 0.0, 0.0]
sol = solve(prob, saveat=0.01)
## retcode: Success
## Interpolation: 1st order linear
## t: 10001-element Array{Float64,1}:
##    0.0
##    0.01
##    0.02
##    0.03
##    0.04
##    0.05
##    0.06
##    0.07
##    0.08
##    0.09
##    ⋮
##   99.92
##   99.93
##   99.94
##   99.95
##   99.96
##   99.97
##   99.98
##   99.99
##  100.0
## u: 10001-element Array{Array{Float64,1},1}:
##  [1.0, 0.0, 0.0]
##  [0.9179244486277306, 0.2663399883958265, 0.0012638961610231109]
##  [0.8679193806275423, 0.5117405221648474, 0.0046554489845949155]
##  [0.8453600810424241, 0.7446542578856288, 0.009835866378798786]
##  [0.8468056451056966, 0.9723322686162557, 0.01673452988174231]
##  [0.8697858478642188, 1.2011328308675078, 0.025486243607962873]
##  [0.9126414552178186, 1.4367663939448458, 0.03640115766854124]
##  [0.9743847413621038, 1.6845201726702408, 0.0499584260358821]
##  [1.0546231178470253, 1.9494049510715725, 0.0668156858164365]
##  [1.1534564425013134, 2.236343236012357, 0.08784004081242848]
##  ⋮
##  [6.308988418917138, 10.034508455329648, 16.666741893400562]
##  [6.694294477197805, 10.6616223774606, 16.89262743745928]
##  [7.103490720791537, 11.307921316239156, 17.19617808515391]
##  [7.535723915953175, 11.967755076126197, 17.584159255358543]
##  [7.989771855958641, 12.634312203856176, 18.06363047390422]
##  [8.463949520591994, 13.298647824439136, 18.64123724761683]
##  [8.955627647759492, 13.948244596296133, 19.322132634934754]
##  [9.461062547781847, 14.56883567414296, 20.110058358591274]
##  [9.97537965430362, 15.143884806010783, 21.00643286956427]
plot(sol,vars=(1,2,3))

Modèle de Lorenz

Le package diffeqr permet d’utiliser le package julia depuis R:

de <- diffeqr::diffeq_setup()

Définition de la fonction dérivée:

f <- function(u,p,t) {
  du1 = p[1]*(u[2]-u[1])
  du2 = u[1]*(p[2]-u[3]) - u[2]
  du3 = u[1]*u[2] - p[3]*u[3]
  return(c(du1,du2,du3))
}

Les paramètres sont contenus dans un vecteur p.

u0 <- c(1.0,0.0,0.0)
tspan <- c(0.0,100.0)
p <- c(10.0,28.0,8/3)
prob <- de$ODEProblem(f, u0, tspan, p)
sol <- de$solve(prob, saveat=0.01)

Les méthodes numériques pour la résolution sont très nombreuses. Voir la page ODE solvers.

La solution sol$u est une liste de vecteurs, et sol$u[i] est le vecteur u[i] au temps sol$t[i]. On peut le transformer en matrice R avec sapply:

mat <- sapply(sol$u,identity)

Chaque ligne est une série temporelle, on peut transformer la solution en data.frame

udf <- as.data.frame(t(mat))
matplot(sol$t,udf,"l",col=1:3)

plotly::plot_ly(udf, x = ~V1, y = ~V2, z = ~V3, type = 'scatter3d', mode = 'lines')
## Warning: `arrange_()` is deprecated as of dplyr 0.7.0.
## Please use `arrange()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.

Ameliorer les performances

  • Compilation de la fonction dérivée: Chaque calcul de la dérivée est un appel d’une fonction R. diffeqr propose une fonction qui permet de compiler cette fonction. La fonction R sera traduite en Julia et compilée dans le langage LLVM qui est un dialecte du langage C. Ce langage et sa compilation sont le “moteur” de Julia qui lui permet d’avoir des performances comparables aux langages compilés comme le C++ ou le Fortran.
prob2 <- diffeqr::jitoptimize_ode(de,prob)
sol <- de$solve(prob2, de$Tsit5() )

On fixe ici la méthode de résolution avec Tsit5 (Tsitouras 5/4 Runge-Kutta).

Pour améliorer un peu plus les performances, on peut utiliser la version Julia de la fonction dérivée. Transformons toutes les variables R en variables Julia:

julf <- JuliaCall::julia_eval("
function julf(du,u,p,t)
  du[1] = 10.0*(u[2]-u[1])
  du[2] = u[1]*(28.0-u[3]) - u[2]
  du[3] = u[1]*u[2] - (8/3)*u[3]
end")
JuliaCall::julia_assign("u0", u0)
JuliaCall::julia_assign("p", p)
JuliaCall::julia_assign("tspan", tspan)
prob3 <- JuliaCall::julia_eval("ODEProblem(julf, u0, tspan, p)")
sol <- de$solve(prob3,de$Tsit5())
system.time({ for (i in 1:100){ de$solve(prob, de$Tsit5()) }})
##    user  system elapsed 
##   9.110   0.256   9.367
system.time({ for (i in 1:100){ de$solve(prob2, de$Tsit5()) }})
##    user  system elapsed 
##   0.167   0.040   0.208
system.time({ for (i in 1:100){ de$solve(prob3, de$Tsit5()) }})
##    user  system elapsed 
##   0.258   0.080   0.338

Stochastic Differential Equation (SDE) Examples

Les performances de diffeqr seront plus intéressantes dans le cas des EDS. En effet, pour résoudre numériquement ce type d’équation, nous avons besoin de calculer la solution d’une EDO plusieurs milliers de fois. Le gain qui peut paraître minime pour une EDO devient mille fois plus intéressant pour une EDS.

On utilise deux fonctions f et g, où du = f(u,t)dt + g(u,t)dW_t

Résolution d’une SDE

f <- function(u,p,t) {
  du1 = p[1]*(u[2]-u[1])
  du2 = u[1]*(p[2]-u[3]) - u[2]
  du3 = u[1]*u[2] - p[3]*u[3]
  return(c(du1,du2,du3))
}
g <- function(u,p,t) {
  return(c(0.3*u[1],0.3*u[2],0.3*u[3]))
}
u0 <- c(1.0,0.0,0.0)
tspan <- c(0.0,1.0)
p <- c(10.0,28.0,8/3)

tspan <- c(0.0,100.0)
prob <- de$SDEProblem(f,g,u0,tspan,p)
fastprob <- diffeqr::jitoptimize_sde(de,prob)
sol <- de$solve(fastprob,saveat=0.005)
udf <- as.data.frame(t(sapply(sol$u,identity)))
plotly::plot_ly(udf, x = ~V1, y = ~V2, z = ~V3, type = 'scatter3d', mode = 'lines')

Version avec desolve

library(deSolve)
Lorenz <- function(t, state, parameters) {
  with(as.list(c(state, parameters)), {
    dX <-  a * X + Y * Z
    dY <-  b * (Y - Z)
    dZ <- -X * Y + c * Y - Z
    list(c(dX, dY, dZ))
  })
}

parameters <- c(a = -8/3, b = -10, c = 28)
state      <- c(X = 1, Y = 1, Z = 1)
times      <- seq(0, 100, by = 0.01)
out <- ode(y = state, times = times, func = Lorenz, parms = parameters)

lorenz_solve <- function (i){
  state      <- c(X = runif(1), Y = runif(1), Z = runif(1))
  parameters <- c(a = -8/3 * runif(1), b = -10 * runif(1), c = 28 * runif(1))
  out <- ode(y = state, times = times, func = Lorenz, parms = parameters)
}
system.time({ lapply(1:1000,lorenz_solve) })
##    user  system elapsed 
## 210.109   0.245 210.388
prob_func <- function (prob,i,rep){
  de$remake(prob,u0=runif(3)*u0,p=runif(3)*p)
}
ensembleprob = de$EnsembleProblem(fastprob, prob_func = prob_func, safetycopy=FALSE)
system.time({ de$solve(ensembleprob,de$Tsit5(),de$EnsembleSerial(),trajectories=1000,saveat=0.01) })
##    user  system elapsed 
##  88.042   2.684  90.733

Le package diffeqr fonctionne également sur GPU, vous pouvez voir un exemple dans ce billet de blog.

Estimation des paramètres

On chercher mainenant à estimer les paramètres structurels d’un système diférentiel à partir d’observations bruitées. On l’applique sur le SIR.

sigma <-  20
noisy <- out.SIR
noisy$S  <- pmax(out.SIR$S  + rnorm(length(out.SIR$time),0,sigma),0)
noisy$I  <- pmax(out.SIR$I  + rnorm(length(out.SIR$time),0,sigma),0)
noisy$R  <- pmax(out.SIR$R  + rnorm(length(out.SIR$time),0,sigma),0)


noisy %>%
  gather(variable,value,-time) %>%
  ggplot(aes(x = time,y = value,color = variable)) +
  geom_point(size = 2) +
  theme_classic() +
  labs(x = 'time (yr)',y = 'number of individuals')

observ <- noisy[,2:4]

Utiliser une méthode des moindres carrés habituelles est rendue compliquée par le fait que la fonction de régression \(f\) est solution d’une quat diff donc n’a pas de solution explicite. De plus, chaque évaluation de la fonction (par un schéma numérique) est couteux d’un point de vue computationnel.

Une autre approche est d’exprimer \(f\) dans une base (spline par exemple), cette fonction devra d’une part s’ajuster aux donnnées et d’autre part être solution du système (qui est introduit comme une pénalité). Cette méthode qui date de 2007, est implémentée dans le package pcode La méthode est décrite dans la vignette du pacakge.

On définit dabord une base de décomposition pour les fonctions (package fda)

#" basis list
knots <- seq(0,max(times),length.out=21)
#order of basis functions
norder <- 4
#number of basis funtions
nbasis <- length(knots) + norder - 2
#creating Bspline basis
basis_dim1  <- create.bspline.basis(c(0,max(times)),nbasis,norder,breaks = knots)
basis = list(basis_dim1,basis_dim1,basis_dim1)

Puis on optimise:

pcode.result <- pcode(data = observ, time = times, ode.model = closed.sir.model,
                      par.initial = c(100,1), par.names = c('beta','gamma'),state.names = c('S','I','R'),
                      basis.list = basis, lambda = 1e2)

Jeton de reproductilité

sessionInfo()
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-conda_cos6-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.5 LTS
## 
## Matrix products: default
## BLAS/LAPACK: /usr/share/miniconda/envs/finistR2020/lib/libopenblasp-r0.3.10.so
## 
## locale:
##  [1] LC_CTYPE=C.UTF-8          LC_NUMERIC=C             
##  [3] LC_TIME=C.UTF-8           LC_COLLATE=C.UTF-8       
##  [5] LC_MONETARY=C.UTF-8       LC_MESSAGES=C.UTF-8      
##  [7] LC_PAPER=C.UTF-8          LC_NAME=C.UTF-8          
##  [9] LC_ADDRESS=C.UTF-8        LC_TELEPHONE=C.UTF-8     
## [11] LC_MEASUREMENT=C.UTF-8    LC_IDENTIFICATION=C.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] fda_5.1.5.1          Matrix_1.2-18        deSolve_1.28        
##  [4] GGally_2.0.0         ggdist_2.2.0         distributional_0.2.0
##  [7] DT_0.15              forcats_0.5.0        stringr_1.4.0       
## [10] dplyr_1.0.2          purrr_0.3.4          readr_1.3.1         
## [13] tidyr_1.1.2          tibble_3.0.3         ggplot2_3.3.2       
## [16] tidyverse_1.3.0     
## 
## loaded via a namespace (and not attached):
##  [1] httr_1.4.2            jsonlite_1.7.1        viridisLite_0.3.0    
##  [4] splines_3.6.3         modelr_0.1.8          assertthat_0.2.1     
##  [7] highr_0.8             blob_1.2.1            cellranger_1.1.0     
## [10] yaml_2.2.1            pillar_1.4.6          backports_1.1.9      
## [13] lattice_0.20-41       glue_1.4.2            readODS_1.7.0        
## [16] digest_0.6.25         RColorBrewer_1.1-2    rvest_0.3.6          
## [19] colorspace_1.4-1      htmltools_0.5.0.9000  plyr_1.8.6           
## [22] JuliaCall_0.17.1.9000 pkgconfig_2.0.3       broom_0.7.0          
## [25] haven_2.3.1           scales_1.1.1          jpeg_0.1-8.1         
## [28] generics_0.0.2        farver_2.0.3          ellipsis_0.3.1       
## [31] withr_2.2.0           lazyeval_0.2.2        cli_2.0.2            
## [34] magrittr_1.5          crayon_1.3.4          readxl_1.3.1         
## [37] diffeqr_1.0.0         evaluate_0.14         fs_1.5.0             
## [40] fansi_0.4.1           xml2_1.3.2            data.table_1.12.8    
## [43] tools_3.6.3           hms_0.5.3             lifecycle_0.2.0      
## [46] plotly_4.9.2.1        munsell_0.5.0         reprex_0.3.0         
## [49] compiler_3.6.3        rlang_0.4.7           grid_3.6.3           
## [52] rstudioapi_0.11       htmlwidgets_1.5.1     crosstalk_1.1.0.1    
## [55] labeling_0.3          rmarkdown_2.3         gtable_0.3.0         
## [58] codetools_0.2-16      DBI_1.1.0             reshape_0.8.8        
## [61] R6_2.4.1              lubridate_1.7.9       knitr_1.29           
## [64] utf8_1.1.4            stringi_1.4.6         Rcpp_1.0.5           
## [67] png_0.1-7             vctrs_0.3.4           dbplyr_1.4.4         
## [70] tidyselect_1.1.0      xfun_0.16