Chapitre 08
Calcul scientifique sous Python
Introduction
Dans ce chapitre, nous nous intéressons à des méthodes numériques pour la résolution d'équations sur les réels, d'équations différentielles et le calcul approché d'intégrales. Nous rappelons tout d'abord quelques fonctions mathématiques qui sont faciles à programmer, puis nous expliquons comment utiliser les fonctions prédéfinies de Python et ses bibliothèques dédiées pour ce type de calcul.
Les fonctions présentées dans ce chapitre nécessitent l'importation des modules suivants :
-
import numpy as np -
import scipy.optimize as resol -
import scipy.integrate as integr -
import matplotlib.pyplot as plt
Avec :
-
NumPy: Le module numpy est utilisé pour manipuler des vecteurs et des matrices de nombres. -
SciPy: Le module scipy regroupe un certain nombre de sous modules qui sont très riches en fonctions prédéfinies pour assurer plusieurs routines. Scipy est basé sur numpy, mais il en étend considérablement les possibilités de ce dernier et il permet de faire des calculs en statistiques, optimisation, intégration numérique, traitement du signal, traitement d'image, algorithmes génétiques, etc. -
MatPlotLib: Matplotlib est une bibliothèque en Python très utilisée pour tracer des graphiques en deux et trois dimensions.
Le module NumPy
Le module numpy permet d'effectuer des calculs sur des vecteurs ou des matrices, élément par élément, via un nouveau type d'objet appelé array. Ce module contient plusieurs fonctions pour faire par exemple de l'algèbre linéaire et la génération de nombre aléatoire.
Voici des exemples d'utilisation de plusieurs fonctions du module NumPy :
- array(L) : crée un tableau à partir d'une liste L.
>>> import numpy as np
>>> L=[1,2,3]
>>> T=np.array(L) # créer un tableau à partir d'une liste
>>> type(T)
<class 'numpy.ndarray'>
>>> T
array([1, 2, 3])
Il est également possible de construire des objets array à deux
dimensions, il suffit de passer en argument une liste de listes à
la fonction array().
>>> np.array([[1,2],[3,4]]) # Créer une matrice à partir d'une liste de listes
array([[1, 2],
[3, 4]])
-
arange(a, b, k) : est équivalente à
range()et permet de construire un array à une dimension de manière simple.
>>> np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.arange(2,10)
array([2, 3, 4, 5, 6, 7, 8, 9])
>>> np.arange(2,10,2)
array([2, 4, 6, 8])
>>> np.arange(10,2,-2)
array([10, 8, 6, 4])
Un des avantages de la fonction
arange() est qu'elle permet de
générer des objets array qui contiennent des entiers ou des réels
selon l'argument qu'on lui passe.
>>> np.arange(2,3,0.1)
array([ 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9])
Pour récupérer un ou plusieurs élément(s) d'un objet array, vous pouvez utiliser l'indiçage ou les tranchages (slicing), de la même manière que pour les listes.
>>> T=np.arange(2,10)
>>> T
array([2, 3, 4, 5, 6, 7, 8, 9])
>>> T[0] # élément d'indice 0
2
>>> T[1:4] # les éléments d'indices 0,1,2 et 3
array([3, 4, 5])
>>> T[5:] # du 5ème au dernier élément
array([7, 8, 9])
>>> T[:5] # du début au 4ème élément
array([2, 3, 4, 5, 6])
>>> T[::2] # tous les éléments d'indice pair
array([2, 4, 6, 8])
Dans le cas d'un objet array à deux dimensions, vous pouvez récupérer une ligne, une colonne ou bien un seul élément.
>>> T=np.array([[1,2,3],[4,5,6]])
>>> T
array([[1, 2, 3],
[4, 5, 6]])
>>> T[0,0] # élément d'indice (0,0)
1
>>> T[0,:] # une ligne entière, ligne numéro 0
array([1, 2, 3])
>>> T[0] # une ligne entière, ligne numéro 0
array([1, 2, 3])
>>> T[:,2] # une colonne entière, colonne numéro 2
array([3, 6])
>>> T[[0,2]] # les lignes numéros 0 et 2
array([[1, 2, 3],
[7, 8, 9]])
- linspace(a, b, n) : crée un vecteur de n valeurs régulièrement espacées entre a et b (inclus).
>>> T=np.linspace(0,10,5)
>>> T
array([ 0. , 2.5, 5. , 7.5, 10. ])
- zeros(n) : crée un tableau de taille n rempli de zéros.
- zeros((n, m)) : crée une matrice de taille (n, m) rempli de zéros.
>>> z=np.zeros((2,2)) # matrice nulle de taille 2*2
>>> z
array([[ 0., 0.],
[ 0., 0.]])
>>> b=np.zeros((2,2),dtype=np.bool) # changer le type des éléments de la matrice
>>> b
array([[False, False],
[False, False]], dtype=bool)
- ones(n) : crée un tableau de taille n rempli de uns.
- ones((n, m)) : crée une matrice de taille (n, m) rempli de uns.
>>> o=np.ones((2,3),int) # créer une matrice d'entiers contenant que des uns
>>> o
array([[1, 1, 1],
[1, 1, 1]])
Par défaut, les fonctions
zeros() et
ones() génèrent des réels, mais
vous pouvez demander des entiers en passant l'option
int en second argument.
- eye(n) : crée une matrice identité de taille (n, n).
>>> I=np.eye(3) # matrice identité 3*3
>>> I
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
- diag(L) : crée une matrice diagonale.
>>> d=np.diag([1,2,3])
>>> d
array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
- concatenate((a1, a2, ⋯, an), axis=1 ou 0) : permet d'accoler deux ou plusieurs tableaux (horizontalement avec axis=1, verticalement avec axis=0). En cas de concaténation horizontale par exemple, toutes les colonnes doivent avoir la même hauteur. Les différents tableaux à concaténer doivent être donnés sous la forme d'un tuple.
>>> A=np.random.randint(1,6,(2,3))
array([[4, 3, 2],
[4, 1, 2]])
>>> B=np.random.randint(7,11,(2,4))
array([[10, 7, 10, 9],
[ 9, 8, 7, 9]])
>>> np.concatenate((A,B),axis=1)
array([[ 4, 3, 2, 10, 7, 10, 9],
[ 4, 1, 2, 9, 8, 7, 9]])
- T.shape : pour obtenir la taille du tableau T.
- T.dtype : indique le type des éléments de T.
- T.size : indique le nombre d'éléments du tableau T.
- T.ndim : le nombre d'indices nécessaires au parcours du tableau, c'est-à-dire le nombre d'éléments du tuple indiquant son format.
>>> T=np.array([[1,2,3],[4,5,6]])
>>> T.shape # un tuple (nombre de ligne, nombre de colonne)
(2, 3)
>>> T.dtype
dtype('int32')
>>> T.size
6
>>> T.ndim
2
-
reshape(M, (dimensions)) et
resize(M, (dimensions)) : les fonctions
reshape()etresize()permettent de redimensionner à volonté les dimensions d'un array. Il faut leur passer en argument l'objet array à redimensionner ainsi qu'un tuple indiquant la nouvelle dimension.
>>> T=np.arange(8)
>>> a=np.reshape(T,(2,4)) # redimensionner le vecteur en matrice 2*4
>>> a
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
>>> b=np.reshape(T,(4,2))
>>> b
array([[0, 1],
[2, 3],
[4, 5],
[6, 7]])
>>> c=np.reshape(T,(4,3))
ValueError: total size of new array must be unchanged
La fonction reshape() attend un
tuple dont la dimension est compatible avec le nombre d'éléments
contenus dans l'array de départ, alors que
resize() remplira le nouvel objet
array généré même si les longueurs ne coincident pas.
>>> c=np.resize(T,(4,3))
>>> c
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 0],
[1, 2, 3]])
- dot(M, N) : pour effectuer un produit matriciel de deux matrices.
>>> A=np.array([[1,2],[3,4]])
>>> A
array([[1, 2],
[3, 4]])
>>> B=np.array([[1,1,1],[2,2,2]])
>>> np.dot(A,B) # produit matricielle de A par B
array([[ 5, 5, 5],
[11, 11, 11]])
- vdot(v1, v2) : pour effectuer un produit scalaire de deux vecteurs.
>>> A=np.array([1,2])
>>> B=np.array([3,4])
>>> np.vdot(A,B) # le produit scalaire de A par B
11
- transpose(M) : pour transposer une matrice.
>>> T=np.array([[1,2,3],[4,5,6]])
>>> T
array([[1, 2, 3],
[4, 5, 6]])
>>> np.transpose(T)
array([[1, 4],
[2, 5],
[3, 6]])
- rank(M) : rang d'une matrice.
>>> M=np.array([[1,2,3],[1,2,4]])
>>> np.rank(M)
2
- mean(T) : valeur moyenne d'un tableau.
- min(T) : Le minimum d'un tableau.
- max(T) : Le maximum d'un tableau.
- sum(T) : La somme des valeurs d'un tableau.
>>> M=np.array([[1,2,3],[1,2,4]])
>>> np.mean(M)
2.1666666666666665
>>> np.min(M)
1
>>> np.max(M)
4
>>> np.sum(M)
13
- linalg.inv(M) : inversion d'une matrice.
- linalg.det(M) : déterminant d'une matrice.
>>> M=np.array([[1,2],[3,4]])
>>> np.linalg.det(M)
-2.0000000000000004
>>> np.linalg.inv(M)
array([[-2. , 1. ],
[ 1.5, -0.5]])
- linalg.eig(M) : renvoie un tuple dont le premier élément correspond aux valeurs propres et le second élément aux vecteurs propres.
>>> M=np.array([[1,2],[3,4]])
>>> np.linalg.eig(M)
(array([-0.37228132, 5.37228132]), array([[-0.82456484, -0.41597356],
[ 0.56576746, -0.90937671]]))
>>> np.linalg.eig(M)[0]
array([-0.37228132, 5.37228132])
>>> np.linalg.eig(M)[1]
array([[-0.82456484, -0.41597356],
[ 0.56576746, -0.90937671]])
- linalg.solve(A, b) : résolution du système linéaire AX = b.
>>> A,b=np.array([[1,2],[3,4]]), np.array([[1,1]])
>>> x=np.linalg.solve(A,np.transpose(b))
>>> np.dot(A,x)
array([[ 1.],
[ 1.]])
Les opérations sur des tableaux
Les opérations +, *, /, ==, etc.
s'appliquent aux tableaux numpy, mais terme à terme.
>>> A=np.array([2,2,2])
>>> B=np.array([3,3,3])
>>> A+B
array([5, 5, 5])
>>> A-B
array([-1, -1, -1])
>>> A**B
array([8, 8, 8], dtype=int32)
>>> A/B
array([ 0.66666667, 0.66666667, 0.66666667])
>>> A//B
array([0, 0, 0], dtype=int32)
>>> A%B
array([2, 2, 2], dtype=int32)
>>> A*B
array([6, 6, 6])
>>> A<B
array([ True, True, True], dtype=bool)
>>> A!=B
array([ True, True, True], dtype=bool)
>>> A==B
array([False, False, False], dtype=bool)
Les fonctions universelles appliquées sur des tableaux
Les fonctions définies dans le module numpy sont universelles, ou vectorialisées, c'est-à-dire qu'elles acceptent comme argument un tableau numpy et renvoient le tableau numpy des images.
>>> np.exp(A)
array([ 2.71828183, 7.3890561 , 20.08553692])
>>> np.log(A)
array([ 0. , 0.69314718, 1.09861229])
>>> f=lambda x:x**2
>>> f(2)
4
>>> f(A)
array([1, 4, 9])
Les tableaux pseudo-aléatoires
Les fonctions qui forment des tableaux pseudo-aléatoires sont présentes dans le sous-module random du module numpy.
- random.rand(n) : renvoie un tableau de n valeurs pseudo-aléatoires et uniformément distribuées dans l'intervalle [0, 1].
>>> import numpy as np
>>> x=np.random.rand(6)
>>> x
array([ 0.9595917, 0.92122968, 0.3544391,
0.10543147, 0.81636296,
0.44601582])
-
random.randn(n) : suit la même syntaxe que
random.rand, mais elle renvoie un échantillon de n valeurs pseudo-aléatoires au sens de la loi normale centrée réduite (c'est-à-dire d'espérance 0 et d'écart-type 1).
import matplotlib.pyplot as plt
import numpy as np
# 1000 tirages de loi normale de moyenne 0 et d'écart type 1
x = np.random.randn(1000) # Génération de n valeurs de loi normale centrée réduite
n, bins, patches = plt.hist(x, 50, density=True, facecolor='b', alpha=0.5) # La valeur 50 donné en deuxième paramètre de la méthode hist indique le nombre de barres à afficher.
plt.xlabel('x')
plt.ylabel('Probabilité de x N(0,1)(x)')
plt.axis([-5, 5, 0, 0.5])
plt.grid(True)
plt.show()
- random.randint(a, b) : renvoie une valeur entière pseudo-aléatoire au sens de la distribution uniforme dans un intervalle semi-ouvert [a, b[.
- random.randint(a, b, (n, m)) : renvoie un tableau de taille n × m valeurs entières pseudo-aléatoires au sens de la distribution uniforme dans un intervalle semi-ouvert [a, b[. Si on omet la valeur a, les entiers pseudo-aléatoires sont choisis dans [0, b[.
>>> np.random.randint(5)
1
>>> np.random.randint(1,7) # un lancer de dé
5
>>> np.random.randint(1,7,3) # trois lancers de dé
array([2, 5, 4])
>>> np.random.randint(1,7,(3,2)) # 6 lancers de dé sous forme d'une matrice 3x2
array([[1, 1],
[4, 5],
[6, 1]])
import matplotlib.pyplot as plt
import numpy as np
# 1000 tirages entre 1 et 6
x = np.random.randint(1,7,1000)
n, bins, patches = plt.hist(x, 6, density=True, facecolor='pink', alpha=0.8)
plt.xlabel('dé')
plt.ylabel('Probabilité')
plt.axis([1, 6, 0, 0.3])
plt.grid(True)
plt.show()
Le module MatPlotLib
Pour le simple tracé de courbes nous n'utiliserons que le
sous-module pyplot, importé, avec alias, à l'aide
de l'instruction :
import matplotlib.pyplot as plt.
Pour plus de documentation :
http://www.matplotlib.org.
Les fonctions essentielles de pyplot sont :
- plot() : pour le tracé de points, de courbes, etc.
- Les fonctions xlabel() et ylabel() : sont utiles pour donner un nom aux axes.
- title() : permet de définir le titre du graphique.
- grid() : affiche une grille en filigrane.
- show() : pour afficher le graphique créé.
- savefig(fich) : permet de sauvegarder la figure en cours dans le fichier fich.
- axis([xmin, xmax, ymin, ymax]) : choisir les valeurs extrémales des axes.
Utiliser plot() avec :
- en 1er argument la liste des abscisses,
- en 2e argument la liste des ordonnées,
-
en 3e argument (optionnel) le motif des points :
- '.' pour un petit point,
- 'o' pour un gros point,
- '+' pour une croix,
- '*' pour une étoile,
- '-' points reliés par des segments,
- '--' points reliés par des segments en pointillés,
- '-o' gros points reliés par des segments (on peut combiner les options),
- 'b', 'r', 'g', 'y' pour de la couleur (bleu, rouge, vert, jaune, etc.),
- consulter http://matplotlib.org/api/pyplot_api.html pour plus d'options.
Tracé d'un nuage de points
import matplotlib.pyplot as plt
abs = [0, 1, 2, 3, 4, 5]
ord = [0, 1, 1.5, 1, 2.5, 2]
plt.plot(abs, ord, 'o')
plt.grid(True) # Affiche la grille
plt.xlabel('axe des x') # Label de l'axe des abscisses
plt.ylabel('axe des y') # Label de l'axe des ordonnées
plt.title('Nuage de Points')
plt.show()
Tracé d'une ligne brisée
import matplotlib.pyplot as plt
x = [0, 2, 3, 3, -3, 0]
y = [0, 6, -2, 3, 2, 0]
plt.plot(x, y, 'b', linewidth=2, linestyle='--', marker='o')
plt.xlabel('Les x')
plt.ylabel('Les y')
plt.axis([-4, 4, -3, 7])
plt.title("Tracé d'une ligne brisée")
plt.show()
Représentation graphique d'une fonction
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 4 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
plt.plot(x, y, 'b', marker='o')
plt.plot(x, z, 'm', marker='v')
plt.xticks([0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi])
plt.title('Graphes de sin et cos')
plt.axis([-1, 14, -1.2, 1.2])
plt.axhline()
plt.axvline()
plt.legend(['Sinus', 'Cosinus'])
plt.show()
Exemple de courbe paramétrique — courbe du cœur :
import matplotlib.pyplot as plt
import numpy as np
t = np.linspace(0, 2 * np.pi, 100)
x = 16 * np.sin(t) ** 3
y = 13 * np.cos(t) - 5 * np.cos(2*t) - 2 * np.cos(3*t) - np.cos(4*t)
plt.plot(x, y)
plt.show()
Plusieurs graphiques
La fonction
subplot(n, m, k) permet de
diviser la fenêtre en une grille de n lignes et
m colonnes, et de sélectionner le ke
graphique.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2, 14, 50)
y = np.sin(x)
z = np.cos(x)
w = np.exp(x) / (1 + x)
plt.subplot(2, 2, 1)
plt.plot(x, y, 'b', marker='o')
plt.title('Graphe de sin')
plt.axis([-2, 10, -2, 2])
plt.grid(True)
plt.subplot(2, 2, 2)
plt.plot(x, z, 'm', marker='v')
plt.title('Graphe de cos')
plt.axis([-2, 14, -1.5, 1.5])
plt.subplot(2, 2, (3, 4))
plt.plot(x, w, 'r', marker='<', linestyle='-.')
plt.title('Graphe de exp(x)/(1+x)')
plt.xticks(np.arange(0, 4, 0.5))
plt.axis([0, 4, 0, 5])
plt.savefig('figure.jpg')
plt.show()
Tracé d'une courbe paramétrique
import numpy as np
import matplotlib.pyplot as plt
theta = np.linspace(0, 2 * np.pi, 40)
x = np.cos(theta)
y = np.sin(theta)
plt.subplot(121)
plt.plot(x, y, 'r')
plt.axis('equal')
plt.xlim(-2, 2)
plt.title('Cercle de centre 0 et de rayon 1')
plt.xticks([-1, 0, 1])
plt.yticks([-1, 0, 1])
plt.axhline()
plt.axvline()
plt.subplot(122)
T = np.linspace(0, 8 * np.pi, 1000)
x = T * np.cos(T)
y = T * np.sin(T)
plt.plot(x, y)
plt.title('Une spirale')
plt.savefig('figure.jpg')
plt.show()
Recherche approchée de f(x) = 0
Dans les problèmes d'ingénierie, il faut souvent rechercher les solutions d'une équation de la forme f(x) = 0, où f est une fonction à valeurs réelles. Le but est donc de trouver une solution approchée tout en minimisant le temps d'exécution et en obtenant une bonne approximation. Deux méthodes classiques de recherche de zéro sont présentées dans ce cours, à savoir :
- La méthode de dichotomie
- La méthode de Newton
On considérera par la suite une fonction f continue sur un intervalle [a, b] à valeurs réelles, telle que f(a) · f(b) < 0. D'après le théorème des valeurs intermédiaires, la fonction f s'annule au moins une fois sur l'intervalle [a, b].
La méthode de dichotomie
L'idée est très simple et elle consiste à diviser l'intervalle [a, b] en deux intervalles [a, x0[ et ]x0, b], avec x0 = (a+b)/2. La solution est soit égale à x0, ou bien elle est dans l'un de ses deux intervalles. Ainsi, il faut examiner le signe des équations suivantes :
- si f(a) · f(x0) < 0, alors la racine se trouve dans l'intervalle [a, x0[ ;
- si f(a) · f(x0) = 0, alors la racine est égale à x0 ;
- si f(a) · f(x0) > 0, alors la racine se trouve dans l'intervalle ]x0, b].
Puis on recommence.
Écrire une fonction Python
dichotomie(f, a, b, e) qui
recherche et renvoie un zéro de f dans l'intervalle [a,
b] et selon la précision e.
Code Python pour la méthode par dichotomie :
def dichotomie(f,a,b,e):
while b-a > e:
m = (a+b)/2
if f(a)*f(m) < 0:
b = m # Dichotomie à gauche
else:
a = m # Dichotomie à droite
return m
def f(x):
return x**2-2
print(dichotomie(f,0,2,10**(-6)))
# résultat 1.4142141342163086
from math import *
print(sqrt(2))
# résultat 1.4142135623730951
La méthode de Newton
La méthode de Newton est une méthode célèbre pour la résolution approchée d'une équation f(x) = 0.
Elle considère la suite définie par son premier terme U0 et par la relation de récurrence :
Un = Un-1 - f(Un-1) / f'(Un-1)
Écrire une fonction Python
newton(f, g, u0, e) qui recherche
et renvoie un zéro de f selon la précision e,
avec g la fonction dérivée de f et
u0 la valeur initiale.
Code Python pour la méthode de Newton :
def newton(f, g, u0, e):
u = u0
# la fonction g est la dérivée de f
v = u0 - f(u0)/g(u0)
while abs(v-u) > e:
u = v
v = v - f(v)/g(v)
return v
def f(x):
return x**2-2
def g(x):
return 2*x
print(newton(f,g,2,10**(-6)))
# résultat 1.4142135623730951
from math import *
print(sqrt(2))
# résultat 1.4142135623730951
Concernant les méthodes de résolution approchée d'équations, tout se trouve dans le module scipy.optimize, qui est lui-même un sous-module de scipy. Il contient entre autres les fonctions suivantes :
- brentq(f, a, b) : détermine une racine de la fonction f dans l'intervalle [a, b] par la méthode de Brent.
- bisect(f, a, b) : détermine une racine de f dans [a, b] en effectuant une dichotomie.
- newton(f, x0) : détermine une racine par la méthode de Newton en partant de x0.
- root(fun, x0) : détermine une racine de la fonction fun, qui peut ici être une fonction de plusieurs variables.
- fsolve(fun, x0) : détermine une racine de la fonction fun, qui peut ici être une fonction de plusieurs variables.
import scipy.optimize as resol
def f(x):
return x**2-2
a=1
b=2
x=resol.bisect(f,a,b)
print("sol=",x)
x=resol.newton(f,a)
print("sol=",x)
x=resol.fsolve(f,a)
print("sol=",x)
x=resol.root(f,a)
print("sol=",x)
Résoudre un système d'équations
Résoudre graphiquement et numériquement le système d'équations suivant :
{ x − 2y = 3 ; 4x + 5y = 6 }
import matplotlib.pyplot as plt
import numpy as np
def d1(x):
return (3-x)/2
def d2(x):
return (6-4*x)/5
x=np.linspace(-2,2,100)
y1=d1(x)
y2=d2(x)
plt.plot(x,y1,'r')
plt.plot(x,y2,'b')
plt.grid("on")
plt.show()
Résoudre graphiquement et numériquement le même système d'équations.
La déclaration de la fonction f de la manière suivante :
def f(x):
return [x[0]+2*x[1]-3, 4*x[0]+5*x[1]-6]
En considérant comme point de départ le vecteur (0, 0), on obtient :
print(resol.fsolve(f,(0,0)))
# Résultat: array([-1, 2])
Les fonctions vectorielles
Une démarche strictement identique doit être suivie pour obtenir la solution à un système d'équations non linéaires, à condition de n'utiliser qu'un seul argument (list ou tuple) pour stocker les différentes composantes du vecteur x = (x1, x2, ⋯, xn) considéré.
En guise d'exemple, considérons le système non-linéaire formé par les fonctions :
f1(x1, x2) = x1 + 3 · log10(x1) − x22
f2(x1, x2) = 2 · x12 − x1 · x2 − 5 · x1 + 1
La déclaration de la fonction f est désormais classique :
def f(x):
return [x[0]+3*log10(x[0])-x[1]**2, 2*x[0]**2-x[0]*x[1]-5*x[0]+1]
En considérant comme point de départ le vecteur, x0 = (5.0, 5.0), on obtient :
print(resol.fsolve(f,(5.0,5.0)))
# Résultat: array([3.48744279, 2.26162863])
Les racines d'un polynôme
Le module roots de la bibliothèque numpy détermine les racines dans ℂ d'un polynôme.
import numpy as np
P1=[1,0,-2] # x**2-2
racines1=np.roots(P1)
print(racines1)
# résultat [ 1.41421356 -1.41421356]
P2=[1,0,1,1] # x**3+x+1
racines2=np.roots(P2)
print(racines2)
# résultat [ 0.3411639+1.1615414j
# 0.3411639-1.1615414j
# -0.6823278+0.j ]
Calcul approché d'une intégrale
- Calculer une valeur approchée d'une intégrale n'est en général pas très difficile.
- Les méthodes les plus simples consistent à subdiviser régulièrement l'intervalle d'intégration pour approcher le calcul d'intégrale par un calcul d'aires de figures géométriques plus simples (par exemple dans la méthode des trapèzes).
- Subdivision régulière du segment [a; b] :
- Soit [a; b] un intervalle non vide et non réduit à un point (i.e. a < b).
-
Une subdivision régulière de [a; b] en n+1 points est une suite
finie de n+1 réels :
-
(ak)0 ≤ k ≤ n vérifiant :
- a0 = a, an = b
- et pour tout k ∈ [0; n], ak = a + k · (b − a)/n.
-
(ak)0 ≤ k ≤ n vérifiant :
- Sous Python :
>>> import numpy as np
>>> x = np.linspace(a, b, n+1)
Méthode des rectangles
La méthode des rectangles revient à une approximation par une fonction en escalier, avec n « marches » de longueur (b−a)/n. La valeur approchée R de l'intégrale vaut alors :
R = ((b−a)/n) · Σi=0n−1 f(ai)
Exemple :
∫0π/2 sin(x) dx = 1
import numpy as np # pour la fonction linspace()
def rectangle(f,a,b,n):
x = np.linspace(a,b,n+1) # Subdivision régulière
Y = f(x[:n]) # Séquence des f(ak)
return (b-a)/n * sum(Y)
print(rectangle(np.sin,0,np.pi/2,10))
# résultat 0.919403170015
print(rectangle(np.sin,0,np.pi/2,1000))
# résultat 0.99921439622
Méthode des trapèzes
La valeur approchée R de l'intégrale vaut :
R = ((b−a)/n) · ((f(a) + f(b))/2 + Σi=1n−1 f(ai))
Exemple :
∫0π/2 sin(x) dx = 1
import numpy as np # pour la fonction linspace()
def trapeze(f,a,b,n):
x = np.linspace(a,b,n+1) # Subdivision régulière
Y = f(x[1:n]) # Séquence des f(ak)
return (b-a)/n * (sum(Y)+(f(a)+f(b))/2)
print(trapeze(np.sin,0,np.pi/2,10))
# résultat 0.997942986354
print(trapeze(np.sin,0,np.pi/2,1000))
# résultat 0.999999794383
scipy dispose de plusieurs méthodes d'intégration de fonctions dans son sous-module integrate :
>>> import scipy.integrate as integr
La méthode quad(f, a, b) pour intégrer f sur l'intervalle [a; b] :
>>> integr.quad(np.sin,0,np.pi/2)
0.9999999999999999, 1.1102230246251564e-14
Elle retourne un couple constitué de la valeur approchée de l'intégrale (1er élément) et d'une estimation de l'erreur commise.
- trapz() applique la méthode des trapèzes à un échantillonnage :
>>> x = np.linspace(0,np.pi/2,1001)
>>> integr.trapz(np.sin(x),dx=(np.pi/2)/1000)
0.99999979438323383
- simps() applique la méthode de Simpson à un échantillonnage :
>>> x=np.linspace(0,np.pi/2,1001)
>>> integr.simps(np.sin(x),dx=(np.pi/2)/1000)
1.0000000000000338
Équations et systèmes différentiels
On considère l'équation différentielle ordinaire (EDO) du premier ordre suivante :
Si l'on se donne en plus une condition initiale de la forme y(a) = y0, on obtient un problème de Cauchy. Sous certaines hypothèses de régularité sur la fonction f, on sait qu'il existe une unique solution y sur [a, b]. On utilise des méthodes permettant d'approcher cette solution en calculant des approximations de proches en proches.
L'intervalle [a, b] est donc découpé en N intervalles de taille h = (b − a) / N et la solution y est approchée aux points xn = a + nh, n = 0, …, N.
Le module SciPy de Python est consacré au calcul
scientifique avancé. En particulier, en utilisant la fonction
scipy.integrate.odeint, il est
possible d'approcher la solution d'une EDO grâce à des méthodes
très performantes :
Exemple : Résolution de y′(t) = y(t)² − y(t)·sin(t) + cos(t), avec y(0) = 0 :
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
def f(y, u):
return y**2 - y * np.sin(u) + np.cos(u)
t = np.linspace(0, np.pi, 100)
y0 = 0
y = odeint(f, y0, t)
plt.plot(t, y)
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title("Résolution de y'(t) = y²(t) − y(t)·sin(t) + cos(t)")
plt.grid(True)
plt.show()
Exemple de système différentiel à deux équations couplées :
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integr
def f(x, t):
return np.array([-x[0] - x[1], x[0] - x[1]])
T = np.linspace(0, 5, 100)
X = integr.odeint(f, np.array([2., 1.]), T)
plt.plot(T, X[:, 0])
plt.axis([-0.5, 2, -1, 2])
plt.title('Résolution du système différentiel')
plt.xlabel('t')
plt.grid(True)
plt.show()
Exercices avec corrigés
Énoncés
Considérons le cercle trigonométrique (la bordure d'un billard circulaire) et le point A de coordonnées [1, 0]. Soit un point suivant la trajectoire qui part de A vers le point A1 d'affixe (eiθ). Puis qui rebondit au point A2 d'affixe (ei2θ) ainsi de suite… Le point A1 a pour coordonnées [cos(θ), sin(θ)].
Travail demandé :
-
Écrire une fonction Python nommée
TRAJECTOIR qui prend en paramètre l'angle
θ en radians et n un entier positif
représentant le nombre de rebonds sur le cercle
trigonométrique.
La fonction retourne une liste de tuples, où chaque tuple représente les coordonnées d'un point Ai pour i de 0 à n, avec A0 = (1, 0). Le i-ième point Ai a pour coordonnées (cos(iθ), sin(iθ)). -
Tracer sur le même graphique :
- le cercle trigonométrique x² + y² = 1,
-
les segments de droites à partir de la liste des points
formée par l'appel à la fonction
TRAJECTOIR(2π/2.9, 30).
On cherche à trouver la solution y(t) d'une EDO du premier ordre : y′(t) = f(t, y(t)), avec la condition initiale y(t0) = y0.
On pose h = (tn − t0) / (n+1) et ti = t0 + i·h avec i ∈ [1, n].
Algorithme d'Euler explicite :
| y(t0) | = | y0 |
| y(ti+1) | = | y(ti) + h·f(ti, y(ti)) |
| ti | = | t0 + i·h, i ∈ [1, n] |
Méthode Runge-Kutta d'ordre 2 (RK2) :
| y(t0) | = | y0 |
| k1 | = | h·f(ti, y(ti)) |
| k2 | = | h·f(ti + h/2, y(ti) + k1/2) |
| y(ti+1) | = | y(ti) + k2 |
| ti | = | t0 + i·h, i ∈ [1, n] |
Méthode Runge-Kutta d'ordre 4 (RK4) :
| y(t0) | = | y0 |
| k1 | = | h·f(ti, y(ti)) |
| k2 | = | h·f(ti + h/2, y(ti) + k1/2) |
| k3 | = | h·f(ti + h/2, y(ti) + k2/2) |
| k4 | = | h·f(ti + h/2, y(ti) + k3) |
| y(ti+1) | = | y(ti) + (1/6)(k1 + 2k2 + 2k3 + k4) |
| ti | = | t0 + i·h, i ∈ [1, n] |
Travail demandé :
-
Écrire une fonction Python f(a, b) qui
calcule et retourne :
f(a, b) = −b + 4·cos(2a + π/4)·e−a -
Écrire une fonction Python
Euler(t0, tn, y0, n) résolvant l'EDO par la
méthode d'Euler. La fonction renvoie une liste de tuples
P tels que
P[i][0] = tᵢetP[i][1] = y(tᵢ). - Écrire une fonction Python RK2(t0, tn, y0, n) résolvant l'EDO par la méthode de Runge-Kutta 2.
- Écrire une fonction Python RK4(t0, tn, y0, n) résolvant l'EDO par la méthode de Runge-Kutta 4.
-
Écrire une fonction Compare(P) comparant la
méthode d'Euler à la solution exacte. Pour l'EDO :
y′(t) = −y(t) + 4·cos(2t + π/4)·e−t, avec y(0) = 0,
la solution exacte est : y(t) = 2·sin(2t + π/3)·e−t − e−t·√3.
La fonction retourne la somme : S = Σ (Pi,1 − y(Pi,0))²
Soit f définie sur [0 ; 5] par f(x) = sin(4x)·exp(−x² + 3x).
Partie A
- Écrire en Python la définition de la fonction f.
- Construire la liste lx des abscisses variant de 0 à 5 avec un pas de 0.01, puis la liste ly des ordonnées correspondantes y = f(x).
- Tracer la courbe représentative de la fonction f.
-
Écrire dans un fichier
data.txtles coordonnées x et y séparées par une tabulation ("\t"), une paire par ligne.
Partie B — Échantillonnage
-
Ouvrir
data.txtet construire les listes Lx et Ly en prenant une ligne sur dix (lignes 0, 10, 20, …). - Afficher sur deux colonnes « abscisses » et « ordonnées », arrondis à 10−4 près.
- Sur une même figure, tracer la courbe de f et placer les points de Lx, Ly.
Partie C — Dichotomie et zéros
- Définir une fonction dichotomie(f, a, b, eps) résolvant f(x) = 0 sur [a, b] à ε près.
- Utiliser cette fonction pour déterminer la solution strictement positive α de f(x) = 0 sur [0 ; 1] à 10−5 près.
-
Comparer avec les fonctions
bisect,newton,fsolveetrootdescipy.optimize.
Partie D — Intégrale de f sur [0, α]
- Tracer la courbe de f sur [0, α].
-
Écrire une fonction trapeze(x, y) renvoyant
une valeur approchée de I par la méthode des
trapèzes :
I ≈ Σ (xᵢ₊₁ − xᵢ) · (yᵢ₊₁ + yᵢ) / 2 -
Comparer avec
scipy.integrate.trapzetsimps.
On souhaite illustrer graphiquement la construction des termes d'une suite récurrente définie par u0 et un+1 = f(un), où f(x) = ax(1 − x), a ∈ ]0, 4], u0 ∈ ]0, 1[.
- Le programme demande à l'utilisateur les valeurs de a, u0 et le nombre maximal d'itérations imax.
- Écrire la définition de la fonction f(x) renvoyant ax(1 − x).
- Tracer sur une même figure la courbe de f sur [0, 1] et la bissectrice y = x.
- Pour le tracé des itérations, créer deux listes pour les abscisses et ordonnées. Commencer par (u0, 0), puis compléter avec les points (x, y) et (y, y) où y = f(x).
On dispose d'un fichier
donnees.txt
contenant vingt lignes du type :
0.0;1.00988282142
0.1;1.07221264497
Chaque ligne contient deux valeurs flottantes séparées par un point-virgule (abscisse ; ordonnée), ordonnées par abscisses croissantes.
- Lire le fichier et construire la liste LX des abscisses et LY des ordonnées.
- Représenter les points sur une figure.
-
Écrire une fonction trapeze(y, x) renvoyant
:
Σi=1n−1 (xᵢ − xᵢ₋₁) · (yᵢ + yᵢ₋₁) / 2 -
Retrouver la valeur approchée de I avec
scipy.integrate.trapz.
Le Morpion est un jeu sur une grille (numpy
array 2D) de dimension n × n (typiquement
n = 3) à deux joueurs. Les cases peuvent valoir : 0
(vide), 1 (joueur 1), 2 (joueur 2).
- Créez une fonction create_morpion(n) qui crée et renvoie une grille n × n initialisée à 0.
-
Créez une fonction
next_turn(morpion, player, i, j) qui simule
l'ajout d'un jeton en position (i, j) par le joueur
player. Renvoie
Nonesi le coup est invalide. - Créez une fonction finished(morpion, i, j) renvoyant −1 si la partie n'est pas terminée, 1 si le joueur 1 a gagné, 2 si le joueur 2 a gagné.
- Créez une fonction play_morpion() qui demande la taille de la grille et propose une partie complète à deux joueurs.
Corrigés
import numpy as np
import matplotlib.pyplot as plt
from math import sin, cos
def Trajectoir(theta, n):
A = [(1, 0)]
for i in range(1, n + 1):
A.append((cos(i * theta), sin(i * theta)))
return A
t = np.linspace(0, 2 * np.pi, 40)
x = np.cos(t)
y = np.sin(t)
plt.plot(x, y, 'r')
plt.axis('equal')
plt.xlim(-2, 2)
plt.title('Cercle de centre 0 et de rayon 1')
plt.xticks([-1, 0, 1])
plt.yticks([-1, 0, 1])
plt.axhline()
plt.axvline()
A = Trajectoir(2 * np.pi / 2.9, 30)
Lx = [a[0] for a in A]
Ly = [a[1] for a in A]
plt.plot(Lx, Ly, 'b')
plt.show()
from math import *
import numpy as np
def f(a, b):
return -b + 4 * cos(2 * a + np.pi / 4) * exp(-a)
def Euler(t0, tn, y0, n):
x = [(t0, y0)]
h = (tn - t0) / (n + 1)
t = t0
for i in range(1, n + 1):
y0 = y0 + h * f(t, y0)
t = t0 + i * h
x.append((t, y0))
return x
def RK2(t0, tn, y0, n):
x = [(t0, y0)]
h = (tn - t0) / (n + 1)
t = t0
for i in range(1, n + 1):
k1 = h * f(t, y0)
k2 = h * f(t + h / 2, y0 + k1 / 2)
y0 = y0 + k2
t = t0 + i * h
x.append((t, y0))
return x
def RK4(t0, tn, y0, n):
x = [(t0, y0)]
h = (tn - t0) / (n + 1)
t = t0
for i in range(1, n + 1):
k1 = h * f(t, y0)
k2 = h * f(t + h / 2, y0 + k1 / 2)
k3 = h * f(t + h / 2, y0 + k2 / 2)
k4 = h * f(t + h / 2, y0 + k3)
y0 = y0 + (k1 + 2 * k2 + 2 * k3 + k4) / 6
t = t0 + i * h
x.append((t, y0))
return x
def f2(a):
return 2 * sin(2 * a + np.pi / 3) * exp(-a) - exp(-a) * sqrt(3)
def compare(p):
return sum((p[i][1] - f2(p[i][0])) ** 2 for i in range(len(p)))
t0 = float(input("t0= "))
y0 = float(input("y0= "))
n = int(input("n= "))
tn = float(input("tn= "))
print("Euler :", compare(Euler(t0, tn, y0, n)))
print("RK2 :", compare(RK2(t0, tn, y0, n)))
print("RK4 :", compare(RK4(t0, tn, y0, n)))
import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as spo
import scipy.integrate as spi
# Partie A
def f(x):
return np.sin(4 * x) * np.exp(-x * x + 3 * x)
lx = np.linspace(0, 5, int((5 - 0) / 0.01) + 1)
ly = f(lx)
plt.plot(lx, ly)
plt.title('f(x) = sin(4x)·exp(−x²+3x)')
plt.show()
with open('data.txt', 'w') as f1:
for i in range(len(lx)):
f1.write(str(lx[i]) + '\t' + str(ly[i]) + '\n')
# Partie B
Lx, Ly = [], []
with open('data.txt', 'r') as f2:
for i, line in enumerate(f2):
if i % 10 == 0:
a, b = line.split('\t')
Lx.append(float(a))
Ly.append(float(b.strip()))
for i in range(len(Lx)):
print("{:.4f} | {:.4f}".format(Lx[i], Ly[i]))
plt.plot(lx, ly, 'r-')
plt.plot(Lx, Ly, 'bo')
plt.show()
# Partie C
def dichotomie(f, a, b, eps):
while True:
c = (a + b) / 2
if abs(f(c)) < eps:
break
if f(a) * f(c) < 0:
b = c
else:
a = c
return c
alpha = dichotomie(f, 0, 1, 1e-5)
print("Dichotomie :", alpha)
print("Bisect :", spo.bisect(f, 0.01, 1))
print("Newton :", spo.newton(f, 1))
print("Fsolve :", spo.fsolve(f, 1))
print("Root :", spo.root(f, 1).x)
# Partie D
plt.plot(lx, ly)
plt.xlim(0, alpha)
plt.title('f sur [0, α]')
plt.show()
def trapeze(x, y):
return sum((x[1:] - x[:-1]) * (y[1:] + y[:-1]) / 2)
print("Trapèze :", trapeze(lx, ly))
print("trapz :", spi.trapz(ly, dx=0.01))
print("simps :", spi.simps(ly, dx=0.01))
import matplotlib.pyplot as plt
import numpy as np
def f(x, a):
return a * x * (1 - x)
a = float(input("a= "))
u0 = float(input("u0= "))
imax = int(input("imax= "))
x = np.linspace(0, 1, imax)
y = f(x, a)
plt.plot(x, y)
plt.plot(x, x)
lx = [u0]
ly = [0]
for i in range(imax):
u = lx[-1]
lx.append(u)
u = f(u, a)
lx.append(u)
ly.append(u)
ly.append(u)
plt.plot(lx, ly)
plt.title(f'Suite récurrente — a={a}, u0={u0}')
plt.show()
import matplotlib.pyplot as plt
LX, LY = [], []
with open('donnees.txt', 'r') as f:
for line in f:
t = line.split(';')
LX.append(float(t[0]))
LY.append(float(t[1]))
plt.plot(LX, LY)
plt.title('Points du fichier donnees.txt')
plt.show()
def trapeze(y, x):
s = 0
for i in range(1, len(x)):
s += (x[i] - x[i - 1]) * (y[i] + y[i - 1]) * 0.5
return s
I = trapeze(LY, LX)
print("Intégrale (trapèzes) :", I)
import numpy as np
def create_morpion(n):
return np.zeros((n, n), dtype=int)
def next_turn(morpion, player, i, j):
if 0 <= i < morpion.shape[0] and 0 <= j < morpion.shape[1] and morpion[i][j] == 0:
morpion = morpion.copy()
morpion[i][j] = player
return morpion
return None
def finished(morpion, i, j):
n = morpion.shape[0]
v = morpion[i][j]
if v == 0:
return -1
if all(morpion[k][j] == v for k in range(n)):
return v
if all(morpion[i][k] == v for k in range(n)):
return v
if i == j and all(morpion[k][k] == v for k in range(n)):
return v
if i + j == n - 1 and all(morpion[k][n - 1 - k] == v for k in range(n)):
return v
if all(morpion[k][l] != 0 for k in range(n) for l in range(n)):
return 0
return -1
def play_morpion():
n = int(input("Taille de la grille : "))
morpion = create_morpion(n)
p = 1
while True:
print(morpion)
i, j = map(int, input(f"Joueur {p} — ligne colonne : ").split())
result = next_turn(morpion, p, i, j)
if result is None:
print("Coup invalide, recommencez.")
continue
morpion = result
r = finished(morpion, i, j)
if r != -1:
print(morpion)
print(f"Joueur {r} gagne !" if r > 0 else "Match nul !")
return r
p = 2 if p == 1 else 1
print(play_morpion())