Python pour le calcul scientifique/Manipulation de matrices

Un livre de Wikilivres.
Aller à la navigation Aller à la recherche

Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons :

  1. L'algèbre linéaire est au cœur de nombreux calculs.
  2. Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable.

Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début :

import numpy as np
import matplotlib.pyplot as plt

Rappels et complément sur le tranchage[modifier | modifier le wikicode]

Soit une liste L composée de n éléments.

  • Les éléments sont numérotés de 0 à n – 1.
  • Le premier élément s'obtient par L[0].
  • Le i-ème élément s'obtient par L[i - 1].
  • L'avant-dernier élément s'obtient par L[-2].
  • Le dernier élément s'obtient par L[-1].
  • La sous-liste composée des éléments contigus i à j s'obtient par L[i - 1:j].

Ainsi, L[0:1] va extraire le premier élément, L[1:-1] va extraire tous les éléments sauf le premier et le dernier.

NumPy fournit des fonctions permettant de manipuler les matrices :

  • np.append(A, B) : fusionne les vecteurs A et B ; s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;
  • np.append(A, B, axis = i) : fusionne les tenseurs selon l'indice i (0 pour le premier indice, 1 pour le deuxième…) ;
  • np.insert(A, i, m) : insère le vecteur m dans le vecteur A (ou la matrice A aplatie) à l'emplacement i ;
  • np.insert(A, i, M, axis = j) : insère le tenseur M dans le tenseur A à l'emplacement i de l'indice j ;
  • np.delete(A, I) : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ;
  • np.delete(A, I, axis = j) : efface les éléments définis par le tranchage I selon l'indice j du tenseur A.

Définir un tenseur[modifier | modifier le wikicode]

Un tenseur est similaire à une liste mais il est défini par la fonction np.array(). La définition et l'extraction de composante utilise la méthode du découpage en tranches (slicing).

Exemples

a = np.array([1, 3, 5, 7]) # vecteur
bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne)
bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne)
c = np.array([[1, 2, 3],
              [2, 3, 4],
              [3, 4, 5]]) # matrice 3 × 3
d = np.array([[[1,  2, 3],
               [2,  3, 4]],
              [[10, 9, 8],
               [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2

Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension n est un tenseur d'ordre 1 et de dimension n ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × n ou n × 1.

a = np.array([1, 2, 3])
b = np.array([[1, 2, 3]])
c = np.array([[1], [2], [3]])
print(a.size, b.size, c.size) # 3 3 3
print(a.ndim, b.ndim, c.ndim) # 1 2 2
print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1)

La fonction np.arange() est similaire à la fonction range() pour les liste ; elle génère un vecteur de réels. La fonction np.linspace() permet également de créer un vecteur de même type mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à np.arange() est le premier nombre qui ne figure pas dans le vecteur.). La fonction np.zeros() génère une matrice nulle, np.zeros_like() une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, np.ones() et np.ones_like() crée des matrice dont toutes les composantes sont à 1. La fonction np.eye() crée une matrice unité.

Exemples

e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9]
f = np.linspace(0, 2, 5) # 5 nombre entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2]
g = np.zeros(3) # vecteur nul de dimension 3
h = np.zeros((3, 3)) # matrice nulle 3 × 3
k = np.ones_like(a) # matrice de 1 de même dimension que a 
u = np.eye(3) # matrice unité 3 × 3

Le paramètre dtype permet de forcer le type. Par exemple

a = np.array([1, 2, 3], dtype="complex")
k = np.ones_like(a, dtype="int")

La méthode .reshape() remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 :

a = np.arange(1, 10)
b = a.reshape(3, 3)
# ou bien directement
c = np.arange(1, 10).reshape(3, 3)

La méthode .fill() remplit la matrice avec un scalaire :

b.fill(5) # remplace les valeurs de b par la valeur 5

Opérations matricielles[modifier | modifier le wikicode]

Les quatre opérations classiques +, -, * et / ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément (elementwise operations) :

  • (A + B)[i, j, k] vaut A[i, j, k] + B[i, j, k] ;
  • (A - B)[i, j, k] vaut A[i, j, k] - B[i, j, k] ;
  • (A * B)[i, j, k] vaut A[i, j, k] * B[i, j, k] ;
  • (A / B)[i, j, k] vaut A[i, j, k] / B[i, j, k].

De même, les fonctions np.add(), np.subtract(), np.multiply() et np.divide() effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions :

  • np.add(A, B)[i, j, k] vaut A[i, j, k] + B[i, j, k] ;
  • np.subtract(A, B)[i, j, k] vaut A[i, j, k] - B[i, j, k] ;
  • np.multiply(A, B)[i, j, k] vaut A[i, j, k] * B[i, j, k] ;
  • np.divide(A, B)[i, j, k] vaut A[i, j, k] / B[i, j, k].

La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions np.dot() ou np.matmul() :

  • pour le produit par un scalaire, les fonctions np.multiply() et l'opérateur * sont plus performants ;
  • la fonction np.dot() est plus performante pour le produit scalaire de deux vecteurs réels ;
  • lorsque l'on a des vecteurs complexes, la fonction np.vdot() fait le produit par le conjugué du premier membre (np.vdot(a, b) == np.dot(a.conj(), b)) ;
  • la fonction np.matmul() et l'opérateur @ (A @ B) sont plus performants pour un produit matriciel.

Calcul vectorisé[modifier | modifier le wikicode]

Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple :

x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π
y = np.sin(x)
plt.plot(x, y)

La variable x est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus np.sin().

Outre le tranchage (slicing), on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice :

  • utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ;
  • utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux True, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple :
a = np.arange(0, 10)
b = np.array([1, 3, 5, 7, 9])
c = np.array([True, True, False, False, True, False, True, False, False, True])
print(a[b], "\n", a[c])

Exercice

Écrire un programme Python mettant en œuvre le crible d'Érathostène pour trouver les nombres premiers inférieurs à une valeur donnée.

Attributs[modifier | modifier le wikicode]

La classe ndarray, qui définit les matrices, possède un certain nombre d'attributs :

  • .shape : dimensions de la matrice ;
  • .ndim : ordre du tenseur ;
  • .size : nombre d'éléments ;
  • .dtype : type des éléments.
a = np.linspace(1, 9, 9)
print("a", a, "\n",
    " ; shape :", a.shape,
    " ; dim : ", a.ndim,
    " ; size : ", a.size,
    " ; dtype : ", a.dtype, "\n")
  • .real, .imag : parties réelle et imaginaire de la matrice ;
  • .flat : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ;
  • .T : transposée.
a = np.arange(0, 9).reshape(3, 3)
print(a)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]] 
print(a.T)
# [[0 3 6]
#  [1 4 7]
#  [2 5 8]] 
print(a.flat[:])
# array([0, 1, 2, 3, 4, 5, 6, 7, 8])
Ressources
Section « Attribute », « numpy.ndarray », sur Numpy and Scipy Documentation (consulté le 16 mars 2019)

