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.
Python
>>> 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().

Python
>>> 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.
Python
>>> 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.

Python
>>> 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.

Python
>>> 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.

Python
>>> 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).
Python
>>> 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.
Python
>>> 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.
Python
>>> 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).
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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() et resize() 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.
Python
>>> 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.

Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.
Python
>>> 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.

Python
>>> 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.

Python
>>> 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].
Python
>>> 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).
Python
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[.
Python
>>> 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]])
Python
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

Python
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

Python
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

Python
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 :

Python
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.

Python
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

Python
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

Principe de la 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 :

Python
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

Principe de 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 :

Python
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 :

Fonctions de scipy.optimize
  • 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.
Python
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 }

Python
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 :

Python
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 :

Python
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 :

Python
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 :

Python
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.

Python
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.
  • Sous Python :
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

Python
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

Python
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 :

Python
>>> import scipy.integrate as integr

La méthode quad(f, a, b) pour intégrer f sur l'intervalle [a; b] :

Python
>>> 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 :
Python
>>> 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 :
Python
>>> 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 :

y′(x) = f(y(x), x), x ∈ [a, b]

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 :

odeint(f, y0, t)  résout  y′(t) = f(y(t), t), avec y(a) = y0 et t ∈ [a, b]

Exemple : Résolution de y′(t) = y(t)² − y(t)·sin(t) + cos(t), avec y(0) = 0 :

Python
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 :

Python
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

Exercice 1 — Billard circulaire → Voir le corrigé

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 (e). Puis qui rebondit au point A2 d'affixe (ei2θ) ainsi de suite… Le point A1 a pour coordonnées [cos(θ), sin(θ)].

Travail demandé :

  1. É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θ)).
  2. Tracer sur le même graphique :
    1. le cercle trigonométrique x² + y² = 1,
    2. les segments de droites à partir de la liste des points formée par l'appel à la fonction TRAJECTOIR(2π/2.9, 30).
Exercice 2 — Méthodes numériques pour EDO → Voir le corrigé

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é :

  1. Écrire une fonction Python f(a, b) qui calcule et retourne :
    f(a, b) = −b + 4·cos(2a + π/4)·e−a
  2. É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ᵢ et P[i][1] = y(tᵢ).
  3. Écrire une fonction Python RK2(t0, tn, y0, n) résolvant l'EDO par la méthode de Runge-Kutta 2.
  4. Écrire une fonction Python RK4(t0, tn, y0, n) résolvant l'EDO par la méthode de Runge-Kutta 4.
  5. É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))²
Exercice 3 — f(x) = sin(4x)·exp(−x² + 3x) → Voir le corrigé

Soit f définie sur [0 ; 5] par f(x) = sin(4x)·exp(−x² + 3x).

Partie A

  1. Écrire en Python la définition de la fonction f.
  2. 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).
  3. Tracer la courbe représentative de la fonction f.
  4. Écrire dans un fichier data.txt les coordonnées x et y séparées par une tabulation ("\t"), une paire par ligne.

Partie B — Échantillonnage

  1. Ouvrir data.txt et construire les listes Lx et Ly en prenant une ligne sur dix (lignes 0, 10, 20, …).
  2. Afficher sur deux colonnes « abscisses » et « ordonnées », arrondis à 10−4 près.
  3. Sur une même figure, tracer la courbe de f et placer les points de Lx, Ly.

Partie C — Dichotomie et zéros

  1. Définir une fonction dichotomie(f, a, b, eps) résolvant f(x) = 0 sur [a, b] à ε près.
  2. Utiliser cette fonction pour déterminer la solution strictement positive α de f(x) = 0 sur [0 ; 1] à 10−5 près.
  3. Comparer avec les fonctions bisect, newton, fsolve et root de scipy.optimize.

Partie D — Intégrale de f sur [0, α]

  1. Tracer la courbe de f sur [0, α].
  2. É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
  3. Comparer avec scipy.integrate.trapz et simps.
Exercice 4 — Suite récurrente f(x) = ax(1 − x) → Voir le corrigé

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[.

  1. Le programme demande à l'utilisateur les valeurs de a, u0 et le nombre maximal d'itérations imax.
  2. Écrire la définition de la fonction f(x) renvoyant ax(1 − x).
  3. Tracer sur une même figure la courbe de f sur [0, 1] et la bissectrice y = x.
  4. 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).
Exercice 5 — Intégration à partir d'un fichier → Voir le corrigé

On dispose d'un fichier donnees.txt contenant vingt lignes du type :

Données
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.

  1. Lire le fichier et construire la liste LX des abscisses et LY des ordonnées.
  2. Représenter les points sur une figure.
  3. Écrire une fonction trapeze(y, x) renvoyant :
    Σi=1n−1 (xᵢ − xᵢ₋₁) · (yᵢ + yᵢ₋₁) / 2
  4. Retrouver la valeur approchée de I avec scipy.integrate.trapz.
Exercice 6 — Jeu du Morpion → Voir le corrigé

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).

  1. Créez une fonction create_morpion(n) qui crée et renvoie une grille n × n initialisée à 0.
  2. 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 None si le coup est invalide.
  3. 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é.
  4. Créez une fonction play_morpion() qui demande la taille de la grille et propose une partie complète à deux joueurs.

Corrigés

Corrigé Exercice 1 ← Retour à l'exercice
Python
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()
Corrigé Exercice 2 ← Retour à l'exercice
Python
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)))
Corrigé Exercice 3 ← Retour à l'exercice
Python
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))
Corrigé Exercice 4 ← Retour à l'exercice
Python
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()
Corrigé Exercice 5 ← Retour à l'exercice
Python
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)
Corrigé Exercice 6 ← Retour à l'exercice
Python
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())