from IPython.core.display import HTML, display
css_file = './custom.css'
HTML(open(css_file, "r").read())
%reset -f
%matplotlib inline
from matplotlib.pylab import *
from scipy.optimize import fsolve
import sympy
sympy.init_printing()
from IPython.display import display, Math
prlat= lambda *args: display(Math(''.join( sympy.latex(arg) for arg in args )))
Q1 [2 points]
Pour quelles valeurs des paramètres $\alpha$ et $\vartheta$ la méthode est zéro-stable?
Le premier polynôme caractéristique est $$ \varrho(r)=r^{p+1}-\sum_{j=0}^p a_jr^{p-j}=r^2-\alpha r+(\alpha-1)=(r-1)\big(r-(\alpha-1)\big) $$ dont les racines sont $$ r_0=1,\quad r_1=\alpha-1. $$ La méthode est donc zéro-stable ssi $$ \begin{cases} |r_j|\le1 \quad\text{pour tout }j=0,1 \\ \varrho'(r_j)\neq0 \text{ si } |r_j|=1 \end{cases} $$ donc ssi $0\le\alpha<2$.
Q2 [3 points]
Quel est l'ordre de convergence de la méthode?
On rappelle qu'une méthode est convergente ssi elle est zéro-stable et consistante. Nous allons donc étudier la consistance (puis l'ordre de consistance = ordre de convergence) pour les seules valeurs de $\alpha$ pour lesquelles on a la zéro-stabilité.
Pour que la méthode soit consistante il faut que $$\begin{cases} \displaystyle\sum_{j=0}^p a_j=a_0+a_1=1, \\ \displaystyle\sum_{j=0}^p (-j)a_j+\sum_{j=-1}^p b_j=1. \end{cases}$$
De plus, la première barrière de Dahlquist affirme qu'un schéma implicite à $q=2$ pas consistante et zéro-stable peut être au mieux d'ordre $\omega=q+2=4$.
Pour que la méthode soit
%reset -f
%matplotlib inline
from matplotlib.pylab import *
from scipy.optimize import fsolve
import sympy
sympy.init_printing()
from IPython.display import display, Math
prlat= lambda *args: display(Math(''.join( sympy.latex(arg) for arg in args )))
# j = 0...p
# q = p+1 # nb de pas
# ω # ordre de la méthode
CAS_GENERAL = True
p = 1
q = p+1
ordre_max = q+2 if q%2==0 else q+1
print(f"{'★'*70}\nC'est une méthode à q = {q} pas d'ordre ω ≤ {ordre_max}")
if CAS_GENERAL:
aa = sympy.symbols(f'a_0:{q}')
bb = sympy.symbols(f'b_0:{q}')
bm1 = sympy.Symbol('b_{-1}')
else : # cas particulier
α = sympy.Symbol('α')
ϑ = sympy.Symbol('ϑ')
aa = [α, 1-α]
bb = [-3*ϑ/2, ϑ/2]
bm1 = 2
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"{'★'*70}\nLa méthode est d'ordre ω ≥ {i} (= consistante) 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"{'★'*70}\nLa méthode est d'ordre ω ≥ {i} si elle est d'ordre {i-1} et, de plus, ")
prlat((sa).factor()+(i*sb).factor(),"=1" )
Dans notre cas
Conclusion: si $0\le\alpha=\vartheta<1$ le schéma est convergent à l'ordre 1, dans les autres cas il n'est pas convergente.
Q3 [3 points]
Nous allons maintenant fixer $\alpha$. Pour cela, écrivez votre nom et prénom dans la cellule ci-dessous et vous obtiendrez un choix pour le paramètre $\alpha$. Pour $\vartheta$, choisir une valeur qui garantie la convergence.Vérifier empiriquement l'ordre de convergence sur le problème de Cauchy $$\begin{cases} y'(t) = t^2-y(t), &\forall t \in I=[0,1],\\ y(0) = 2, \end{cases} $$
nom = "Faccanoni"
prenom = "Gloria"
NUM = list(range(1,16))
idx = sum([ord(c) for c in nom+prenom])%len(NUM)
my_alpha = NUM[idx]/sympy.S(8)
prlat(r'\alpha=',my_alpha)
my_alpha = float(my_alpha) # conversion en float
sympy
ou à la main). # variables globales
t0 = 0
tfinal = 1
y0 = 2
phi = lambda t,y: t**2-y
##############################################
# solution exacte
##############################################
t = sympy.Symbol('t')
y = sympy.Function('y')
edo = sympy.Eq( sympy.diff(y(t),t) , phi(t,y(t)) )
solgen = sympy.dsolve(edo)
consts = sympy.solve( sympy.Eq( y0, solgen.rhs.subs(t,t0)) , dict=True)[0]
solpar = solgen.subs(consts).simplify()
display(solpar)
sol_exacte = sympy.lambdify(t,solpar.rhs,'numpy')
##############################################
# schéma (initialisation avec sol exacte pour ordre de convergence)
##############################################
def multipas(phi, tt, sol_exacte):
h = tt[1] - tt[0]
uu = [sol_exacte(tt[0])]
uu.append(sol_exacte(tt[1]))
for i in range(1,len(tt) - 1):
eq = lambda x : -x+my_alpha*uu[i]+(1-my_alpha)*uu[i-1]+h*(2*phi(tt[i+1],x)-3*my_alpha/2*phi(tt[i],uu[i])+my_alpha/2*phi(tt[i-1],uu[i-1]))
temp = fsolve( eq ,uu[i])
uu.append( temp[0] )
return uu
##############################################
# ordre
##############################################
H = []
err = []
N = 50
figure(figsize=(16,5))
ax1 = subplot(1,2,1)
for k in range(4):
N += 20
tt = linspace(t0, tfinal, N + 1)
H.append( tt[1] - tt[0] )
yy = sol_exacte(tt)
uu = multipas(phi, tt, sol_exacte)
err.append( norm(uu-yy,inf) )
ax1.plot(tt,uu,label=f'Approchée avec N={N}')
ax1.plot(tt,yy,label='Exacte')
xlabel('$t$')
ylabel('$y$')
ax1.grid(True)
ax1.legend()
ax2 = subplot(1,2,2)
ax2.loglog( H, err, 'r-o')
xlabel('$h$')
ylabel('$e$')
title(f'Multipas ordre = {polyfit(log(H),log(err),1)[0]:1.2f}')
ax2.grid(True)
%reset -f
%matplotlib inline
from matplotlib.pylab import *
from scipy.optimize import fsolve
import sympy
sympy.init_printing()
from IPython.display import display, Math
prlat= lambda *args: display(Math(''.join( sympy.latex(arg) for arg in args )))
nom="Faccanoni"
prenom="Gloria"
L=list(range(-5,0))
idx=sum([ord(c) for c in nom+prenom])%len(L)
eta=L[idx]
display(Math(r'\eta='+sympy.latex(eta)))
N = -15*eta
print(f'{N = }')
Q1 [1 point]
Calculer la solution exacte.
t0 = 0
tfinal = 10
y0 = 1
z0 = 1
Calculons la solution exacte avec sympy
par exemple:
t = sympy.Symbol('t')
y = sympy.Function('y')
z = sympy.Function('z')
phi1 = lambda t,y,z : eta*z
phi2 = lambda t,y,z : y+eta*z
edo1 = sympy.Eq( sympy.diff(y(t),t) , phi1(t,y(t),z(t)) )
edo2 = sympy.Eq( sympy.diff(z(t),t) , phi2(t,y(t),z(t)) )
# display(edo1)
# display(edo2)
solgen = sympy.dsolve([edo1,edo2],[y(t),z(t)])
# display(solgen)
consts = sympy.solve( [ sympy.Eq( y0, solgen[0].rhs.subs(t,t0)) , sympy.Eq( z0, solgen[1].rhs.subs(t,t0)) ] , dict=True)[0]
# display(consts)
solpar_1=solgen[0].subs(consts)
solpar_2=solgen[1].subs(consts)
display(solpar_1,solpar_2)
On remarque que $y(t)\xrightarrow[t\to+\infty]{}0$ et $z(t)\xrightarrow[t\to+\infty]{}0$
func_1 = sympy.lambdify(t,solpar_1.rhs,'numpy')
func_2 = sympy.lambdify(t,solpar_2.rhs,'numpy')
tt = linspace(t0,tfinal,N+1)
h = tt[1]-tt[0]
figure(figsize=(12,5))
yy=func_1(tt)
zz=func_2(tt)
subplot(1,2,1)
plot(tt,yy,'-*',tt,zz,'-*')
legend([r'$t\mapsto y(t)$',r'$t\mapsto z(t)$'])
subplot(1,2,2)
plot(yy,zz,'-*')
xlabel(r'$y$')
ylabel(r'$z$')
axis('equal');
Dans la suite on notera $u_n\approx y(t_n)$ et $w_n\approx z(t_n)$.
Pour l'affichage des solutions exactes VS approchées, on peut écrire une fonction qui prend en paramètres la discrétisation tt
, les deux listes solution exacte yy
et zz
, les deux listes solution approchée uu
et ww
et pour finir une chaîne de caractères s
qui contient le nom du schéma.
NOTA BENE : on a juste fixé le nombre de points mais nous n'avons pas fixé la taille du domaine. On regardera la solution exacte pour choisir un $T$ final significatif.
def affichage(tt,yy,zz,uu,ww,s):
subplot(1,2,1)
plot(tt,uu,'o--',tt,ww,'d--')
plot(tt,yy,tt,zz)
xlabel('t')
legend([r'$u(t)$ approchée',r'$w(t)$ approchée','$y(t)$ exacte','$z(t)$ exacte'])
title(f'{s} - y(t) et z(t)')
grid()
subplot(1,2,2)
plot(uu,ww,'o--')
plot(yy,zz)
xlabel('y')
ylabel('z')
legend(['Approchée','Exacte'])
title(f'{s} - z(y)')
grid()
axis('equal');
Q2 [3 points]
Calculer la solution approchée obtenue par la méthode d'Euler modifié.
Afficher $t\mapsto y(t)$, $t\mapsto z(t)$ et $y\mapsto z(y)$ en comparant solution exacte et solution approchée.
# x0, y0, h variables globales
def EM(phi1,phi2,tt):
uu = [y0]
ww = [z0]
for i in range(len(tt)-1):
uu_tmp = uu[i]+h/2*phi1(tt[i],uu[i],ww[i])
ww_tmp = ww[i]+h/2*phi2(tt[i],uu[i],ww[i])
uu.append(uu[i]+h*phi1(tt[i]+h/2,uu_tmp,ww_tmp))
ww.append(ww[i]+h*phi2(tt[i]+h/2,uu_tmp,ww_tmp))
return [uu,ww]
[uu, ww] = EM(phi1,phi2,tt)
figure(figsize=(12,5))
affichage(tt,yy,zz,uu,ww,"Euler modifié")
Q3 [3 points]
Calculer la solution approchée obtenue par la méthode de Crank-Nicolson.
Afficher $t\mapsto y(t)$, $t\mapsto z(t)$ et $y\mapsto z(y)$ en comparant solution exacte et solution approchée.
from scipy.optimize import fsolve
# x0,y0, h variables globales
def CN(phi1,phi2,tt):
uu = [y0]
ww = [z0]
for i in range(len(tt)-1):
sys = lambda z : [ -z[0]+uu[i]+h/2*phi1(tt[i],uu[i],ww[i])+h/2*phi1(tt[i+1],z[0],z[1]) , -z[1]+ww[i]+h/2*phi2(tt[i],uu[i],ww[i])+h/2*phi2(tt[i+1],z[0],z[1]) ]
utemp,wtemp = fsolve( sys , (uu[i],ww[i]) )
uu.append(utemp)
ww.append(wtemp)
return [uu,ww]
[uu, ww] = CN(phi1,phi2,tt)
figure(figsize=(12,5))
affichage(tt,yy,zz,uu,ww,"Crank Nicolson")
Dans ce cas particulier il est possible d'expliciter cette récurrence en exploitant la linéarité des $\varphi_1$ et $\varphi_2$: $$ \text{(CN) } \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=u_n+\frac{h}{2}\eta \left(w_{n+1}+w_{n}\right),\\ w_{n+1}=w_n+\frac{h}{2}\left(u_{n+1}+ u_{n}\right)+\frac{h}{2}\eta\left(w_{n+1}+\eta w_{n}\right) \end{cases} $$ ce qui donne le schéma explicite calculé ci-dessous par sympy:
t_n = sympy.Symbol('t_n')
dt = sympy.Symbol('h')
t_np1 = t_n+dt
u_n = sympy.Symbol(r'u_{n}')
u_np1 = sympy.Symbol(r'u_{n+1}')
w_n = sympy.Symbol(r'w_{n}')
w_np1 = sympy.Symbol(r'w_{n+1}')
Eq1 = sympy.Eq (u_np1, u_n+dt/2*(phi1(t_n,u_n,w_n)+phi1(t_np1,u_np1,w_np1)) )
Eq2 = sympy.Eq (w_np1, w_n+dt/2*(phi2(t_n,u_n,w_n)+phi2(t_np1,u_np1,w_np1)) )
# prlat(Eq1)
# prlat(Eq2)
sol=sympy.solve([Eq1,Eq2],(u_np1,w_np1))
prlat(u_np1,"=",sol[u_np1].factor(u_n))
prlat(w_np1,"=",sol[w_np1].factor(w_n))
# x0,y0, h variables globales
def CN(tt):
uu = [y0]
ww = [z0]
for i in range(len(tt)-1):
uu.append( -((h**2-2*h-4)*uu[i]+4*h*ww[i])/(h**2+2*h+4) )
ww.append( -((h**2+2*h-4)*ww[i]-4*h*uu[i])/(h**2+2*h+4) )
return [uu,ww]
[uu, ww] = CN(tt)
figure(figsize=(12,5))
affichage(tt,yy,zz,uu,ww,"Crank Nicolson")
Q5 [1 point]
Calculer la solution approchée obtenue par la fonctionodeint
du modulescipy
.
Afficher $t\mapsto y(t)$, $t\mapsto z(t)$ et $y\mapsto z(y)$ en comparant solution exacte et solution approchée.
from scipy.integrate import odeint
pphi = lambda yy,t : [ phi1(t,yy[0],yy[1]), phi2(t,yy[0],yy[1]) ]
yy0 = [y0,z0]
uu,ww = odeint(pphi,yy0,tt).T
figure(figsize=(12,5))
affichage(tt,yy,zz,uu,ww,"Scipy")
Q1 [3 points] Étudier théoriquement la A-stabilité du schéma suivant
Soit $\beta>0$ un nombre réel positif et considérons le problème de Cauchy $$\begin{cases} y'(t)=-\beta y(t), &\text{pour }t>0,\\ y(0)=1. \end{cases}$$ Sa solution est $y(t)=e^{-\beta t}$ donc $\lim\limits_{t\to+\infty}|y(t)|=0$.
Le schéma appliqué à ce problème de Cauchy s'écrit $$ \begin{cases} u_0=y(t_0)=y_0,\\ \tilde u_{n+1/2}=u_n-\frac{\beta h}{4}u_{n}-\frac{\beta h}{4}\tilde u_{n+1/2},\\ u_{n+1}=u_n-\beta h \tilde u_{n+1/2}& n=0,1,2,\dots N-1 \end{cases} $$ d'où $$ u_{n+1} = \left( 1-\beta h\frac{4-\beta h}{4+\beta h} \right)u_n $$
Vérifions ces calculs:
import sympy as sym
sym.init_printing()
from IPython.display import display, Math
# pour ne pas effacer l'affectation de "h", ici je vais l'appeler "dt" mais afficher "h"
dt = sym.Symbol('h')
beta = sym.Symbol(r'\beta')
u_np1 = sym.Symbol(r'u_{n+1}')
sym.var('u_n,x')
u_tilde = sym.solve (-x + u_n - beta*dt/4*u_n-beta*dt/4*x, x)[0]
display(Math(r'\tilde u='+sym.latex(u_tilde)))
u_np1 = (u_n-beta*dt*u_tilde).factor()
display(Math('u_{n+1}='+sym.latex(u_np1)))
On note $x=\beta h$. Le schéma est A-stable ssi $|q(x)|<1$. On étudie alors la fonction $q\colon \mathbb{R}^+\to\mathbb{R}$ définie par $q(x)=\dfrac{x^2-3x+4}{4+x}$.
from IPython.display import display, Math
import sympy as sym
sym.init_printing()
sym.var('x',nonnegative=True)
print('Dans R^+ on étudie la fonction')
q = (x**2-3*x+4)/(x+4)
display(Math('q(x)='+sym.latex(q)))
print('Limites aux bornes du domaine de definition:')
display(Math('q(0)='+sym.latex(q.subs(x,0))))
lim=sym.Limit(q,x,sym.oo)
display(Math(sym.latex(lim)+'='+sym.latex(lim.doit())))
print('On cherche les points stationnaires dans R^+:')
dq=(q.diff(x)).factor()
display(Math("q'(x)="+sym.latex(dq)))
print("On remarque que le numerateur de q' est une parabole et on a")
sol=sym.solve(dq)
display(Math("q'(x)=0 \iff x\in"+sym.latex(sol)))
print("De plus q'(x)>0 ssi x est supérieur à cette valeur, donc")
minimum=sol[0]
display(Math("x="+sym.latex(minimum)+"\\text{ est un minimum et}"))
qmin=q.subs(x,sol[0])
display(Math("q("+sym.latex(minimum)+")="+sym.latex(qmin)+"="+str(float(qmin))+">0"))
print("Donc q(x)>0 pour tout x.")
print("De plus, on a q(x)<1 ssi")
display(sym.solve(q<1))
print("Conclusion: -1<q(x)<1 pour tout x<4.")
sym.plot(q,1,-1,xlim=[-1,15],ylim=[-1.5,1.5]);
Q2 [2 points] Approcher la solution du problème de Cauchy suivant avec le schéma donné d'abord avec 120 points puis avec 130 points. Commenter le résultat. $$\begin{cases} y'(t)=-50y(t), &t\in[0;10]\\ y(0)=1 \end{cases}$$
Pour la A-stabilité il faut que $h=\frac{10-0}{N}$ soit $<\frac{4}{\beta}$ donc $N>\frac{(10-0)\beta}{4}=\frac{10\times50}{4}=125$. De plus, le problème est stiff.
%reset -f
from matplotlib.pylab import *
from scipy.optimize import fsolve
def schema(tt,phi,y0):
uu = [y0]
h = tt[1]-tt[0]
for i in range(len(tt)-1):
u_tilde = fsolve ( lambda x : -x+uu[i]+h/4*phi(tt[i],uu[i])+h/4*phi(tt[i+1],x) , uu[i] )[0]
uu.append( uu[i]+h*phi(tt[i]+h/2,u_tilde) )
return uu
t0, y0, tfinal = 0, 1, 10
beta=50
phi = lambda t,y : -beta*y
sol_exacte = lambda t : exp(-beta*t)
figure(figsize=(10,5))
subplot(1,2,1)
N = 120
tt = linspace(t0,tfinal,N+1)
h = tt[1]-tt[0]
yy = [sol_exacte(t) for t in tt]
uu = schema(tt,phi,y0)
plot(tt,yy,'b-',label=("Exacte"))
plot(tt,uu,'ro',label=("RK"))
title(rf' $N$={N}, $h$={h:1.5f}')
xlabel('$t$')
ylabel('$u$')
legend()
grid(True)
subplot(1,2,2)
N = 130
tt = linspace(t0,tfinal,N+1)
h = tt[1]-tt[0]
yy = [sol_exacte(t) for t in tt]
uu = schema(tt,phi,y0)
plot(tt,yy,'b-',label=("Exacte"))
plot(tt,uu,'ro',label=("RK"))
title(rf' $N$={N}, $h$={h:1.5f}')
xlabel('$t$')
ylabel('$u$')
legend()
grid(True);
figure(figsize=(10,5))
H = []
err = []
N = 130
for k in range(7):
N+=100
tt = linspace(t0, tfinal, N + 1)
h = tt[1] - tt[0]
yy = [sol_exacte(t) for t in tt]
uu = schema(tt,phi,y0)
H.append(h)
err.append(max([abs(uu[i] - yy[i]) for i in range(len(yy))]))
omega=polyfit(log(H),log(err), 1)[0]
figure(figsize=(8,5))
plot(log(H), log(err), 'c-o')
xlabel('$\ln(h)$')
ylabel('$\ln(e)$')
title(rf'Ordre $\omega$={omega:1.2f}')
grid(True);