None
from IPython.display import display, Latex
from IPython.core.display import HTML
css_file = './custom.css'
HTML(open(css_file, "r").read())
import sys #only needed to determine Python version number
print('Python version ' + sys.version)
$\renewcommand{\R}{\mathbb{R}}$ $\renewcommand{\N}{\mathbb{N}}$ $\renewcommand{\dt}{\mathrm{d}t}$ $\renewcommand{\dx}{\mathrm{d}x}$ $\renewcommand{\dtau}{\mathrm{d}\tau}$ $\renewcommand{\CC}[1]{\mathscr{C}}$
Considérons le problème de Cauchy
trouver une fonction $y \colon I\subset \mathbb{R} \to \mathbb{R}$ définie sur un intervalle $I=[t_0,T]$ telle que $$\begin{cases} y'(t) = \varphi(t,y(t)), &\forall t \in I=[t_0,T],\\ y(t_0) = y_0, \end{cases}$$ avec $y_0$ une valeur donnée et supposons que l'on ait montré l'existence et l'unicité d'une solution $y$ pour $t\in I$.
Remarques :
Rappeles :
Dans cette partie nous allons étudier d'abord la convergence puis la A-stabilité d'une méthode multistep.
Dans l'analyse de ces méthodes, trois polynômes jouent un rôle clé. Pour cela, commençons par réécrire le schéma multistep comme suit: $$ u_{n+1} -\sum_{j=0}^p a_ju_{n-j}= h\left(\sum_{j=0}^p b_j\varphi(t_{n-j},u_{n-j})+b_{-1}\varphi(t_{n+1},u_{n+1})\right). $$
Formellement,
On verra que les racines du polynôme $\varrho$ sont liées à la zéro-stabilité tandis que celles du polynôme $p$ sont liées à la stabilité absolue (en association avec un choix particulier de $z=h\lambda$).
Exemples :
Une méthode est dite consistante si $\tau(h)=\max_n |\tau_n(h)|$ tend vers zéro quand $h$ tend vers zéro, où $\tau_n(h)$ est l’erreur de troncature locale de la méthode multi-pas définie par $$ \tau_n(h)=\frac{1}{h}\left(y_{n+1}-\sum_{j=0}^p a_jy_{n-j}-h\sum_{j=0}^p b_j\varphi(t_{n-j},y_{n-j})-hb_{-1}\varphi(t_{n+1},y_{n+1})\right). $$
On a
Exemple avec un schéma à $q=4$ pas et donc $p=3$:
from IPython.display import display, Math, Latex
from sympy import *
def xi(i):
aa = symbols(f'a_0:{q}')
bb = symbols(f'b_0:{q}')
bm1 = Symbol('b_{-1}')
p = len(aa)
sa = sum( [ (-j)**i * aa[j] for j in range(p) ] )
sb = bm1+sum( [(-j)**(i-1) * bb[j] for j in range(1,p)] )
# ??? si j=0 et i=0, il calcule (-j)**i =1 et on a bien a_0 dans la somme
# mais si j=0 et i=1 il ne calcule pas b_0 et il faut l'ajouter à la main
if i==1:
sb += bb[0]
return (sa).factor()+(i*sb).factor()
p = 1 # j = 0...p # coeffs du schema
omega = 5 # i = 0, ..., omega
q = p+1
print(f"Méthode à q={p+1} pas, on affiche ξ(i) pour i=0,...,{omega}")
for i in range(omega+1):
display(Math(f"ξ({i}) = {latex(xi(i))}") )
Par un développement de Taylor (assez fastidieux), on peut montrer que:
Remarque: la première condition de consistance (i.e. $\xi(0)=1$) équivaut à $\varrho(1)=0$, la deuxième (i.e. $\xi(1)=1$) à $\varrho'(1)=\sigma(1)$.
Si, de plus, $\tau(h)=\mathscr{O}(Ch^\omega)$, on dit que la méthode est d'ordre $\omega$. Par des développements de Taylor, on peut montrer que la méthode est d'ordre $\omega$ ssi $\xi(0)=\xi(1)=\dots=\xi(\omega)=1$, autrement dit:
Exemple: schéma AB$_2$
En particulier:
sympy
ci-dessous).Le 🎅 n'existe pas : la zero stabilité implique une restriction sur l’ordre atteignable pour une méthode multipas. On appelle cette contrainte la première barrière de Dahlquist :
Exemples :
Soit une méthode explicite ($b_{-1}=0$):
Soit une méthode implicite ($b_{-1}\neq0$):
from IPython.display import display, Math, Latex
def prlat(*args):
str = ''
for arg in args:
str += latex(arg)
display(Math(str))
# j = 0...p # coeffs du schema
# q = p+1 # nb de pas
# ω # ordre de la méthode
p = 3
q = p+1
if q%2==0:
ordre_max = q+2
else:
ordre_max = q+1
print(f"C'est une méthode à {q} pas d'ordre au plus {ordre_max}")
from sympy import *
aa = symbols(f'a_0:{q}')
bb = symbols(f'b_0:{q}')
bm1 = Symbol('b_{-1}')
i=1
sa=sum( [-j*aa[j] for j in range(len(aa))] )
sb=bm1+sum( [bb[j] for j in range(len(bb))] )
print(f"==========================\nLa méthode est consistante d'ordre >={i} si ")
prlat(sum(aa).factor(),"=1" )
prlat((sa).factor()+(i*sb).factor(),"=1")
for i in range(2,ordre_max+1):
sa=sum( [(-j)**i*aa[j] for j in range(len(aa))] )
sb=bm1+sum( [(-j)**(i-1)*bb[j] for j in range(1,len(bb))] )
print(f"==========================\nLa méthode est consistante d'ordre >={i} si ")
prlat((sa).factor()+(i*sb).factor(),"=1" )
Récapitulatif des méthodes multipas vues jusqu'ici:
%reset -f
from matplotlib.pylab import *
# rcdefaults()
rcParams.update({'font.size': 16})
from fractions import Fraction
from IPython.display import display, Math
import sympy as sym
sym.init_printing()
r=sym.Symbol('r')
sc = { "EE_AB1" : { "aa":[1], "bb":[ 1], "bm1":0 },
"EI_AM0_BDF1" : { "aa":[1], "bb":[ 0], "bm1":1 },
"CN_AM1" : { "aa":[1], "bb":[Fraction(1,2)], "bm1":1/2 },
"AB2": { "aa":[1,0], "bb":[Fraction(3,2),Fraction(-1,2)], "bm1":0 },
"AB3": { "aa":[1,0,0], "bb":[Fraction(23,12),Fraction(-16,12), Fraction(5,12)], "bm1":0 },
"AB4": { "aa":[1,0,0,0], "bb":[Fraction(55,24),Fraction(-59,24), Fraction(37,24), Fraction(-9,24)], "bm1":0 },
"AB5": { "aa":[1,0,0,0,0], "bb":[Fraction(1901,720), Fraction(-2774,720), Fraction(2616,720), Fraction(-1274,720), Fraction(251,720)], "bm1":0 },
"AM2": { "aa":[1,0], "bb":[Fraction(8,12),Fraction(-1,12)], "bm1":Fraction(5,12) },
"AM3": { "aa":[1,0,0], "bb":[Fraction(19,24),Fraction(-5,24),Fraction(1,24)], "bm1":Fraction(9,24) },
"AM4": { "aa":[1,0,0,0], "bb":[Fraction(646,720),Fraction(-264,720),Fraction(106,720),Fraction(-19,720)], "bm1":Fraction(251,720) },
"BDF2": { "aa":[Fraction(4,3),Fraction(-1,3)], "bb":[0,0], "bm1":Fraction(2,3) },
"BDF3": { "aa":[Fraction(18,11),Fraction(-9,11),Fraction(2,11)], "bb":[0,0,0], "bm1":Fraction(6,11) },
"BDF4": { "aa":[Fraction(48,25),Fraction(-36,25),Fraction(16,25),Fraction(-3,25)], "bb":[0,0,0,0], "bm1":Fraction(12,25) },
"BDF5": { "aa":[Fraction(300,137),Fraction(-300,137),Fraction(200,137),Fraction(-75,137),Fraction(12,137)], "bb":[0,0,0,0,0], "bm1":Fraction(60,137) },
"BDF6": { "aa":[Fraction(120,49),Fraction(-150,49),Fraction(400,147),Fraction(-75,49),Fraction(24,49),Fraction(-10,147)], "bb":[0,0,0,0,0,0], "bm1":Fraction(20,49) },
"MS2": { "aa":[0,1], "bb":[Fraction(4,3),Fraction(1,3)], "bm1":Fraction(1,3) }
}
for schema in sc.keys():
print("\n",schema)
q=len(sc[schema]["aa"])
p=q-1
print(f"\tq = {q}")
aa=sc[schema]["aa"]
bb=sc[schema]["bb"]
bm1=sc[schema]["bm1"]
if bm1==0:
print("\tExplicite")
else:
print("\tImplicite")
print("\tConsistance: ",end="")
ordre=[]
ordre.append( sum(aa)==1 and sum([-j*aa[j] for j in range(len(aa))])+bm1+sum(bb)==1 )
print(ordre[-1])
i=1
while ordre[-1]:
i+=1
ordre.append( sum([(-j)**i*aa[j]+i*(-j)**(i-1)*bb[j] for j in range(len(aa))])+i*bm1==1)
print(f"\tOrdre {len(ordre)-1}")
rho=array([1])
rho=append(rho,-array(aa))
display(Math(r'\varrho(r)='+sym.latex(sym.expand(poly1d(rho)(r)))))
# print(f"\tZéro-stabilité: |r_j|= {abs(roots(rho))}")
# print("\tRacines: ",end="")
# display(sym.solve(sym.expand(poly1d(rho)(r))))
display( Math(r' \{|r_j|\} = '+sym.latex( [abs(rac).simplify().evalf() for rac in sym.solve(sym.expand(poly1d(rho)(r)))] )))
La première barrière de Dalquist affirme qu'on peut augmenter arbitrairement l'ordre d'un schéma multipas en augmentant le nombre de pas. La convergence dépend ensuite juste du premier polynôme caractéristique. Cependant, dans la section précédente, on a considéré la résolution du problème de Cauchy sur des intervalles bornés et la convergence suppose $h\to0$.
Il existe tutefois de nombreuses situations dans lesquelles le problème de Cauchy doit être intégré sur des intervalles en temps très grands ou même infini. Dans ce cas, même pour $h$ fixé, $N$ tend vers l’infini, et
un résultat comme la zéro-stabilité n’a plus de sens puisque le membre de droite contient lq quantité non bornée ($T-t_0$).
On s’intéresse donc à des méthodes capables d’approcher la solution pour des intervalles en temps arbitrairement grands, même pour des pas de temps $h$ "assez grands".
Soit $\lambda\in\mathbb{C}$ un nombre complexe tel que $\Re(\lambda)=-\beta<0$ et considérons le problème de Cauchy $$\begin{cases} y'(t)=\lambda y(t), &\text{pour }t>0,\\ y(0)=1 \end{cases}$$ Sa solution est $y(t)=e^{\lambda t}$ et comme $\Re(\lambda)<0$ alors $$\lim_{t\to+\infty}|y(t)|=0.$$
Soit $h>0$ un pas de temps donné, $t_n=nh$ pour $n\in\mathbb{N}$ et notons $u_n\approx y(t_n)$ une approximation de la solution $y$ au temps $t_n$.
Si, sous d'éventuelles conditions sur $h$, on a $$ \lim_{n\to+\infty} |u_n| =0, $$ alors on dit que le schéma est A-stable (absolument stable).
On appelle région de stabilité absolue $A$ d’une méthode numérique l’ensemble des nombres complexes $z = h\lambda$ pour lesquels la méthode est absolument stable: $$ A=\Big\{z = h\lambda \in \mathbb{C} \ \Big| \ |u_n|\xrightarrow[n\to+\infty]{}0 \Big\} $$ Une méthode numérique est inconditionellement A-stable si $\mathbb{C}^-\subset A$ où $\mathbb{C}^-=\{z\in\mathbb{C}|\Re(z)<0\}$.
Une méthode à $q=p+1$ pas appliquée à ce problème modèle conduit à l'équation récurrente à $$ u_{n+1}=\sum_{j=0}^p a_j u_{n-j} + z \sum_{j=0}^p b_ju_{n-j} + z b_{-1}u_{n+1} $$
Conséquences:
tous les schémas AB_q ont une condition de A-stabilité.
Exemple: le schéma AB_1 (Euler explicite): $p(r)=(r-1)-z$. Il a une seule racine: $r=1+z$. On demande à ce que $|1+z|<1$. On obtient dans $\mathbb{C}$ le cercle (ouvert) de centre $(-1,0)$ et rayon $1$. Si on considère le cas réel, i.e. $\varphi(t,y)=\beta y$ avec $\beta=-\Re(\lambda)$, la condition devient $0<\beta h<1$ comme nous avons déjà trouvé lors du cours CM-3.
les schémas AM_q avec $q>1$ ont une condition de A-stabilité.
Exemple: le schéma AM_0 (Euler implicite): $p(r)=(r-1)-zr$. Il a une seule racine: $r=\frac{1}{1+z}$. On demande à ce que $\left|\frac{1}{1+z}\right|<1$. On obtient dans $\mathbb{C}$ la partie exterieure du cercle (fermé) de centre $(1,0)$ et rayon $1$. Comme $\mathbb{C}^-$ est contenu dans cette region, la méthode est inconditionellement A-stable.
Exemple: le schéma AM_1 (Crank-Nicolson): $p(r)=(r-1)-z\frac{1}{2}(r+1)$. Il a une seule racine: $r=\frac{2+z}{2-z}$. On demande à ce que $\left|\frac{2+z}{2-z}\right|<1$. On obtient dans $\Re(z)<1$. Comme $\mathbb{C}^-$ est contenu de cette region, la méthode est inconditionellement A-stable.
Régions de A-stabilité de diverses méthodes d'Adams-Bashforth (à gauche) et d'Adams-Moulton (à droite).
Source: A. Quarteroni, F. Saleri, P. Gervasio Calcul Scientifique: Cours, exercices corrigés et illustrations en Matlab et Octave.
On remarque que les régions de stabilité absolue des méthodes d'Adams-Bashforth et d'Adams-Moulton sont bornées et diminuent quand l'ordre augmente.
Régions de A-stabilité de diverses méthodes BDF.
Source: A. Quarteroni, F. Saleri, P. Gervasio Calcul Scientifique: Cours, exercices corrigés et illustrations en Matlab et Octave.
On remarque que les régions de stabilité absolue des méthodes BDF, pour $q\le6$, sont non bornées. En particulier, elles contiennent toujours les réels négatifs.
Tracer les régions de A-stabilité.
Posons $z=h\lambda$.
Il est en général difficile de déterminer la région de stabilité absolue car on doit décider pour quels $z\in\mathbb{C}$ les racines du polynôme de stabilité vérifient la condition de racines stricte (i.e. $|r| < 1$).
Il est plus aisé de déterminer la frontière de cette région car au moins une des racines de $p$ à la frontière est de module $1$.
On a donc $|r| = 1$ sur $\partial A$.
La frontière de $A$ est alors un sous ensemble des points $z\in\mathbb{C}$ pour lesquels $r=e^{i\vartheta}$, $\vartheta\in\mathbb{R}$.
On remplace alors $r =e^{i\vartheta}$ dans $p$ et comme $e^{i\vartheta}$ est racine, $p(e^{i\vartheta}) = 0$ et on résoud l’équation. On obtient $z = z(\vartheta)$ ce qui donne une courbe dans $\mathbb{C}$ en fonction de l’angle $\vartheta$, ce qui décrit donc une courbe polaire.
Par conséquent, pour obtenir une représentation approchée de $\partial A$, il suffit d’évaluer le second membre pour diverses valeurs de $r$ sur le cercle unité, diverses valeurs de $\vartheta\in\mathbb{R}$ et $r=e^{i\vartheta}$.
%reset -f
%matplotlib inline
import matplotlib.pyplot as plt
# plt.rcdefaults()
plt.rcParams.update({'font.size': 16})
import numpy as np
from IPython.display import display, Math
import sympy as sp
sp.init_printing()
def polyCar(schema):
sp.var('r z')
p,aa,bb,bm1=schema["p"],schema["aa"],schema["bb"],schema["bm1"]
rho = r**(p+1)-sum(aa[j]*r**(p-j)for j in range(p+1))
sigma = bm1*r**(p+1)+sum(bb[j]*r**(p-j)for j in range(p+1))
display(Math(r'\varrho(r)='+sp.latex(rho)))
display(Math(r'\sigma(r)='+sp.latex(sigma)))
def polyCarIMPL(schema):
sp.var('r z')
p,aa,bb,bm1=schema["p"],schema["aa"],schema["bb"],schema["bm1"]
rho=r**(p+1)-sum(aa[j]*r**(p-j)for j in range(p))
sigma=bm1*r**(p+1)+sum(bb[j]*r**(p-j)for j in range(p))
display(Math(r'\varrho(r)='+sp.latex(rho)))
display(Math(r'\sigma(r)='+sp.latex(sigma)))
# Returns > 1 if argument is not in region of absolute stability
def stabilityFunction(z,schema):
schema["aa"] = [float(a) for a in schema["aa"]]
schema["bb"] = [float(b) for b in schema["bb"]]
schema["bm1"] = float(schema["bm1"])
pol = np.array(schema["aa"])+z*np.array(schema["bb"])
pol = np.append([1-z*schema["bm1"]],-pol)
return max(abs(np.roots(pol)))
def Astabilite(schema):
schema["aa"] = [float(a) for a in schema["aa"]]
schema["bb"] = [float(b) for b in schema["bb"]]
schema["bm1"] = float(schema["bm1"])
p,aa,bb,bm1=schema["p"],schema["aa"],schema["bb"],schema["bm1"]
# cc liste de points sur le circle unitaire
rr = np.linspace(0,2*np.pi,201)
cc = np.cos(rr)+1j*np.sin(rr)
## ff fonction vectorisée qui donne zz
ff = lambda cc,p,aa,bb,bm1 : (cc**(p+1)-np.polyval(aa,cc))/(bm1*cc**(p+1)+np.polyval(bb,cc))
zz = ff(cc,p,aa,bb,bm1)
plt.figure(figsize=(18,7))
plt.subplot(1,2,1)
plt.plot(zz.real,zz.imag,'r')
plt.grid(True)
# plt.axis('equal')
plt.xlabel(r'$\Re(h\lambda)$')
plt.ylabel(r'$\Im(h\lambda)$');
plt.title(schema["Nom"])
Lx,Ly = plt.xlim(),plt.ylim()
plt.subplot(1,2,2)
x = np.linspace(Lx[0],Lx[1], 300)
y = np.linspace(Ly[0],Ly[1], 300)
[X,Y] = np.meshgrid(x,y)
Z = np.zeros(X.shape)
for m in range(X.shape[0]):
for n in range(X.shape[1]):
Z[m,n] = stabilityFunction(X[m,n] + 1j * Y[m,n], schema)
plt.contour(X, Y, Z, [1], colors='k')
plt.contourf(X, Y, Z, [0,1], colors=[[1, 0.5, 0.8]])
return None
EE = { "Nom":"EE=AB1", "p":0, "aa":[1], "bb":[1], "bm1":0 }
polyCar(EE)
Astabilite(EE)
AB2 = { "Nom":"AB2", "p":1, "aa":[1,0], "bb":[sp.Rational(3,2),-sp.Rational(1,2)], "bm1":0 }
polyCar(AB2)
Astabilite(AB2)
AB3 = { "Nom":"AB3", "p":2, "aa":[1,0,0], "bb":[sp.Rational(23,12),-sp.Rational(16,12), sp.Rational(5,12)], "bm1":0 }
polyCar(AB3)
Astabilite(AB3)
AB4 = { "Nom":"AB4", "p":3, "aa":[1,0,0,0], "bb":[sp.Rational(55,24),-sp.Rational(59,24), sp.Rational(37,24), -sp.Rational(9,24)], "bm1":0 }
polyCar(AB4)
Astabilite(AB4)
AB5 = { "Nom":"AB5", "p":4, "aa":[1,0,0,0,0], "bb":[sp.Rational(1901,720), -sp.Rational(1387,360), sp.Rational(109,30), -sp.Rational(637,360), sp.Rational(251,720)], "bm1":0 }
polyCar(AB5)
Astabilite(AB5)
AM0 = { "Nom":"AM0=EI", "p":0, "aa":[1], "bb":[0], "bm1": 1 }
polyCarIMPL(AM0)
#Astabilite(AM0)
AM1 = { "Nom":"AM1=CN", "p":1, "aa":[1], "bb":[sp.Rational(1,2)], "bm1": sp.Rational(1,2) }
polyCarIMPL(AM1)
#Astabilite(AM1)
AM2 = { "Nom":"AM2", "p":1, "aa":[1,0], "bb":[sp.Rational(8,12),-sp.Rational(1,12)], "bm1":sp.Rational(5,12) }
polyCar(AM2)
Astabilite(AM2)
AM3 = { "Nom":"AM3", "p":2, "aa":[1,0,0], "bb":[sp.Rational(19,24),-sp.Rational(5,24),sp.Rational(1,24)], "bm1":sp.Rational(9,24) }
polyCar(AM3)
Astabilite(AM3)
AM4 = { "Nom":"AM4", "p":3, "aa":[1,0,0,0], "bb":[sp.Rational(646,720), sp.Rational(-264,720), sp.Rational(106,720), sp.Rational(-19,720)], "bm1":sp.Rational(251,720) }
polyCar(AM4)
Astabilite(AM4)
MS2 = { "Nom":"MS2", "p":1, "aa":[0,1], "bb":[sp.Rational(4,3),sp.Rational(1,3)], "bm1":sp.Rational(1,3) }
polyCar(MS2)
#Astabilite(MS2)
BDF1 = { "Nom":"BDF1=EI", "p":1, "aa":[1], "bb":[0], "bm1":1 }
polyCarIMPL(BDF1)
Astabilite(BDF1)
BDF2 = { "Nom":"BDF2", "p":1, "aa":[sp.Rational(4,3),-sp.Rational(1,3)], "bb":[0], "bm1":sp.Rational(2,3) }
polyCarIMPL(BDF2)
Astabilite(BDF2)