.. include:: Equations différentielles ========================= Intégration numérique d'une équation différentielle du premier ordre -------------------------------------------------------------------- Comme exemple d'une équation différentielle du premier ordre, considérons le cas d'une fonction :math:`y:\mathbb{R}\rightarrow\mathbb{R}` vérifiant l'équation différentielle du premier ordre à coefficients constants suivante : .. math:: y'(t)+\frac{1}{\tau}y(t)=g(t) :label: equationDiff La variable est notée :math:`t` car il s'agit souvent du temps. :math:`\tau` est une constante et :math:`g` une fonction de :math:`t`. Si la valeur de :math:`y` est donnée pour :math:`t=t_0`, il existe une et une seule fonction :math:`y` vérifiant cette équation différentielle. :math:`y(t_0)` constitue la **condition initiale**. L'intégration numérique de l'équation différentielle consiste à rechercher des valeurs approchées de :math:`y` pour des valeurs de :math:`t` réparties sur l'intervalle :math:`[t_0,t_0+T]`. Nous considérons le cas d'un échantillonnage régulier : .. math:: t_n=t_0+\frac{T}{N-1}\ \ \ (n=0,1,\ldots N-1) Le **pas de temps** est l'intervalle de temps entre deux instants successifs : .. math:: h=t_{n+1}-t_n=\frac{T}{N-1} Soit :math:`y_0=y(t_0)` la valeur initiale de la fonction. Une méthode numérique d'intégration consiste à obtenir une approximation de :math:`y(t_1)`, notée :math:`y_1`, puis une approximation de :math:`y(t_2)`, notée :math:`y_2`, et ainsi de suite jusqu'à une approximation de :math:`y(t_{N-1})`, notée :math:`y_{N-1}`. Les approximations successives :math:`y_1,y_2,\ldots y_{N-1}` sont calculées itérativement au moyen d'une relation de récurrence. .. image:: integrationEquation.svg :scale: 80 L'erreur à l'instant :math:`t_n` est, par définition, l'écart entre la approchée et la valeur exacte de la fonction : .. math:: e_n=y_n-y(t_n) :label: erreur Cette erreur est aussi nommée **erreur de troncature globale**, ou plus rarement **erreur de consistance**. Méthode d'Euler -- Définition ----------------------------- La méthode d'Euler (1770) repose sur l'idée suivante. Supposons que :math:`y(t_n)` soit connue. La dérivée de :math:`y` à cet instant peut être approximée par la différence finie suivante : .. math:: y'(t_n)\approx\frac{y(t_{n+1})-y(t_{n})}{h} ce qui conduit à l'approximation : .. math:: y(t_{n+1})\approx y(t_n)+hy'(t_n) :label: approx1 Plus précisément, le développement de Taylor : .. math:: y(t_n+h)=y(t_n)+y'(t_n)h+O(h^2) montre que l'erreur commise par l'approximation :eq:`approx1` est :math:`O(h^2)` (grand O de :math:`h^2`). Cette erreur est appelée **erreur de troncature locale** ou simplement erreur locale. La méthode d'Euler consiste à utiliser la relation :eq:`approx1` pour définir la relation de récurrence suivante permettant de calculer les approximations successives : .. math:: y_{n+1}=y_n+hy'(t_n) :label: euler Si l'on reprend l'exemple :eq:`equationDiff`, cette relation de récurrence s'écrit : .. math:: y_{n+1}=y_n+h\left(g(t_n)-\frac{1}{\tau}y_n\right) Elle permet bien de calculer les approximations :math:`y_1,y_2,\ldots y_{N-1}` de manière itérative. L'erreur de troncature globale :math:`e_n` résulte de l'erreur :math:`e_{n-1}` et de l'erreur de troncature locale commise par application du schéma d'Euler :eq:`euler`. L'erreur globale résulte donc d'un effet cumulatif des erreurs de troncature locales, mais d'une manière en général complexe (l'erreur globale n'est pas nécessairement croissante avec :math:`n`). La méthode d'Euler est une **méthode d'ordre 1 au moins**, ce qui signifie que l'erreur globale est :math:`O(h)`, autrement dit qu'il existe une constante :math:`C` telle que sa valeur absolue vérifie l'inégalité suivante (pour :math:`h` assez petit) : .. math:: |e_n|\le Ch Il existe donc en général un majorant de l'erreur proportionnel à :math:`h`. Cette propriété garantit la convergence de la méthode d'Euler : plus :math:`h` est petit, c'est-à-dire plus :math:`N` est grand, plus l'erreur est petite. On a donc : .. math:: \lim_{h\rightarrow 0} y_n=y(t_n) Dans certains cas, il est possible que l'erreur soit proportionnelle à :math:`h^2` mais un exposant supérieur à 2 est très peu probable car l'erreur locale est :math:`O(h^2)`. La méthode d'Euler est considérée comme une **méthode d'ordre 1** car seul l'ordre 1 est garanti dans le cas général. La proportionnalité de l'erreur avec :math:`h` est d'ailleurs souvent vérifiée en pratique (on peut mesurer l'erreur lorsqu'on connaît la solution exacte). La constante :math:`C` dépend de l'équation différentielle considérée et augmente avec l'intervalle de :math:`t` sur lequel on fait le calcul (la durée :math:`T`). Tout ce qu'on peut dire en général est que, pour une durée :math:`T` donnée, la réduction de :math:`h` d'un facteur 10 a pour effet la réduction de l'erreur d'un facteur 10 (au moins) pour tout :math:`n=0,1,\ldots N-1`. Une méthode d'ordre 1 comme la méthode d'Euler est qualifiée de méthode peu précise car il existe des méthodes d'ordre plus élevé. Même une méthode d'ordre 2 offrira une précision beaucoup plus grande que la méthode d'Euler. Malgré ses piètres performances, la méthode d'Euler est intéressante car elle est très simple à mettre en oeuvre. Par ailleurs, son étude met en évidence des problèmes de précision et de stabilité, que l'on retrouve dans toutes les méthodes numériques à des degrés divers. La question de l'évolution de l'erreur lorsqu'on augmente :math:`T` constitue le problème de la **statibilité**. La méthode numérique est dite stable si l'erreur reste bornée lorsque :math:`T` augmente, ce qui revient à dire que la constante :math:`C` est bornée. Dans le cas contraire, la méthode est dite instable. En pratique, une méthode instable se reconnaît par le fait qu'elle conduit à une solution non bornée lorsque la solution exacte est bornée. L'étude générale de la stabilité n'est possible que pour les équations différentielles linéaires et la méthode d'Euler est instable pour ces équations. Elle partage cet inconvénient avec d'autres méthodes beaucoup plus précises, ce qui montre que stabilité et précision sont deux notions différentes. De manière générale, on parvient à définir une méthode stable en faisant appel à un schéma numérique implicite. Par exemple, la **méthode d'Euler implicite** est définie par la relation suivante : .. math:: y_{n+1}=y_n+hy'(t_{n+1}) La méthode d'Euler implicite, comme toute méthode implicite, est beaucoup plus difficile à mettre en oeuvre car elle nécessite la résolution d'une équation algébrique à chaque itération et cette équation est en générale résolue par une méthode itérative. Méthode d'Euler -- Implémentation --------------------------------- Pour réaliser la méthode d'Euler, il est judicieux d'écrire une fonction Python qui permettra de le faire dans le cas général d'une équation différentielle du premier ordre. En effet, cela permettra de réutiliser le code pour différents problèmes et l'écriture du cas général est plus simple et plus claire que l'implémentation d'un cas particulier. Cela nous permettra en outre d'introduire les conventions utilisées dans la fonction :py:obj:`scipy.integrate.odint` que nous étudierons plus loin. La forme générale d'une équation du premier ordre est : .. math:: y'(t)=F(y(t),t) :label: eqDiffOrdre1 où :math:`F:\mathbb{R}^2\rightarrow\mathbb{R}`. Par exemple, dans le cas de l'équation :eq:`equationDiff` on a : .. math:: F(y(t),t)=g(t)-\frac{y(t)}{\tau} La variable (ici le temps) apparaît explicitement via la fonction :math:`g` et implicitement via :math:`y(t)`. Dans d'autres cas, l'équation différentielle ne fait pas intervenir explicitement le temps mais on garde quand même la forme générale :eq:`eqDiffOrdre1`. Il faut donc définir une fonction Python permettant d'évaluer :math:`F`. Voici le prototype de cette fonction : .. code-block:: python def derive(y,t): deriv_y = # calcul de y' return deriv_y Reprenons l'exemple et supposons que la fonction :math:`g` soit définie par : .. math:: \begin{align} &g(t)=E\ {\rm si\ }t\in[0,t_1]\\&g(t)=0\ {\rm si\ }t>t_1 \end{align} L'instant initial sera :math:`t=0`. La fonction :py:obj:`derive` a besoin des valeurs de :math:`\tau,t_1,E`. Il y a deux manières de procéder : attribuer ces valeurs à trois variables globales ou bien les transmettre en arguments de la fonction. Voyons d'abord la première méthode, plus simple à mettre en oeuvre. .. code-block:: python :linenos: def derive(y,t): # calcule la dérivée de y # utilise les variables globales E,t1,tau if t \epsilon_a+|y_n|\epsilon_r alors le pas :math:`h` est diminué. Dans le cas contraire, il est augmenté. Cela permet de réduire le pas lorsque l'erreur de troncature estimée est trop grande ou au contraire de l'augmenter lorsque c'est possible. Lorsque la réduction du pas devient trop forte, l'algorithme bascule automatique à la méthode BDF, plus lourde mais pouvant traiter les équations raides. Les tolérances permettent à l'utilisateur de contrôler la précision du calcul dans le sens ou faire varier ces tolérances permet de faire varier l'erreur globale. Cependant, l'erreur globale ne peut en aucun cas être déduite directement de ces valeurs de tolérance. Pour utiliser :py:obj:`odeint`, il faut écrire une fonction Python pour définir le système différentiel du premier ordre, dont la signature est la même que celle que nous avons déjà utilisée. Par exemple pour l'équation du pendule : .. code-block:: python a = (2*np.pi)**2 def derive(y,t): return np.array([y[1],-a*np.sin(y[0])]) Un appel de cette fonction se présente comme suit : .. code-block:: python Y = odeint(derive,Y0,t,atol,rtol,args) :py:obj:`Y0` est une liste (ou un tableau à une dimension) contenant les conditions initiales. :py:obj:`t` est un tableau (ou une liste) contenant les instants auquels on souhaite mémoriser les différentes approximations :math:`Y_n`. La période d'échantillonnage doit être choisie assez petite pour obtenir une bonne représentation graphique des fonctions. Il ne faut pas confondre cette période avec le pas de temps de la méthode numérique, qui est plus petit que la période d'échantillonnage et dont la valeur n'est pas fixée puisqu'elle change automatiquement en fonction de l'estimation de l'erreur locale et des tolérances. L'utilisateur ne choisit pas le pas :math:`h` mais les tolérances absolue et relative. Les deux paramètres correspondant sont :py:obj:`atol` et :py:obj:`rtol` (arguments optionnels). Voici un exemple : .. code-block:: python Y0 = [1,0] T = 10 t = np.linspace(0,T,1000) Y = odeint(derive,Y0,t,rtol=1e-4,atol=1e-12) plt.figure() plt.plot(t,Y[:,0],"b-") plt.xlabel('t',fontsize=16) plt.ylabel(r'$\theta$',fontsize=16) plt.grid() plt.show() .. image:: odeint-pendule-fig1.png :scale: 60 Le fait de choisir une tolérance absolue très faible permet de faire intervenir principalement la tolérance relative. Voyons, pour cette même valeur de tolérance, le comportement sur une durée beaucoup plus grande : .. code-block:: python T = 1000 t = np.linspace(0,T,100000) Y = odeint(derive,Y0,t,rtol=1e-4,atol=1e-12) plt.figure() plt.plot(Y[:,0],Y[:,1],"b-") plt.xlabel(r'$\theta$',fontsize=16) plt.ylabel(r'$\omega$',fontsize=16) plt.grid() plt.show() .. image:: odeint-pendule-fig2.png :scale: 60 Cette représentation de la trajectoire dans l'espace des phases montre que la solution approchée est très mauvaise sur une temps aussi long, ce qui semble indiquer un comportement instable. Réduisons la tolérance : .. code-block:: python T = 1000 t = np.linspace(0,T,100000) Y = odeint(derive,Y0,t,rtol=1e-5,atol=1e-12) plt.figure() plt.plot(Y[:,0],Y[:,1],"b-") plt.xlabel(r'$\theta$',fontsize=16) plt.ylabel(r'$\omega$',fontsize=16) plt.grid() plt.show() .. image:: odeint-pendule-fig3.png :scale: 60 Avec cette tolérance, le résultat est conforme à ce qu'on attend d'un pendule. Lorsqu'on résout numériquement une équation différentielle dont on ne connaît pas la solution, il faut essayer des valeurs de plus en plus petites de la tolérance. Lorsque la diminution de la tolérance ne donne plus de variation visible de la solution, la bonne tolérance est atteinte. L'obtention d'une tolérance asse petite ne peut être faite que de maière empirique, car il n'existe pas de relation générale permettant de calculer *a priori* la bonne tolérance pour une erreur globale souhaitée. Il est possible d'utiliser :py:obj:`odeint` sans préciser les tolérances. Cela donne dans la plupart des cas un très bon résultat car les valeurs par défaut sont très faibles (:math:`10^{-8}`) mais on se prive alors de vérifier que ce résultat est correct. Par ailleurs, il peut être intéressant d'augmenter la tolérance jusqu'à une valeur acceptable lorsque le système à résoudre comporte beaucoup d'équations (pour réduire le temps de calcul).