Cours n°7 - Page d`accueil

publicité
Lycée Jules Ferry
PTSI 1&2
2016-2017
Informatique
Cours n°7 : calcul matriciel avec numpy, tracé de graphes avec
matplotlib.pyplot et méthode d’Euler
Commandes introduites dans ce cours : np.dot,np.linalg.inv,np.transpose,np.linalg.matrix_power,
np.linalg.solve,np.arange,np.linspae, matplotlib.pyplot,plt.plot,plt.show(),plt.xlabel,
plt.ylabel,plt.legend,plt.grid,plt.axis('saled'),plt.figure(),plt.lose(),plt.lf(),
plt.subplot, sipy.integrate.odeint.
1 Calcul matriciel avec numpy
1.1 Calcul matriciel élémentaire
Nous avons déjà vu comment définir des tableaux à l’aide de la bibliothèque numpy.
import numpy as np
>>> B=np.array([[0,1,2℄,[1,0,-1℄℄)
array([[ 0, 1, 2℄,
[ 1, 0, -1℄℄)
>>> B[1,2℄
-1
>>> A=np.zeros([3,2℄)
array([[0, 0℄,
[0, 0℄,
[0, 0℄℄)
>>> np.shape(A)
(3, 2)
Numpy offre les primitives permettant de faire du calcul matriciel sur ces tableaux :
— on peut additionner ou soustraire deux matrices à l’aide de + et - ;
— on peut multiplier une matrice par un scalaire à l’aide de * ;
— on peut multiplier deux matrices à l’aide de la commande np.dot (attention à la notation préfixée) ;
— la commande np.linalg.inv calcule l’inverse d’une matrice et renvoie un message d’erreur si
la matrice n’est pas inversible (en anglais on parle de singular matrix) ;
— la commande np.linalg.matrix_power permet de calculer les puissances d’une matrice ;
— la commande np.transpose permet de calculer la transposée de A ;
— enfin la ligne de code np.linalg.solve(A,B) permet de résoudre le système linéaire AX = B :
elle renvoie X = A−1 B (et un message d’erreur si A n’est pas inversible).
Notons enfin que la commande np.identity(n) permet de définir la matrice identité de taille n,
>>> A=np.array([[0,1℄,[4,0℄℄)
>>> np.transpose(A)
array([[0, 4℄,
[1, 0℄℄)
>>> B=np.identity(2)
>>> A+2*B
array([[ 2., 1.℄,
[ 4., 2.℄℄)
>>> C=np.dot(A,A)
array([[4, 0℄,
[0, 4℄℄)
>>> np.linalg.inv(C)
array([[ 0.25, 0. ℄,
[ 0. , 0.25℄℄)
>>> np.linalg.matrix_power(C,3)
array([[64, 0℄,
[ 0, 64℄℄)
1
1.2 Inversion d’une matrice et algorithme du pivot de Gauss
Un crash-test classique en calcul numérique matriciel consiste à inverser les matrices de Hilbert.
Nous allons voir que ces matrices sont très mal conditionnées et que tout calcul numérique portant
sur elles peut induire des erreurs d’approximation importantes. La matrice de Hilbert d’ordre n ∈ N
est la matrice Hn ∈ Mn (R ) de terme général hi,j = i +1j−1 pour 1 6 i, j 6 n. On peut la définir de la
manière suivante (en prenant garde au décalage d’indice) :
def hilbert(n):
return(np.array([[1/(i+j+1) for j in range(n)℄ for i in range(n)℄))
>>> hilbert(3)
[[1.0, 0.5, 0.3333333333333333℄, [0.5, 0.3333333333333333, 0.25℄,
[0.3333333333333333, 0.25, 0.2℄℄
On peut aisément vérifier que l’inverse que donne Numpy de la matrice H5 , a priori obtenu par
la méthode de Gauss, n’est pas correct. Il est naturel de penser que cette erreur est due aux arrondis
effectués lors des divisions par les pivots successifs du sytème linéaire.
>>> A=hilbert(5)
>>> B=np.linalg.inv(A)
>>> np.dot(A,B)
array([[ 1.00000000e+00,
-7.81597009e-14,
[ 2.66453526e-15,
-2.67341704e-13,
[ 8.61116733e-15,
-8.60644889e-13,
[ 2.27595720e-15,
1.00000000e+00,
[ 1.80411242e-16,
1.11910481e-13,
-6.43929354e-15, -9.76996262e-14,
-1.42108547e-14℄,
1.00000000e+00, -7.81597009e-14,
-3.19744231e-14℄,
-1.00142117e-13, 1.00000000e+00,
2.96207503e-13℄,
6.21724894e-15, -3.90798505e-14,
-1.15463195e-14℄,
3.96349620e-14, -1.73638881e-13,
1.00000000e+00℄℄)
Sur cet exemple les erreurs peuvent sembler infimes, mais nous allons voir sur un autre exemple
qu’elles preuvent prendre des proportions inquiétantes.
En notant Y la matrice de Mn,1 (R ) avec des zéros partout, sauf un 1 en dernière position, la
résolution de Hn X = Y va renvoyer la dernière colonne de Hn−1. On peut démontrer que Hn−1 a des
−1
coefficients entiers et que le terme d’indice (10, 10) de H10
est 44914183600. Grâce à la commande
−1
linalg.solve, on peut calculer la dernière composante de la dernière colonne de H10
:
>>> Y=np.array([[0℄,[0℄,[0℄,[0℄,[0℄,[0℄,[0℄,[0℄,[0℄,[1℄℄)
>>> np.linalg.solve(hilbert(10),Y)[9,0℄
44908633925.999527
On constate que non seulement le résultat n’est pas un nombre entier, mais en plus celui-ci est loin
de la solution !
2 La bibliothèque matplotlib.pyplot
2.1 Principe
Pour tracer la courbe représentative d’une fonction f sur un intervalle [ a, b], nous allons fabriquer
une liste [ x1 , . . . , xn ] contenant un certain nombre de réels de cet intervalle, et ensuite fabriquer la liste
image [ f ( x1 ), . . . , f ( xn )] associée. La primitive plot de la bilbiothèque matplotlib.pyplot se chargera
alors de placer sur un graphe chacun des points Mi de coordonnées ( xi , f ( xi )), puis de relier chaque
point Mi à son successeur Mi +1 par des segments de droite. On obtient ainsi une approximation de
la courbe représentative de f par une ligne polygonale (communément appelée une ligne brisée).
Ce procédé est simple, mais efficace : si l’on se donne suffisament de points, on obtient une ligne
polygonale qui paraît lisse « à l’œil. » Tous les logiciels de calcul scientifique fonctionnent de cette
manière.
2
La figure ci-contre présente un exemple d’approximation d’une courbe
par une ligne polygonale.
2.2 Subdivision régulière d’un intervalle
En mathématiques on utilise la définition suivante.
Définition 1.
Soit I = [ a, b] un intervalle de R. On appelle « subdivision régulière de [ a, b] » la suite t0 , . . . , tn où n > 1 et
∀i ∈ J0, nK, ti = a + i
et donc en particulier on a a = t0 et b = tn . Le réel h =
b− a
n
b−a
,
n
est alors appelé le « pas de la subdivision. »
b−a
d’un intervalle [ a, b] est donc le découpage de celui-ci en n
n
intervalles [ti , ti +1 ], chacun d’eux étant de longueur égale à h. Pour définir une subdivision en Python,
qui sera renvoyée sous forme de liste, il suffit d’utiliser une définition par compréhension. Voilà deux
définitions possibles, la première à partir du nombre de points n + 1 souhaités, la seconde à partir du
pas h.
Une subdivision de pas h =
>>> a,b=0,1
>>> n=10
>>> L=[a+k*(b-a)/n for k in range(n+1)℄
>>> print(L)
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0℄
>>> import math as ma
>>> h=1/10
>>> L2=[a+k*h for k in range(ma.floor((b-a)/h)+1)℄
>>> print(L2)
[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001,
0.8, 0.9, 1.0℄
On utilise souvent ce concept en informatique, à ceci près que l’on n’impose pas à t0 (resp. tn )
d’être égal à a (resp. b) ; on dira alors que l’on a donné une « discrétisation » de l’intervalle [ a, b]. Pour
discrétiser un intervalle, on peut alors utiliser les primitives np.linspae et np.arange : la première
prendra en entrée le nombre de points souhaités, et la seconde le pas (en plus des bornes a, b de
l’intervalle).
>>> print(np.linspae(0,1,11))
[ 0.
0.1 0.2 0.3 0.4 0.5
0.6
0.7
0.8
0.9
>>> print(np.arange(0,1,1/10))
[ 0.
0.1 0.2 0.3 0.4 0.5
0.6
0.7
0.8
0.9℄
1. ℄
La seconde liste est différente de la première, puisqu’elle ne contient pas le réel 1, mais si l’on souhaite
utiliser cette subdivision pour tracer une courbe représentative cela ne posera pas de problème (n sera
suffisament grand en pratique pour que l’abscence d’un point ne modifie pas le graphe).
3
2.3 La commande plot du module matplotlib.pyplot
La commande plot de la bibliothèque matplotlib.pyplot permet de tracer des lignes polygonales. Cette fonction prend en entrée deux listes Listex et Listey qui contiennent respectivement
les abscisses et les ordonnées des points à relier. En argument optionel, on peut donner un nom à la
courbe avec la syntaxe label='nom'.
Pour tracer un graphique, il faudra alors définir un grand nombre de points à placer. Par exemple,
pour tracer les courbes représentatives des fonction cosinus et sinus sur l’intervalle [0, 4π ], on commence par définir la liste des abscisses en discrétisant l’intervalle [0, 4π ] en N + 1 points, puis les listes
des ordonnées des points à placer. Par exemple, pour N = 1000 :
import matplotlib.pyplot as plt
N=1000
Listex=[k*4*ma.pi/N for k in range(N+1)℄
Listeyos=[ma.os(x) for x in Listex℄
Listeysin=[ma.sin(x) for x in Listex℄
plt.plot(Listex,Listeyos,label='y=os(x)')
plt.plot(Listex,Listeysin,label='y=sin(x)')
On remarque que le graphique ne s’affiche pas encore à l’écran. La commande plt.show() permet
cet affichage. On peut encore définir les noms et unités éventuelles des axes avec xlabel et ylabel, un
titre avec title. Enfin, la commande legend permet de définir la position du nom de la courbe sur la
figure.
Graphique test
1.0
0.5
y
plt.xlabel('x')
plt.ylabel('y')
plt.title("Graphique test")
plt.legend(lo=3)
plt.show()
0.0
−0.5
−1.00
y = cos(x)
y = sin(x)
2
4
6
x
8
10
12
14
La commande plt.plot ouvre une fenêtre graphique délimitée par les plus grandes et plus petites
abscisses et ordonnées apparaissant lors de la définition de la courbe. Cela peut être modifié en
utilisant a posteriori les primitives xlim et ylim ; par exemple, le code plt.xlim(-5,5) tronquera la
fenêtre graphique de sorte que la plus grande (resp. plus petite) abscisse apparaissant soit -5 (resp.
5).
Remarques. — Il existe d’autres options de personnalisation des graphiques, dont certaines seront présentées en TP. On peut par exemple utiliser la commande plt.grid() pour afficher un
quadrillage correspondant à la graduation des axes, et la commande plt.axis('saled') pour
avoir la même échelle sur les deux axes.
— la commande plt.figure permet de définir une nouvelle fenêtre graphique (si on souhaite
tracer des courbes dans une autre fenêtre), la commande plt.lf() permet d’effacer la dernière
fenêtre graphique ouverte, et la commande plt.lose() permet de fermer la dernière fenêtre
graphique ouverte.
4
2.4 La commande subplot
La commande subplot permet de tracer plusieurs courbes représentatives dans une même fenêtre
graphique. Pour cela, on commence par définir une figure vide avec la commande plt.figure, puis
on utilise la commande plt.subplot(n,p,k) signifiant : « la fenêtre graphique est découpée en n × p
figures (n est le nombre de ligne et p le nombre de colonnes), et le code suivant concerne la k-ème de
ces figures (les figures étant numérotées de haut en bas et de gauche à droite). » Chacune des figures
peut alors recevoir une courbe représentative, ainsi qu’un titre, une légende, etc. . . .
plt.figure()
1 .0
plt.subplot(2,1,1)
plt.plot(Listex,Listeyos,label='y=os(x)')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(lo=3)
y
0 .5
0 .0
− 0 .5
y= c o s (x)
− 1 .0
0
2
4
6
plt.subplot(2,1,2)
plt.plot(Listex,Listeysin,label='y=sin(x)')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(lo=3)
8
10
12
14
8
10
12
14
x
1 .0
y
0 .5
0 .0
− 0 .5
y= s in (x)
− 1 .0
0
2
4
6
x
plt.show()
3 Résolution d’une équation différentielle
3.1 La méthode d’Euler
Le calcul explicite des solutions d’une équation différentielle n’est pas toujours aisé, et il existe
un grand nombre de cas que l’on ne sait pas traiter. On dispose néanmoins de méthodes numériques
qui permettent d’obtenir une valeur approchée de la solution, ce qui est suffisant dans le cadre des
sciences expérimentales. La plus simple d’entre elles est la méthode d’Euler.
Cadre. Considérons une fonction f : R2 → R, et I un intervalle véritable de R. On cherche à déterminer les fonctions y : I → R qui vérifient l’équation différentielle
∀t ∈ I, y′ (t) = f (y(t), t).
On a ici affaire à une équation différentielle du premier ordre (non linéaire et à coefficients non
constants a priori). On admet que, si la fonction f est dérivable de sorte que f ′ soit continue, alors
quels que soient y0 ∈ R, t0 ∈ I, cette équation possède une unique solution y qui vérifie y(t0 ) = y0 .
Approximation de la courbe représentative de la solution par une ligne polygonale. Le principe de
la méthode d’Euler est le suivant. Soit y la solution de l’équation différentielle considérée. On commence par se donner une subdivision (t0 , . . . , tn ) régulière de I, et on cherche une valeur approchée
yi de chacun des réels y(ti ). En reliant par des segments de droite les points de coordonnées (ti , yi )
on obtient alors une ligne polygonale qui est une approximation de la courbe représentative de f . A
priori, plus le nombre n de points que l’on s’est donné est important, plus le graphe obtenu sera une
approximation de qualité.
Principe de la méthode, ou approximation d’une dérivée par la pente d’une corde. Naïvement, le
principe de la méthode peut être exprimé comme suit : partant d’une valeur approchée de yi de y(ti ),
il suffit de suivre la tangente en (ti , y(ti )) (à la courbe représentative Cy de y) jusqu’à l’abscisse ti +1 ;
l’ordonnée obtenue est alors une valeur approchée de y(ti +1 ), que l’on appelle yi +1.
5
Pour formaliser cette méthode, on utilise le fait que si y désigne une fonction dérivable sur un
intervalle [ti , ti +1 ], lorsque la valeur de h = ti +1 − ti est « suffisamment petite, » on peut écrire :
y ′ ( ti ) ≃
y ( ti + 1 ) − y ( ti )
y ( ti + 1 ) − y ( ti )
,
=
ti + 1 − ti
h
ce qui équivaut à y(ti +1 ) ≃ y(ti ) + hy′ (ti ). En d’autres termes, si h est « suffisament petit », c’est-àdire si ti et ti +1 sont suffisament proches, y(ti ) + hy′ (ti ) est une valeur approchée de y(ti +1 ), obtenue
en suivant la tangente en (ti , y(ti )) jusqu’à l’abscisse ti +1 (on reconnait l’équation de la tangente en
(ti , y(ti )) dans le second membre).
Mais ce n’est pas si simple, car on ne connait pas la valeur de y′ (ti ) et donc pas l’équation de la tangente en (ti , y(ti )). Mais comme y vérifie l’équation y′ (t) = f (y(t), t), on en déduit y′ (ti ) = f (y(ti ), ti ).
Avec la relation y(ti +1 ) ≃ y(ti ) + hy′ (ti ), on obtient alors y(ti +1 ) ≃ y(ti ) + h f (y(ti ), ti ).
La suite des approximations (yi )06i6n peut alors être définie par récurrence, en « imitant ce comportement » : on utilisera la relation de récurrence yi +1 = yi + h f (yi , ti ).
Implémentation de la méthode d’Euler. Considérons une équation différentielle y′ (t) = f (y(t), t), à
résoudre sur un intervalle [ a, b] avec la condition initiale y(a) = α. On commence par se donner une
a
subdivsion (t0 , . . . , tn ) de [ a, b] de pas h = b−
n « suffisament petit, » et on définit alors notre suite
6
d’approximation y0 , . . . , yn en posant
y0 = α
et
∀i ∈ J0, nK, yi +1 = yi + h f (yi , ti ).
On a ainsi défini par récurrence une suite (y0 , . . . , yn ) qui est une approximation de la suite y(t0 ), . . . , y(tn ),
où y désigne la solution de l’équation considérée. En effet, y vérifie la relation
y(ti +1 ) = y(ti ) + h f (y(ti ), ti ) + hε(h),
puisque l’on a y′ (ti ) = f (y(ti ), ti ) pour tout entier i ∈ J0, nK.
Exemple 1. On implémente la méthode d’Euler, puis on l’applique à l’équation différentielle y′ = y
sur [0, 10] munie de la condition initiale y(0) = 1 (l’équation différentielle est donc ici associée à la
fonction définie par f (y, t) = y). La solution recherchée est donc l’exponentielle, et on représente les
valeurs approchées sur un graphe pour observer l’erreur commise.
def Euler(f,a,b,y0,pas):
listey=[y0℄
listet=[a℄
k=0
while listet[k℄+pas<=b:
y=listey[k℄
t=listet[k℄
listey.append(y+pas*f(y,t))
listet.append(t+pas)
k=k+1
return(listet,listey)
25000
va le u rs a p p ro c h é e s
va le u rs e xa c t e s
20000
15000
10000
5000
def f(y,t):
return(y)
0
0
2
4
6
8
10
a,b,pas=0,10,10**(-1)
listet,listey=Euler(f,a,b,1,pas)
plt.plot(listet,listey,label="valeurs approhées")
listeexp=[ma.exp(t) for t in listet℄
plt.plot(listet,listeexp,label="valeurs exates")
plt.legend(lo=2)
plt.show()
cos x
Exercice 1. Soit y′ =
à résoudre sur l’intervalle [0, 10] muni de la condition initiale y(0) = 1.
1 + y2
2 .5
Le graphique ci-contre fait apparaitre la courbe représentative de la solution, ainsi que les courbes
obtenues en appliquant la méthode d’Euler, avec
les différents pas suivants : h = 2, h = 1, h = 12 , h =
1
1
10 et h = 100 . On observe bien la convergence de la
ligne polygonale vers la courbe représentative de
la solution.
2 .0
1 .5
1 .0
h= 2
h= 1
h = 0 .5
h = 0 .1
h = 0 .0 1
0 .5
0 .0
0
2
4
6
8
10
Écrire un programme Python qui permet d’obtenir le graphique ci-dessus.
7
Remarque. Pour associer le nom h = 0.1 à la courbe calculée avec le pas 1/10, on utilisera la commande label="h="+str(h). En effet, "h="+str(h) est la chaîne de caractère h=x où x est la valeur
contenue dans la variable h (qui peut être un entier, un réel, une chaîne de caractères,. . . ).
Remarque. Intuitivement, il est clair que plus le pas h est petit, plus l’approximation de la solution
est précise : on peut démontrer que « la méthode converge » lorsque h tend vers 0.
En pratique, l’implémentation de la méthode pose d’autre difficultés, des erreurs d’arrondis pouvant apparaître à chaque étape : en diminuant la valeur de h on augmente le nombre d’étapes lors
de l’exécution de l’algorithme, et ainsi le nombre d’erreurs commises, ce qui peut faire diverger la
méthode.
Il existe d’autres méthodes de résolution approchée d’une équation différentielle, dont certaines
seront vues plus tard dans l’année (comme la méthode d’Euler implicite). Mais voilà déjà un exemple.
Exercice 2. La méthode de Heun consiste à remplacer dans celle d’Euler la relation
∀i ∈ J0, nK, yi +1 = yi + h f (yi , ti )
par
h
( f (yi , ti ) + f (yi + h f (yi , ti )), ti +1 ) .
2
1. Expliquer à l’aide d’un dessin pourquoi cette méthode semble plus cohérente, et donc plus
efficace que la méthode d’Euler (on s’inspirera de la genèse de la méthode d’Euler).
∀i ∈ J0, nK, yi +1 = yi +
2. Programmer la méthode de Heun.
3. Vérifier sur un exemple (par exemple l’équation y′ = y avec la condition y(0) = 1) que la
méthode de Heun converge plus rapidement que celle d’Euler.
3.2 La commande scipy.integrate.odeint
Pour résoudre numériquement une équation différentielle, on peut utiliser la primitive odeint implémentée dans la bibliothèque sipy.integrate. La commande sipy.integrate.odeint(f,y0,listet)
permet de résoudre l’équation y′ = f (y, t) munie de la condition initiale y(a) = y0 sur un intervalle
[ a, b] dont listet contient une subdivision : elle renvoie une liste (y0 , . . . , yn ) où yi est une valeur
approchée de y(ti ) (où ti est le i-ème point de la subdivision et y l’unique solution de l’équation).
L’exemple suivant montre que odeint utilise une méthode plus évoluée que la méthode d’Euler :
1
avec un pas h = 10
, il est déjà impossible à l’œil de faire la différence entre la courbe représentative
de l’exponentielle et son approximation.
import sipy.integrate as intr
20000
def f(y,t):
return(y)
15000
10000
listet=np.arange(0,10,1/5)
listey=intr.odeint(f,1,listet)
plt.plot(listet,listey,label="valeurs approhées")
plt.plot(listet,listeexp,label="valeurs exates")
plt.legend(lo=2)
8
5000
0
0
2
4
6
8
10
Téléchargement