Fonctions et méthodes de base[modifier | modifier le wikicode]

La classe ndarray possède un certain nombre de méthodes :

  • .min() et max() : valeurs respectivement minimale et maximale ;
  • .ptp() : amplitude « max – min » (peak to peak) ;
  • .argmin() et argmax() : indice où se trouvent les valeurs respectivement minimale et maximale ;
  • .sum(), prod() : somme et produit de tous les éléments de la matrice ;
  • .cumsum(), cumprod() : somme et produit cumulés.
a = np.linspace(1, 9, 9)
print("min : ", a.min(),
    "; max : ", a.max(), "\n",
    "sum : ", a.sum(),
    "; cumsum : ", a.cumsum(), "\n",
    "prod : ", a.prod(),
    "; cumprod : ", a.cumprod(), "\n")

Méthodes statistiques :

  • .mean() : moyenne ;
  • .std() : écart type (standard deviation).

Extraction de données :

  • .diagonal() : vecteur contenant les éléments de la diagonale ;
  • .flatten() : vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut .flat, on peut choisir le sens de linéarisation (par lignes, .flatten(C), ou par colonnes, .flatten(F)) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ;
  • .tofile() : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice a et pour séparer les valeurs par un point-virgule :
a.tofile("matriceA.txt", sep=" ; ")

Algèbre linéaire :

  • a.dot(b) : produit matriciel ab ; on peut aussi écrire a@b ;
  • .trace() : trace de la matrice (somme des éléménts diagonaux) ;
  • .transpose() : transpose la matrice, résultat similaire à l'attribut .T ;
  • np.cross() : produit vectoriel dans ℝ³.

Matrices de booléens :

  • .all() : applique un « et » logique à toutes les valeurs de la matrice ;
  • .any() : applique un « ou » logique à toutes les valeurs de la matrice.

Autre méthodes :

  • .conj() : conjugué des valeurs complexes ;
  • .nonzero() : n-uplet contenant les indices des valeurs non-nulles ;
  • .round(n) : arrondit les valeurs à la n-ième décimale.
Ressources
Section « Method », « numpy.ndarray », sur Numpy and Scipy Documentation (consulté le 16 mars 2019)

Propagation[modifier | modifier le wikicode]

Le terme « propagation » (broadcasting) désigne la manière dont Python complète les matrice lorsque des dimensions manquent.

Supposons que l'on veuille additionner deux matrices M1 et M2 de dimensions m1 × n1 et m2 × n2 différentes. Alors :

  • le résultat a pour dimension max(m1, m2) × max(n1, n2) ;
  • si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ;
  • sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1.

Par exemple :

La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions
A = np.array([[1, 2], [3, 4]])
print(5 + A)
# [[6 7]
#  [7 8]]
print(np.array([[5, 4]]) + A)
# [[6 6]
#  [6 8]]
print(np.array([[5], [4]]) + A)
# [[6 7]
#  [7 8]]

Fonctions « universelles »[modifier | modifier le wikicode]

Les fonctions universelles (ufunc) sont les fonctions s'appliquant aux matrices, des fonctions vectorisées.

Ressource
« Available ufuncs », sur SciPy documentation (consulté le 21 mars 2019)

Algèbre linéaire[modifier | modifier le wikicode]

Pour plus de détails voir : Python pour le calcul scientifique/Algèbre linéaire.

Notes et références[modifier | modifier le wikicode]


Graphiques < > Polynômes