Introduction à la Physique Numérique
Introduction à la Physique Numérique
INTRODUCTION
À LA
PHYSIQUE NUMÉRIQUE
Notes de cours
par
Automne 2022
ii
iii
MINI-PRÉFACE
Ces notes contiennent l’essentiel de la matière couverte dans le cours PHY-1234, “Introduction à
la Physique Numérique”. Certains éléments plus techniques portant spécifiquement sur le langage de
programmation utilisé et sur les particularités des phénomènes à modéliser en laboratoire seront présentés
dans des notes de laboratoire séparées. Divers ouvrages de références sont proposés à travers le document de
notes, en particulier au début du chapitre 2 pour ce qui est des outils de référence pour la programmation.
Ce document de notes – à l’exception des chapitres 2 et 3 – et le contenu du cours dans sa forme
actuelle ont été développés par Paul Charbonneau, qui a enseigné le cours de 2008 à 2013. Je dois donc
dire un gros merci à Paul pour m’avoir généreusement transmis le produit de son excellent travail !
En prenant la charge du cours en 2014, j’ai opté pour un nouveau langage de programmation : Python.
Effectivement le cours était enseigné avec le langage C auparavant, mais je crois que le langage Python est
plus approprié pour les fins du cours et surtout, qu’il vous sera plus utile de maitriser ce langage pour la
suite de vos études de baccalauréat, pour vos études supérieures, et possiblement pour votre futur emploi.
Les principales motivations pour ce choix de langage sont données à la section 1.7. Donc, le principal
changement par rapport au document de notes préparé par Paul est que j’ai adapté le tout à ce nouveau
langage de programmation : ceci comprend une bonne révision de quelques sections du chapitre 1, et la
conversion des différents exemples de codes montrés dans les notes. De plus, en 2015, j’ai rédigé deux
nouveaux chapitres (chap. 2 et 3) pour introduire les principaux éléments de programmation Python que
vous aurez besoin pour le cours ; certaines parties de ces deux chapitres sont inspirées d’éléments figurant
dans d’anciennes notes pour ce cours préparées par Normand Mousseau et Suzanne Talon et dans certains
ouvrages de références mentionnés au chapitre 2. J’ai révisé ces deux chapitres en 2016 et encore cette
année, en y ajoutant certains éléments. Au-delà de ces changements, je n’ai fait que des modifications que
l’on pourrait qualifier de mineures par rapport aux notes préparées par Paul.
Enfin, vous pouvez contribuer à améliorer la qualité de l’ensemble ces notes et la qualité du cours
en me faisant part de tous commentaires, suggestions, critiques (en particulier constructives) : passages
obscurs, diagrammes portant à confusion, manque de détails ici, détails superflus là, fautes de frappw ou
d’autographe, etc. Merci d’avance !
–David Lafrenière
7 août 2017
iv
Table des matières
1 La physique numérique 1
1.1 La modélisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Les algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 La programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Représentations numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 L’analyse numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.6 La physique numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.7 Pourquoi le Python ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Python : la base 9
2.1 Notions préliminaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2 Modes d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3 Identificateurs (noms d’objets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4 Assignation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 Types de variables et d’objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5.1 Entier (int) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5.2 Réel (float) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5.3 Complexe (complex) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5.4 Booléen (bool) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5.5 Chaîne de caractères (str) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5.6 Liste (list) et Tuple (tuple) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5.7 Transformation de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5.8 Autres types par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6 Opérateurs de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6.1 Opérateurs arithmétiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6.2 Opérateurs logiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6.3 Opérateurs relationnels (de comparaison) . . . . . . . . . . . . . . . . . . . . . . . 19
2.6.4 Opérateurs de chaînes de caractères, de listes et de tuples . . . . . . . . . . . . . . 20
2.7 Entrées et sorties standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.8 Le contrôle du flux d’instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.8.1 L’instruction conditionnelle if ... . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.8.2 Boucles conditionnelles : l’instruction while . . . . . . . . . . . . . . . . . . . . . . 23
2.8.3 Boucles inconditionnelles : l’instruction for . . . . . . . . . . . . . . . . . . . . . . 23
2.8.4 Les commandes break et continue . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.9 Fonctions et méthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.9.1 Librairie de fonctions standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.9.2 Définition de fonctions originales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.9.3 Modules et importations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.9.4 Espace de noms et résolution des identificateurs . . . . . . . . . . . . . . . . . . . . 29
2.10 Écriture d’un code source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
v
vi TABLE DES MATIÈRES
5 Monte Carlo 75
5.1 Nombres aléatoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2 Fonctions de distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.3 Distributions non-uniformes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.3.1 Distribution exponentielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.3.2 Distributions gaussiennes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.4 Évaluation d’intégrales par Monte Carlo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.5 L’approche à l’équilibre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.5.1 Formulation en équation différentielle . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.5.2 Formulation Monte Carlo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.6 Réalisme physique et irréversibilité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.6.1 Comment choisir les alternatives en modélisation ? . . . . . . . . . . . . . . . . . . 91
5.6.2 L’irréversibilité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
TABLE DES MATIÈRES vii
6 Racines et optimisation 95
6.1 La diffraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.2 La bissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.3 La méthode de Newton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4 Maximisation (et minimisation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
6.5 Méthodes de grimpe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.6 La grimpe stochastique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10 Complexité 173
10.1 La complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
10.2 Le modèle Tas-De-Sable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
10.3 La criticalité auto-régulée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
10.4 Le modèle Feu-de-Forêt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
10.5 Retour sur l’invariance d’échelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
10.6 Le modèle embouteillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
10.7 Complexité ̸= Stochasticité ̸= chaos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
10.8 Complexité et émergence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
viii TABLE DES MATIÈRES
1
Chapitre 1
La physique numérique
1.1 La modélisation
Nous devons tout d’abord faire la distinction entre une théorie et un modèle. Un exemple devrait
suffire. La très célèbre deuxième loi de Newton,
F = ma , (1.1)
nous informe qu’une force agissant sur un mobile produit une accélération dans la direction de la force
appliquée, et de grandeur inversement proportionnelle à la masse m du mobile en question. Ceci exprime
une théorie (dynamique) du mouvement accéléré.
Considérons maintenant l’application de cette théorie à la chute libre d’un caillou vers le sol. Travaillant
en coordonnées cartésiennes avec z pointant vers le haut, la force gravitationelle s’écrit,
F = −mgêz , (1.2)
où êz est un vecteur unitaire pointant dans la direction de l’axe-z, m est la masse du caillou, et g = 9.8 m
s−2 . On en déduit que a ≡ a â = g (−êz ), et la position d’un corps relaché d’une hauteur z0 est donnée
par la petite équation qu’on vous a fait mémoriser au secondaire :
a
z(t) = z0 + v0 t + t2 , (1.3)
2
où v0 est la vitesse initiale du corps, nulle ici pour la situation considérée. Les équations (1.2)—(1.3)
définissent un modèle de la chute d’un corps. C’est un modèle parce qu’à partir de l’éq. (1.1) nous
avons fait certains choix arbitraires (e.g., le choix des coordonnées cartésiennes), et surtout plusieurs
approximations :
1. La gravité pointe exactement vers le bas ;
2. La gravité est supposée constante, à une valeur g = 9.8 m s−2 ;
3. La seule force agissant sur le corps est la gravité.
La première approximation s’avère excellente, et ce même si vous êtes aux environs du Mont Everest
ou collé sur le plus sphérique de vos mononcles ; la seconde ne tient que si le corps est relâchée d’une
hauteur beaucoup plus petite que le rayon terrestre ; et la troisième devient rapidement mauvaise dès que
le corps atteint une vitesse de quelques dizaines de mètres par secondes (ce qui ne prendra que quelques
secondes !), après quoi la résistance de l’air représente une force de grandeur comparable à la gravité. La
prise en considération de cette force demanderait, au minimum, la spécification de la forme du corps, et
une connaissance de la densité et coefficient de viscosité de l’air.
Un modèle est donc une représentation approximative de la réalité, basée sur des théories qui se veulent
exactes (jusqu’à preuve du contraire). Théories et modèles n’ont pas nécessairement besoin d’être expri-
més mathématiquement, mais depuis Galilée, en physique les deux habituellement le sont. Les équations
mathématiques décrivant le modèle peuvent être solutionnées analytiquement, comme pour le mouvement
rectiligne uniformément accéléré considéré ci-dessus, mais pour un modèle le moindrement complexe ces
équations doivent le plus souvent être solutionnées numériquement sur ordinateur.
1.3 La programmation
La programmation consiste à exprimer un algorithme sous une forme déchiffrable par l’ordinateur.
C’est habituellement un processus en trois étapes : l’algorithme est d’abord écrit en un langage de
programmation sujet à des règles syntaxiques spécifiques ; le code source ainsi produit est ensuite
compilé ou interprété, selon le langage de programmation utilisé, c’est-à-dire traduit en instructions
spécifiques à l’architecture du processeur de calcul de l’ordinateur ; et les instructions ainsi produites sont
finalement exécutées. Le petit bout de code source suivant correspond à une implémentation en langage
Python de notre algorithme de calcul de π :
9 for i in range(0,npt):
10 x=random() # un nombre aléatoire entre 0 et 1
11 y=random() # et un autre
12 if (x**2 + y**2) <= 1:
13 somme+=1
14
15 estimePi=4*somme/npt
16 print("valeur estimée de pi = {0}".format(estimePi))
Le code source est habituellement “lisible” par un humain familier avec le langage de programmation,
et même si vous ne connaissez encore rien à Python vous devriez être capable d’y reconnaitre les diffé-
rent éléments de notre algorithme. Remarquez l’indentation du bloc d’instructions devant êtres répétées
(lignes 10–13 du code, étapes 2 et 3 dans l’algorithme ci-dessus), et à l’intérieur de ce bloc, l’indentation
additionnelle d’une ligne indiquant l’instruction sujette à une exécution conditionnelle (ligne 13, étape 3
de l’algorithme). De telles indentations sont essentielles en Python, ceci fait partie des règles syntaxiques
de ce langage de programmation ; nous y reviendrons aux deux prochains chapitres et en labo. Notez
également que la fonction random est une fonction prédéfinie que l’on doit importer à partir d’un module
nommé random (en général les fonctions ne portent pas le même nom que le module dans lequel elles
sont définies, mais cela peut arriver comme ici). La fonction random produit un nombre réel aléatoire
dans l’intervalle semi-fermé [0, 1), tel que requis pour notre algorithme. Il existe toute une batterie de
ces fonctions prédéfinies à travers plusieurs modules ; nous reviendrons également sur l’importation de
fonctions et de modules aux deux prochains chapitres et en labo.
Sur mon ordinateur portable MAC, l’exécution du code source ci-haut produit, après environ 0.6
seconde, la sortie suivante :
valeur estimee de pi = 3.142452
ce qui approxime bien π (= 3.1415926536...) à 0.04% près. Comme vous pouvez certainement l’imaginer,
un algorithme donné peut s’exprimer de différentes manières même si écrit dans le même langage de
programmation. C’est comme au primaire, quand on vous demandait une production écrite de 10 lignes
sur Le Brontosaure ; votre texte pouvait bien dire essentiellement la même chose que celui d’un(e) de
vos petits(es) camarades, mais son style était assurément différent, et certains styles se lisent mieux que
d’autres. Il y a également des styles en programmation, qui souvent visent un compromis entre la lisibilité
du code source, la modularité, et la vitesse d’exécution du code une fois compilé. Par exemple, le code
suivant produit exactement la même sortie que le précédent en seulement ∼0.036 seconde, mais est un
peu plus opaque pour un(e) débutant(e) :
Code Python pour un calcul de PI par Monte Carlo
#Ce programme calcule un estimé de la valeur de pi par Monte Carlo
import numpy as np
npt=1000000
estimePi=4*[Link]((([Link](npt)**2+[Link](npt)**2) <= 1))
print("valeur estimée de pi = {0}".format(estimePi))
Personnellement, c’est plutôt comme ça que j’aurais tendance à coder l’algorithme (quoique j’y ajou-
terais certainement quelques explications en commentaires). Comme en littérature ou en musique, le goût
personnel y est également pour quelque chose. Nous aurons l’occasion de revenir sur les questions stylis-
tiques au fil des chapitres qui suivent. Au niveau du “ligne-par-ligne”, le calcul scientifique revient souvent
à encoder, utilisant les règles syntaxiques du langage utilisé, des équations mathématiques exprimant des
lois physiques 1 . Tous les opérateurs arithmétiques de base y trouvent leur pendant, parfois direct, comme
les symboles “+” et “−” pour les additions et soustractions, parfois via un symbole différent, comme, en
Python, l’utilisation du “∗” pour la multiplication, le “/” pour la division, et le “**" pour l’exponentiation.
0 000000000000000000000000000000
1 000000000000000000000000000001
2 000000000000000000000000000010
3 000000000000000000000000000011
4 000000000000000000000000000100
5 000000000000000000000000000101
. .
68 000000000000000000000001000100
. .
143 000000000000000000000010001111
. .
. .
La représentation numérique des entiers est donc exacte, cependant selon l’architecture du processeur
de calcul de l’ordinateur et/ou le langage de programmation utilisé, la longueur de la chaîne de bits
pouvant définir un entier peut être limitée, ce qui pose une limite supérieure à sa grandeur possible. Par
exemple, pour une limite à 32 bits, l’entier le plus grand est 231 − 1 (= 2147483647), pour 64 bits c’est
263 − 1, etc. Avec Python, la taille des entiers n’est pas limitée.
Les nombres réels sont également encodés en utilisant une chaîne de bits, mais les bits en questions ne
sont plus une simple expression en base-2 comme ci-haut. Un nombre réel (“float”, pour “floating-point
number”, en anglais) est d’abord généralement (mais pas toujours) exprimé comme
où S est un bit encodant le signe +/−, 0 ≤ M < 1 est la mantisse (ou la significande), b est un entier
représentant la base, E est l’exposant encodé comme un entier, et e est un entier de décalage de l’exposant
ayant une valeur pré-établie pour une architecture donnée (en général e ≡ 127 en mode 32-bits et e ≡ 1023
en 64-bits, ce qui est le défaut en Python pour une variable de type “float”). Notez que la quasi-totalité
des architectures utilisent b ≡ 2, et, comme e, ceci est une constante pré-établie et donc qui n’a pas besoin
d’être incluse explicitement dans l’encodage d’un nombre réel. La longueur de la chaine de bits définissant
la mantisse M est en général restreinte à 23 en mode 32-bits et à 52 en mode 64-bits. La mantisse est
habituellement encodée en fraction binaire, c’est-à-dire que le n-ième bit de la mantisse représente la valeur
2−n . Chacune des chaines de bits représentant S, E et M sont concaténées en une seule longue chaine
de bits, S:E:M , qui est alors la représentation interne du nombre réel. Par exemple, en mode 32-bits la
1. Le tout premier langage de programmation scientifique avait d’ailleurs été baptisé FORTRAN, pour “FORmula
TRANslation.
• Python favorise une bonne lisibilité du code, et sa syntaxe, simple, permet d’exprimer des concepts
en moins de lignes de code qu’il ne le serait possible dans d’autres langages tels que le C.
• Python jouit d’une très grande bibliothèque de modules fournissant des outils pour accomplir effica-
cement des tâches dans divers contextes (calculs scientifiques/statistiques, analyse de données, pro-
duction de graphiques/animations, création d’interfaces graphiques, production de contenu HTML,
traitement vidéo/audio, etc.).
• Python peut être utilisé en mode interactif.
• Python supporte autant la programmation structurée que la programmation orientée objet.
• Python est très facile à combiner avec d’autres langages de programmation, comme Fortran, C,
C++, Java, etc.
Pour plus d’information sur le langage Python, vous pouvez consulter le site web [Link], ou
encore son entrée Wikipédia. Notez que même si nous utilisons Python pour les fins de ce cours,
vous apprendrez d’abord et avant tout la logique de la programmation informatique ainsi
que les principaux éléments et concepts propres à la programmation dans tout langage. Vous
serez donc en mesure d’apprendre plus rapidement plusieurs autres langages de programmation lorsque le
besoin se fera sentir. Ceci étant dit, je vous garantis que vous adorerez Python et que vous le réutiliserez
tout au long de votre baccalauréat, et même après.
Un dernier commentaire sur le choix du langage, ou plus précisément sur la version de Python que
nous utiliserons. La 3e version de Python, Python 3, a été introduite en 2008 et représente un changement
majeur par rapport à la version précédente, Python 2. Notamment, certaines fonctions de la librairie de
base et d’autres aspects du langage ont été modifiés de sorte que Python 3 n’est pas rétrocompatible
avec Python 2, c’est-à-dire qu’un script (ou code source) composé selon les normes et fonctions de Python
2 ne pourra être compris et exécuté par l’interprèteur de Python 3, et vice-versa. Plusieurs personnes
ayant construit au fil du temps une banque de scripts personnels continuent donc encore aujourd’hui
à travailler et à développer de nouveaux programmes avec Python 2 plutôt que Python 3. Cependant,
pour quelqu’un qui se lance dans la programmation en Python, sans expérience préalable, l’utilisation de
Python 3 m’apparait un meilleur choix. D’ailleurs de plus en plus d’utilisateurs chevronnés de Python
décident maintenant de faire le saut vers la version 3, et on peut supposer que cette version dominera le
paysage sous peu.
Bon, un dernier dernier commentaire et je m’arrête là-dessus. Même s’il est utile et puissant pour le
calcul scientifique, Python, comme la plupart des langages interprétés, n’est pas le langage de choix pour
effectuer des calculs de “haute-performance" où la rapidité d’exécution doit primer. Pour de tels calculs,
des langages comme le C ou le Fortran sont plus appropriés.
Exercices:
Bibliographie:
Les notes que vous avez/aurez en main contiennent toute la matière qui sera couverte en PHY-1234. À
moins d’être déjà absolument et totalement convaincu(e) que vous ne toucherez plus au calcul scientifique
de votre vie (ce qui risque de fort limiter vos options de carrière, soit dit en passant...), le livre suivant
pourrait vous être utile :
Press, W.H., Teukolsky, S.A., Vetterling, W.T., & Flannery, B.P., Numerical Recipes : the Art of
Scientific Computing, Cambridge University Press (1992–2007)
Ce sera fort probablement le premier et dernier dans lequel vous aurez à investir sur ce sujet. Plusieurs
versions du bouquin existent, et ne varient qu’au niveau du langage de programmation utilisé. Dans la
troisième édition (2007) les auteurs ont opté pour le langage C++, un langage basé pour le C mais incluant
des fonctionnalités additionnelles au niveau de la programmation dite “orientée objet”. Mais bon, l’essen-
tiel demeure la présentation des algorithmes, qui peuvent ensuite être programmés dans n’importe quel
langage. Cet ouvrage demeure également inégalé (à mon avis) dans l’équilibre qu’il atteint entre l’analyse
numérique, la conception d’algorithmes simples et robustes, et les bonnes habitudes de programmation.
Plusieurs ouvrages, traitant spécifiquement de modélisation numérique en physique ont été écrits et
les meilleurs souvent réédités au fil des années. Parmi ces grands classiques, l’ouvrage suivant demeure
recommandable :
Gould, H., & Tobochnik, J., An Introduction to Computer Simulation Methods, 3e éd., Addison-Wesley
(2006).
Même si la troisième édition est relativement récente, à bien des points de vue elle conserve la “saveur” de
l’édition originale. À un niveau physique et mathématique plus avancé que celui de ce cours, j’aime bien :
Pang, T., An Introduction to Computational Physics, 2e éd., Cambridge University Press (2006).
Chapitre 2
Python : la base
Avant de se lancer dans le vif du sujet du cours, soit différents algorithmes et diverses approches
utiles à la physique numérique, il importe de poser les bases de la programmation en Python, langage que
nous utiliserons pour mettre en application tout ce que nous verrons dans les chapitres suivants. Dans ce
chapitre je présente donc les notions et les commandes de bases en Python, c’est-à-dire les éléments qui
sont indispensables à quiconque veut utiliser ce langage. Le matériel de ce chapitre devrait vous suffire
pour faire vos premiers pas en Python et pour aborder les laboratoires du cours sans problème. Quelques
notions plus avancées seront présentées au chapitre suivant, dans les notes de laboratoire et en classe.
En plus des notes qui vous sont fournies, un bon livre de référence sur Python vous sera certainement
utile pour ce cours et pour vos autres besoins futurs en programmation Python. Il existe peu d’ouvrages
en français, du moins à ma connaissance, mais en voici deux assez intéressants, et de surcroit ils sont
gratuits ! Donc procurez-vous les.
Le premier s’adresse aux débutants les plus néophytes en programmation et se lit très facilement ; il a à
l’origine été conçu pour des élèves du secondaire, mais il demeure tout à fait approprié pour des débutants
de tous âges, même des universitaires ! Le deuxième s’adresse probablement plus aux gens ayant une
certaine base en programmation, sans toutefois avoir de notions de Python ; c’est un livre écrit de façon
très succincte, presque télégraphique. En anglais, je vous recommande :
Langtangen, H.P., A Primer on Scientific Programming with Python, 5e éd., Springer (2016), version
HTML disponible à [Link]
Swaroop, C. H., A byte of Python, disponible à [Link]
Le premier est un livre bien construit et fort approprié pour des étudiants en physique. D’ailleurs, plu-
sieurs exemples sont basés sur des problèmes de nature physique. L’emphase est mise sur le comment
“penser comme un programmeur" et sur l’utilisation de la programmation comme outil pour résoudre
des problèmes. Au-delà de l’enseignement de Python, ce livre traite aussi de plusieurs sujets que nous
verrons dans le cours. Notez que la 4e édition en format PDF est disponible gratuitement à ici 1 , et que
la 3e édition en format PDF est offerte gratuitement sur le site web des bibliothèques de l’Université de
Montréal. Le deuxième livre est une bonne introduction qui s’adresse à un publique plus général ; il est
aussi disponible gratuitement en format électronique à l’adresse indiquée. D’autres livres d’introduction
sont suggérés ici 2 .
Il existe aussi plusieurs tutoriels et ressources en ligne. Je vous encourage à visiter le site et l’excellent
tutoriel interactif Python de Code Academy 3 , ainsi que le tutoriel de Python 3 4 offert par [Link].
1. [Link]
2. [Link]
3. [Link]
4. [Link]
Un outil intéressant pour les débutants en programmation est le Python tutor 5 , qui montre de façon
interactive et illustrée ce qui se produit lors de l’exécution d’un script en Python.
Certaines ressources en français pour Python sont compilées ici 6 , vous pouvez y jeter un coup d’oeil.
Notamment, un tutoriel d’introduction à Python en français est offert ici 7 par l’université de Waterloo.
Un dernier conseil : Malgré toutes ces références, vous ferez inévitablement face à certains problèmes
pour lesquels la solution semblera vous échapper. Dans une telle situation, pourquoi ne pas demander de
l’aide “au monde entier” ! Allez-y, tapez simplement vos questions dans Google ! et quelqu’un, quelque part
aura fort probablement déjà répondu à votre question. Et je vous encourage à formuler vos recherches en
anglais, si vous le maitrisez raisonnablement, car ceci augmentera vos chances de tomber sur la réponse
recherchée. Dans le même genre, il y a Stack Overflow 8 , un site de questions-et-réponses pour program-
meurs professionnels et enthousiastes. C’est généralement sur ce site que je trouve moi-même la réponse
à mes questions.
Code source Expression d’un algorithme complet dans un langage de programmation. Il s’agit de toute
la série d’instructions nécessaire à l’exécution de l’algorithme, rédigée avec le vocabulaire et les règles de
syntaxe du langage de programmation. Le code source est lisible et compréhensible par l’utilisateur. Le
code source est parfois appelé script, code, ou encore programme, bien qu’on réserve souvent ce dernier
terme pour l’expression de l’algorithme dans un langage compréhensible par le microprocesseur. L’encadré
de la page 3 est un exemple de code source. Le code source est sauvegardé dans un fichier de type “texte”,
dont le contenu représente uniquement une suite de caractères. 9
Fonction Partie autonome d’un code qui accomplit une tâche spécifique. Une fonction est nommée et
invoquée par un identificateur unique. Les fonctions peuvent ou non comporter des arguments à l’aide
desquels les opérations codées dans la fonction seront effectuées. Plusieurs fonctions sont disponibles par
défaut en Python, et on peut en créer de nouvelles. On utilise parfois les termes routine ou procédure pour
désigner une fonction.
Commande Une instruction donnée à l’ordinateur pour qu’il exécute une certaine tâche ou opération.
Le code source est essentiellement formé d’une série de commandes. Les commandes correspondent souvent
à l’invocation de fonctions.
Expression Petite partie de code qui peut être évaluée lors de l’exécution, produisant une valeur. Par
exemple, “a>b” ou “x+10”. Les expressions peuvent être composées et peuvent faire intervenir des fonctions.
5. [Link]
6. [Link]
7. [Link]
8. [Link]
9. Notez qu’un logiciel de traitement de texte tel que Microsoft Word ne sauvegarde pas ses documents dans des fichiers
de type texte.
Mode interactif Dans ce mode, l’utilisateur peut taper une commande au clavier pour que l’interpré-
teur Python l’exécute immédiatement et, le cas échéant, affiche le résultat. L’utilisateur peut, à son gré,
enchainer avec une autre commande et accéder immédiatement au résultat, d’où “l’interactivité”. Pour
démarrer Python en mode interactif, il suffit de taper la commande python à l’invite de commande d’une
fenêtre terminal. 10 Après avoir lancé cette commande, un message similaire à celui-ci s’affiche :
Les trois caractères >>> marquent l’invite de commande (prompt) de l’interpréteur Python. C’est à cette
invite de commande que l’on peut taper une instruction (et commander son exécution/évaluation en
appuyant sur entrée). Le résultat de l’exécution de la commande (ou de l’évaluation de l’expression)
s’affiche sur la ligne suivante dans la fenêtre du terminal. Pour quitter l’interpréteur Python, tapez la
commande exit() ou la combinaison de touches Ctrl-D.
Il existe une alternative à l’interpréteur Python de base, soit IPython. Pour exécuter IPython, il suffit
de tape la commande ipython dans un terminal. Au lancement de cette commande, un message similaire
à celui-ci s’affiche :
In [1]:
Notez que l’invite de commande n’est plus “>>>”, comme avec l’interpréteur Python de base, mais plutôt
“In [n]:”, où n est le numéro de la ligne d’Input. Le résultat de l’exécution d’une instruction don-
née s’affiche sur la ligne suivante, comme auparavant, sauf qu’avec IPython cette ligne est précédée de
10. Selon la configuration du système utilisé, le nom de la commande pourrait différer légèrement, par exemple il pourrait
être python3.
“Out [n]:”, pour “Output” (sortie). Mis à part ces petits changements, le fonctionnement est sensible-
ment le même que pour l’interpréteur de base. Les principaux attraits de IPython sont, à mon avis, la
coloration de syntaxe, la possibilité d’exécuter un script (voir mode scripté) à partir du mode interactif à
l’aide de la commande run, l’accès dynamique à l’information d’un object ou d’une fonction donné à l’aide
de la commande ? ou ??, et l’accès aux commandes systèmes à l’aide de la commande !. Par exemple,
la commande ?round affichera l’information sur la fonction round(), ?var affichera l’information sur la
variable var, et la commande !ls exécutera la commande système ls qui affiche le contenu du répertoire
courant. IPython affiche également des messages d’erreurs plus complet lors d’erreur d’exécution d’un
code.
Le mode interactif est utile pour faire rapidement des calculs simples ou pour expérimenter avec de
nouvelles commandes.
Note : Dans ce chapitre des notes, plusieurs exemples seront présentés en répliquant ce que verrait un
utilisateur en mode interactif : les instructions entrées par l’utilisateur apparaitront à droite de l’invite de
commande (>>>) et les résultats de leur exécution apparaitront sur la ligne suivante. En voici un exemple :
1 >>> 5+12
2 17
Mode scripté Dans ce mode, l’interpréteur Python lit et exécute séquentiellement une série d’instruc-
tions enregistrées dans un fichier : le code source. L’usager n’entre pas les instructions une à une, et il
n’intervient pas non plus au cours de l’exécution de l’ensemble des instructions contenues dans le code
source. Ce mode est le plus utilisé 11 car il permet d’automatiser l’exécution de plusieurs opérations plus
complexes, sans avoir à retaper les instructions une à une à chaque fois que l’on veuille faire une modifi-
cation ou répéter leur exécution. Pour exécuter un code en mode scripté, il suffit de taper la commande
python suivie du nom du fichier code source à l’invite de commande du terminal. 12 Par exemple, en
supposant que le code source soit contenu dans le fichier [Link], il faudrait taper la commande “python
[Link]”. Le cas échéant, les résultats seront affichés directement dans le terminal au cours de l’exécution.
Après l’exécution de toutes les instructions du code source, le système revient à l’invite de commande du
terminal.
Pour utiliser le mode scripté, il est possible de travailler avec un logiciel d’environnement de déve-
loppement intégré (ou IDE pour Integrated Development Environment, en anglais). Il s’agit d’un logiciel
qui intègre au minimum un éditeur de texte pour composer un script et différentes fonctionnalités pour
lancer son exécution. Avec la distribution Python Anaconda, le logiciel IDE Spyder est automatiquement
installé, celui-ci inclut un éditeur de texte et intègre l’interpréteur IPython pour exécution des scripts.
Il existe aussi une autre façon d’utiliser Python, qui n’est pas à travers un mode d’exécution propre à
Python, mais plutôt à l’aide de l’application Jupyter Notebook 13 , laquelle peut agir de concert avec Python
pour permettre une utilisation dans un mode que l’on pourrait qualifier d’hybride entre le mode interactif
et le mode scripté. Cette application utilise une interface web pour afficher une séquence ordonnée de
cellules pouvant contenir du code, du texte, des équations, des graphiques, etc. On peut entrer une ou
plusieurs lignes de code (des commandes) dans chaque cellule et exécuter d’un coup toutes les commandes
contenues dans une cellule donnée, dans lequel cas le résultat s’affichera immédiatement sous la cellule.
On peut aussi modifier les commandes de chaque cellule et les ré-exécuter à notre guise. Une illustration
de cette application est montrée à la figure 2.1.
L’aspect interactif de l’application Jupyter Notebook et sa capacité à exécuter en bloc un grand nombre
de commandes, cellule par cellule, la rend très utile pour le développement et le déboggage de nouveaux
codes. De plus, en classe nous ferons souvent des exemples interactifs en utilisant Jupyter Notebook.
11. C’est d’ailleurs le seul mode possible pour plusieurs autres langages de programmation, comme le C, le C++, et le
Fortran.
12. Il est aussi possible d’utiliser la commande ipython.
13. [Link]
1. Un identificateur peut inclure toute combinaison de lettres (sans accent), de chiffres, et le caractère
de soulignement (_). Toutefois, le premier caractère de tout identificateur ne peut pas être un
chiffre.
2. La longueur maximale de chaque identificateur est de 256 caractères.
3. On distingue les majuscule des minuscules ; ainsi, les identificateurs votreNom, VotreNom, et votrenom
réfèrent à trois objets distincts.
4. Python réserve certaines chaînes de caractères pour définir des mots-clés qui jouent un rôle spé-
cifique au niveau de la programmation ; s’il vous vient la brillante idée de définir un identificateur
ayant le même nom, il pourrait vous arriver des choses É-POU-VAN-TA-BLES ! ! Voici une liste
alphabétique de ces mots-clés à éviter comme identificateur :
Une bonne pratique stylistique, largement répandue, consiste à nommer les variables et les fonc-
tions par un identificateur commençant par une lettre minuscule, et si plusieurs mots sont joints en-
semble pour former l’identificateur, à commencer chaque mot suivant par une majuscule. Par exemple,
ceciEstUnNomDeVariable. Selon cette convention, les identificateurs des classes commencent par une ma-
juscule, pour les distinguer des variables. Enfin, les constantes numériques (e.g. la variable représentant
la valeur numérique de la constante de Boltzmann) ont un identificateur formé entièrement de majuscules
(e.g. KBOLTZ).
2.4 Assignation
L’assignation, aussi appelée affectation, est le processus par lequel on crée un lien entre un identificateur
et un objet. Dans le cas simple d’une variable, ceci revient essentiellement à donner un nom et une valeur
à la variable. L’assignation se fait à l’aide de l’opérateur “=” en utilisant la syntaxe suivante :
identificateur = valeur_ou_objet_ou_expression
Cette instruction lie l’identificateur apparaissant à gauche avec la valeur ou l’objet apparaissant à droite.
Si la partie de droite est une expression, alors l’expression est d’abord évaluée et c’est le résultat de cette
évaluation qui est lié à l’identificateur apparaissant à gauche. Voici quelques exemples d’assignations :
1 a=5
2 b=a+10
3 a=a+1
4 c=a
Il est possible de faire des assignations parallèles (en une seule ligne) en fournissant plusieurs identifica-
teurs séparés par des virgules à gauches du “=” et un nombre correspondant de valeurs/objets/expressions
à droite. En voici un exemple qui permet d’assigner la valeur 5 à a et la valeur 10 à b :
1 a,b=5,10
Vous aurez donc compris qu’en programmation Python (ainsi que dans bien d’autres langages de
programmation), l’opérateur “=” n’a pas le même sens qu’en mathématiques : il ne signifie pas qu’il
y a égalité entre les termes apparaissant à sa gauche et à sa droite. Il s’agit plutôt d’un opérateur
unidirectionnel qui effectue une assignation de l’objet de droite vers l’identificateur de gauche.
Note : Pour qu’un identificateur puisse être utilisé dans une commande, il faut impérative-
ment qu’il ait été défini au préalable par une assignation.
1 >>> type(5)
2 <class 'int'>
3 >>> a=5
4 >>> type(a)
5 <class 'int'>
1 12
2 0
3 -2345
4 9999999999
La représentation des valeurs entières est exacte et leur taille n’est limitée que par la mémoire de
l’ordinateur. 14
1 12.
2 12E4
3 12e-4
4 1.234
1 12. + 3.0J
2 12E4 - 0.04j
3 21.887e+3J + 12e-4
14. Ce n’est pas le cas dans la plupart des autres langages de programmation, pour lesquels les valeurs entières sont
représentées à l’aide d’un nombre fixe de bits, ce qui limite le domaine des valeurs représentables.
1 'allo'
2 'Bonjour le monde!'
3 "Bonjour le monde!"
4 "L'écriture d'apostrophes"
5 'Écriture de "guillemets"'
6 """Une chaîne
7 de caractères formée de
8 trois lignes"""
À l’intérieur d’une chaîne de caractères, le caractère “\” donne une signification spéciale à certaines
séquence de caractères ; ces “séquences d’échappement” sont présentées à la table 2.1. Certaines de celles-ci
ont un effet visible seulement lors de l’affichage de la chaîne à l’écran.
Séquence Signification
\\ affiche un antislash
\’ apostrophe
\" guillemet
\a sonnerie (bip)
\b retour arrière
\f saut de page
\n saut de ligne
\r retour en début de ligne
\t tabulation horizontale
\v tabulation verticale
\N{nom} caractère sous forme de code Unicode nommé
\uhhhh caractère sous forme de code Unicode 16 bits
\Uhhhhhhhh caractère sous forme de code Unicode 32 bits
\ooo caractère sous forme de code octal
\xhh caractère sous forme de code hexadécimal
1 [5,49,17]
2 ['Bonjour','le','monde!']
3 [24,'heures']
4 [12. + 3.0J, True, 22, a, x, 'test', [4,5,6]]
5 ['A']
6 []
Notez que chaque élément d’une liste peut être n’importe quel objet, de tous les types. On peut aussi
créer une liste vide, comme montré à la dernière ligne ci-dessus. Les liste sont modifiables, c’est-à-dire que
l’on peut changer leur contenu comme bon nous semble : changer la valeur de ses éléments, ajouter ou
retirer des éléments, changer l’ordre de ses éléments, etc. On peut accéder à chaque élément d’une liste à
l’aide d’indices spécifiants leur position dans la séquence ordonnée. Nous verrons comment faire un peu
plus tard.
Un objet de type tuple est essentiellement la même chose qu’une liste, à la différence qu’il n’est pas
modifiable : on ne peut pas changer la valeur de ses éléments ou ajouter/retirer des éléments. Une fois
créé, un tuple est immuable. Pour spécifier un tuple, on doit écrire chaque élément séparé par une virgule
et entourer le tout de parenthèses, comme dans les exemples ci-bas :
1 (5,49,17)
2 ('Bonjour','le','monde!')
3 (24,'heures')
4 (12. + 3.0J, True, 22, a, x, 'test', [4,5,6])
5 (2,)
Notez que pour définir un tuple d’un seul élément il faut obligatoirement écrire la virgule après celui-ci
(ligne 5), sans quoi les parenthèses marqueraient uniquement une priorité dans l’ordre d’évaluation d’une
expression. L’intérêt du tuple, par rapport à la liste, est qu’il conduit à une exécution plus rapide du
programme ; on a donc avantage à l’adopter si on n’a pas besoin de modifier les éléments d’une séquence
donnée.
Table 2.2 – Sommaire des types d’objets de base
dict Ensemble non-ordonné de paires “clé–objet”, où clé est un identifiant unique (généralement une
chaîne de caractères ou un chiffre) qui est utilisé pour accéder à l’objet auquel il est lié. Ce type d’objet
tire son nom du mot “dictionnaire”. Défini à l’aide des caractères {}, chaque paire étant séparée par
une virgule, et la clé et l’objet d’une paire étant séparés par un deux-points. Par exemple le dictionnaire
d={’nom’:’Mathieu’, ’age’:30, ’taille’:180.} contient trois paires clé–objet, le deuxième objet
(30) pouvant être accédé à l’aide de la clé “age” : d[’age’].
bytes Séquence ordonnée d’octets unitaires (i.e. une séquence d’entiers entre 0 et 255, ou une séquence
de caractères ASCII).
Notez bien la distinction entre les deux types de division : la division réelle (/) retourne toujours
un nombre réel correspondant à la valeur “exacte” de la division, alors que la division entière retourne
l’entier inférieur le plus près de cette valeur exacte, son type dépendra alors des types des variables avec
lesquelles la division est faite. Voici quelques exemples d’opérations arithmétiques de base et leur résultat
correspondant :
1 >>> 5+2
2 7
3 >>> 5+2.
4 7.0
5 >>> 5/2
6 2.5
7 >>> 6/2
8 3.0
9 >>> 5//2
10 2
11 >>> 5.//2
12 2.0
13 >>> -5.//2
14 -3.0
Promotion numérique : Afin de ne pas perdre d’information lorsqu’une opération est exécutée sur des
valeurs de types différents, Python promeut le type le plus bas vers le type le plus haut avant de faire
l’opération, selon l’ordre de priorité suivant : complex, float, int, bool. Ainsi, si on additionne un
nombre de type int et un nombre de type float, alors le nombre int est converti en float avant de
calculer l’addition et le résultat sera un nombre de type float (lignes 3–4 de l’exemple ci-haut). De plus,
la division réelle de deux nombres de type int les promeut d’emblée vers le type float et produira un
résultat de type float, afin de ne pas “perdre” la partie décimale résultant de la division (lignes 5–6).
Vous aurez sans doute noté la présence du type bool dans la liste de priorité de promotion donnée
ci-haut, la raison est qu’il est bel et bien possible de faire des opérations arithmétiques avec des variables
booléennes. Dans ce cas, Python interprète la valeur True comme étant un entier valant 1, et la valeur
False comme étant un entier valant 0 ; les règles standards mentionnées ci-haut s’appliquent ensuite.
Ainsi, l’opération 2.3*False produira le résultat 0.0 de type float.
Il existe des opérateurs additionnels qui combinent une opération arithmétique à une opération d’as-
signation, ceux-ci sont indiqués à la table 2.4. On peut ainsi de façon succincte modifier la valeur d’une
variable existante en lui appliquant une opération arithmétique.
Table 2.4 – Opérateurs arithmétiques additionnels (on prend int a=3, i.e. un entier)
a b a or b a and b
False False False False
False True True False
True False True False
True True True True
Lors d’une opération logique, la valeur zéro sous toutes ses formes (0, 0.0, 0.0+0.0j) est considérée
comme valant False ; toutes les autres valeurs numériques sont considérées comme valant True.
1 >>> 'Bonjour'+'Monde'
2 'BonjourMonde'
3 >>> 'Bonjour'*3
4 'BonjourBonjourBonjour'
5 >>> [2,3]+['Trou','Noir']
6 [2, 3, 'Trou', 'Noir']
7 >>> [2,3]*4
8 [2, 3, 2, 3, 2, 3, 2, 3]
L’opérateur [] est plus particulier, il permet d’accéder à un ou plusieurs éléments de la séquence pour
en obtenir la/les valeur(s) ou en assigner une(de) nouvelle(s). 16 À l’interne, Python assigne un indice
numérique (un nombre entier) à chaque élément de la séquence, en commençant par 0 pour le premier, 1
pour le deuxième, et ainsi de suite. C’est avec ces indices et l’opérateur [] que l’on peut accéder à un ou
plusieurs éléments d’une séquence.
Pour accéder à un seul élément, la syntaxe est la suivante : a[i], où a est l’identificateur de la séquence
et i est l’indice de l’élément désiré. Un indice négatif indique que l’on compte à partir de la fin.
Pour accéder à plusieurs élément adjacents, une sous-séquence ou une “tranche”, la syntaxe est la
suivante : a[i:j], où i est l’indice du premier élément désiré et j est la position du dernier élément désiré
(attention : le deuxième chiffre est la position et non l’indice du dernier élément désiré), de sorte que j − i
donne le nombre d’éléments dans la sous-séquence correspondante. On peut omettre l’un ou l’autre de
i ou j, ou les deux, dans lequel cas la sous-séquence va jusqu’au début ou jusqu’à la fin de la séquence
originale. Ces opérations sont illustrées dans la figure 2.2 et dans les exemples suivants.
1 >>> s=[1,2,3,4,5]
2 >>> s[3]
3 4
4 >>> s[3]=8
5 >>> s
15. mise bout à bout et liaison en une seule séquence
16. Sauf pour les tuples, qui sont immuables.
Figure 2.2 – Indexation d’une chaîne de caractères. Figure tirée de Cordeau et Pointal (Une introduction
à Python 3).
6 [1, 2, 3, 8, 5]
7 >>> s[1:4]
8 [2, 3, 8]
9 >>> s[-2:]=50,100
10 >>> s
11 [1, 2, 3, 50, 100]
12 >>> s[:]
13 [1, 2, 3, 50, 100]
1 >>> print('Bonjour!')
2 Bonjour!
3 >>> a,b=5,6
4 >>> print("a<b=",a<b)
5 a<b= True
6 >>> print("J'ai",5*' très','faim!')
7 J'ai très très très très très faim!
L’entrée standard se fait avec la fonction input(). Cette fonction interrompt l’exécution du pro-
gramme, affiche une invite à l’écran et attend que l’utilisateur entre une chaîne de caractères au clavier
et la valide en appuyant sur entrée. L’argument optionnel de cette fonction est le message à afficher au
moment de l’exécution de la commande, i.e. l’invite présentée à l’utilisateur pour qu’il entre quelque chose
au clavier. La valeur retournée par la fonction input() est toujours de type str (chaîne de caractères) ; si
on désire obtenir un chiffre, il faut convertir cette valeur vers le bon type (par exemple en entier ou réel).
Voici quelques exemples d’utilisation de cette fonction :
8 >>> print('2x'+a+'=',2*x)
9 2x225= 450
L’effet est le suivant : si la condition 1 est vraie, alors le premier bloc d’instruction est exécuté, sinon
(et seulement sinon) on vérifie la condition 2 et si elle est vraie alors le deuxième bloc d’instruction est
exécuté, et on continue ainsi tant qu’il y a des instructions elif, enfin si (et seulement si) aucune des
conditions n’est vraie alors le bloc d’instructions suivant le else est exécuté. Selon le besoin, on peut
omettre la partie elif ou else.
Les instructions conditionnelles peuvent être imbriquées, dans le sens qu’un bloc d’instructions sujet à
exécution conditionnelle peut lui-même contenir d’autres instructions conditionnelles impliquant une ou
plusieurs instructions if et/ou elif/else.
Test conditionnel “en ligne” Il existe une syntaxe simplifiée et bien pratique pour assigner une valeur
ou une autre à une variable selon qu’une condition soit vraie ou non. Cette syntaxe est la suivante :
while condition:
␣␣␣␣instructions
L’effet d’une telle boucle est simple : si la condition est vraie, on exécute le bloc d’instructions et on
ré-évalue ensuite la condition, si elle est toujours vraie on ré-exécute le bloc d’instructions, on ré-évalue
la condition, et on continue ainsi tant que la condition est vraie, dès que la condition est fausse on saute
par-dessus le bloc d’instructions et on continue l’exécution du programme. Par exemple, le petit bout de
code suivant écrirait en sortie les entiers de un à dix :
1 k=1
2 while k <= 10:
3 print(k)
4 k+=1
La boucle while est fort utile, mais notez bien le danger qu’une boucle se mette à tourner sans jamais
stopper, si la condition de contrôle ne se retrouve jamais fausse suite à une erreur conceptuelle ou de codage.
Si jamais cela se produit, ou si pour une toute autre “fausse manoeuvre” vous vous retrouvez dans une
situation où le code tourne “dans le vide”, vous pouvez stopper l’exécution en pressant simultanément sur
les touches “Ctrl+c” sur le clavier.
for i in sequence:
␣␣␣␣instructions
La boucle for ci-dessus répète le bloc d’instructions pour chaque élément i dans la séquence sequence.
Plus précisément, la variable i prendra initialement la valeur du premier élément de sequence et le bloc
d’instructions sera exécuté, la variable i prendra ensuite la valeur du deuxième élément de sequence et le
bloc d’instructions sera exécuté, et ainsi de suite jusqu’à ce que i ait pris la valeur de tous les éléments de
sequence. La séquence en question peut-être un objet de type list, str ou tuple, ou de tout autre type
correspondant à une séquence. Le nombre de répétitions est ici prédéterminé et correspond au nombre
d’éléments dans sequence. Par exemple, le code suivant,
1 mot='Bonjour'
2 for i in mot:
3 print(i)
écrira l’une après l’autre sur une ligne à part chaque lettre du mot “Bonjour”, et le code suivant,
1 xx=['Voici','les',7,'elements','de','la','liste']
2 for x in xx:
3 print(x)
écrira l’un après l’autre sur une ligne à part chaque élément de la liste xx.
Souvent, on désire itérer sur des nombres entiers et pour ce faire on peut utiliser la fonction range(),
qui est en fait un itérateur de nombres entiers. La syntaxe de cette fonction est range(debut,fin,pas),
ce qui produira une séquence d’itération partant du nombre debut et allant jusqu’au plus grand nombre
strictement plus petit que fin, avec un intervalle de pas entre chaque nombre de la séquence ; si pas est
omis alors une valeur de 1 sera adoptée par défaut. Le résultat de cette fonction est un objet qui a son
propre type : range. Si on le veut, on peut ne fournir que l’argument fin, dans lequel cas l’itération
débutera à 0 avec un intervalle de 1 entre chaque nombre.
Par exemple, le code suivant affichera un à un sur une ligne à part les chiffres de 1 à 10 (équivalent à
la boucle while présentée ci-haut) :
1 for x in range(1,11):
2 print(x)
Création de listes Une variation de la boucle for telle que construite ci-haut permet de créer des listes
de façon très compacte. Cette variation (appelée list comprehension en anglais) prend la forme suivante :
[expression(i) for i in sequence if condition]
Notez que les symboles “[” et “]” au début et à la fin sont ici essentiels pour marquer explicitement que
le résultat sera une liste. Par exemple, la commande suivante produira une liste contenant le carré des 10
premiers nombres entiers (incluant le zéro, donc de 0 à 9) :
Une méthode est quant à elle simplement une fonction liée à un objet (ou plus généralement à une
classe d’objets). La seule différence entre une fonction et une méthode est la façon dont elles sont invo-
quées. Les fonctions sont appelées par leur identificateur suivi de parenthèses, fonction(), les arguments
figurant à l’intérieur des parenthèses le cas échéant. Les méthodes sont appelées avec la notation ponctuée,
[Link](), et agissent sur l’objet lié à l’identificateur précédent le “.”, comme dans cet
exemple :
1 >>> a=[1,2,3,4]
2 >>> [Link]()
3 >>> a
4 [4, 3, 2, 1]
5 >>> [Link](3)
6 1
À la ligne 2, la méthode reverse() agit sur la liste a et son effet est d’inverser l’ordre de ses éléments. À
la ligne 5, la méthode count() agit sur a et retourne le nombre total de ses éléments dont la valeur est
égale à celle passée en argument.
1 >>> a=[1,2,3,4]
2 >>> dir(a)
3 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__',
4 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
5 '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__',
6 '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
7 '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__',
8 '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index',
9 'insert', 'pop', 'remove', 'reverse', 'sort']
On voit bien dans la liste affichée aux lignes 3–9 la présence des méthodes reverse() et count() citées
en exemple plus haut.
La fonction repr() retourne une chaîne de caractères correspondant à la représentation de l’objet donné
en argument ; en particulier, cette représentation peut être affichée à l’aide de la fonction print(). Par
exemple, pour un objet de type chaîne de caractères, la fonction repr retournera une chaîne de caractères
qui inclura explicitement des apostrophes au début et à la fin, de telle sorte qu’en affichant cette chaîne
on verra les apostrophes et on saura que l’objet en question était du type chaînes de caractères :
1 >>> s='5'
2 >>> repr(s)
3 "'5'"
4 >>> print(repr(s))
5 '5'
17. [Link]
6 >>> print(s)
7 5
8 >>> print(5)
9 5
L’affichage de la ligne 4 ci-dessus nous permet de voir que l’objet “s” est une chaîne de caractères, alors
que l’affichage de la ligne 6 ne nous permet pas de distinguer “s” du nombre entier 5 (cf. ligne 8).
La fonction eval() est aussi intéressante pour certaines applications. Cette fonction accepte un argu-
ment de type chaîne de caractères, interprète cette chaîne de caractères comme si elle était une expression
Python, et l’évalue. Un des usages possibles (parmi bien d’autres) est pour simplifier l’assignation de
valeurs spécifiées par l’utilisateur à des variables, par exemple :
En utilisant cette approche, on n’a pas besoin de séparer en deux la chaîne de caractères retournée par
input et de faire la conversion vers le type d’objet voulu. Une fonction similaire, exec(), permet d’exécuter
une instruction Python plutôt que d’évaluer une expression. Par exemple, on peut faire exec(’x=10’).
Enfin, lorsqu’on utilise des boucles d’itération, les fonctions zip() et enumerate() peuvent être inté-
ressantes. La fonction zip accepte deux ou plusieurs objets itérables comme arguments (e.g. des listes)
et retourne un itérateur de tuples, chaque tuple de cette itération contenant les éléments correspondants
des séquences données en argument. Par exemple :
1 >>> a=['A1','A2','A3']
2 >>> b=['B1','B2','B3']
3 >>> for z in zip(a,b):
4 ... print(z)
5 ...
6 ('A1', 'B1')
7 ('A2', 'B2')
8 ('A3', 'B3')
9 >>> for x,y in zip(a,b):
10 ... print('x={} et y={}'.format(repr(x),repr(y)))
11 ...
12 x='A1' et y='B1'
13 x='A2' et y='B2'
14 x='A3' et y='B3'
La fonction enumerate est similaire, elle accepte un seul objet itérable en argument, et retourne un
itérateur de tuples de deux éléments, chaque tuple de l’itération contenant l’indice de l’itération et l’élément
correspondant de l’objet donné en argument. Par exemple :
1 def coeff_droite(p1,p2):
2 """
3 Calcule les coefficients (m et b) de l'équation de la droite y=m*x+b passant
4 par les points p1=(x1,y1) et p2=(x2,y2).
5 """
6
7 if p2[0]!=p1[0]:
8 m=(p2[1]-p1[1])/(p2[0]-p1[0])
9 b=p1[1]-m*p1[0]
10 else:
11 print('Erreur! x1 et x2 ont la même valeur')
12 m=b=None
13
14 return m,b
Une fonction débute avec def, suivi du nom de la fonction (ici coeff_droite), et ensuite d’une liste
d’arguments séparés par des virgules entre parenthèses (ici p1 et p2). Le bloc d’instructions propres à la
fonction suit sur les lignes suivantes avec une indentation appropriée. À la fin de la fonction, il est commun
de retourner un ou plusieurs objets à l’aide de la commande return (mais ceci est optionnel) ; si plusieurs
objets sont retournés, ils doivent être séparés par des virgules. Pour utiliser la fonction définie ci-haut
il suffit de l’invoquer comme on le ferait pour toute autre fonction, tant que la fonction a été “lue” au
préalable par l’interpréteur Python. Pour cette dernière raison, la définition de la fonction doit apparaître
dans le code avant le programme principal, de façon à ce que la fonction soit lue (et ainsi définie) avant
d’être invoquée plus bas dans le code.
Les arguments passés à une fonction peuvent être nommés et/ou positionnels. Les arguments nommés
(en anglais : keyword arguments) sont précédés d’un identificateur dans la définition de la fonction, ainsi
que dans l’appel à la fonction. 18 Les arguments positionnels n’ont pas d’identificateurs et doivent précéder
tous les arguments nommés dans la définition de la fonction. Par exemple, la définition de fonction suivante
déclare les arguments positionnels x0 et t0 ainsi que les arguments nommés dt et affichage. De plus,
on assigne une valeur par défaut aux arguments nommés, c’est-à-dire que si ces arguments ne sont pas
passés lors de l’appel de la fonction, alors ces valeurs par défaut seront utilisées.
1 def ma_fonction(x0,t0,dt=0.1,affichage=True):
2 if affichage:
3 print(' x0:',x0)
4 print(' t0:',t0)
5 print(' dt:',dt)
6 #normalement des instructions de la fonction suivraient ici
7
Les lignes 8 et suivantes correspondent au programme principal, qui est placé après la définition de la
fonction tel que discuté plus haut. Les lignes ci-bas montrent différentes façon dont des arguments peuvent
être passés à cette fonction, avec le résultat correspondant :
18. En principe, l’identificateur d’un argument nommé peut être omis lors de l’appel à la fonction, dans lequel cas il est
considéré comme un argument positionnel. Cette pratique n’est toutefois pas recommandée.
1 >>> ma_fonction(0.,0.,dt=0.01,affichage=True)
2 x0: 0.0
3 t0: 0.0
4 dt: 0.01
5 >>> ma_fonction(1.,0.)
6 x0: 1.0
7 t0: 0.0
8 dt: 0.1
9 >>> ma_fonction(5.,100.,affichage=0)
10 >>> ma_fonction(5.,100.,affichage=1,dt=10.)
11 x0: 5.0
12 t0: 100.0
13 dt: 10.0
L’erreur la plus commune dans l’écriture d’un code Python incluant des fonctions est d’introduire une
ou plusieurs incompatibilités dans le nombre et type des variables en argument ou en sortie à l’évaluation
de la fonction. Donc portez-y attention.
Pour plus d’information sur la définition de fonctions, vous pouvez consulter cette page. 19
En programmation scientifique, les modules NumPy, SciPy, Matplotlib et Pyplot (qui est un sous-
module de Matplotlib) sont fréquemment utilisés et par convention on recommande fortement d’utiliser
les commandes d’importation suivantes :
import numpy as np
import scipy as sp
import matplotlib as mpl
import [Link] as plt
Il existe aussi un module Math définissant les fonctions mathématiques et quelques constantes de base,
mais celles-ci sont aussi (re)définies dans les modules NumPy et SciPy que l’on préfère généralement.
1 """
2 Ceci est un commentaire en bloc. Il permet de mettre
3 plusieurs lignes de texte dans un commentaire.
4 """
1 ##############################################
2 #Entête:
3 #bref descriptif du programme, auteur, date
4 ##############################################
5
6 ##############################################
7 #Importation de modules et fonctions externes:
8
9 import numpy as np
10 import [Link] as plt
11 #etc...
12
13 ##############################################
14 #Définitions de (valeurs) constantes:
15
16 M_ELECTRON=9.10938215d-31 #kg
17 omegaB=200 #fréquence d'oscillation du champ B
18 #etc...
19
20 ##############################################
21 #Définitions locales de fonctions:
22
23 def fonction1(x,y):
24 """Cette fonction calcule ..."""
25 #instruction...
26
27 return z
28
29 ##############################################
30 #Programme principal:
31
32 #instructions...
Chapitre 3
Instruction Signification
[Link](x) Ajoute l’objet x à la fin de la liste.
[Link](y) Ajoute tous les éléments de la séquence y à la fin de la liste.
[Link]() Ordonne les éléments en ordre croissant.
[Link]() Inverse l’ordre des éléments.
[Link](i) Retourne l’élément à l’indice i et l’élimine de la liste.
[Link](x) Élimine de la liste la première occurrence de la valeur de x.
[Link](x) Retourne l’indice du premier élément égal à x.
[Link](x) Retourne le nombre d’éléments dont la valeur est égale à celle de x.
[Link](i,x) Ajoute l’objet x à la position d’indice i.
[Link]() Crée une copie de la liste.
Instruction Signification
Méthodes retournant une nouvelle chaîne
[Link]() premier mot commence avec une majuscule, tous les autres avec une
minuscules.
[Link]() première lettre de chaque mot en majuscule.
[Link]() remplace toutes les majuscules par des minuscules.
[Link]() remplace toutes les minuscules par des majuscules.
[Link](n,s) centre la chaîne dans une chaîne de longueur n, en comblant de
chaque côté avec le caractère s au besoin.
[Link](n,s) justifie la chaîne à gauche dans une chaîne de longueur n.
[Link](n,s) justifie la chaîne à droite dans une chaîne de longueur n.
[Link](n) complète la chaîne à gauche avec des 0 jusqu’à une longueur de n.
[Link](s) enlève la chaîne de caractères s apparaissant à la fin de la chaîne, le
cas échéant. Si s n’est pas défini, les espaces seront enlevés.
[Link](s) enlève s au début de la chaîne, le cas échéant.
[Link](s) enlève s au début et en fin de chaîne, le cas échéant.
[Link](a,b,n) remplace la sous-chaîne a par b, un nombre maximal n de fois. Si n
est omis, on remplace toutes les occurrences de a.
[Link](s) découpe la chaîne en morceaux aux occurrences de la chaîne s. Si s
n’est pas spécifié, le découpage se fait aux espaces.
[Link](seq) concatène les chaînes de la séquence seq en les intercalant avec la
chaîne ch.
[Link]() pour affichage formaté de variables, voir la sous-section suivante.
Méthodes de test d’une chaîne
[Link]() vrai si ne contient que des chiffres
[Link]() vrai si ne contient que des lettres
[Link]() vrai si ne contient que des chiffres et des lettres
[Link] vrai si tous les caractères sont des majuscules.
[Link] vrai si tous les caractères sont des minuscules.
[Link](ch1,i1,i2) vrai si ch[i1:i2] commence par la chaîne ch1. Si i1 et i2 sont omis,
on considère la chaîne au complet.
[Link](ch1) vrai si ch[i1:i2] termine par la chaîne ch1.
[Link](ch1,i1,i2) compte le nombre de fois que ch1 est répété dans ch[i1:i2].
[Link](ch1,i1,i2) retourne la position de la première occurence de ch1 dans ch[i1:i2].
qui sont décrites à la page web mentionnée ci-haut. Par exemple, le type “f” affiche un nombre réel sous
forme décimale avec un nombre fixe de chiffres après le point. L’élément precision doit être un entier et
indique le nombre de chiffres devant apparaître après le point décimal pour les nombres réels formatés avec
le type f, F, e ou E, ou le nombre total de chiffres (avant et après le point décimal) pour les nombres réels
formatés avec le type g ou G. Rien ne vaut des exemples pour comprendre, alors en voici quelques-uns :
1 >>> pi=3.141592653589793
2 >>> format(pi,'10.4f')
3 ' 3.1416'
4 >>> format(pi,'-010.4f')
5 '00003.1416'
6 >>> format(pi,'.4e')
7 '3.1416e+00'
8 >>> print('Le résultat est {}'.format(pi))
9 Le résultat est 3.141592653589793
10 >>> print('Le résultat est {:10.4f}'.format(pi))
11 Le résultat est 3.1416
12 >>> print('Le résultat est {:010.4f}'.format(pi))
13 Le résultat est 00003.1416
14 >>> print('Le résultat est {:+010.4f}'.format(pi))
15 Le résultat est +0003.1416
16 >>> print('Le résultat est {:-010.4f}'.format(pi))
17 Le résultat est 00003.1416
18 >>> print('Le résultat est {:.4e}'.format(pi))
19 Le résultat est 3.1416e+00
20 >>> print('Le résultat est {:.4g}'.format(pi))
21 Le résultat est 3.142
Certaines fonctions requièrent une série d’arguments positionnels. Ces différents arguments sont parfois
emmagasinés dans une liste ou un tuple et pour les transmettre à la fonction en question il faut taper
au long le nom de la liste suivi de crochets et du bon numéro d’élément pour chaque argument. Par
exemple, si la variable iterParam est une liste contenant les 3 valeurs nécessaires pour construire une
certaine itération désirée (e.g. 10, 21, et 2, resp. pour début, fin et pas), on devra écrire la commande
“range(iterParam[0],iterParam[1],iterParam[2])”. Étant généralement paresseux, on peut trouver
ceci trop fastidieux... Heureusement, il existe un petit truc pour expédier ceci : le déballage de séquence,
ou en anglais le “list unpacking”. L’opérateur “*” placé immédiatement devant une séquence en argument
d’une fonction permet d’en déballer les éléments et de les passer à la fonction en tant qu’objets individuels
formant une séries d’arguments positionnels. En voici un exemple :
1 >>> iterParam=[10,21,2]
2 >>> for i in range(*iterParam):
3 ... print(i)
4 ...
5 10
6 12
7 14
8 16
9 18
10 20
Ceci ouvre le fichier pour lecture/écriture comme auparavant, mais a l’avantage d’automatiquement fermer
le fichier à la fin de l’exécution du bloc d’instructions.
3.2.2 Lecture
Lorsqu’un fichier est ouvert pour la lecture, il existe plusieurs méthodes pour lire les données qu’il
contient. On peut lire tout le fichier d’un seul coup avec l’une des deux méthodes suivantes :
contenu=[Link]()
contenu=[Link]()
La première de ces commandes place tout le contenu du fichier dans une seule chaîne de caractères, alors
que la deuxième crée une liste de chaînes de caractères, chacun des éléments de celle-ci étant une ligne
individuelle du fichier. On peut aussi lire le fichier ligne-par-ligne avec la méthode .readline(), qui lit
une seule ligne à la fois. Enfin, on peut itérer sur les lignes avec une boucle for, par exemple en faisant :
Notez que les fins de lignes (i.e. les retours à la ligne) sont lues explicitement et retournent le caractère
“\n”. Si on veut les retirer, on peut facilement le faire en utilisant diverses méthodes présentées à la section
précédente (.split(’\n’), .replace(’\n’,”), ou .rstrip(’\n’)).
Par exemple, pour placer le contenu complet d’un fichier dans une liste dont chaque élément corres-
pondra à une ligne du fichier, et en éliminant les retours à la lignes, on pourra faire :
1 fic=open('[Link]','r')
2 lignes=[Link]().split('\n')
3 [Link]()
3.2.3 Écriture
Pour l’écriture, on peut utiliser l’une des deux commandes suivantes :
[Link](chaine)
[Link](listeChaine)
où chaine est une chaîne de caractères et listeChaine est une liste de chaînes de caractères. Notez que
toute écriture dans un fichier est séquentielle, c’est-à-dire que les éléments y sont ajoutés à la suite, au
fur et à mesure que l’on écrit dans le fichier. De plus, si on veut faire un changement de ligne en écriture,
il faut explicitement écrire le caractère de fin de ligne ('\n') dans le fichier. Par exemple pour écrire une
chaîne sur une ligne et terminer cette ligne, on peut faire,
[Link](chaine+'\n')
et pour écrire chaque élément d’une liste sur sa propre ligne on peut d’abord joindre tous ces éléments
ensemble en y insérant le caractère '\n' avec la méthode .join(), et ensuite écrire la chaîne résultante,
comme suit,
[Link]('\n'.join(listeChaine))
Notez comment l’effet des opérateurs + et * diffère ici de l’effet qu’ils auraient sur une liste. Plusieurs
autres opérations sur des tableaux NumPy agissent de façon similaire et sont optimisées de telle sorte
qu’elles s’exécutent beaucoup plus rapidement que si on les faisait sur des listes.
Les tableaux de type ndarray ont une taille fixe, établie lors de leur création ; il n’est pas possible de
les allonger ou les raccourcir comme on peut le faire pour les listes, à moins bien sûr de créer un nouveau
tableau (i.e. un objet différent). La taille d’un tableau est emmagasinée dans son attribut shape (pour
forme) ; cet attribut est de type tuple et chacun de ses éléments correspond au nombre d’éléments dans la
dimension correspondant à sa position. L’attribut size (pour taille), quant à lui, emmagasine le nombre
total d’éléments contenu dans le tableau. Enfin, le nombre de dimensions est emmagasiné dans l’attribut
ndim.
Aussi, tel que mentionné ci-haut, tous les éléments d’un même tableau ont le même type (tableau
homogène) ; la liste de tous les types numériques disponibles pour ces tableaux est donnée ici 2 , les prin-
cipaux sont int, float, complex et bool. Contrairement au type entier par défaut de Python, le type
entier de NumPy est représenté à l’aide d’un nombre fixe de bits (64 par défaut), leurs valeurs sont donc
limitées à un certain intervalle (±9223372036854775807 pour 64-bits). Le type des données contenues dans
un tableau est emmagasiné dans l’attribut dtype. Les exemples suivants illustrent ces différents concepts.
1 >>> v=[Link]([1,2,3,4,5])
2 >>> [Link]
3 (5,)
4 >>> [Link]
5 5
6 >>> [Link]
7 dtype('int64')
8 >>> v=[Link]([[1,2,3],[4,5,6]]) # tableau de 2 rangées et 3 colonnes
9 >>> v
10 array([[1, 2, 3],
11 [4, 5, 6]])
12 >>> [Link]
13 (2, 3)
14 >>> [Link]
15 6
16 >>> [Link]
17 2
Outre les tableaux homogènes, le module NumPy fournit aussi toutes les fonctions mathématiques
du module Math (sinus, round, exp, log, etc.) ; la liste complète des fonctions mathématiques est dis-
ponible ici 3 . NumPy fournit aussi plusieurs sous-modules pour des applications avancées, par exemple
[Link] pour la génération de nombres aléatoires, [Link] pour l’algèbre linéaire, ou [Link]
pour les transformées de Fourier discrètes.
Des manuels d’utilisation et de référence pour NumPy sont disponibles ici 4 et un tutoriel est disponible
ici 5 ; je vous encourage fortement à aller voir ce tutoriel.
forcer le type des éléments du tableau résultant en fournissant l’argument dtype (2e argument positionnel,
ou argument nommé), par exemple array(seq,’complex’) ou array(seq,dtype=’complex’).
On peut aussi créer des tableaux où tous les éléments ont la même valeur prédéfinie à l’aide des fonctions
ones() (valeur 1), zeros() (valeur 0) et full() (valeur spécifiée en argument). Pour ces fonctions, il faut
spécifier la forme (nombre d’éléments dans chaque dimension) par un argument. Alternativement, les
variantes de ces fonctions ayant le suffixe _like() permettent d’adopter directement la forme et le type
d’un autre tableau fournit en argument.
Enfin, on peut créer des tableaux contenant des intervalles de valeurs numériques à l’aide des fonctions
arange() et linspace().
La table 3.3 résume ces fonctions.
Table 3.3 – Principales fonctions de création de tableaux numériques NumPy
Fonction∗ Signification
À partir d’une séquence existante
array(seq) Convertit la séquence seq en tableau.
Avec assignations de valeurs prédéfinies
ones(shape) Tableau rempli de 1 de forme spécifiée par le tuple shape.
ones_like(a) Tableau rempli de 1 de la même forme que le tableau a.
zeros(shape) Tableau rempli de 0 de forme spécifiée par le tuple shape.
zeros_like(a) Tableau rempli de 0 de la même forme que le tableau a.
full(shape, v) Tableau rempli de valeurs v de forme spécifiée par le tuple
shape.
full_like(a, v) Tableau rempli de valeurs v de la même forme que le tableau a.
Intervalles numériques
arange([debut,] fin[, pas]) Intervalle allant de debut (inclusivement) à fin (exclusive-
ment) avec espacement de pas. Similaire à la fonction standard
range() mais retourne un tableau.
linspace(debut, fin[, num]) Exactement num valeurs espacées également dans l’intervalle al-
lant de de debut (inclusivement) à fin (inclusivement).
*
Toutes ces fonctions possèdent aussi l’argument positionnel/nommé dtype pour spécifier le type
de données voulu.
1 >>> v=[Link]((2,3))
2 >>> v
3 array([[ 1., 1., 1.],
4 [ 1., 1., 1.]])
5 >>> w=np.zeros_like(v)
6 >>> w
7 array([[ 0., 0., 0.],
8 [ 0., 0., 0.]])
9 >>> x=[Link](8.,12.5,5)
10 >>> x
11 array([ 8. , 9.125, 10.25 , 11.375, 12.5 ])
1 >>> x=[Link](10)
2 >>> x
3 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
4 >>> y=[Link]((2,5))
5 >>> y
6 array([[0, 1, 2, 3, 4],
7 [5, 6, 7, 8, 9]])
8 >>> [Link]()
9 array([[0, 5],
10 [1, 6],
11 [2, 7],
12 [3, 8],
13 [4, 9]])
14 >>> [Link]()
15 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
On peut accéder aux éléments d’un tableau de la même façon qu’on le fait avec une liste, c’est-à-dire
à l’aide de l’opérateur [i1:i2]. Si le tableau a plus d’une dimension, on utilise des virgules à l’intérieur
des crochets pour séparer les indices correspondant aux différentes dimensions. La correspondance entre
chaque élément d’un tableau à deux dimensions et leur indice selon chaque dimension est illustrée à la
figure 3.1 : le premier indice spécifie la rangée et le deuxième indice spécifie la colonne. À la même figure
sont aussi montrés quelques exemples de sélection de sous-section d’un tableau à deux dimensions.
Figure 3.1 – (Gauche) Correspondance des indices des éléments d’un tableau à deux dimensions. (Droite)
Illustration de l’effet de la sélection d’une sous-section d’un tableau avec l’opérateur [i1:i2] pour un
tableau à deux dimensions. Figures tirées du livre Python for data analysis, Wes McKinney, 2012.
1 >>> x=[Link](10).reshape((2,5))
2 >>> x
3 array([[0, 1, 2, 3, 4],
4 [5, 6, 7, 8, 9]])
5 >>> x[1,3]
6 8
7 >>> x[1,2:4]
8 array([7, 8])
9 >>> x[:,2:4]
10 array([[2, 3],
11 [7, 8]])
12 >>> x[1,[2,4]]
13 array([7, 9])
14 >>> a=[2,4]
15 >>> x[1,a]
16 array([7, 9])
17 >>> x[[1,0],[3,1]]
18 array([8, 1])
Notez que l’on peut remplacer le terme “i1:i2” par une liste d’indices, ce qui permet d’accéder à des
indices non-adjacents ; voir les exemples des lignes 12, 15 et 17 ci-haut. Si de telles listes sont fournies pour
plusieurs dimensions, alors elles doivent avoir la même taille, et le nombre d’éléments accédés correspondra
à la taille de ces listes.
1 >>> x=[Link](10)
2 >>> y=[Link](10,5,'int')
3 >>> x
4 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
5 >>> y
6 array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5])
7 >>> x*2
8 array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
9 >>> x+y
10 array([ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
11 >>> x*y
12 array([ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45])
13 >>> [Link](x)
14 array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ,
15 -0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849])
Plusieurs fonctions du module NumPy et plusieurs méthodes du type ndarray sont disponibles pour
faire des opérations plus complexes ; les principales sont présentées au tableau 3.4.
Pour comprendre l’idée générale de l’utilisation de PyPlot pour la production de graphiques, commen-
çons par un exemple simple :
7. [Link]
8. [Link]
9. [Link]
Table 3.4 – Principales fonctions/méthodes pour opérer sur des tableaux numériques NumPy
Fonction/méthode Signification
Méthodes∗
[Link]() Calcule la somme de tous les éléments de x.
[Link]() Calcule le produit de tous les éléments de x.
[Link](y) Calcule le produit scalaire de x avec y pour des tableaux 1D, ou
leur produit matriciel pour des tableaux 2D.
[Link]() Calcule la valeur minimum des éléments de x. Optionnellement, on
peut fournir un argument entier spécifiant la dimension sur laquelle
calculer le minimum.
[Link]() Similaire pour calculer la valeur maximum.
[Link]() Similaire pour calculer la valeur moyenne.
[Link]() Similaire pour calculer la déviation standard.
[Link](d) Arrondi chaque élément au nombre de décimales d spécifié.
[Link](amin,amax) Retourne un tableau où les valeurs plus petites que amin sont rem-
placées par amin et celles plus grandes que amax sont remplacées
par amax.
[Link]() Retourne l’indice de l’élément égal à la valeur maximum du tableau.
[Link]() Retourne l’indice de l’élément égal à la valeur minimum du tableau.
Fonctions
[Link](x) Arrondi chaque élément à l’entier inférieur (ne modifie pas le type).
[Link](x) Arrondi chaque élément à l’entier supérieur.
[Link](x) Calcule la valeur médiane des éléments d’un tableau.
[Link](x,y) Calcul le produit vectoriel de deux vecteurs.
[Link](x,y) Élément par élément, retourne le plus petit du tableau x ou y.
[Link](x,y) Élément par élément, retourne le plus grand du tableau x ou y.
[Link](condition) Retourne le tuple d’indices pour lesquels la condition est vraie.
np.logical_and(c1,c2) Opérateur logique and agissant élément par élément sur les ta-
bleaux booléens c1 et c2.
np.logical_or(c1,c2) Opérateur logique or agissant élément par élément sur les tableaux
booléens c1 et c2.
np.logical_not(c1,c2) Opérateur logique not agissant élément par élément sur le tableau
booléen c1.
*
Toutes ces méthodes possèdent une fonction équivalente. Par exemple [Link]() et [Link](x)
produiront le même résultat.
1 import numpy as np
2 import [Link] as plt
3
4 x=[Link](0.0,10.0,0.1)
5 y=[Link](x)
6
7 [Link](x,y)
8
9 [Link]([-0.5,10.5,-1.3,1.3])
10 [Link]('Abscisse')
11 [Link]('Ordonnée')
12 [Link]('Fonction sinus')
13
14 [Link]()
En bref, on importe le sous-module de fonction graphiques PyPlot (ligne 2), on prépare un graphique
(lignes 4–12), et on l’affiche à l’écran (ligne 14). Le code ci-haut produit le résultat montré à la figure 3.2.
Fonction sinus
1.0
0.5
Ordonnée
0.0
0.5
1.0
0 2 4 6 8 10
Abscisse
Figure 3.2 – Figure produite par le code montré ci-haut.
Examinons ces commandes un peu plus en détail. La fonction plot(x,y) crée une courbe spécifiée par
les vecteurs x et y sur un système d’axes à deux dimensions. La fonction axis([xmin,xmax,ymin,ymax])
permet de définir les limites des axes x et y du graphique. La fonction xlabel() permet de définir le titre
de l’axe des x, et ylabel() celui de l’axe des y. La fonction title() définit le titre du graphique. Enfin,
la fonction show() ouvrira une fenêtre montrant le graphique. Notez que lorsque la fenêtre d’affichage
graphique est ouverte, vous n’avez plus le contrôle de l’invite de commande Python (si vous êtes en mode
interactif) ou l’exécution est interrompue (si l’affichage est produit par un script), et ce sera ainsi tant que
la fenêtre graphique ne sera pas fermée. Pour éviter ce blocage, si on le veut, il faut fournir l’argument
block=0 à la commande show(), i.e. il faut utiliser show(block=0).
Notez que dans le corps du texte de cette section j’omettrai d’écrire le préfixe “plt.” devant chaque
fonction de PyPlot pour alléger le texte, mais souvenez-vous bien que ce préfixe doit être présent dans un
code si le module est importé avec la commande import [Link] as plt.
Les principales couleurs sont ‘b’ (blue), ‘g’ (green), ‘r’ (red), ‘c’ (cyan), ‘m’ (magenta), ‘y’ (yellow), ‘k’
(black) et ‘w’ (white).
Les principaux styles de traits sont ’-’ (plein), '--' (hachuré), ’-.’ (hachuré-pointillé), ’:’ (poin-
tillé) et ’ ’ (pas de trait).
Les principaux symboles sont ’.’ (point), ’,’ (pixel), ’o’ (cercle), ’v’ (triangle bas), '^' (triangle
haut), ’<’ (triangle gauche), ’>’ (triangle droite), ’s’ (carré), ’p’
(pentagone), ’*’ (étoile), ’h’ (hexagone1), ’H’ (hexagone 2), ’+’ (plus),
’x’ (x), ’D’ (losange), et ’d’ (losange étroit).
Pour plus d’aide sur cette fonction, incluant la liste complète des symboles, couleurs et arguments
nommés, consultez l’aide ici 10 . Sur cette page d’aide, vous verrez que les couleurs peuvent être spécifiées
de plusieurs façons, dont notamment par leur nom de couleur X11/CSS4 ; la liste de celles-ci est disponible
ici. 11
Graphiques avec axe(s) logarithmique(s) Trois fonctions analogues à plot() sont disponibles pour
tracer une courbe avec un ou deux axes logarithmiques : loglog() pour faire un graphique log-log,
semilogx() pour un graphique log-normal, et semilogy() pour un graphique normal-log. La syntaxe et
les arguments de ces fonctions sont identiques à ceux de la fonction plot(). Si ces fonctions sont utilisées
après un appel à plot() dans une même figure, alors les axes existants seront modifiés.
[Link](’[Link]’)
sauvegardera la figure dans un fichier au format PDF. Les formats de fichiers graphiques supportés
par Matplotlib sur le système esibac de l’UdeM sont : .png, .rgba, .svg, .eps, .ps, .pgf, .svgz,
.raw, .pdf. Il est possible que d’autres formats soient supportés sur vos ordinateurs personnels, pour voir
la liste vous pouvez taper la commande “print([Link]().canvas.get_supported_filetypes().keys())”.
La commande savefig([Link]) comporte plusieurs arguments optionnels ; vous pouvez
consulter sa référence ici 12 .
10. [Link]
11. [Link]
12. [Link]
Table 3.5 – Principales fonctions PyPlot pour modifier l’apparence d’un graphique
Fonction∗ Description
title(’titre’) Définit le titre du graphique.
xlabel(’x’) Définit l’étiquette de l’axe des x.
ylabel(’y]) Définit l’étiquette de l’axe des y.
xscale(’log’) Établit/modifie l’échelle de l’axe des x, ’log’, ’linear’. Pour log
on peut aditionnellement spécifier une base (base 10 par défaut).
yscale(’log’) Établit/modifie l’échelle de l’axe des y.
axes([xmin,xmax,ymin,ymax]) Définit les limites des axes x et y.
xlim(xmin,xmax) Définit les limites de l’axe des x. On peut aussi utiliser les argu-
ments nommés xmin= ou xmax= pour ne modifier qu’une limite.
ylim(ymin,ymax) Définit les limites de l’axe des y. Ou arguments nommés ymin= ou
ymax=.
axis(’equal’) Adapte les limites des axes pour forcer une échelle identique sur les
deux axes (pour qu’un cercle paraisse circulaire, par exemple).
axis(’scaled’) Adapte la taille du graphique pour forcer une échelle identique sur
les deux axes.
axis(’off’) Omet les échelles et les marques sur les axes.
xticks(pos[,noms]) Place les graduations de l’axe des x aux positions spécifiées par
le tableau/liste pos. Optionnellement, la liste noms définit les éti-
quettes de ces graduations.
yticks(pos[,noms]) Similaire pour l’axe des y.
tick_params() Permet de modifier plusieurs paramètres des graduations des axes.
minorticks_on() Affiche des graduations secondaires.
legend(loc=’best’) Ajoute une légende. La description de chaque courbe corresponde
à l’argument label passé à plot(). (location : ’best’, ’upper
left’, ’center right’, etc.)
axvline(x) Ajoute une ligne verticale sur le graphique à la position x spécifiée.
axhline(y) Ajoute une ligne horizontale sur le graphique à la position y spéci-
fiée.
text(x,y,s) Ajoute le texte de la chaîne s à la position (x, y).
*
La plupart de ces fonctions ont plusieurs autres paramètres optionnels, consultez leur référence ici 13 pour les
connaître.
print(r'allo\ttoi')
qui affichera à l’écran le résultat “allo\ttoi” (sans le r devant la chaîne, print('allo\ttoi') afficherait
le résultat “allo toi”). Une fois cette précaution prise, il suffit d’insérer dans la chaîne de caractères
des commandes mathématiques LATEX (entre deux caractères $ comme on le ferait en LATEX). La liste des
commandes mathématiques LATEX supportées par PyPlot est donnée ici 14 ; consultez cette page si vous
n’êtes pas familiers avec LATEX.
Le code montré à la page 44 fournit quelques exemples de commandes pour l’affichage de symboles et
d’équations ; le résultat correspondant est montré à la figure 3.3.
14. [Link]
1 import numpy as np
2 import [Link] as plt
3
6 [Link](1,figsize=(6.5,3.5),dpi=200)
7
15. [Link]
14
31 [Link]()
32 [Link](x,x**4,'b')
33 [Link](color='b',size='small')
34 [Link](r'$f(x)=x^4$',color='b',size='small')
35 plt.minorticks_on()
36
37 plt.tight_layout(pad=0.5,h_pad=0)
38
39 [Link]()
0.5 600
0.0 0.5 500
0.5
5 )
400
f(x) =sin(2πx
1.0
f(x) = x4
1.0 0.0
300
0.8
2
f(x) = e−(x−2.5)
Notez que l’on aurait pu obtenir le même résultat en utilisant des appels à axes([g,b,l,h]) plutôt
qu’à subplot() avec le code suivant :
1 #...
2 [Link]([0.106,0.571,0.345,0.385])
3 [Link](x, [Link](-x)*[Link](2*[Link]*x), 'bo')
4 #...
5
6 [Link]([0.106,0.147,0.345,0.385])
7 [Link](x, [Link](-([Link]()/2)**2), 'g--')
8 #...
9
10 [Link]([0.556,0.147,0.345,0.8])
11 [Link](x,[Link](2*[Link]/5*x), 'r')
12 plt.tick_params(labelsize='small')
13 #...
14 [Link]()
15 [Link](x,x**4,'b')
16 #...
La fonction gca() retourne une référence à l’objet correspondant aux axes courants. Pour savoir la
position et la taille des axes courants dans la figure on peut utiliser la commande print([Link]()), et
se baser sur ces valeurs pour positionner un autre système d’axes à notre guise.
1 import numpy as np
2 import [Link] as plt
3
4 t = [Link](-[Link],[Link],2000)
16. [Link]
17. [Link]
18. [Link]
19. [Link]
20. [Link]
5 r = (1+0.3*[Link](5*t))*(1+0.1*[Link](50*t))
6 x = r*[Link](t)
7 y = r*[Link](t)
8
21 [Link]()
0.0
0.5
1.0
1.5 1.0 0.5 0.0 0.5 1.0 1.5
x = r cos(θ)
Figure 3.4 – Figure produite par le code montré à la page 46.
Chapitre 4
Dans ce chapitre et deux autres à venir, nous allons voir comment effectuer numériquement sur ordina-
teur la série de grandes manoeuvres mathématiques qu’on vous a appris en calcul intégral et différentiel :
calculer des dérivées et intégrales (ce chapitre), solutionner des équations différentielles simples (chapitre
7), et trouver les zéros et extrema d’une fonction (chapitre 6).
Nous devrons en fait d’abord discuter le contexte mathématique qui est à la base du calcul numérique
des dérivées et intégrales, soit le développement en série de Taylor (§4.1). Nous examinerons ensuite
le processus de discrétisation d’une fonction, pour en arriver au calcul de formules dites de différences
finies pour le calcul numérique des dérivées (§4.2). Nous examinerons ensuite l’interpolation (§4.3), ce
qui paraitra être un détour mais nous fournira une base solide pour passer au calcul numérique des
intégrales (§4.4). Nous concluerons en généralisant toutes ces grandes manoeuvres aux fonctions de plus
d’une variable (§4.5).
∞
X (∆x)n dn f
= f (x0 ) + , (4.2)
n=1
n! dxn
n! ≡ n × (n − 1) × (n − 2) × ... 2 × 1 . (4.3)
La Figure 4.2 illustre l’idée générale d’un tel développement. La fonction développée est ici un polynôme
quartique de la forme :
f (x) = 1 + x4 . (4.4)
Figure 4.1 – La dérivée comme un procesuss de calcul de pente dans la limite ∆x → 0. Plus ∆x est
petit, plus la droite passant par x et x + ∆x a une pente se rapprochant de la tangente à f (x) au point
x0 (droite en tirets).
Les quatre autres courbes résultent de l’utilisation du développement (4.2), en ne conservant que 1, 2,
3 ou 4 termes, tel qu’indiqué sur la Figure. Ne conserver que le premier terme revient à dire que f (x)
assume une valeur constante :
Examinez bien la Figure 4.2, pour vous convaincre que le développement devient de plus en plus précis à
mesure que le nombre de termes retenus dans le développement augmente, ou que l’intervalle ∆x diminue.
Remarquez également qu’une fois les dérivées évaluées, le développement se retrouve à prendre la forme
d’un polynôme en puissances de ∆x.
La précision du développement peut évidemment être améliorée en subdivisant l’intervalle ∆x en sous-
pas, et en reévaluant les dérivées au membre de droite de (4.2) après chaque sous-pas. Un exemple est
présenté à la Figure 4.3, toujours pour notre polynôme quartique. Comparant cette Figure à la Fig. 4.2,
notez comment la précision de l’estimé de f (x0 +2∆x) d’ordre n est comparable à celui de l’estimé d’ordre
n + 1 quand le pas est deux fois plus grand.
Si ∆x → 0, chaque terme successif au membre de droite de l’éq. (4.2) est plus petit que le précédent
par un facteur ∆x, donc si ∆x ≪ 1 on peut s’attendre à ce que le développement soit dominé par les
Figure 4.2 – Développement en série de Taylor d’un polynôme quartique (trait noir), en ne conservant
qu’un, deux, trois ou quatre termes dans le développement, tel qu’indiqué. Le développement devient
de plus en plus précis à mesure que le pas ∆x diminue, et/ou le nombre de termes conservés dans le
développement augmente.
quelques premiers termes, et je vous laisse vérifier que l’équation (4.1) est obtenue en ne conservant que
les deux premiers.
Le développement en série de Taylor apparaitra de manière récurrente dans les chapitres qui suivent.
Pour l’instant, nous nous limiterons à la situation suivante : on cherche à évaluer de manière discrète les
dérivées d’une fonction continue d’une variable f (x). On suppose que cette fonction est calculable pour
tout x, mais a une forme telle qu’il nous est impossible d’en calculer la dérivée analytiquement.
Un maillage est dit régulier si les intervalles entre chaque xj successifs sont égaux :
L’évaluation de f (x) aux xj produit une ensemble de N valeurs de f , qui collectivement définissent la
discrétisation de f (x) :
Figure 4.3 – Semblable à la Figure 4.2, sauf que le pas ∆x est subdivisé en deux, et les dérivées
apparaissant dans le développement reévaluées à la position x0 + ∆x pour obtenir un estimé final à
x0 + 2∆x.
Cette procédure de discrétisation est illustrée à la Figure 4.4, pour une fonction parabolique discrétisée
sur un maillage régulier avec N = 8.
df fj+1 − fj
= + O(h) . (4.12)
dx xj h
Le terme en O(h) au coté droit de cette formule de différences finies indique que l’erreur de dis-
crétisation y étant associée décroit proportionnellement à h (≡ ∆x) dans la limite h → 0, conséquence
directe de notre omission ici des termes en (∆x)2 , (∆x)3 , ... dans l’éq. (4.2). Notez que cette approche de
troncation “brute” fonctionne pour la dérivée première, mais ne nous permet pas de produire de formule
équivalente pour la dérivée seconde. Voyons comment faire mieux.
Notre point de départ consiste à établir les deux développements suivants pour f (xj ± h) au voisinage
de xj :
df h2 d2 f h3 d3 f
fj+1 ≡ f (xj+1 ) ≡ f (xj + h) = f (xj ) + h + + + ... (4.13)
dx xj 2! dx2 xj 3! dx3 xj
df h2 d2 f h3 d3 f
fj−1 ≡ f (xj−1 ) ≡ f (xj − h) = f (xj ) − h + − + ... (4.14)
dx xj 2! dx2 xj 3! dx3 xj
Figure 4.4 – Discrétisation d’une fonction parabolique sur un maillage régulier, avec N = 8. Plus N est
grand, plus la discrétisation donne une représentation précise de la fonction.
De nouvelles formules de différences finies sont obtenues par manipulations algébriques simples des
éqs. (4.13)—(4.14) ; par exemple, en soustrayant l’équation (4.14) de (4.13) et en éliminant les termes
proportionnels aux puissance de h de trois et plus, on peut isoler ∂f /∂x et on obtient :
df fj+1 − fj−1
= + O(h2 ) . (4.15)
dx xj 2h
Et voilà pour la dérivée première. Maintenant, On peut aussi additionner les équation (4.14) et (4.13) et
isoler d2 f /dx2 , ce qui produit
d2 f fj+1 − 2fj + fj−1
= + O(h2 ) , (4.16)
dx2 xj h2
Par utilisation récursive des éqs. (4.13) et (4.14) on peut ensuite obtenir des formules de différences finies
pour les dérivées d’ordre plus élevées :
Qu’avons nous gagné ici ? Le terme en O(h2 ) aux cotés droits des éqs. (4.15)—(4.16) indique que l’erreur
de discrétisation associée à ces formules de différences finies pour la dérivée première décroit maintenant
proportionnellement à h2 dans la limite h → 0. Ces différences finies sont donc dites d’ordre 2. En
principe la formule (4.15) calcule la même chose que l’éq. (4.12), mais pour une maille donnée, est en
général plus précise.
Par d’autres manipulations des éqs. (4.13) et (4.14) et utilisation récurrente des formules d’ordre
O(h) and O(h2 ) ci-dessus, on peut obtenir des formules alternatives, caractérisées par des erreurs de
discrétisation d’ordre plus élevé :
Les formules de différences finies (4.15)—(4.20) sont dites centrées sur le noeud xj où la dérivée est
évaluée. Elles ne peuvent dont pas être utilisées sur le premier (j = 1) et dernier (j = N ) noeud de
la maille. Cependant d’autres manipulation algébriques des éqs. (4.13) et (4.14) peuvent produire des
formules de différences finies avant ou arrière. Les intéressé(e)s peuvent consulter les références citées
en fin de chapitre.
où la dérivée est évaluée à l’aide de nos trois différences finies pour la dérivée première, soit les équations
(4.12), (4.15), et (4.19). On remarque que dans tous les cas l’erreur diminue à mesure que h diminue, mais
que l’ordre de la formule utilisée détermine le taux auquel diminue l’erreur à mesure que h diminue :
puisque les axes sur la Fig. 4.5 sont logarithmiques. La figure 4.6 montre aussi l’erreur associée au calcul
de la dérivée de sin θ, mais cette fois sur la plage de x allant de 0 à 2π et pour différents pas de mailles,
en utilisant la formule de l’éq.(4.16). On remarque ici que l’erreur dépend non seulement de la taille du
pas de maille, mais aussi de la forme de la fonction à dériver au point x où le calcul est fait. Ici, l’erreur
est minimisée aux extrema de sin θ alors qu’elle est plus importante aux extrema de cos θ ; en fait avec la
formule utilisée on peut montrer que l’erreur est ∝ d3 f (x)/dx3 × h2 .
Les données requises pour construire les courbes sur la Figure 4.5 se calculent en Python à l’aide du code
ci-dessous (ici pour la formule O(h2 ) donnée par l’éq. (4.15)). Ce code utilise les fonctions mathématiques
prédéfinies sin(x) et cos(x), qui sont importées du module math au début du code. Il en est de même
pour la valeur de π (la variable pi), qui est définie dans le même module (et dans bien d’autres, comme
NumPy par exemple). Remarquez comment, à chaque itération de la boucle, la valeur du pas spatial h
utilisé pour le calcul de la dérivée décroit d’un facteur 1.1.
Code Python pour calculer les erreurs portées en graphique à la Fig. 4.5
"""
Ce programme calcule la derivee de sin(x), i.e. cos(x), par differences finies,
a une valeur de x donnee (pi/6) et pour differents pas de mailles.
10 0
10 -1
O(h)
10 -2
O(h 2 )
O(h 4 )
Erreur
10 -3
10 -4
10 -5
10 0 10 -1 10 -2
Taille du pas de maille (h)
Figure 4.5 – Variations de l’erreur de discrétisation en fonction de la taille du pas, pour l’évaluation
numérique de la dérivée de sin θ à θ = π/6, utilisant les formules de différences finies données par les
éqs. (4.15) (△), (4.12) (∗) et (4.19) (⋄). Notez bien que les deux axes sont logarithmiques, et que l’abscisse
est telle que h décroit de la gauche vers la droite.
"""
for i in range(0,niter):
deriv=(sin(t+h)-sin(t-h))/(2.*h) #formule O(h^2)
err=abs(deriv-cos(t)) #erreur
print("h:",h,"erreur:",err)
h/=1.1
Aussi à titre d’exemple, le code nécessaire pour réaliser la Figure 4.6 est montré ci-dessous.
10-1 1.0
10-2
0.5
10-3
Erreur absolue
10-4
sin(x)
0.0
10-5
10-6
0.5
10-7
10-8 0 1 2 3 4 5 6
1.0
Angle (rad)
Figure 4.6 – Variations de l’erreur de discrétisation (points) pour l’évaluation numérique de la dérivée
de sin θ en fonction de θ et de la taille du pas de maille, en utilisant la formule de différences finies donnée
par l’éq. (4.12), menant à une erreur ∝ h2 . Les pas de mailles sont de 2π/10 (bleu), 2π/50 (vert), 2π/100
(rouge), et 2π/1000 (turquoise). Notez bien que l’axe des y est logarithmique. Pour référence, la courbe
en bleu associée à l’axe y de droite montre la fonction sin θ.
import numpy as np
import [Link] as plt
npts=[11,51,101,1001]
for npt in npts:
x=[Link](0,2*[Link],npt)
h=x[1]-x[0]
y=[Link](x)
deriv=[Link](npt)
deriv[1:npt-2]=(y[2:npt-1]-y[0:npt-3])/(2.*h)
[Link](x[1:npt-2],abs([Link](x[1:npt-2])-deriv[1:npt-2]),'.')
[Link]('log')
[Link]('Angle (rad)')
[Link]('Erreur absolue')
[Link]([0,2*[Link],1.e-8,0.1])
[Link]()
[Link](x,[Link](x))
[Link]('sin(x)')
[Link]([0,2*[Link],-1,1])
[Link]('[Link]',bbox_inches='tight')
[Link]()
Dans le choix de l’une ou l’autre des formules de différences finies listées ci-dessus pour traiter un
problème numériquement, il est recommandé d’utiliser des formules ayant le même ordre de discrétisa-
tion pour toutes les dérivées quelque soit leur ordre. Il faut également garder en tête que les erreurs
de discrétisation associées aux différentes formules (O(h2 ), etc) ne tiennent vraiment que dans la limite
d’un maillage serré, i.e., h → 0 ; si le maillage spatial n’est pas suffisamment serré pour bien résoudre la
fonction discrétisée, l’éq. (4.20) produira des résultats probablement d’aussi piètre qualité que l’éq. (4.16).
Mais “suffisamment”, c’est combien ? Il n’y a pas de réponse unique à cette question ; mais en première
approximation on peut s’attendre à ce que h doive être pas mal plus petit que l’échelle sur laquelle varie
la fonction pour qu’un résultat soit crédible.
4.3 Interpolation
Dans bien des situations en sciences, incluant en physique, vous tomberez sur des séries de mesures
expérimentales tabulées qui devront être utilisées comme base à des calculs subséquents. Considérons par
exemple les “données” portées en graphique sur la Figure 4.8, qui montrent cinq “mesures” de la variation
d’une quantité f en fonction d’une variable x dont dépend f . Ceci est typique des plusieurs types de
mesures expérimentales ; on peut bien faire un grand nombre de mesures, mais on se retrouve toujours
avec un échantillonnage discret et fini de la quantité mesurée.
Supposons maintenant que sur la base de ces mesures, on vous demande de calculer la valeur que
prendrait f si x = 5 ; problème, car vous disposez de mesures à x = 4 et x = 7, mais pas à x = 5. Vous
vous trouvez donc dans l’obligation de supposer que f varie d’une manière choisie a priori entre x = 4 et
x = 7, et ainsi produire un estimé de f à x = 5. Ce genre de tâche numérique s’appelle une interpolation.
On pourrait aussi vous demander quelque chose à prime abord de complètement différent, par exemple
d’évaluer, sur la base des mesures expérimentales, une intégrale définie du genre :
Z 10
f (x)dx (4.25)
1
Comme f (x) n’a pas une forme analytique connue, vous devrez encore une fois supposer que f varie d’une
manière analytiquement intégrable entre les points où les mesures de f sont disponibles. C’est là le lien
fondamental entre l’interpolation et l’évaluation numérique des intégrales définies.
10 0
10 -1
10 -2
10 -3 O(h)
10 -4 O(h 2 )
O(h 4 )
Erreur
10 -5
10 -6
10 -7
10 -8
10 -9
10 -10 0
10 10 -1 10 -2 10 -3 10 -4 10 -5
Taille du pas de maille (h)
Figure 4.7 – Variations de l’erreur de discrétisation en fonction de la taille du pas, toujours pour le calcul
de la dérivée de sin(x) par différences finies. Les symboles donnent l’erreur associée au calcul des dérivées
avec une précision de 32-bits (float32) tandis que les traits plein donnent l’équivalent calculé avec une
précision de 64-bits (float64). Les traits pointillés délimitent l’intervalle couvert par la Figure 4.5
L’interpolation consiste à obtenir un estimé f ∗ de f (x∗k ) à un ensemble discret de P valeurs x∗k ne coincidant
habituellement pas avec les xk :
On exige habituellement que la fonction d’interpolation f ∗ (x) soit continue et exacte sur l’ensemble des
données étant interpolées, i.e.,
L’interpolation la plus simple qui satisfait à ces contraintes consiste à approximer la variation de f (x) entre
deux points successifs [xk , xk+1 ] par une droite. On décrit alors ceci comme une interpolation linéaire.
L’idée générale est illustrée à la Figure 4.9. Les points (xk , fk ) définissant la fonction à interpoler sont en
Figure 4.8 – Une série de données artificielles correspondant à des mesures d’une certaine quantité f (x)
en fonction d’un ensemble fini de valeurs x dont dépend cette quantité.
noir, et les valeurs interpolées indiquées en gris. Notons déjà que la fonction d’interpolation est l’union
ici de quatre tronçons linéaires contigus qui se rejoignent aux xk ’s ; la fonction d’interpolation est donc
continue, mais sa dérivé première par rapport à x ne l’est pas, puisqu’elle varie de manière discontinue
aux xk ’s, et toutes ses dérivées d’ordre supérieur sont nulles.
Une fois la fonction d’interpolation choisie, son évaluation à chaque x∗p est un processus en trois étapes :
1. trouver la plage d’interpolation, soit l’intervalle [xk , xk+1 ] tel que xk ≤ x∗p < xk+1 ;
2. Calcul des paramètres de la droite passant par (xk , fk ) et (xk+1 , fk+1 ).
3. Calcul de fp∗ ≡ f ∗ (x∗p )
Chercher la plage
Étant donné un tableau x de dimension N contenant les abscisses de la fonction à interpoler, le
petit bout de code suivant détermine le k associé à la plage d’interpolation [xk , xk+1 ] pour un point
d’interpolation xetoile :
k=len(x)-1
while xetoile<x[k]: k-=1
La façon de faire ci-dessus est simple, compacte et lisible, mais n’est pas terriblement efficace du point de
vue numérique ; on verra plus loin comment faire mieux.
Calculer la droite
Avec xetoile= 4.5, disons, nous travaillons dans la plage d’interpolation c. Une droite est définie
paramétriquement par la relation
y = mx + b , (4.30)
Figure 4.9 – Interpolation linéaire d’une fonction discrète définie par x = [1, 2, 4, 7, 9], f (x) = [1, 2, 3, 4, 7]
(N = 5, points noirs), aux points x∗ = [2.5, 4.5, 6.5, 8.5] (P = 4, points gris). La fonction d’interpolation
est ici définie par quatre plages d’interpolation, indiquées par a, b, c et d.
où m est la pente et b l’ordonnée à l’origine. Évaluons ceci aux points x3 et x4 qui définissent les limites
de la plage c :
Comme les x3 ,, x4 , f3 et f4 sont connus, ceci représente un système de deux équations pour deux inconnues,
m et b. La solution en est :
f4 − f3 4−3 1
m= = = , (4.32)
x4 − x3 7−4 3
f4 − f3 4 5
b = f3 − x3 = 3 − = (4.33)
x4 − x3 3 3
L’interpolation dans la plage c est donc définie par :
x∗ 5
f ∗ (x∗ ) = + , x3 ≤ x∗ ≤ x4 . (4.34)
3 3
Calculer l’interpolation
C’est ici trivial. On n’a qu’à évaluer l’éq. (4.34) à la valeur de x∗ désirée. Dans le cas des données de
la Figure 4.9, on aurait donc
N’oubliez surtout pas que l’évaluation de f ∗ à x∗1 = 2.5 et x∗4 = 8.5 doit se faire dans les plages b et d
respectivement, où la droite (4.34) n’est pas valide ! La code Python ci-bas effectue l’interpolation illustrée
à la Figure 4.9. Ici la fonction discrétisée fk ≡ f (xk ) est définie directement au début du code, mais la
valeur x∗ à laquelle l’interpolation est désirée est lue au moment de l’exécution, via la fonction input ;
une fois l’interpolation terminée, le programme se met en attente de la valeur suivante. Ceci est notre
premier exemple d’interaction entre le programme et l’usager durant l’exécution.
#l'interpolation
while 1:
rep=input('SVP donner une valeur de x (q/Q pour quitter): ')
if [Link]() in 'qQ': break
try:
xx=float(rep)
except ValueError:
print('Entree invalide!')
continue
if xx<x[0] or xx>=x[-1]:
print("Valeur hors de l'intervalle! Recommencer.")
continue
k=len(x)-1
while xx<x[k]: k-=1
m=(y[k+1]-y[k])/(x[k+1]-x[k])
b=y[k]-m*x[k]
yy=m*xx+b
print('Resultat: x={:6.4f}, f(x)={:6.4f}'.format(xx,yy))
Figure 4.10 – Interpolation quadratique par polynômes de Lagrange des données de la Figure 4.8. Avec
N = 5, on a ici deux plages distinctes d’interpolation, a + b et c + d, l’interpolation dans chacune étant
définie par un segment de parabole. Remarquez la discontinuité de la dérivée première à la fonction des
deux plages (x = 4), et le minimum secondaire produit dans la plage c.
polynomiale cubique (plage d’interpolation de 4 points), avec une contrainte supplémentaire de continuité
des dérivés premières et secondes aux jonctions des plages d’interpolation. La Figure 4.11 en montre
un exemple, toujours pour les mêmes données que précédemment. Comparant aux Figs. 4.9 et 4.10, on
constate que la fonction d’interpolation est maintenant beaucoup plus lisse. Il existe plusieurs autres
façons d’interpoler des valeurs échantillonnées discrètement. Parmi celles-ci, on trouve l’interpolation sinc
(ou de Whittaker–Shannon) et l’interpolation par convolution cubique, qui sont particulièrement efficaces
pour interpoler des valeurs issues de fonctions limitées en bande et suffisamment bien échantillonnées ;
nous n’entrerons pas dans les détails ici, mais ces techniques sont importantes dans les applications de
traitement de signal et de traitement d’images.
La majorité des logiciels de traitements de données ou de graphisme incluent généralement des fonctions
prédéfinies effectuant une interpolation par spline cubique. La théorie sous-jacente est expliquée dans la
plupart des bouquins d’analyse numérique (voir bibliographie en fin de chapitre). En Python, le sous-
module interpolate du module scipy, i.e. [Link], fournit plusieurs fonctions et outils
pour l’interpolation.
Comparant les Figures 4.9, 4.10 et 4.11, il est clair que le choix de la méthode d’interpolation peut
avoir un impact substantiel sur les valeurs interpolées. Le Tableau 4.1, listant les valeurs numériques de
ces trois interpolations aux mêmes x∗ confirme cette impression. Les différences, qui atteignent les 10%
dans plusieurs cas, n’ont rien à voir avec des imprécisions dans les données, des erreurs d’arrondissement,
ou tout autre truc du genre ; elles résultent entièrement du choix de la méthode d’interpolation. Laquelle
est la meilleure ici ? Difficile à dire...
En général, on pourrait penser que l’interpolation par splines cubiques est à préférer, si ce n’est
parce que cette technique d’interpolation assure la continuité des dérivées de la fonction interpolée aux
frontières des plages d’interpolation. Mais les splines ne doivent pas être utilisés aveuglément. La Figure
4.12 présente un exemple d’une situation où une interpolation en splines cubiques produit des résultats
plus que douteux. Ici la fonction à interpoler (points noirs) a l’allure une fonction escalier, avec un f (x)
Figure 4.11 – Interpolation par splines cubiques, à partir des mêmes données que sur les Figure 4.9 et
4.10. Les valeurs de f (x) aux points interpolés (gris) ne différent pas énormément de celles obtenues avec
l’interpolation linéaire, mais ici les dérivées première et seconde sont continues partout.
constant pour x ≤ 3, sautant à une valeur constante plus élevée pour x ≥ 4. Il est important de réaliser
que les données ne contiennent aucune information nous permettant de spécifier où exactement entre
x = 3 et x = 4 la discontinuité est située,... ou même si il y a vraiment une discontinuité dans l’intervalle,
par opposition à une variation très rapide mais continue en x.
Dans une telle situation l’interpolation par spline cubique produit une variation rapide et continue
entre x = 3 et x = 4, comme on s’y attendrait d’un polynôme d’ordre relativement élevé, mais dans les
deux intervalles voisins la contrainte de continuité de la fonction et de ses dérivées caractérisant les splines
cubiques produit des “sursauts” où f ∗ s’éloigne substantiellement de l’intervalle défini par les deux valeurs
constantes de la fonction (discrétisée) f étant interpolée ici. Dans un cas comme celui-ci, une interpolation
linéaire (tirets) serait probablement préférable.
Dernier commentaire de nature quasi-philosophique : qu’une interpolation se fasse par polynôme de
Lagrange ou par spline cubique, ultimement c’est de la tricherie ; il n’y a tout simplement pas d’information
entre les points (xk , fk ) où la fonction est échantillonnée ! Le choix d’un type d’interpolation ou d’un autre
est, en général, tout à fait arbitraire, et le plus souvent déterminé par nos préjugés et/ou attentes par
rapport au système ayant “généré” les données (xk , fk ).
Figure 4.12 – Exemple d’une situation où une interpolation linéaire (tirets) est très possiblement plus
appropriée qu’une interpolation par splines cubiques.
Figure 4.13 – Exemple d’une situation où le choix de l’une ou l’autre méthode d’interpolation (linéaire
et quadratique) conduit à des résultats divergents lorsque l’interpolation est utilisée pour extrapoler en
dehors (x∗ = 15) des plages d’interpolation (1 ≤ x ≤ 9). Les données (points noirs) sont les mêmes que
sur les Figures précédentes.
peu plus de 2 degrés C à presque 5◦ C. Les extrapolations ne sont pas contraintes, et donc évoluent selon
la dynamique interne (très complexe) de chaque modèle. Une différence de 3 degrés dans la température
moyenne terrestre peut paraitre mineure à prime abord, mais en pratique elle est en fait énorme : à +2◦ C,
il y a encore des écosystèmes arctiques ; à +5◦ C, non ; À +5◦ C Bangkok (population métropolitaine : 14.6
million d’habitants en 2010) est complètement sous l’eau ; à +2◦ C, pas encore. Maintenant vous savez
pourquoi, si vous commencez à parlez d’extrapolation à un(e) climatologue, vous allez sentir très vite le
niveau de stress monter...
4.4 Intégration
Passons maintenant au calcul numérique des intégrales, plus précisément des intégrales définies. De la
même façon que la dérivée d’une fonction en un point donné peut être interprétée comme la pente d’une
droite tangente à la courbe définie par la fonction en ce point, son intégrale définie entre deux points
peut être interprétée comme l’aire sous la courbe entre ces points. Tout comme la dérivée (voir éq. (4.1)),
l’intégrale définie peut être définie mathématiquement comme un passage à la limite ∆x → 0 :
Z b X
f (x)dx = lim f¯(xk )∆x (4.36)
a ∆x→0
k
où f¯(xk ) est une valeur représentative de f (x) sur le kième intervalle ∆x dans la plage [a, b]. L’idée de ce
passage à la limite conduisant à “l’aire sous la courbe” est illustrée schématiquement à la Figure 4.15.
Si la fonction f (x) est calculable pour tout x, le membre de droite de l’équation (4.36) peut être
directement utilisée pour évaluer l’intégrale, en choisissant un ∆x suffisamment petit (en se méfiant
toutefois des erreurs d’arrondissement...). Cependant, pour une fonction tabulée, comme sur la Figure
Figure 4.14 – Exemple concret d’une extrapolation, soit ici la température moyenne de la Terre jusqu’en
2100. Les prédictions ont été faites en 2007, à l’aide de huit modèles climatiques distincts, tous très “haut-
de-gamme”. Toutes les extrapolations sont calibrées sur la seconde moitié du vingtième siècle, et donc
y sont en excellent accord, mais divergent par la suite. Source : P.A. Rohde/The Global Warming Art
Project, téléchargé de [Link]
4.8, où la grandeur du ∆x est fixée, l’utilisation de l’éq. (4.36) peut conduire à des résultats plutôt
imprécis. Il est en fait assez facile de faire mieux.
L’idée derrière la méthode du trapèze est simplement de supposer que la fonction f (x) varie linéairement
entre les points où elle est échantillonnée ; l’aire sous le segment de droite reliant les points (xk , fk ) et
(xk+1 , fk+1 ) est :
(fk+1 + fk )
fk × (xk+1 − xk ) + (fk+1 − fk ) × (xk+1 − xk )/2 = (xk+1 − xk ) ,
| {z } | {z } 2
rectangle triangle
d’où :
Z xN N −1
X (fk+1 + fk )
f (x) dx ≃ (xk+1 − xk ) + O(h2 ) , (4.37)
x1 2
k=1
avec comme d’habitude fk ≡ f (xk ). Plus la maille est serrée, plus la somme discrète au coté droit de
cette expression approximera bien l’intégrale au coté gauche. Dit autrement, plus la maille est petite, plus
Figure 4.15 – Le passage à la limite ∆x → 0 dans le calcul d’une intégrale définie, correspondant à l’aire
contenue sous la courbe définie par f (x) entre les bornes d’intégrations a et b.
un développement en série de Taylor tronqué après le second terme offre une représentation adéquate de
la variation de la fonction dans l’intervalle d’intégration. L’erreur associée à la méthode du trapèze est
formellement O(h2 ). En Python, ça pourrait avoir l’air de ceci :
somme = 0.
for k in range(0,n-1):
somme+= 0.5*(f[k+1]+f[k])*(x[k+1]-x[k])
Ceci présuppose que les valeurs de f et x sur la plage désirée ont été déjà placées dans deux tableaux
1D chacun de longueur N . Notez l’utilisation d’une variable d’accumulation somme, qui, à la sortie de
la boucle, contiendra la valeur de l’intégrale. Il faut s’assurer d’initialiser cette variable à zéro avant le
début de la boucle calculant l’intégrale. Notez également que la boucle terminera à k = N − 2 plutôt que
N − 1, de manière à éviter un débordement de tableau sur évaluation de xk+1 et fk+1 à l’intérieur de la
boucle. Ce petit bout de code est une implémentation on ne peut plus directe de l’équation 4.37, mais elle
utilise une boucle for itérant sur tous les indices de f, et comme nous avons vu au chapitre 3, on devrait
éviter l’utilisation de telles boucles dans la mesure du possible. Donc réfléchissez un instant à une façon
de vectoriser ce petit bout de code, c’est-à-dire d’accomplir la même chose sans boucle for en exploitant
NumPy... est-ce possible ? Une fois votre réflexion terminée, continuez au paragraphe suivant.
Il est en fait assez simple de vectoriser l’intégration par la méthode du trapèze, la ligne suivante fait
exactement la même chose que le bout de code ci-haut :
somme = 0.5*[Link]( (f[1:]+f[:-1])*(x[1:]-x[:-1]) )
L’idée est la suivante. Le tableau f[1:] contient les valeurs f2 à fN de la fonction discrétisée (équivalent
aux éléments d’indices 1 à N − 1 du tableau f), et le tableau f[:-1] contient les valeurs f1 à fN −1
de la fonction discrétisée (équivalent aux éléments d’indices 0 à N − 2 du tableau f). Donc l’opération
f[1:]+f[:-1] calcule d’un seul coup, élément-par-élément, toutes les sommes fk+1 + fk pour k allant de
1 à N-1. Le calcul est similaire pour le tableau x. On fait ensuite le produit élément-par-élément des deux
Figure 4.16 – Calcul d’une intégrale définie par la méthode du trapèze. La fonction à intégrer (trait
noir) est ici un spline cubique basé sur les cinq valeurs discrètes indiquées par des points noirs.
tableaux résultants et enfin on somme le tout avec [Link](). Cette approche est de loin préférable à la
précédente du point de vue de la rapidité d’exécution.
Dans le cas particulier où la maille est régulière (i.e. incréments équidistants), il est inutile de calculer
chaque xk+1 − xk individuellement car on sait que ça donne toujours la même chose, on peut donc
sortir h ≡ xk+1 − xk de la somme et l’écrire comme simple facteur multiplicatif devant la somme sur
les (fk+1 + fk ), ce qui rend l’opération encore plus efficace. De plus, on peut voir que dans la somme à
effectuer, les valeurs f2 à fN −1 apparaissent chacune exactement deux fois alors que f1 et fN apparaissent
une fois. Donc, plutôt que d’additionner deux fois les f2 à fN −1 , on peut les additionner une seule fois
et multiplier par 2, ce qui rend l’opération encore plus efficace. Au final pour une maille régulière, on
obtient donc l’expression suivante pour l’intégration par la méthode du trapèze :
−1
Z xN N
!
h X
f (x) dx ≃ f1 + fN + 2 fk + O(h2 ) . (4.38)
x1 2
k=2
connues sous le nom de Règles de Simpson. Voir les références cités en fin de chapitre si vous désirez
approfondir le sujet.
où C est une constante qui est en fait une propriété de l’algorithme appliqué au problème donné, mais
pas du choix de la maille. Considérons maintenant une seconde solution obtenue sur une maille h2 ̸= h1 .
On aura encore une fois,
et c’est là la clé, avec le même C ! Les équations (4.39) et (4.40) forment un système de deux équations
pour deux inconnues, I ∗ et C ; ce système peut être solutionné pour produire une évaluation de I ∗ :
4.5.1 Discrétisation
La procédure de discrétisation d’une fonction continue introduite à la §4.2.1 se généralise facilement
à une fonction de plus d’une variable. Considérons par exemple une fonction f (x, y) de deux variables,
discrétisée sur une maille cartésienne régulière en deux dimensions spatiales (voir Figure 4.17) :
Une maille est dite cartésienne si toutes les lignes x = constante sont des droites parallèles les unes aux
autres, les y = constante le sont également, et chaque famille de droites coupe l’autre en formant des
angles droits ; autrement dit, comme sur du papier quadrillé ! On identifie chaque noeud du maillage 2D
par une paire d’indices (j, k), et on écrit pour la fonction discrétisée :
Je vous laisse imaginer comment ceci se généralise à des fonctions de plus de deux variables.
f (x, y) = 1 + xy − y 2 . (4.45)
Figure 4.17 – Discrétisation d’une fonction de deux variables f (x, y) sur un maillage cartésien régulier,
ici équidistant dans les direction x et y et avec N = M = 11. Le noeud (j, k) est indiqué en noir.
∂f fj,k+1 − fj,k−1
= + O(h2 ) , (4.50)
∂y xj ,yk 2∆y
où fj,k ≡ f (xj , yk ), et on a considéré que l’intervalle de maille en x (h = ∆x) puisse être différent de
l’intervalle en y (h = ∆y), ce qui est tout-à-fait légal sur une maille Cartésienne 2D. Notons que pour les
formules d’ordre plus élevé, les noeuds diagonaux (e.g. fj+1,k+1 ) font leur apparition. Voir les références
citées en bibliographie pour plus de détails.
et en particulier, si (x∗ , y ∗ ) est situé exactement au centre de la plage d’interpolation, la relation ci-dessus
se réduit à :
1
f ∗ (x∗ , y ∗ ) = (fj+1,k+1 + fj,k+1 + fj,k + fj+1,k ) , (4.56)
4
soit la moyenne des quatres valeurs de f aux quatre coins de la plage d’interpolation. Ce résultat nous
servira maintenant à établir un équivalent de la règle du trapèze pour une intégrale en deux dimensions
spatiales.
Figure 4.18 – Interpolation bilinéaire en deux dimensions spatiales. L’interpolation de f (x, y) au point
(x∗ , y ∗ ) revient au calcul d’une moyenne pondérée de f évaluée aux quatre noeuds définissant la plage
d’interpolation, avec une pondération donnée par la surface du rectangle diagonalement opposé à chaque
noeud, tel qu’indiquée ici par le code couleur.
intégrales de fonctions d’une seule variable est simple : cette dernière correspond à calculer “l’aire sous
la courbe” délimitée par les bornes d’intégrations ; pour une fonction de deux variables, l’intégrale par
rapport à ces deux variables correspond à calculer le “volume sous la surface” (2D) délimitée par les
bornes d’intégration, qui sont maintenant des lignes ou courbes dans le plan [x, y].
Notez que dans certains cas, les intégrales sont séparables et on peut tout simplement les évaluer
comme le produit de deux intégrales 1D, e.g.,
Z Z Z Z Z Z
x+y x y x y
e dx dy = e e dx dy = e dx × e dy , (4.57)
Supposons que la fonction f (x, y) à intégrer est définie sur une maille cartésienne régulière (comme sur
la Fig. 4.17). À partir de la fonction d’interpolation bilinéaire introduite ci-dessus, il est de nouveau
possible, par intégration directe, d’obtenir l’équivalent de la règle du trapèze applicable maintenant en
deux dimensions spatiales :
Z Z N −1 M −1
X X 1
f (x, y)dx dy = (fj,k + fj+1,k + fj,k+1 + fj+1,k+1 )(xj+1 − xj )(yk+1 − yk ) . (4.59)
j=1 k=1
4
Exercices:
Commencez par démontrez qu’à partir de la troncation à l’ordre 4, le développement devient exact
quelque soit la grandeur de ∆x. Ensuite,
(a) Calculez maintenant le développement de Taylor d’une fonction sinus :
f (x) = sin(x) ;
(b) Comparez la précision des estimés f (x0 + ∆x) par rapport à la valeur exacte pour x0 = π/6
(rad) et ∆x = 0.01 rad, pour les développements tronqués aux ordres 1 à 4.
(c) Démontrez qu’aucun ordre fini de troncation ne peut conduire à un estimé exact, contrairement
au cas du polynôme quartique.
2. Écrivez un petit code en Python qui calcule la factorielle d’un entier n (voir éq. (4.3)).
3. À partir des formules de différences finies centrées pour les dérivées première et seconde (éqs. 4.15)
et (éqs. 4.16)), obtenez les formules de différences finies centrées pour les dérivées troisième et
quatrième ordres (éqs. 4.17) et (éqs. 4.18)).
4. Établissez la forme analytique de l’interpolation linéaire d’une fonction f (x) entre deux points x1
et x2 où la fonction vaut f1 et f2 , respectivement. Intégrez cette interpolation par rapport à x entre
x1 et x2 , et vérifiez que vous retrouvez bien la règle du trapèze.
5. Vérifiez que la solution du système d’équations algébriques (4.39)—(4.40) conduit bien à l’éq. (4.41).
6. Considérez l’intégrale définie suivante :
Z 1
sin(2πx)dx ,
0
Répétant le calcul pour un maillage de plus en plus serré (avec ∆x = ∆y = h), déterminez si
cette formule d’intégration est d’ordre O(h), ou O(h2 ), etc., en mesurant l’erreur par rapport à la
solution exacte (facilement calculable ici puisque l’intégrale est séparable en x et y).
9. Par intuition, trouvez les fonctions d’interpolation requises pour une interpolation trilinéaire (i.e.,
d’ordre 2) d’une fonction de trois variables spatiales. Commencez par une comparaison attentive
des fonctions correspondantes en 1D et 2D.
Bibliographie:
Le calcul des formules de différences finies est traité dans tous les bouquins d’analyse numérique, in-
cluant Numerical Recipes cité en bibliographie au chapitre 1. J’ai gardé une préférence pour la présentation
du sujet faite à la §2.1 de
Lapidus, L., & Pinder, G.F. 1982, Numerical solution of partial differential equations in science and
engineering, John Wiley & Sons.
L’interpolation et l’intégration sont des sujets également traités dans tous les ouvrages de méthodes numé-
riques. Encore une fois le Numerical Recipes vaut la peine d’être consulté : chapitre 3 pour l’interpolation,
et chapitre 4 pour l’intégration. Les chapitres 3 et 4 de l’ouvrage suivant sont également une bonne
introduction :
Gerald, C.F. 1978, Applied numerical analysis, Addison-Wesley.
Sur l’interpolation, les matheux(ses) pourront consulter également le chapitre 2 de
Stoer, J., & Bulirsch, R. 1980, Introduction to numerical analysis, Springer.
Chapitre 5
Monte Carlo
Supposons que vous vous voyez assigner la tâche (ingrate) de mesurer la superficie d’un étang de
forme irrégulière. Vous pourriez obtenir une carte topographique de l’étang et de ses environs immédiats,
quadriller le tout, compter le nombre de petit carrés contenus dans l’étang, et multiplier ce nombre par la
surface d’un de vos petits carrés (voir Figure 5.1A). Ceci revient en fait à une forme d’intégration en deux
dimensions spatiales, d’une fonction qui (par exemple) vaut un dans l’étang, et zéro sur la terre ferme. À
examiner la Figure 5.1A, vous pouvez fort bien imaginer que la principale source d’incertitude ici est le
degré auquel votre quadrillage réussit à bien capturer la forme du contour de l’étang, et que le plus fin
sera votre quadrillage de l’étang, le plus précis sera votre détermination de sa surface.
Vous avez aussi une autre option ; délimiter une surface A facilement calculable (e.g., un carré) en-
globant l’étang ; lancez ensuite un nombre N de cailloux distribués de manière complètement aléatoire
spatialement, et comptez le nombre P de fois où vous entendez un “plouf”. L’idée est illustrée à la Figure
5.1B, pour N = 103 . Pour un N très grand, la surface de l’étang sera donnée simplement par (P/N ) × A.
Certains d’entre vous auront reconnu que cette approche à la mesure de la superficie de l’étang est
celle sous-jacente à notre calcul de π du chapitre 1. L’équivalent de l’étang y est un quart de cercle de
rayon unitaire, et l’estimé de π vient du fait que l’aire d’un cercle (complet) de rayon unitaire vaut π,
donc celle d’un quart de cercle vaut π/4, et la surface d’un carré englobant ce quart de cercle de rayon
6
unitaire vaut 1. La boucle lance 10p cailloux à des positions aléatoires (x, y) = (r1 , r2 ), donc la distance
radiale à l’origine est donnée par r12 + r22 ; si cette distance est plus petite que un, le caillou est tombé
dans le quart de cercle, et on ajoute un au compteur. La valeur finale de ce compteur divisée par 106
correspond donc au rapport de la surface du quart de cercle à celle du carré l’englobant ; il ne reste plus
qu’à multiplier par 4, et on a le nombre π. Voilà !
Cette approche statistique au calcul de quelque chose qui en principe pourrait se faire de manière
déterministe est appelée une méthode Monte Carlo. Dans ce chapitre nous nous limiterons à deux
applications spécifiques, soit le calcul des intégrales (§5.4), et une modélisation de type Monte Carlo
qui revient effectivement à la solution de certaine classes d’équations différentielles (§5.5). Une troisième
emergera “spontanément” au chapitre suivant (§6.6). Si vous avez suivi l’exemple de l’étang, vous vous
doutez déjà qu’un aspect essentiel de l’approche Monte Carlo est la génération de nombres aléatoires, que
nous devons d’abord régler avant de passer au Monte Carlo comme tel.
4−2−6−3−4−4−2−6−1−5−2−6 ,
6−6−1−2−3−3−5−6−3−6−2−3 .
Vous serez peut-être surpris de noter que la seconde séquence ne contient pas de “4”. La probabilité
de ne pas rouler 4 est 1 − 1/6 = 5/6 ; donc la probabilité de ne pas rouler un quatre 12 fois de suite
Figure 5.1 – Deux approches fondamentalement différentes à la mesure de la surface d’une surface de
forme irrégulière (en gris ici). À gauche, un quadrillage cartésien régulier, où la détermination de la surface
revient à compter le nombre de carrés couvrant la surface. À droite, une approche statistique où la surface
est donnée par la fraction du nombre de lancers aléatoires tombant sur la surface (voir texte).
est (5/6)12 = 0.112, ce qui est petit mais bien loin de l’astronomiquement minuscule (contrairement à
votre probabilité de gagner au 6-49, qui elle l’est). Moins évident mais tout aussi vrai, si chaque lancer
est vraiment entièrement indépendant des précédents, la probabilité d’obtenir exactement l’une de ces
séquences est (1/6)12 , soit un minuscule 4.6×10−10 , et exactement la même que celle d’obtenir la séquence
qu’une majorité d’individus jugeraient (incorrectement) encore plus improbable :
1−2−3−4−5−6−1−2−3−4−5−6 ,
ou encore
6−6−6−6−6−6−6−6−6−6−6−6 .
Eh oui ! Si vous roulez maintenant le dé un très grand nombre de fois (N , disons), on s’attendrait
à obtenir N/6 fois le “1”, N/6 fois le “2”, et ainsi de suite jusqu’à 6. C’est la définition même d’un dé
non-truqué ! Le dé est donc un générateur de nombres aléatoires, ici des entiers distribués uniformément
dans l’intervalle [1, 6].
Il s’agit maintenant de produire l’équivalent d’un dé utilisable sur l’ordinateur. Ceci peut paraitre un
non-sens absolu et total ; un programme d’ordinateur est déterministe au plus haut point, dans le sens
que sur une architecture donnée, l’exécutable d’un algorithme auquel on fournit la même entrée produira
toujours la même sortie. Donc, si on revient à notre séquence de lancers d’un dé, le résultat du n-ième
lancer sera toujours complètement déterminé par l’état du générateur après le n−1-ième lancer, autrement
dit, les valeurs numériques des lancers n − 1 et n sont complètement corrélées, l’antithèse même de notre
idée de l’aléatoire !
La solution à ce paradoxe tient au fait que même si les lancers de pseudo-dés numériques sont corrélés
à 100%, il est possible de s’assurer que, tout comme avec un vrai dé, chaque valeur sort en moyenne
aussi fréquemment que les autres, et que sur l’ensemble de la séquence aucune corrélation n’existe en la
valeur numérique d’un lancer et celle du suivant. Ce sont ces propriétés statistiques de la séquence qui lui
méritent le nom d’aléatoire (certains puristes insisteraient ici sur l’appellation “pseudo-aléatoires”.
Les bases théorique, arithmétique, statistique et informatique de la génération des nombres aléatoires
pourraient nous tenir occupés bien longtemps. Pour nos besoin présents, il suffit de réaliser que la géné-
ration de séquences qui satisfont aux propriétés énoncées ci-dessus est possible mais non-triviale, et que
certains générateurs sur le marché sont nettement meilleurs que d’autres.
Par exemple, le module random de Python fournit la fonction [Link]() qui produit un nombre
(pseudo-)aléatoire réel distribué uniformément dans l’intervalle [0, 1). Le sous-module random du module
NumPy fournit une fonction équivalente, [Link](), qui offre de plus l’option (à l’aide d’un argu-
ment) de générer plusieurs nombres aléatoires avec un seul appel. L’un ou l’autre de ces deux générateurs
suffira à tous nos besoins pour le cours. 1
Un générateur de nombre aléatoire doit toujours être initialisé à une valeur (arbitraire) pour débuter
le processus de production de la séquence aléatoire. Cette valeur est appelée le germe (ou en anglais :
seed). Sa valeur numérique importe peu, mais doit être fournie. Une fonction apparemment sans germe,
comme random(), utilisera en fait un germe déterminé au moment de l’importation du module qui contient
cette fonction, et sa valeur correspond alors généralement au temps système actuel. Il est parfois utile
(et souvent important !) de pouvoir générer des séquences qui soient distinctes et/ou qui puissent être
reproduites à une date ultérieure. En définissant plusieurs germes distincts, il devient donc possible de
produire des séquences de nombres aléatoires complètement indépendantes l’une de l’autre.
Une autre distribution avec laquelle vous avez probablement déjà fait connaissance est la distribution dite
normale (ou Gaussienne), notée N (µ, σ 2 ), pour laquelle la FDP est :
2
1 −x
f (x) = √ exp . (5.2)
2πσ 2σ 2
Notons que, par leur définition même, les FDP sont des distributions normalisées, dans le sens que
Z
f (x)dx = 1 . (5.3)
Une fois la FDP connue, elle peut servir à calculer une multitude de caractéristiques globales de l’enemble
de valeurs de x ; la moyenne, par exemple, est donnée par
Z
⟨x⟩ = f (x) x dx . (5.4)
Le hic est que la distribution statistique d’une séquence de N valeurs discrètes de x produites par un
générateur basé sur de telles FDP deviendra identique à ces FDP seulement dans la limite N → ∞.
Comme en pratique on travaille toujours avec un N fini, il est important de comprendre, et de quantifier,
jusqu’à quel point les deux distributions dévient l’une de l’autre.
Travaillons avec la distribution uniforme associée à la FDP (5.1), et supposons que l’on a produit N
valeurs numériques rn ∈ [0, 1] à l’aide notre générateur de nombre aléatoire uniforme. Définissons d’abord
la fonction histogramme associée à cet ensemble de nombre aléatoires. Ces fonctions sont construites
comme suit : on définit une séquence de M intervalles d’échantillonnage contigus et de largeur fixe b :
avec ici x1 = 0 et xM + b = 1. Puisque les intervalles sont contigus, on a évidemment b = 1/M . On fait
ensuite le décompte Nm de toutes les valeurs de r tombant dans chaque intervalle :
(
1 sixm < rn ≤ xm + b
Nm = Nm + , m = 1, ..., M , n = 1, ..., N (5.6)
0 sinon
De plus, dans la limite N → ∞, on s’attendrait à ce que Nm = N/M (= N b) valeurs tombent dans chaque
“compartiment”, donc on aura
démontrant bel et bien que la fonction histogramme est l’équivalent discret de notre FDP uniforme (5.1).
La Figure 5.2 montre des fonctions histogrammes construites à partir de séquences de nombres aléa-
toires produits par notre générateur uniforme, pour diverses valeurs de N . Dans tous les cas on a compar-
timenté ici l’intervalle [0, 1] en M = 20 intervalles d’échantillonnage. On voit immédiatement que plus N
est grand, plus les déviations par rapport à la valeur attendue hm = 1 sont petites. Changer l’initialisation
du générateur de nombre aléatoires changerait le détail de ces fonctions histogramme (i.e., quels hm se
retrouve au dessus ou au dessous de la valeur attendue), mais pas leur allure générale.
Essayons de quantifier un peu ce dernier énoncé. L’importance des déviations par rapport à la va-
leur moyenne d’un échantillon est souvent quantifiée par la variance de l’échantillon, aussi appelée
déviation quadratique moyenne, définie comme :
M
1 X
σ2 = (hm − ⟨h⟩)2 , (5.10)
M m=1
La quantité σ comme telle est habituellement appelée “déviation RMS”, pour “Root-Mean-Squared”. Sa
variation avec N est illustrée à la Figure
√ 5.4, toujours pour nos nombres aléatoires uniformes. On remarque
que la déviation RMS y varie en 1/ N . Cette situation n’est pas particulière à une FDP uniforme, mais
caractérise en fait tous les processus statistiques sans effets de mémoire, dans la mesure où, comme
quand on roule un dé ou on tire à pile ou face, chaque mesure tirée de la distribution est complètement
indépendante des précédentes 2 .
Une autre mesure souvent intéressante (et importante) de la grandeur des fluctuation dans les distri-
butions construites pour un N fini est la valeur moyenne de la distribution même :
N
1 X
⟨r⟩ = rn . (5.12)
N n=1
2. Comment réconciliez-vous cet énoncé avec le fait que notre générateur de nombre aléatoire est complètement détermi-
niste, dans le sens qu’une fois initialisé, la valeur du nombre aléatoire rn est complètement déterminée par celle du nombre
rn−1 ?
Figure 5.2 – Fonction de distribution des valeurs de séquences de N nombres aléatoires rn ∈ [0, 1], pour
quatres valeurs de N croissant par des facteurs 10 successifs. Les traits pointillés horizontaux indiquent
l’intervalle correspondant à ±1 déviations rms. Dans tous les cas la valeur attendue est f (x) = 1.
Figure 5.3 – Variation de l’écart rms (éq. (5.10)) sur la valeur attendue f (x) = 1, √en fonction du du
nombre N de candidats r produits. La droite en tirets indique une décroissance en 1/ N .
Figure 5.4 – Variation de l’erreur sur la moyenne de la distribution en fonction du nombre N de candidats √
r produits. L’erreur est définie ici selon l’éq. (5.14). La droite en tirets indique une décroissance en 1/ N .
Pour notre distribution uniforme dans [0, 1], on devrait évidemment avoir
1
lim ⟨r⟩ = , (5.13)
N →∞ 2
Donc on peut se définir ici une mesure d’erreur comme :
1
ε(N ) = ⟨r⟩ − . (5.14)
2
Si f (x) peut être intégré analytiquement, ceci offre une manière simple et efficace de produire des nombres
aléatoires extraits de distributions statistiques non-uniforme. Voyons comment ca marche avec un exemple
simple mais utile, soit la distribution exponentielle.
d’où on déduirait
Figure 5.5 – Illustration de la production d’une distribution non-uniforme en x à partir d’une distribution
uniforme en y, via une transformation nonlinéaire (voir texte). Si les y étaient des nombres aléatoires
∈ [0, 1] produits par un générateur du genre ran0 alors la distribution des x correspondants serait ici plus
concentrée aux petites valeurs de x.
Mais ici, comme la variable aléatoire 1 − r se distribue dans l’intervalle [1, 0] identiquement à la manière
dont r se distribue dans [0, 1], on peut tout aussi bien écrire :
La procédure pour générer un nombre aléatoire x respectant la distribution spécifiée par l’éq. (5.16)
est donc de générer un nombre aléatoire r uniforme entre 0 et 1, avec la fonction random() par exemple,
et ensuite de calculer x selon l’éq. (5.20).
Figure 5.6 – Fonction de distribution de probabilité de forme exponentielle, telle que décrite par
l’éq. (5.16), pour trois valeurs du facteur d’échelle (traits plein). L’histogramme est la distribution de
104 nombres aléatoires produits à l’aide de l’éq. (5.20) avec λ = 1.
p
g2 = −2 ln r1 sin(2πr2 ) , (5.22)
où g1 , g2 ∈ [−∞, ∞]. La Figure 5.7 en montre un exemple de la distribution produite ainsi, pour 104
nombres aléatoires calculés utilisant les relations ci-dessus. Cette procédure fonctionne, mais elle est loin
d’êre optimale au niveau du temps de calcul requis ; on peut en fait faire beaucoup mieux, mais je vous
laisse ça comme “lecture supplémentaire”.
En pratique, avec Python, vous n’aurez pas à appliquer cette méthode car une fonction pour générer
des nombres aléatoire selon une distribution Gaussienne de moyenne 0 et variance 1, i.e. N (0, 1), est déjà
fournie avec le module NumPy ; il s’agit de la fonction [Link](). Pour générer des nombres
aléatoires selon N (µ, σ 2 ), i.e. une distribution Gaussienne de moyenne µ et variance σ 2 , il suffit de faire
la commande suivante,
sigma * [Link](...) + mu
f (x) = 1 + x4 , (5.23)
Figure 5.7 – Fonction de distribution de probabilité de forme Gaussienne, telle que décrite par l’éq. (5.2),
pour trois valeurs de la largeur à mi-hauteur (traits plein). L’histogramme est la distribution de 104
nombres aléatoires produits à l’aide des éqs. (5.21)—(5.22).
f1 + f2 f2 + f3 f3 + f4 fN −2 + fN −1 fN −1 + fN
= + + + ... + + ×h
2 2 2 2 2
−1
N
!
f1 + fN X
= + fn ×h , (5.25)
2 n=2
où ici le pas de maille h = 1/(N − 1). Notons, et retenons, que le nombre d’évaluations de f requis ici
est égal à N . L’expression ci-dessus ressemble dangereusement au calcul d’une valeur moyenne de f (x)
échantillonnée uniformément sur l’ensemble de l’intervalle, et c’est bien là une interprétation habituelle
de l’intégrale comme une aire sous la courbe (valeur moyenne de f dans l’intervalle fois la longueur de
l’intervalle, ici un).
Il y aurait d’autres façons de calculer cette valeur moyenne. Plutôt que d’échantillonner f (x) réguliè-
rement en x, on aurait pu choisir d’échantillonner f à N points positionnés aléatoirement (de manière
statistiquement uniforme) en x dans l’intervalle [0, 1]. La valeur de l’intégrale serait alors donnée par
N
1 X
I= f (rn ) , rn ∈ [0, 1] , (5.26)
N n=1
Figure 5.8 – Diminution de l’erreur dans le calcul de l’intégrale définie par les éqs. (5.23) et (5.24) en
fonction du nombre N d’évaluation de f (x), pour la méthode du trapèze et l’intégration Monte Carlo, tel
qu’indiqué. La droite pointillée correspond à une variation en N −1/2 .
où rn est un nombre aléatoire extrait d’une distribution uniforme couvrant l’intervalle [0, 1]. Pourquoi
pas ? Vous commencez j’espère à percevoir le lien avec l’exemple de l’étang... En fait, cette approche est
appelée intégration Monte Carlo. Dans une forme un peu plus générale, la formule correspondante serait,
b N
b−a X
Z
I= f (x)dx ≃ f (rn ) , rn ∈ [a, b] , (5.27)
a N n=1
pour l’évaluation de notre intégrale par la méthode du trapèze (éq. (5.25)), et par la méthode Monte Carlo
(éq. (5.26)). La première converge en 1/N 2 , comme on s’y attendrait d’une méthode O(h2 ) √ ; la seconde
converge de manière beaucoup plus irrégulière, mais en moyenne comme un pitoyable 1/ N (droite
pointillée). Ici, la méthode Monte Carlo requiert en moyenne ∼ 105 évaluations de f pour en arriver à
ε = 10−3 , tandis que la méthode du trapèze atteint ce niveau d’erreur en seulement 17 évaluations de f .
Suis-je en train de vous faire perdre votre temps avec toutes ces histoires de Monte Carlo ?
Non. C’est certainement le cas qu’en une dimension spatiale, le Monte Carlo n’est jamais compétitif.
Mais en plusieurs dimensions spatiales, c’est une toute autre histoire. On peut montrer que l’équivalent
où α est la probabilité de passage par particule par unité de temps. C’est une quantité qui dépend de la
taille du trou, de la vitesse des particules, de leur taux de collision, mais pas de leur nombre, cette dernière
dépendance étant déjà explicitée au membre de droite. Et pour le nombre de particules passant de 2 à 1 :
avec la même valeur de α qu’à l’éq. (5.30). Pour avoir une situation stationnaire, il faudra qu’il n’y ait
plus de transfer net de particules d’une boîte à l’autre (sinon N1 et N2 varieront en conséquence, et donc
nous n’avons plus une situation stationnaire). Exiger que δN12 = δN21 nous donne alors immédiatement
la condition d’équilibre :
N1 = N2 , [equilibre] . (5.32)
Ce résultat concorde avec l’intuition. Supposons que les deux boîtes sont en fait deux classes remplies
d’étudiant(e)s, et que les particules sont en fait des macromolécules nauséabondes d’aftershave Irish Brut
45 Magnum dont un de vos petits camarades s’est trop généreusement aspergé le matin même, ayant
manqué de temps pour la douche. L’expérience montre que si l’on attend assez longtemps, ca puera
uniformément dans les deux classes, en d’autres mots le système a atteint un état d’équilibre où le nombre
de molécules puantes par unité de volume est le même partout. Voyons maintenant à quelle vitesse cet
état d’équilibre est atteint.
Figure 5.9 – Deux boîtes communiquant par un petit trou contiennent un total de N particules, mais
l’une (ici celle de gauche) en contient un nombre N1 plus grand que l’autre, soit N2 < N1 . Les collisions
contre les parois et interparticules feront que le nombre de particules traversant de la gauche vers la droite
sera plus grand que dans le sens inverse, et ce tant et aussi longtemps que N1 > N2 . La solution d’équilibre
est ici N1 = N2 = N/2 (voir texte).
Le premier terme au membre de droite est le nombre de particules déjà présentes dans chaque boîte au
temps t, le second terme correspond aux particules entrant dans la boîte, et le troisième aux particules
sortant de la boîte. Je vous laisse vérifier que la forme de ces expressions garantit que le nombre total de
particules est une quantité conservée, i.e., N1 (t) + N2 (t) = N . Un peu d’algèbre simple permet de ramène
ces expression à la forme suivante :
N1 (t + ∆t) − N1 (t)
= α(N2 (t) − N1 (t)) , (5.35)
∆t
N2 (t + ∆t) − N2 (t)
= α(N1 (t) − N2 (t)) . (5.36)
∆t
Examinez bien les membres de gauche de ces deux expressions. Vous réaliserez j’espère qu’ils ressemblent
fort à une différence finie d’ordre O(∆t) pour les dérivées temporelles de N1 et N2 . On peut donc écrire :
dN1
= α(N2 − N1 ) , (5.37)
dt
dN2
= α(N1 − N2 ) . (5.38)
dt
Il s’avérera pratique d’exprimer le nombre de particules dans chaque boîte en terme de la fraction du
nombre total N de particules :
N1 N2
f1 = , f2 = . (5.39)
N N
Notre contrainte de conservation N1 + N2 = N devient simplement
f1 + f2 = 1 , (5.40)
df1
= α(1 − 2f1 ) , (5.41)
dt
df2
= α(1 − 2f2 ) . (5.42)
dt
Remarquez que l’utilisation de la contrainte de conservation a effectivement découplé nos deux équations
différentielles, ce qui fait que nous n’avons plus qu’à en solutionner une, et la solution de l’autre s’obtient
trivialement via l’éq. (5.40). Remarquez également que si l’on pose f1 = f2 = 0.5, on obtient bien
df1 /dr = df2 /dt = 0, comme on s’y attend pour une solution d’équilibre.
L’équation (5.41) est une équation différentielle ordinaire linéaire mais inhomogène, du à la présence
d’un terme constant α au membre de droite. Un simple changement de variable permet cependant de
convertir ça en une équation différentielle homogène qui se solutionne facilement. Introduisons une nouvelle
fonction ξ(t) définie selon
1
ξ(t) = f1 (t) − ; (5.43)
2
comme ξ et f1 sont reliées l’une à l’autre par une relation linéaire, on aura
dξ df1
= , (5.44)
dt dt
ce qui permet de réexprimer l’éq. (5.41) sous la forme
dξ
= −2αξ , (5.45)
dt
qui est l’équation homogène promise. Ceci a évidemment comme solution :
où ξ0 correspond à la valeur de ξ à t = 0. Si l’on choisit comme condition initiale qu’à t = 0 toutes les
particules sont dans la boîte 1, alors f1 (t = 0) = 1, et on aura ξ0 = 1/2. On utilise finalement l’éq. (5.43)
pour exprimer ce résultat en terme de notre fonction primaire f1 :
1
f1 (t) = (1 + exp (−2αt)) . (5.47)
2
La solution asymptotique est bien
1
lim f1 (t) = , (5.48)
t→∞ 2
soit une répartition égale des particules dans les boîtes 1 et 2, atteinte exponentiellement avec une constante
de temps τ = 1/(2α) ; se rappelant que α est une mesure de la taille du trou, on voit bien que plus le
trou est petit, plus l’approche à l’équilibre est lente, ce qui est tout à fait logique puisque les particules
ont plus de difficulté à passer d’une boîte à l’autre.
3 import numpy as np
4
13 for i in range(niter):
14 de1a2=([Link](n1)<=alpha).sum() # nombre de particules passant de 1 a 2
15 de2a1=([Link](n2)<=alpha).sum() # nombre de particules passant de 2 a 1
16
Notez bien comment, aux lignes 14 et 15, on compte le nombre de particules qui traversent d’une
boîte à l’autre : on génère d’abord un tableau contenant un nombre aléatoire (uniformément distribué
dans l’intervalle [0, 1]) pour chaque particule dans la boîte donnée, on applique ensuite une vérification
logique élément-par-élément sur ce tableau pour déterminer ceux qui sont inférieurs à la probabilité α,
i.e. le critère pour qu’une particule traverse, et enfin on somme le tableau booléen résultant pour avoir
le nombre total de particules qui traversent (rappelez-vous que pour une opération arithmétique, True
est considéré comme 1 alors que False est considéré comme 0). Vous pouvez facilement voir que poser
α = 0 ferait qu’aucune particule ne traverserait (test ci-dessus jamais satisfait), et α = 1 au contraire
conduirait à un passage de toutes les particules en un pas de temps, puisque le test probabiliste serait
alors toujours satisfait. Notez aussi que le “transfert” de particules d’une boîte à l’autre se fait seulement
une fois que toutes les particules ont été testées, dans les deux boîtes, les nombres à transférer de la boîte
1 vers la 2 et de la 2 vers la 1 étant accumulées dans des compteurs (ici les variables de1a2 et de2a1). Si
on transférait les particules immédiatement après vérification de chaque boîte, alors l’ordre dans lequel
on teste les boîtes (ici la 1 avant la 2) aurait une influence sur l’évolution.
Pour les intéressés, la somme des “succès” d’une séquence de N essais gouvernés par un processus
aléatoire de probabilité de succès p est représentée par une distribution statistique binomiale, B(N, p), et
on aurait donc pu remplacer les lignes 14 et 15 du code ci-haut par les deux lignes suivantes, complètement
équivalentes,
de1a2=[Link](n1,alpha)
de2a1=[Link](n2,alpha)
La Figure 5.10 montre le résultat de trois réalisations Monte Carlo de l’approche à l’équilibre, pour
des systèmes de 103 , 104 , et 105 particules, toutes initialement plaçées dans la boîte 1. Les entiers N1 et
N2 ont été convertis en fraction f1 et f2 selon l’éq. (5.39), mais ici la nature discrète de la variable est
bien évidente, en particulier pour la solution à N = 103 (en rouge).
La forme exponentielle de l’approche à l’équilibre est facilement visible ici, mais on remarque aussi
des déviations irrégulières par rapport à une exponentielle “pure”. Le détail de ces variations est différent
pour une initialisation différente du générateur de nombre aléatoire, mais elles sont toujours présentes. On
remarque également sur la Figure 5.10 que plus N est grand, plus ces déviations sont petites. La Figure
5.11 montre la variation de la déviation RMS évaluée à quatre temps différents durant l’évolution, à partir
Figure 5.10 – Variation temporelle de la fraction de particules dans la boîte 1, pour un nombre total de
particules N = 102 (rouge), 103 (vert) et 104 (bleu). La variation correspondante de f2 est obtenue via
la contrainte de conservation f1 + f2 = 1, et correspond à une réflexion par rapport à la droite f1 = 0.5.
La décroissance est exponentielle dans les trois cas, mais les fluctuations diminuent rapidement à mesure
que N augmente (voir texte).
Figure 5.11 – Variation de l’écart relatif moyen en fonction du nombre total de particules N , associé
à des ensembles de 50 solutions statistiquement distinctes au problème de l’approche à l’équilibre. Les
différents symboles correspondent à des écarts mesurés à des temps différents, soit t = 50 (∗), t = 100 (+),
t = 150 (△) et t = 200 (⋄), avec t mesuré ici comme
√ le nombre d’itérations du processus Monte Carlo. Le
trait pointillé correpond à une décroissance en 1/ N .
d’un ensemble de 50 simulations statistiquement distinctes (i.e., utilisant une initialisation différente du
générateur
√ de nombres aléatoires). Ici encore, la fluctuation par rapport à la valeur moyenne décroit selon
1/ N , à n’importe quel temps durant l’approche à l’équilibre.
5.6.2 L’irréversibilité
L’intuition physique, et nos modélisations mathématique et statistique du problème, indiquent toutes
que dans la situation illustrée à la Figure 5.9 le transfert net de particules se fera de la boîte 1 vers la boîte
2, donc que si l’on photographiait ce système à un temps ultérieur le nombre de particules dans la boîte
1 aura diminué, et celui dans la boîte 2 aura augmenté par la même quantité. Vous seriez fort surpris si
la photographie révélait que la boîte 2 s’était vidée de ses quelques particules au profit de la boîte 1, et
vous seriez fort tenté de conclure qu’un petit comique a inversé l’ordre des photos.
Un phénomène comme celui que nous avons modélisé ici, pour lequel il est possible de distinguer
“intuitivement” l’ordre temporel d’une séquence de photos du processus en action, est dit irréversible.
Notre procédure d’échange stochastique de particules entre deux boîtes communiquantes est une représen-
tation très simple d’un processus de diffusion, phénomène sur lequel nous reviendrons au chapitre 9. On
y verra que le mouvements des particules les faisant éventuellement traverser le trou est un ensemble de
mouvements rectilinéaires entrecoupés de collisions (élastiques) avec les parois ou avec d’autres particules.
Comme vous l’avez déjà vu au CEGEP ou même au secondaire, une collision élastique entre deux corps
est un processus réversible ; si vous filmez la collision, que vous rejouez le film à l’endroit ou à l’envers,
rien ne vous permet de distinguer la direction temporelle de l’expérience originale.
Nous avons donc la situation suivante ; à l’échelle “macroscopique” du système (les boîtes), l’évolution
temporelle est irréversible. À l’échelle “microscopique” du système, le phénomène qui en gouverne la
dynamique (les collisions) est lui parfaitement réversible. Comment une séquence d’événements réversibles
peut-elle produire une évolution globale du système qui soit irréversible ? Ceci est une des questions les
plus fondamentales à laquelle vous aurez à faire face durant vos étude en physique, et elle vous tiendra fort
occupé(e)s pendant quelques cours entiers. Celà pourrait donc vous être utile de commencer à y cogiter
dès maintenant...
Exercices:
1. Écrivez un code utilisant l’approche Monte Carlo au calcul de la surface de l’étang (Fig. 5.1) pour
calculer le volume d’une “hypersphère” de rayon R en D dimensions ; une telle hypersphère est
définie par la relation :
x21 + x22 + x23 + ... + x2D = R2 ,
où les xk représentent les axes de coordonnées de votre hyperespace D-dimensionnel. Testez votre
code pour la sphère habituelle (D = 3), pour laquelle vous savez évidemment que V = 4πR3 /3.
2. La Loi dite de Gutenberg-Richter, établie empiriquement, nous informe que le nombre N de tem-
blement de Terre de magnitude entre m et m + dm varie selon la distribution :
N (m) = N0 m−1 , 1 ≤ m ≤ 10 .
Portez en graphique ⟨m⟩n versus n ; la moyenne est-elle une quantité statistiquement stable ici ?
Pourquoi ?
3. Calculez par Monte Carlo l’intégrale définie suivante :
p !
2 1 2 5/2 π 5/2
p
(u2 + x3 + y 2 + z 2 sin2 (2πv) cos( x2 + y 2 πw)
Z Z Z Z Z Z
3
+
1 0 −1 0 −π 1/2 2 (1 − sin(2v + z + π/2) cos(πy))2 exp(−(u + v + w)/3)
×(exp(−z w/2) − 1) du dv dw dx dy dz .
Le codage de l’intégrant devrait à lui seul représenter un excellent exercice... Ensuite,
(a) Portez en graphique le résultat en fonction du nombre d’évaluations Monte Carlo ; combien
d’évaluation sont requises pour obtenir un résultat stable au troisième chiffre significatif ?
(b) Appellons le nombre d’évaluation déterminé ci-dessus N ; comme il s’agit ici d’une intégrale en
six dimensions, calculer l’intégrale par méthode du trapèze généralisée à 6 dimensions avec le
même nombre N d’évaluations de l’intégrand imposerait donc une résolution de N 1/6 points
équidistants par dimension spatiale. Selon vous, considérant la forme de l’intégrand, est-ce
suffisant ?
Bibliographie:
Chapitre 6
Racines et optimisation
Dans ce chapitre nous allons développer quelques algorithmes numériques permettant de traiter deux
classes de problèmes mathématiques à prime abord distincts : la recherche de racines, et celle d’extrema.
Une racine r d’une fonction f (x) est définie comme une valeur r telle que f (r) = 0. Par exemple, une
relation linéaire y = mx + b a une seule racine à r = −b/m, tandis qu’un polynôme quadratique de la
forme,
y = ax2 + bx + c = 0 , (6.1)
6.1 La diffraction
Commençons par quelque chose que vous connaissez déjà, c’est toujours bon pour le moral. Vous avez
appris au CEGEP que la diffraction de la lumière par un orifice (comme une fente simple) peut être calculée
en invoquant le principe de superposition de Huygens, selon lequel les amplitudes des fronts d’onde associés
à une rangée de sources ponctuelles contiguës couvrant la fente s’additionnent linéairement, mais avec un
1. extrema : nominatif pluriel du latin extremum ; donc SVP pas de “s” à extrema (ou minima, maxima, etc). Et encore
moins à extremum, minimum, maximum, etc. Fin de la parenthèse culture générale...
Figure 6.1 – Géométrie du problème de diffraction par une fente simple de largeur d illuminée par une
onde plane monochromatique de longueur d’onde λ. L’intensité lumineuse est mesurée au point P sur un
écran placé derrière la fente.
déphasage relatif correspondant à la différence du chemin optique parcouru. La superposition des fronts
d’onde produits par un ensemble de sources ponctuelles situées le long de la fente peut donc conduire à
une interférence constructive ou destructive, partielle ou totale. Pour une longue fente de largeur d éclairée
par un faisceau lumineux monochromatique de longueur d’onde λ, la variation de l’amplitude A(θ) de
l’onde lumineuse mesurée à un angle θ par rapport à la ligne reliant la fente au centre d’un écran placé
parallèlement et à une distance L de celui dans lequel est découpée la fente (voir Figure 6.1) varie selon,
sin x
A(x) = , (6.3)
x
où la variable x est définie comme :
πd
x= sin θ . (6.4)
λ
Ce profil d’amplitude est porté en graphique sur la Figure 6.2 avec une représentation synthétique du
profil d’intensité (∝ A2 ) tel qu’il serait observé sur l’écran. L’alternance régulière de bandes brillantes et
sombres est caractéristique du phénomème de diffraction, qui n’est évidemment pas restreint aux fentes
rectilignes.
Les minima d’intensité se produisent aux endroits où l’amplitude totale de l’onde plane diffractée est
nulle. La dépendance sinusoïdale du numérateur de l’éq. (6.3) indique que ces minima apparaitront aux
positions telles que,
La question à savoir ce qui se passe à x = 0 se règle par la Règle de l’Hôpital, qui nous informe que
sin x cos x
lim = lim =1. (6.6)
x→0 x x→0 1
Figure 6.2 – Profil d’amplitude du patron de diffraction d’une fente simple (courbe). L’image du haut
montre l’intensité lumineuse associée, qui est proportionnelle au carré de l’amplitude de l’onde. Le para-
mètre x mesure effectivement la distance à partir du centre du patron de diffraction (voir eq. 6.4). Les
tirets pointillés indiquent la position des minima d’intensité, et ceux en trait plein la position des maxima.
Pour trouver les positions des pics d’intensité nous n’avons qu’à dériver le membre de droite de l’éq. (6.3)
par rapport à x, et exiger que le résultat soit égal à zéro :
d sin x cos x sin x
= − 2 =0. (6.7)
dx x x x
Sauf que voilà, aucune manipulation algébrique légale connue ne permet d’isoler le x dans cette expression.
En fait, l’équation (6.7) définit un problème de recherche de racine pour une fonction f (x) que l’on pourrait
écrire comme :
f (x) = x cos x − sin x . (6.8)
Alternativement, on pourrait diviser cette équation par cos x et obtenir,
f (x) = tan x − x , (6.9)
qui est une seconde forme de la fonction ayant les mêmes racines. On vous a peut-être même déjà fait
solutionner ça via une méthode graphique, en mesurant sur du papier quadrillé les points d’intersection
des fonctions y = tan x et y = x. Au sens strictement mathématique, les équations (6.9) et (6.8) sont
évidemment équivalentes. Mais du point de vue d’une recherche numérique de racine, la version définie
par l’éq. (6.8) est préférable à (6.9), car cette dernière diverge là où cos θ = 0, soit à un ensemble discret
de valeurs x = (n + 1/2)π, n = 0, 1, ... Voyons maintenant comment trouver numériquement les racines
de telles fonctions.
6.2 La bissection
Nous cherchons donc les racines de l’éq. (6.8). Supposons que nous pouvons établir a priori un intervalle
[x1 , x2 ], tel que f1 ≡ f (x1 ) et f2 ≡ f (x2 ) soient de signes opposés, dans lequel nous cherchons une racine
r devant être déterminée avec une précision donnée ε. Comme son nom l’indique, la méthode de la
bissection converge vers la racine par bissection successive de l’intervalle de recherche. L’idée de base ici
n’est rien de plus subtil que le fait que la racine de f (x) correspond ici au point où la fonction change
de signe. Étant donné un intervalle de départ [x1 , x2 ] et les valeurs correspondante f1 et f2 (de signes
opposés), on calcule un nouveau fm ≡ f (xm ) à mi-chemin entre x1 et x2 ; si fm est du même signe que
f2 , la racine doit être entre x1 et xm , et on reprend donc la bissection avec x2 = xm ; si par contre fm et
f2 sont de signes opposés, alors la racine est entre xm et x2 , et on reprend la bissection avec x1 = xm .
La valeur de xm correspond à l’estimé de la racine, et l’écart x2 − x1 à la précision absolue de cet estimé.
Un programme en Python effectuant une recherche de racine par bissection pour l’éq. (6.8) est donné
ci-dessous.
Code Python pour trouver une racine par bissection
1 #Ce programme calcule la racine d'une fonction d'une variable
2 #entre deux bornes x1 et x2, par la methode de la bissection.
3
14 f1=fct(x1)
15 f2=fct(x2)
16 if f1*f2>0: #verification
17 print("Fonction non valide ou aucune racine dans l'intervalle!")
18 delta=-1
19
La Figure 6.3 illustre l’application de la bissection dans le cas de notre problème de recherche d’ex-
tremum du patron de diffraction d’une fente simple, tel que décrit par l’éq. (6.8). Dans cette situation
le choix de l’intervalle de départ est facile, puisque, nous sommes en droit de supposer que l’extremum
est situé quelque part entre les premier et second minima, dont nous connaissons les positions : x = π et
x = 2π. La partie du bas de la Figure illustre l’évolution de la taille et position de l’intervalle de recherche,
les itérations allant du haut vers le bas. L’algorithme garantit que la largeur de l’intervalle de recherche
diminue comme 1/2n , où n est le décompte des itérations depuis le début du processus de bissection. Ceci
implique que le nombre total d’itérations requises pour atteindre une tolérance ε est donné par,
x2 − x1
n = log2 , (6.10)
ε
soit une quarantaine d’itérations pour ε = 10−12 et x2 − x1 ∼ 1.
La bissection offre plusieurs caractéristiques avantageuses. D’abords, cette méthode ne requiert que la
capacité d’évaluer la fonction f (x) à toute valeur de x, mais n’exige aucun calcul ou estimé de la dérivée
de cette fonction. De plus, elle garantit la détection d’une racine si une racine existe dans l’intervalle
spécifié. Remarquez cependant que si plusieurs racines se retrouvent dans l’intervalle initial, la méthode
convergera sur l’une d’elles sans donner d’indications que d’autres racines sont présentes. Si le changement
de signe entre x1 et x2 est dû à une discontinuité (comme pour tan x à x = (n + 1/2)π, n = 0, 1, ...), la
bissection vous trouvera la discontinuité sans rechigner.
Notons finalement un de ses désavantages, soit que cette méthode ne fonctionne que pour les racines
qui correspondent à un point où la fonction change de signe, et en tant que telle n’est pas applicable à
la recherche de racines pour toutes les fonctions, ou pour toutes les racines d’une fonction. Par exemple,
essayez de trouver la racine de la fonction (x − 0.3)2 dans l’intervalle [x1 , x2 ] = [0, 1] avec l’algorithme
ci-dessus, échec garanti !
df (∆x)2 d2 f (∆x)3 d3 f
f (x0 + ∆x) = f (x0 ) + (∆x) + + + ... (6.11)
dx x0 2! dx2 x0 3! dx3 x0
Si l’on suppose maintenant que f (x) a une racine à x = r, et que cette racine n’est pas trop éloignée
de x0 , alors on peut définir un ∆x tel que x0 + ∆x = r, et tronquer le développement au second terme :
df
f (r) = f (x0 ) + (r − x0 ) . (6.12)
dx x0
Mais le membre de gauche est nul si r est une racine, et donc r devrait être donné par
f (x0 )
r = x0 − . (6.13)
df /dx
Évidemment, la racine r ainsi calculée ne sera pas exactement égale à la racine réelle, puisque son calcul
résulte de l’utilisation d’un développement tronqué, ce qui introduit automatiquement une erreur dans
le calcul de la racine. Mais le processus peut être répété en réévaluant f et sa dérivée à x = r, et en
réutilisant l’éq. (6.13) pour recalculer un nouvel estimé de r, et ainsi de suite itérativement jusqu’à ce que
la racine ait été localisée au niveau de précision numérique désirée. Un programme en Python effectuant
une recherche de racine par la méthode de Newton est montré ci-bas, toujours pour notre problème de
recherche des maxima du patron de diffraction. Notez que l’on doit spécifier une valeur de départ pour
x0 , et que l’on doit définir deux fonctions, la première calculant f (x), et la seconde sa dérivée df /dx. Ce
code est une implémentation minimale de la méthode de Newton ; en réalité il faudrait au moins ajouter
un test pour limiter la grandeur du pas delta, pour se protéger de situations où l’on atterrit à une valeur
de x pour laquelle df /dx ≃ 0. Ici, on a aussi introduit une variable pour compter le nombre d’itérations,
que l’on limite à une certaine valeur spécifiée par l’usager.
Code Python pour trouver une racine par Newton
1 #Ce programme calcule la racine d'une fonction d'une variable
2 #par la methode de Newton.
3
Figure 6.4 – Les quatre premières itérations de la méthode de Newton pour la recherche de racines, ici
la racine r = 1 du polynôme quartique f (x) = x4 − 1.
11
La Figure 6.4 illustre les premières 4 itérations de ce processus, dans le cas de la recherche d’une
racine du polynôme f (x) = x4 − 1 ; ce polynôme compte quatre racines distinctes (±1, et ±i), et pour
une position de départ x0 = 1.8 la méthode de Newton converge très rapidement sur la racine r = 1. Tel
qu’illustré sur la figure, on notera que la méthode de Newton revient à utiliser la dérivée de f (x) pour
construire une extrapolation linéaire vers f (r) = 0.
La Figure 6.5 illustre le taux de convergence de la méthode de Newton (•), maintenant appliquée à la
recherche du premier pic (non-central) dans le patron de diffraction d’une fente simple. La convergence
est ici extrêmement rapide, avec l’erreur sur r chutant à 10−5 après seulement 2 itérations (et à ∼ 10−16
à la quatrième !). La bissection (△), en comparaison, converge beaucoup plus lentement.
On pourrait être tenté de conclure, après étude de la Figure 6.5, que la méthode de Newton devrait être
préférée à la bissection. En fait ce n’est pas le cas. La méthode de Newton est beaucoup plus rapide, mais
100
10-1
10-2
10-3
10-4
10-5
ε
10-6
10-7
10-8
10-9
10-10
0 5 10 15 20 25
itération
Figure 6.5 – Convergence de la méthode de Newton (•) et de la bissection (△) sur la recherche du
premier maxima du patron de diffraction d’une fente simple. Les symboles indiquent les erreurs exactes à
chaque itération, alors que les lignes indiquent les précisions estimées par les deux méthodes.
beaucoup moins robuste, que la bissection. Pour une fonction monotoniquement croissante ou décroissante,
sans trop de changement de signe de la dérivée seconde (comme pour le polynôme quartique de la Fig. 6.4),
la méthode de Newton est imbattable. Mais imaginez maintenant une fonction de forme plus complexe, et
où une itération de la méthode de Newton vous fait atterrir à une valeur de x où df /dx ≃ 0 ; l’éq. (6.13)
garantit que votre prochaine estimé de r se retrouvera quelque part dans la galaxie d’Andromède (à
moins d’avoir eu la géniale idée d’ajouter un test de divergence à l’algorithme de base listé ici). Ou même
avec une fonction telle que celle que nous avons considérée pour les pics d’intensité de diffraction, si vous
choisissez mal votre point de départ la méthode pourrait foirer (essayez par exemple d’utiliser x0 = π +0.1
comme point de départ plutôt que x0 = 1.5π). De plus, la méthode de Newton appliquée à une fonction
présentant un profil antisymétrique sur réflexion par rapport à la racine peut se coincer dans un cycle
non-convergent. Ces problèmes ne se posent pas avec la bissection.
Une stratégie très judicieuse consiste à débuter la recherche de racine à l’aide de la bissection, et une
fois la racine localisée approximativement, de passer à la méthode de Newton afin d’aller chercher deux
ou trois chiffres de précision supplémentaires sans trop d’efforts. Ou encore, effectuer une ou deux étapes
de bissection entre deux itérations de la méthode de Newton, si la convergence semble trop lente 2 . Ce
genre de méthode hybride est souvent optimal, et on verra qu’il peut l’être également en optimisation,
sujet des prochaine sections.
Vous comprendrez j’espère que trouver le ou les maxima d’une fonction f (x) est absolument équivalent
à trouver les minima de −f (x), en conséquence de quoi nous allons nous restreindre aux problèmes de
maximisation sans aucune perte de généralité.
Pour une fonction d’une seule variable et exprimable analytiquement, la maximisation est souvent
convertie en problème de recherche de racine, comme nous l’avions fait à la §6.1 pour la recherche des
maxima d’intensité du patron de diffraction produit par une fente simple. En plus d’une variable cepen-
dant, cette approche est rarement utilisée. Considérons par exemple le patron de diffraction du à une
2. Vous trouverez un algorithme de ce genre au chapitre 9 du Numerical Recipes.
Figure 6.6 – Le patron d’intensitée I(x, y) produit par la diffraction d’une onde plane à travers une
ouverture carrée, représenté sous la forme d’une surface dans le plan [x, y]. Le maximum est à I(0, 0)/I0 =
1.
ouverture carrée de coté d ; l’intensité I(x, y) est alors donnée par une expression du genre :
2
I(x, y) sin x sin y
= , (6.15)
I0 x y
avec x et y donnés par des expressions semblables à l’éq. (6.4). La Figure 6.6 illustre la surface correspon-
dant à cette fonction de deux variables. Un maximum dans ce “paysage” bidimensionnel correspond à un
point (x∗ , y ∗ ) tel que
∂I(x, y) ∂I(x, y)
∇I(x, y) ≡ êx + êy = 0 . (6.16)
∂x ∂y
Comme l’opérateur gradient est un opérateur vectoriel, chacune des deux dérivées doit valoir zéro sépa-
rément pour un maximum de I(x, y). La substitution de (6.15) dans (6.16) produit donc un système de
deux équations algébriques nonlinéaires couplées, où chacune des équations décrit une courbe ou famille
de courbes (souvent topologiquement complexe) dans le plan [x, y], et les maxima correspondent aux in-
tersections de ces familles de courbes. Trouver ca numériquement, c’est du costaud. Il est habituellement
préférable de rechercher directement les maxima. Voyons comment.
continue en x et y. Une fois remis sur pied, on explore le voisinage local du point (x, y) et on se déplace
dans une direction ascendante (en C). Le processus est répété séquentiellement jusqu’à ce qu’on se re-
trouve à un point (x∗ , y∗ ) où toutes les directions partant de ce point soient descendantes. Le maximum
est alors localisé (D). Caramba ! Pour traduire la Figure 6.7 en un algorithme exprimable en Python, nous
devons d’abord faire plusieurs choix pratiques :
1. Comment choisir la position de départ ;
2. Comment calculer la direction du déplacement ;
3. Comment calculer la grandeur du pas ;
4. Comment utiliser l’information accumulée aux pas précédents ;
5. Comment décider que le maximum est bel et bien localisé.
Ce sont les différentes réponses possibles à ces questions qui sont responsables de l’effrayante multipli-
cité d’algorithmes disponibles dans les bouquins. Nous allons ici en développer un qui est simple à coder,
relativement robuste, et facilement généralisable à des problèmes de maximisation pour des fonctions de
plus de deux variables. Il est basé sur l’utilisation du gradient de la fonction à maximiser pour choisir la
direction du déplacement. Le gradient d’une fonction scalaire (comme dans le cas de l’équation (6.16))
indique la direction de pente maximale dans un espace des paramètres dont ne nombre de dimensions
correspond au nombre de variables indépendantes de la fonction à maximiser. Dans les cas de la diffraction
de la lumière par une ouverture carrée, le gradient est un vecteur contenu dans le plan [x, y], dont les
composantes x et y sont données par ∂I/∂x et ∂I/∂y, avec I(x, y) donné par l’éq. (6.15). À partir d’une
position (x0 , y0 ), on pose simplement
δ ∂f δ ∂f
x = x0 + , y = y0 + , (6.17)
|∇f | ∂x |∇f | ∂y
est la norme du vecteur gradient. Si vous ne l’avez pas déjà deviné, cet algorithme est l’équivalent d’un
double développement en série de Taylor, tronqué aux seconds termes en x et y ! Les composantes du
gradient peuvent être calculées soit analytiquement, soit par différences finies ; par exemple, à l’ordre
O(h2 ),
∂f f (x + δ, y) − f (x − δ, y) ∂f f (x, y + δ) − f (x, y − δ)
= , = . (6.19)
∂x 2δ ∂y 2δ
Le bout de code Python ci-dessous utilise cet algorithme pour la recherche des maxima du patron de
diffraction d’une ouverture carrée, avec évaluation des composantes du gradient par différences finies
centrées. Étudiez bien ce bout de code, et notez bien qu’un test a été ajouté pour réduire la grandeur
du pas δ d’un facteur deux si le pas conduit à une position où f (x, y) < f (x0 , y0 ), afin d’empêcher
de sauter par dessus le maximum et/ou de se coincer dans un cycle non-convergent. Examinez bien, et
comprenez, le canevas if ... else ... contrôlant l’acceptation ou le rejet du pas (et de la nouvelle
valeur de x) en fonction des valeurs de la fonction produite à la nouvelle position. Notez aussi que dans ce
code le dénominateur des expressions de différences finies ont été omis du calcul des gradients, car ceux-ci
s’annulent exactement lors du calcul de la nouvelle valeur de la coordonnée par l’éq. (6.18).
Code Python pour trouver un maximum par la méthode de grimpe
1 #Maximisation d'une fonction de deux variables par methode de grimpe
2
La Figure 6.8 montre les trajectoires conduisant aux maxima, pour une série de points de départ
(x0 , y0 ) (indiqués par des • colorés) choisis aléatoirement. L’espace des paramètres a été restreint ici à
−π ≤ x ≤ π, −π ≤ y ≤ π, soit le pic central d’intensité du patron de diffraction 2D, et les pseudo-cercles
en noir correspondent à des courbes de niveau I(x, y) = constante. Quel que soit le point de départ,
l’algorithme aboutit à (0, 0), comme il se doit. Dépendant du (x0 , y0 ) initial, de 10 à 20 itérations sont
requises pour que l’erreur sur l’évaluation de f (0, 0) = 1 atteigne 10−4 ou moins. De toute évidence, ça
marche !
Mais il ne fait pas trop vite crier victoire... La Figure 6.9 montre le résultat de notre méthode de grimpe,
avec l’intervalle de recherche maintenant élargi à −2π ≤ x ≤ 2π, −2π ≤ y ≤ 2π. Il y a maintenant 50
points de départ, tous choisis aléatoirement mais distribués uniformément dans l’intervalle. Dans tous
les cas la méthode de grimpe fait son travail et livre la marchandise, dans le sens que les grimpes se
terminent toutes sur un maximum, mais dans bien des cas il ne s’agit pas du maximum le plus élevé
à (0, 0), mais plutôt de l’un ou l’autre des 8 autres maxima secondaires présents ici dans l’espace des
paramètres. Le maximum sur lequel converge la grimpe devient fonction de sa position de départ, une
situation embarrassante puisque dans bien des cas nous n’avons pas idée de la position du maximum
global, et donc ne sommes pas en mesure de fournir à l’algorithme un “bon” point de départ.
Les Figures 6.8 et 6.9 offrent conjointement une excellente illustration de la distinction entre un
problème d’optimisation locale versus l’optimisation globale. Cette dernière, qui consiste à trouver le
maximum absolu dans tout l’espace des paramètres, est beaucoup plus coriace numériquement. À l’heure
actuelle il n’existe aucun algorithme qui garantisse la détection d’un maximum global.
La grimpe itérée est une approche simple permettant d’aborder l’optimisation globale, et en fait son
mode de fonctionnement est déjà illustré sur la Figure 6.9. La grimpe itérée consiste simplement à faire
rouler à répétition une méthode de grimpe, chaque fois avec un point de départ différent (habituellement
choisi aléatoirement), et on accumule une liste des maxima ainsi détectés, et ce jusqu’à ce qu’on ne trouve
plus de nouveau maximum. Décider quand arrêter est évidemment l’aspect crucial de cette approche. Dans
bien des situations réelles, le calcul de la fonction f et/ou de ses dérivées peut en soi être une opération très
couteuse numériquement, et donc il existe souvent des limites purement pratiques au nombre de grimpes
successives qui puissent être effectuées.
On peut imaginer que la grimpe itérée puisse être accélérée si les grimpeurs ont accès à “l’information”
accumulée par les grimpeurs antérieurs relative à la structure de l’espace des paramètres, ou encore si les
Figure 6.8 – Application de notre méthode de grimpe à la recherche du pic central du patron de diffraction
d’une ouverture carrée. Les traits noirs sont des courbes de niveau de la fonction à maximiser, soit
l’éq. (6.15) ; son maximum est à (x∗ , y∗ ) = (0, 0), où I(x, y)/I0 = 1. Les points colorés correspondent à 10
positions de départ (x0 , y0 ) choisies aléatoirement, et les trajectoires en émanant résultent de l’application
répétée des éqs. (6.17), avec ajustement du pas δ (voir texte). Quel que soit le point de départ, l’algorithme
localise sans difficulté le maximum recherché.
Figure 6.9 – Même chose que la Figure 6.8, sauf que l’espace des paramètres couvre maintenant l’inter-
valle −2π ≤ x ≤ 2π, −2π ≤ y ≤ 2π, et 50 points de départ ont été choisis aléatoirement. Plusieurs de ces
points de départ convergent vers un maximum secondaire.
grimpeurs peuvent communiquer entre eux durant la grimpe. On verra comment au chapitre ??.
La Figure 6.10 montre le résultat de 10 grimpes stochastiques dans le cadre de notre désormais familier
problème de recherche du maximum central du patron de diffraction d’une ouverture carrée. Pour des
points de départ choisis aléatoirement entre −3π et +3π, on s’attendrait à ce que la grimpe classique
produise un taux de succès de ∼1/10. Avec la grimpe stochastique, on obtient ici un très honorable
8/10. Remarquez comment certaines solutions se “coincent” pendant quelques itérations sur un extremum
secondaire, mais finissent par atterrir sur le pic central suite à l’action d’un pas de taille substantiel ; on
voit ici l’avantage d’avoir utilisé une distribution gaussienne de taille de pas ; bien que leur probabilité soit
faible, de temps en temps un pas de taille substantiellement plus grand que la variance de la distribution
sera produit, ce qui permet de mieux échantillonner l’espace environnant, tout en ne freinant pas trop la
convergence vers l’extremum (qu’il soit local ou global).
Pour ce problème d’optimisation de dimension 2, la grimpe stochastique n’est pas particulièrement
avantageuse par rapport à la grimpe itérée, du point de vue du nombre requis d’évaluations de f (x)
pour en arriver à un niveau de précision donné. Comme dans le cas de l’intégration, l’approche Monte
Carlo à l’optimisation ne devient vraiment compétitive que pour des espace de recherche de plus haute
dimension. La situation est comparable à celle que nous avions rencontré avec le calcul des intégrales par
Monte Carlo ; évaluer numériquement les composantes du gradient d’une fonction de N variables via une
différence finie centré d’ordre deux requiert 2N évaluations de la fonction.
Figure 6.10 – Recherche du maximum central du patron de diffraction 2D par grimpe stochastique (ici
itérée sur plusieurs positions de départ). Remarquez comment certaines grimpes se coincent temporaire-
ment sur un extremum secondaire, mais parviennent à s’en dépêtrer suite à la production d’un pas de
longueur substantielle. Le taux de succès est ici de 8/10, ce qui se compare très avantageusement à la
grimpe itérée, pour laquelle on s’attendrait à ∼1/10 pour le même espace de paramètres.
Exercices:
1. Le code minimal pour la bisection présenté à la §6.2 peut être restructuré de manière telle qu’un
seul appel à la fonction fct(x) soit requis par itération. Faites-le.
2. Écrivez un petit bout de code qui, à partir d’un vecteur x contenant N éléments ordonnés de
manière croissante, utilise l’idée de la bissection pour localiser la plage d’interpolation associée
à une valeur x∗ . Démontrez que le nombre d’itération requis pour trouver cette plage varie en
moyenne comme log2 N , dans la limite où N est grand.
3. Écrivez une version du code pour la recherche de racine par la méthode de Newton qui s’assure
que la racine demeure à l’intérieur d’un intervalle [x1 , x2 ] spécifié par l’usager (comme pour la
bissection).
4. La méthode de grimpe, telle que codée ci-dessus, risque fort de vous péter au nez vers la fin du
processus de convergence, puisque très près du maximum on s’attend à ce que ∇f → 0, ce qui
conduira à des trucs du genre zéro-divisé-par-zéro. Ré-écrivez l’algorithme en terme d’un angle
décrivant l’orientation du gradient dans le plan [x, y]. Est-ce-que cela fonctionne mieux que l’algo-
rithme original ? Pourquoi ?
5. Modifiez le code pour la grimpe stochastique (Figure ??) pour en faire un code de grimpe stochas-
tique itérée, avec points de départ de chaque grimpe choisis aléatoirement.
Bibliographie:
Sur la recherche de racine et l’optimisation, je ne peux vraiment faire guère mieux que vous recom-
mander les chapitres 9 et 10 de l’ouvrage Numerical Recipes.
Pour tout ce qui concerne la modélisation de données, les erreurs de mesure, les χ2 , etc, je trouve l’ou-
vrage suivant absolument magnifique à tous les points de vue, allant de la photo en couverture jusqu’aux
aspects plus “philosophique” du sujet :
Taylor, J.R., An introduction to error analysis, University Science Books (1982).
Chapitre 7
7.1 Repenser F = ma
Plus vous progresserez dans vos études de la physique, plus vous verrez ses grands principes (comme la
conservation de l’énergie, de la quantité de mouvement, etc) sous la forme d’équations différentielles, et
la capacité de solutionner ces équations deviendra essentielle. Ce chapitre vise à vous apprendre quelques
techniques numériques pour la solution d’équations différentielles dites ordinaires, soit une équation
impliquant des dérivées d’une variable dépendante par rapport à une seule variable indépendante.
Un exemple spécifique vaut bien une longue discussion. Prenons encore une fois la très justement
célèbre Loi de Newton :
F = ma . (7.1)
Ceci représente en fait trois équations, puisque la force et l’accélération sont des quantités vectorielles dont
la définition requiert trois composantes distinctes. Vous n’aurez certainement pas oublié que l’accélération
est la dérivée temporelle de la vitesse v, et que cette dernière peut être considérée comme une dérivée
temporelle du vecteur position x. L’équation (7.1) peut donc être écrite sous les formes alternatives :
dv F
= , (7.2)
dt m
d2 x F
= . (7.3)
dt2 m
Ces deux expressions sont en tout point physiquement équivalentes à (7.1).
Appliquée à un mouvement uniformément accéléré par une force de grandeur constante et exercée dans
la direction-x (disons), l’équation (7.3) devient
d2 x F
= , (7.4)
dt2 m
les composantes-y et z étant satisfaite trivialement (elles donnent 0 = 0 !). Deux intégrations successives de
cette expression consuisent à la célèbre formule que vous avez mémorisé déjà dans votre tendre jeunesse :
1
x(t) = x0 + v0 t + at2 , (7.5)
2
où ici a = Fx /m. Les quantités x0 et v0 sont les deux constantes d’intégration, correspondant aux position
et vitesse au temps t = 0. Je me permet de vous rappeler que la spécification de ces constantes d’intégration
est un aspect essentiel de la solution du problème. L’EDO étant ici d’ordre deux (la plus haute dérivée
est une dérivée seconde), deux conditions initiales doivent être spécifiées.
C’est la forme particulièrement simple —une constante— du membre de droite de l’équation (7.3) qui
permet une intégration analytique. Souvent, pour des forces ayant des dépendances explicites sur x et/ou
t, ceci n’est plus possible et on doit solutionner directement l’équation différentielle de manière numérique.
C’est ce que nous ferons dans ce chapitre, dans le contexte d’un problème physique que vous connaissez
bien, soit le mouvement du pendule. Mais pour commencer nous allons développer deux algorithmes
numériques dans le contexte d’une équation différentielle ordinaire encore plus simple.
df
≡ f ′ = g(t, f (t)) , f (t0 ) = f0 , t ∈ [a, b] . (7.6)
dt
Ici, la dépendance fonctionnelle de g(t, f (t)) sur ses deux arguments (t et f ) est connue/donnée, par
exemple g(t, f ) = −f 2 sin(ωt), mais la dépendance fonctionnelle de f (t) sur son argument t ne l’est pas,
c’est ce que l’on cherche. “Intégrer” cette EDO consiste à calculer la fonction f (t) satisfaisant à la fois à
l’EDO même, ainsi qu’à la condition initiale spécifiée. Premier point important ici : un problème bien posé
doit inclure non seulement l’EDO, mais des conditions initiales/limites (ici f (t0 ) = f0 ) , et un domaine
d’intégration (ici t ∈ [a, b]).
Une EDO de premier ordre telle que définie à l’éq. (7.6) est dite linéaire si la dépendance de g sur f est
strictement linéaire (e.g. aucun terme en f 2 , ln f , etc.), sinon elle est dite non-linéaire. Les EDOs linéaires
ont la particularité que si f1 (t) est une solution à l’EDO, alors il est possible de construire une infinité
de nouvelles solutions de la forme f2 (t) = a × f1 , où a est une constante, et de plus toute combinaison
linéaire de solutions satisfaisant à la condition initiale sera également une solution valide. Du point de
vue d’une solution numérique par Euler explicite comme nous le verrons ci-bas, il n’y a vraiment aucune
différence entre une EDO linéaire ou non-linéaire, mais ce serait loin d’être le cas si on cherchait plutôt
une solution analytique !
Comme premier cas de figure pour la discussion qui suit, nous considérerons l’EDO linéaire suivante,
df
=f , f (0) = 1 , t ∈ [0, 2] , (7.7)
dt
dont la solution analytique est donnée par la fonction exponentielle que vous connaissez bien :
En fait, l’EDO (7.7) peut être considérée comme une définition de la fonction exponentielle. Mais voyons
maintenant comment solutionner cette EDO numériquement.
mais, selon l’EDO (7.6) on sait également que df /dt = g(t, f (t)), donc on peut reécrire ceci comme
La stratégie consiste à utiliser cette expression pour “avancer” la solution dans le temps, à partir de la
condition initiale, pour un pas de temps h choisi a priori. Le calcul numérique de la solution commence
par la discrétisation de la variable indépendante t sur une maille couvrant l’intervalle désiré. Choisissons
une maille équidistante :
tk+1 = tk + h , k = 0, 1, 2, ...
où h mesure ici la grandeur (constante) du pas de temps, et où t0 correspond à la valeur de t à laquelle
est spécifiée la condition initiale. On peut donc appliquer l’éq. (7.10) séquentiellement à chaque point
de maille, en commençant par la condition initiale (connue !) fournie pour t0 , correspondant au point de
maille k = 0 :
Figure 7.1 – Solution numérique de l’EDO (7.7) à l’aide de la méthode d’Euler explicite, pour différentes
tailles du pas de temps h, tel qu’indiqué. Le trait épais est la solution analytique donnée par l’éq. (7.8).
tout à fait équivalent à la dérivation ci-dessus puisque la dite formule de différence finie résulte d’un
développement en série de Taylor tronqué au second terme... !
Appliquons maintenant cette méthode à l’EDO (7.7), pour laquelle on obtient la formule très simple
suivante :
h=0.10
t=[Link](0.,2.,h)
f=np.empty_like(t)
f[0]=1.
La Figure 7.1 montre le résultat de l’algorithme, pour 4 tailles de pas de temps. On note que plus le
pas de temps est petit, plus la solution numérique s’approche de la solution analytique (trait épais). Ceci
est tout à fait naturel, puisque la précision d’un développement en série de Taylor tronqué, sur lequel
est basé notre algorithme, s’améliore à mesure que la taille du pas diminue (retournez voir la Figure 4.5,
courbe en ∗ pour la formule O(h)). On remarquera aussi que, quel que soit la grandeur du pas, à tout t la
solution numérique se retrouve toujours systématiquement inférieure à la solution analytique. Ceci vient
du fait que la fonction exp(t) est concave, dans le sens que sa dérivée seconde est positive ; autrement
dit, sa dérivée première augmente avec t. Ceci implique que l’utilisation de fk pour estimer df /dt dans
le passage de l’éq. (7.9) à l’éq. (7.12) sous-estimera toujours la valeur “moyenne” de la dérivée dans
l’intervalle t ∈ [tk , tk+1 ], donc il est inévitable que fk+1 se retrouve sous la valeur “réelle” à tk . Vous aurez
déjà anticipé (j’espère...) que si la fonction intégrée avait été convexe plutôt que concave, alors la solution
h h h h h h
f (tk + h) − f (tk ) = f (tk + + ) − f (tk + − ) = f (tk+1/2 + ) − f (tk+1/2 − ) . (7.13)
2 2 2 2 2 2
Dans un tel cas il deviendrait naturel d’évaluer le membre de droite, f ′ (tk ), également à tk+1/2 , par
exemple comme sa moyenne entre tk et tk+1 ; on pourrait alors écrire :
h ′
fk+1 − fk = (f + fk′ ) , k = 0, 1, 2, ... (7.14)
2 k+1
menant à
h
fk+1 = fk + [g(tk+1 , fk+1 ) + g(tk , fk )] , k = 0, 1, 2, ... (7.15)
2
Mais voilà, nous ne connaissons pas encore la valeur de fk+1 apparaissant aux membres de droite, puisque
nous sommes à ce stade encore en train d’essayer de calculer le pas k + 1 ! Cependant, il est possible
d’approximer ces quantités en effectuant une extrapolation linéaire à partir des valeurs au pas k, basée
sur devinez quoi, notre très cher développement en série de Taylor, tronqué après le second terme :
df
fk+1 = fk + h = fk + h × g(tk , fk ) , (7.16)
dt tk
La substitution de cette expression pour le fk+1 apparaissant au membre de droite de l’éq. (7.15), (mais
attention, pas pour celui du membre de gauche !), produit alors :
h
fk+1 = fk + [g(tk , fk ) + g(tk+1 , fk + h g(tk , fk ))] + O(h3 ) , k = 0, 1, 2, ... (7.17)
2
qui est notre algorithme d’Euler “amélioré”, qui s’appelle en fait la méthode de Heun. On peut montrer
(mais nous ne le ferons pas ici) que cette variation sur le thème de base suffit à réduire l’erreur totale de
discrétisation à O(h2 ).
En appliquant ceci à l’éq. (7.7), pour laquelle g(t, f ) = f , on obtient la formule suivante :
h h
fk+1 = fk + [fk + (fk + h fk )] = fk + h × fk + fk k = 0, 1, 2, ... (7.18)
2 2
h=0.10
t=[Link](0.,2.,h)
f=np.empty_like(t)
f[0]=1.
Comparez bien ce bout de code à celui de la méthode d’Euler standard. Tout ce qui change est
l’évaluation de fk+1 , mais la structure générale demeure la même. La Figure 7.2 montre une comparaison
entre deux solutions avec le même pas de temps h = 0.5, l’un utilisant la méthode d’Euler explicite et la
seconde utilisant la version avec extrapolation linéaire introduite ci-dessus. Comparant ceci à la Fig. 7.1,
Figure 7.2 – Semblable à la Figure 7.1, sauf que cette fois le pas de temps est fixé à h = 0.5, et les deux
solutions numériques portées en graphiques ont été calculée à l’aide des méthodes d’Euler avec ou sans
extrapolation linéaire du membre de droite, tel qu’indiqué.
on constate que l’évaluation du membre de droite de l’EDO en terme d’une valeur moyenne à mi-pas
produit une solution ayant une précision comparable à celle caractérisant la méthode d’Euler explicite
opérant à un pas de temps 5 fois plus petit, et ce même si dans les deux cas la dérivée est approximée
par une formule de différences finies d’ordre O(h). Il y a un message important ici : l’ordre de l’erreur de
discrétisation contrôle le taux auquel diminue l’erreur dans la limite h → 0 ; mais le choix d’un algorithme
plutôt qu’un autre a également un fort impact sur le niveau d’erreur à une taille de maille donnée.
Comme deuxième exemple, prenons l’EDO suivante :
df
= at2 , (7.19)
dt
avec a une constante connue, les équations (7.11) et (7.17) deviendraient respectivement :
h
fk+1 = fk + × a(t2k+1 + t2k ) , k = 0, 1, 2, ... [Euler avec extrapolation, ouHeun] (7.21)
2
Notons que dans ce dernier cas, l’extrapolation du membre de droite est triviale puisque t est la variable
dépendante, et donc tk+1 est directement calculable, sans extrapolation. Assurez vous de bien comprendre
comment arriver aux deux expressions ci-dessus à partir de l’éq. (7.19), avant de passer à la suite.
La “Loi du pendule”, soit le fait que la période d’oscillation d’un pendule n’étant pas déplacé trop
loin de la verticale ne dépende que de la longueur de sa corde (ou tige) est généralement attribuée à
Galilée. La légende veut que l’illustre personnage ait euréké sur la chose au début des années 1580,
alors qu’il n’était encore qu’un vulgaire petit étudiant sous-gradué en médecine à l’Université de Pise, en
observant l’oscillation d’une lampe suspendue à l’intérieur de la nef de la cathédrale de Pise (le sermon
devait être particulièrement gazant...). Les experts ayant décortiqué les écrits et la correspondance de
Galilée placent plutôt la découverte une vingtaine d’années plus tard. Ce qui semble certain, c’est que
Galilée avait rapidement réalisé que le pendule pouvait servir d’étalon de mesure du temps (avant 1603
Galilée utilisait son pouls comme mesure du temps dans ses expériences sur le mouvement des corps !). Il
fallut cependant attendre un demi-siècle et Christian Huygens (1629–1695) pour le perfectionnement de
la première véritable horloge basée sur le battement d’un pendule.
dvθ d2 θ
aθ = L =L 2 , (7.22)
dt dt
Comme la composante de la force gravitationnelle projetée dans la direction θ est donnée par mg sin θ
(voir Figure 7.3), la composante θ de F = ma conduit à :
d2 θ g
= − sin θ . (7.23)
dt2 L
Dans la limite où le déplacement angulaire du pendule (mesuré en radians) est très petit, on peut ap-
proximer les sinus et cosinus comme suit :
sin θ ≃ θ , cos θ ≃ 1 . (7.24)
L’équation (7.23) se réduit alors à :
d2 θ
= −ω 2 θ , (7.25)
dt2
où on a défini
r
g
ω= . (7.26)
L
Contrairement à l’éq. (7.23), cette dernière équation différentielle du 2e ordre est linéaire, puisqu’elle
dépend linéairement de θ. L’équation (7.25) accepte des solutions de la forme générale
θ(t) = A sin(ωt) + B cos(ωt) , (7.27)
où les constantes d’intégration A et B sont déterminées par les conditions initiales du problème, et ω
représente la fréquence angulaire d’oscillation (unités : rad s−1 ) par rapport à la position d’équilibre
θ = 0. Remarquez qu’on a ici deux constantes d’intégration, comme il se doit puisque l’éq. (7.25) est
d’ordre deux. Pour un pendule déplacé à un angle Θ0 de la verticale et relâché à t = 0 sans lui donner de
vitesse initiale, on a A = 0 et B = Θ0 . En terme de conditions initiales sur la variable θ(t), ceci revient à
exiger :
dθ
θ(0) = Θ0 , =0. (7.28)
dt t=0
Les équations (7.25) et (7.27) définissent le mouvement harmonique simple, dont les subtilités vous
tiendront fort occupé(e)s l’hiver prochain en PHY-1620...
Dans le contexte du pendule, tout ceci devrait vous être déjà familier. Utilisons maintenant nos nou-
velles habiletés numériques et voyons où ça nous mènera. Nous allons d’abord solutionner numériquement
l’équation du pendule linéaire (7.25), pour laquelle nous connaissons la solution analytique exacte (7.27),
à l’aide de la méthode d’Euler (§7.2). Nous allons par la suite (§7.4) nous concentrer sur la solution du
pendule nonlinéaire, telle que décrite par l’équation (7.23). Ça, ce sera définitivement du nouveau !
tk+1 = tk + h , k = 0, 1, 2, ...
où (v0 , θ0 ) sont donnés en condition initiale. Pour un pendule déplacé de son point d’équilibre à un angle
Θ0 , et simplement relâché sans lui donner une vitesse initiale, comme auparavant, on a simplement :
Connaissant la condition initiale [v0 , θ0 ], les équations discrétisées (7.31) et (7.32) permettent donc de
calculer (v1 , θ1 ), et ensuite (v2 , θ2 ), et ainsi de suite séquentiellement jusqu’au temps final d’intégration
désiré.
On aurait facilement pu re-dériver cet algorithme sans même avoir recours à l’éq. (7.11). En effet, tel
que mentionné à la section 7.2.1, l’algorithme d’Euler explicite peut être obtenu en substituant la formule
de dérivée première d’ordre O(h) au membre de gauche de l’ODE à intégrer. En appliquant ceci aux
éq. (7.29) et (7.30), on obtient,
θk+1 − θk
+ O(h) = vk , k = 0, 1, 2, ... (7.34)
h
vk+1 − vk
+ O(h) = −ω 2 θk , k = 0, 1, 2, ... (7.35)
h
et après quelques simples manipulations algébriques, on retrouve bien les éq. (7.31) et (7.32) données
ci-haut.
Le code Python suivant montre un exemple de code complet qui effectue cette intégration numérique
du pendule.
Code Python pour intégrer le pendule avec Euler explicite
1 import numpy as np
2
3 omega2=1. #omega**2
4 h=0.01 #taille du pas de temps
5 t=[Link](0,2*[Link]/[Link](omega2),h) #maille de temps
6 npas=[Link][0] #nombre de pas de temps a faire
7
14 for k in range(0,npas-1):
15 theta[k+1]=theta[k]+h*v[k] #eq. 3.31
16 v[k+1]=v[k]-h*omega2*theta[k] #eq. 3.32
Le résultat est illustré à la Figure 7.4, pour trois choix de pas de temps h. Notez que la variable temps est
exprimée en unités de la période d’oscillation T = 2π/ω, et l’amplitude en unités du déplacement angulaire
Figure 7.4 – Solutions numériques de l’équation du pendule linéaire par la méthode d’Euler explicite,
pour différentes valeurs du pas de temps h : (ωh = 2π/25 → △, ωh = 2π/50 → ⋄, ωh = 2π/100 → +). Le
temps est exprimé en unités de l’inverse de la fréquence d’oscillation angulaire ω, donc la période d’oscil-
lation est égale ici à 1 dans ces unités. Les solutions utilisent la discrétisation définie par les éqs. (7.34)—
(7.35), soit l’utilisation d’une différence finie avant d’ordre O(h) pour évaluer la dérivée à tk . La partie (A)
compare ces diverses solutions à la solution exacte (trait plein), avec l’amplitude exprimée en unités de
l’amplitude Θ0 du déplacement angulaire initial. La partie B montre les erreurs des solutions numériques
par rapport à cette solution exacte.
initial Θ0 . La partie A montre l’évolution temporelle des solutions numériques, utilisant différents pas de
temps tous relativement grands pour raisons didactiques, tandis que la partie B illustre la variation de
l’erreur (ε), définie ici comme la différence entre la solution numérique et la solution exacte cos(ωt) :
ε(tk ) = θk − cos(ωt) . (7.36)
Remarquons que :
1. Plus h est petit, plus la solution approche de la solution exacte ; on s’y attendait mais c’est tout
de même rassurant de constater que c’est bien ce qui se passe.
2. De manière générale, l’erreur croit avec le temps ; ceci est une conséquence de la nature séquentielle
de l’algorithme d’Euler ; toute erreur effectuée au pas k est “réinjectée” dans la solution au pas
k + 1, et est donc cumulative.
3. L’erreur de discrétisation conduit à une déviation au niveau de l’amplitude de la variation sinusoi-
dale, mais aussi de sa période.
vk+1 − vk ω2
+ O(h) = − (θk+1 + θk ) , k = 0, 1, 2, ... (7.38)
h 2
On approxime de nouveau les θk+1 et vk+1 aux membres de droite à partir des valeurs au pas k par
extrapolation linéaire basée sur un développement en série de Taylor, tronqué après le second terme :
dθ
θk+1 = θk + h = θk + hvk , (7.39)
dt
dv
vk+1 = vk + h = vk − hω 2 θk , (7.40)
dt
où les secondes égalités résultent de l’utilisation des éqs. (7.29) et(7.30) pour évaluer les deux dérivées
temporelles associées à l’extrapolation. L’algorithme devient alors :
h ω 2 h2
vk − hω 2 θk + vk = θk + hvk −
θk+1 = θk + θk , k = 0, 1, 2, ... (7.41)
2 2
ω2 h h2
2
vk+1 = vk − (θk + hvk + θk ) = vk − ω hθk + vk , k = 0, 1, 2, ... (7.42)
2 2
La seule modification à notre code Python pour la méthode d’Euler consiste à remplacer les deux lignes
de code (lignes 15 et 16) faisant le calcul de theta[k+1] et v[k+1] à l’intérieur de la boucle temporelle
par :
theta[k+1]=theta[k]+h*( v[k]-omega2*h*theta[k]/2.)
v[k+1]=v[k]-h*omega2*( theta[k]+h*v[k]/2.)
Il est important de réaliser que l’erreur associée à l’évaluation des dérivées temporelles aux membres de
gauche par différence finies est toujours O(h). Cependant, à un maillage donné cet algorithme s’avère
à produire des solutions beaucoup plus précises que l’algorithme d’Euler original, comme on peut le
constater en comparant la Figure 7.5 ci-dessous à la Fig. 7.4 ci-dessus. Notons déjà les échelles verticales
très différentes sur les parties B ! De toute évidence, notre ré-evaluation des membres de droite à mi-pas
a produit encore une fois une grande amélioration dans la précision des solutions pour une maille donnée.
Figure 7.5 – Identique à la Figure 7.4, sauf qu’ici le membre de droite des équations est évalué comme
la moyenne aux pas k et k + 1, par extrapolation linéaire à partir du pas k (éqs. (7.41)) et (7.42)). Notez
le changement des échelles verticales, en particulier sur la partie B, par rapport à la Fig. 7.4.
dv
= −ω 2 sin θ . (7.44)
dt
La méthode D’Euler explicite s’applique facilement à cette situation, produisant l’algorithme
dθ
θk+1 = θk + h = θk + hvk , (7.45)
dt
dv
vk+1 = vk + h = vk − hω 2 sin θk . (7.46)
dt
La première de ces expression est identique à son équivalent pour le pendule linéaire (éq. (7.39)), mais
la seconde est maintenant nonlinéaire au membre de droite ; cependant, dans le contexte de la méthode
d’Euler explicite θk est connu, et calculer sin θk ne présente donc aucune difficulté particulière. S’en tenant
à la méthode d’Euler explicite pour l’instant, on n’a qu’à changer la ligne 16 du code ci-haut par :
v[k+1]=v[k]-h*omega2*[Link](theta[k])
Calculons maintenant une série de solutions au problème du pendule nonlinéaire, obtenues à l’aide de
l’algorithme ci-dessus. Les conditions initiales correspondent à de grands déplacements angulaire initiaux
(voir Fig. 7.6), mais le pendule est encore une fois relâché sans lui donner de vitesse initiale. La Figure
7.7 illustre la variation temporelle du déplacement angulaire (mesuré en radians), pour un ensemble de
déplacement initiaux allant jusqu’à π/2, soit une position initiale du pendule à l’horizontale. La première
chose à remarquer est que la fréquence pd’oscillation dépend maintenant de la condition initiale, contrai-
rement au pendule linéaire où ω = g/L quelle que soit l’amplitude initiale. Ceux qui prendront le
cours de mécanique classique 2 verront qu’il est en fait possible de déterminer analytiquement la période
d’oscillation du pendule nonlinéaire, mais le calcul est beaucoup plus complexe que ce que nous avons vu
ci-haut. En bref, la solution analytique est exprimée sous forme d’intégrale comme suit,
Z π
4 2 dϕ
τ=p p , (7.47)
g/L 0 1 − sin (Θ0 /2) sin2 ϕ
2
où l’on voit clairement la dépendance de la période d’oscillation sur son amplitude Θ0 . Cette dépendance
de la fréquence d’oscillation sur l’amplitude explique pourquoi il est si difficile de demeurer synchronisé
avec un(e) petit(e) camarade, chacun sur sa balançoire ; même une petite différence d’amplitude conduira
déjà après quelques cycles d’oscillation à un déphasage perceptible.
Un autre truc qui n’est pas particulièrement évident sur la Figure 7.7, c’est que la forme de l’oscillation
n’est plus exactement sinusoidale ici. La différence ne devient visuellement perceptible que pour des
déplacements initiaux approchant π, comme l’illustre la Figure 7.8. Évidemment, si l’on veut considérer
des déplacements initiaux dépassant π/2 nous sommes forcé de supposer que la masse m n’est pas reliée
au point de pivot par une corde, mais par une tige rigide ; sinon la chute initiale de m se fera verticalement
vers le bas, et une corde plierait ; ceci nous entrainerait dans un tout nouveau régime dynamique.
Figure 7.6 – Le pendule nonlinéaire : le déplacement angulaire initial (Θ0 = [0.1, 0.2, 0.5, 1.0, π/2]) de la
masse m est maintenant suffisamment grand pour que l’approximation sin θ ≃ θ ne tienne plus la route.
Figure 7.7 – Solutions numériques au problème du pendule nonlinéaire, pour des amplitudes initiales
Θ0 = [0.1, 0.2, 0.5, 1.0, π/2] radians, tel qu’illustré sur la Figure 7.6. On remarquera que la période d’oscil-
lation augmente avec l’amplitude du déplacement initial Θ0 . Les lignes verticales pointillées sont tracées
aux demi-périodes du régime linéaire.
Figure 7.8 – Solutions numériques au problème du pendule nonlinéaire, pour une amplitude initiale
θ0 = 0.95 × π, i.e., la masse m est relachée à t = 0 d’un point situé presqu’exactement au dessus du pivot
du pendule. Le trait pointillé est une fonction sinusoidale ayant la même période et amplitude.
maille très serrée, qui se retrouve donc sensible à l’accumulation des erreurs d’arrondissement au niveau
des opérations arithmétiques. Si vous avez suivi le raisonnement dans notre discussion des formules de
différences finies au chapitre précédent, vous aurez peut-être déjà anticipé que des algorithmes plus précis
que la méthode d’Euler peuvent être obtenus en conservant plus de termes dans le développement en série
de Taylor de f (t), et/ou en développant les membres de droite en série. Parmi la quasi-infinité d’algorithmes
pouvant être produits de cette façon, celui que vous risquez de rencontrer le plus fréquemment est sans
nul doute la méthode de Runge Kutta d’ordre 4 (souvent simplement appelée la méthode de Runge-
Kutta tout court) ; l’idée ici est de subdiviser le pas de temps h en sous-pas à travers lesquels la solution
est avancée en réévaluant le membre de droite à chaque sous-pas. Appliqué à note EDO nonlinéaire
prototypique (7.6), l’algorithme prend la forme suivante :
h
fk+1 = fk + (G1 + 2G2 + 2G3 + G4 ) + O(h4 ) , (7.48)
6
où
G1 = g(tk , fk ) , (7.49)
Pour tous les détails concernant le design de ces algorithmes, voir le chapitre 2 de l’excellent ouvrage de
Golub & Ortega cité en bibliographie à la fin du chapitre. Le bouquin de Wood, également listé ci-dessous,
est aussi (à mon avis) une bonne référence, tout comme le chapitre 17 de Press et al. cité précédemment.
Nous n’avons fait ici qu’effleurer ce vaste sujet qu’est la solution numérique des équations différentielles
ordinaires. En particulier, nous n’avons considéré ici que des EDO décrivant des problèmes dits aux
valeurs initiales : on nous fournit une équation d’évolution (genre ∂/∂t = ...) et une condition initiale à
t = t0 , et le problème consiste à “avancer” cette condition initiale dans le temps. Un autre type de situation
décrite par une EDO est le problème dit aux valeurs limites : on nous fournit une EDO décrivant un
aspect d’un système défini sur un domaine (genre x ∈ [a, b]), et deux conditions limites à x = a et x = b ;
le problème consiste alors à obtenir une solution à l’EDO dans le domaine, qui soit compatible avec ces
conditions limites. Vous ferez connaissance avec ce genre de problème en mécanique quantique —entre
autres— lorsque vous solutionnerez l’équation de Schrödinger dans un puits de potentiel.
Les références listées en fin de chapitre devraient suffire à ceux et celles désirant équiper encore plus
leur “boite à outils” numérique de méthodes de solutions de ces diverses classes d’EDO. Pour nous ici, il
est maintenant temps de passer à autre chose.
Exercices:
1. Une unité de temps “naturelle” pour le problème du pendule est l’inverse de la fréquence d’oscil-
lation ω = g/L ; introduisez une nouvelle variable temps (t∗ ) telle que
t∗ = t × ω ,
et montrez que les deux équations différentielles d’ordre 1 décrivant le mouvement pendulaire (les
éqs. (7.29)–(7.30) prennent maintenant la forme :
dθ dv
=v , = −θ .
dt∗ dt∗
2. Déterminez jusqu’à quel angle θ∗ (en rad.) les approximations sin θ ∼ θ et cos θ ∼ 1 demeurent
valides à 10−3 , 10−4 et 10−5 près.
3. Pourquoi cette séparation de la “vraie” équation du pendule (éq. (7.25)) en deux équations différen-
tielles d’ordre 1 ? Utilisez une formule de différence finie pour la dérivée seconde pour produire un
algorithme semblable à la méthode d’Euler explicite ; codez le en Python, et comparez sa précision
(à pas de temps égal) à celle de l’algorithme standard.
4. Codez en Python la méthode d’Euler explicite et sa version avec extrapolation linéaire, pour le
problème du pendule linéaire. Intégrez sur une période complète d’oscillation, et examinez comment
varie l’erreur associée à cette valeur finale à mesure que le pas de temps diminue ; exprimez vos
résultats sous la forme d’un équivalent de la Figure 4.5.
5. Codez en Python la méthode de Runge-Kutta pour le problème du pendule linéaire. Produisez
l’équivalent des Figures 7.4 et 7.5. Comment se compare cette méthode aux deux autres considérées
dans ce chapitre, soit Euler explicite et Euler avec extrapolation linéaire ?
Bibliographie:
Le chapitre 17 du Numerical Recipes, cité en bibliographie au chapitre 1, discute de manière plus appro-
fondie les divers algorithmes décrits dans ce chapitre, en bien d’autres en plus. À un niveau mathématique
plus poussé, les deux ouvrages suivant demeurent des classiques incontournables :
Gear, C.W., Numerical Initial Value Problems in Ordinary Differential Equation, Prentice-Hall (1971),
Stoer, J., & Bulirsch, R., Introduction to Numerical Analysis, 3e éd., Springer (2002).
Plus accessible, quoique que toujours dans le costaud, je recommanderais :
Golub, G.H., & Ortega, J.M., Scientific Computing and Differential Equations, Academic Press (1992),
Wood, W.L., Practical time-stepping schemes, Oxford (1990).
Chapitre 8
Nonlinéarité et chaos
Figure 8.1 – Espace de phase pour le pendule nonlinéaire (à tige rigide). Les trajectoires fermées (en
rouge) correspondent à des mouvements oscillatoires autour de la position d’équilibre (θ, v) = (0, 0).
Les courbes vertes délimitent la région des orbites fermées de celles des trajectoires ouvertes (orange),
correspondant à une masse tournant perpétuellement dans le sens horaire (v > 0) ou antihoraire (v < 0).
Les cinq plus petites orbites fermées correspondent aux solutions de la Figure 7.7.
à vitesse constante d’un point (θ, v/ω) dans le sens horaire. Le point (0, 0) correspond ici à une solution
d’équilibre où le pendule est au repos et pend directement vers le bas.
Si le déplacement initial du pendule ne satisfait pas la condition θ0 ≪ 1 nous permettant de remplacer
sin θ par θ, les trajectoires ne sont plus des cercles, mais l’évolution du pendule nonlinéaire peut toujours
être décrite par une trajectoire fermée dans l’espace de phase. Ceci est illustré par les trajectoires en rouges
sur la Figure 8.1. Partant du centre (0, 0), les cinq premières correspondent aux cinq solutions de la Figure
7.7, et la sixième a comme condition intiale (θ0 , v0 ) = (5π/6, 0). Notons que l’espace de phase du pendule
(linéaire ou non) est périodique dans la direction-θ, la périodicité étant de 2π puisque sin(θ ± 2π) ≡ sin(θ).
Seules les trajectoires centrées sur θ = 0 sont illustrées ici. Les solutions linéaires ne sont valides que très
près des points (θ, v) = (±2nπ, 0), avec n = 0, 1, ...
Le fait que ces trajectoires soient fermées est évidemment une conséquence directe de la périodicité du
mouvement. Cette périodicité est ici une conséquence directe de la conservation de l’énergie. Le contenu
énergétique de la masse m fixée au bout de notre pendule inclut deux contributions, soit son énergie ciné-
tique et son énergie potentielle gravitationnelle. Si on choisit comme point zéro du potentiel gravitationnel
la hauteur du pivot du pendule, on a :
1
E= mv 2 − mgL cos θ , (8.3)
2
En l’absence de toute friction avec l’air ambiant, cette quantité d’énergie, quelle qu’elle soit à t = 0,
doit être conservée pour tout t > 0, et ce que le pendule opère en mode linéaire ou nonlinéaire. Le
mouvement pendulaire peut donc être vu comme un échange cyclique entre l’énergie cinétique et potentielle
gravitationnelle de la masse m.
La transposition du mouvement accéléré dans son espace de phase nous permet de pousser plus loin
notre étude du pendule. Considérons un pendule orienté verticalement vers le haut (θ = π) et au repos
(v = 0). Pour une masse m montée au bout d’une tige rigide, c’est là une position d’équilibre, car la
force de gravité est complètement équilibrée par la rigidité de la tige. L’énergie est ici E = mgL. Sujet
maintenant à tout déplacement angulaire même des plus minuscules, l’équilibre sera rompu et le pendule
√ le sens du petit déplacement. Rendu en bas, à θ = 0, l’éq. (8.3) nous garantit
se mettra à tourner, dans
que la vitesse sera v = 2gL, et retombera à zéro une fois que le pendule aura complété un tour complet.
Que se passe-t-il maintenant si on donne une vitesse initiale v0 > 0 au pendule dans sa position
d’équilibre (instable) θ = π ? Comme auparavant le pendule accélérera durant la descente et décélérera
durant la remontée, mais son tour complété et arrivé à θ = π sa vitesse sera redevenu égale à sa vitesse
initiale... et donc ca repart pour un second tour,
p et ainsi de suite ad aeternam. On est ici dans une situation
où la vitesse oscille entre des limites v0 et v02 + 4gL, et θ augmente continuellement. Ceci corresponds
aux trajectoires tracées en orange dans la moitié supérieure de l’espace de phase sur la Figure 8.1. Si la
vitesse initiale est négative, le mouvement sera décrit par les trajectoires oranges dans la moitié inférieure
de la Figure.
Les courbes vertes sur la Figure 8.1 définissent les séparatrices de l’espace de phase. Le point d’inter-
section de ces séparatrices avec la ligne v = 0 correspond au pendule en équilibre (instable) à la position
(θ, v) = ((2n ± 1)π, 0), n = 0, 1, ..., soit la masse m positionné verticalement exactement au dessus du
pivot.
Dans un système déterministe comme notre pendule, deux trajectoires ne peuvent pas se croiser dans
l’espace de phase ; il n’est pas possible non plus à une trajectoire de se croiser soi-même ; si c’était le cas,
au point de croisement il y aurait un “choix” à faire, à savoir laquelle des deux branches possibles prendre
pour la suite de l’évolution. Les points de croisement des séparatrices peuvent paraitre violer ce précepte,
mais on verra au chapitre 8 que le temps requis pour atteindre un de ces points tend vers l’infini. Et
attendre un temps infini, c’est très long, surtout vers la fin...
FD = −αv . (8.4)
où α (> 0) est un coefficient de friction (unités : N s/m). Remarquez déjà que si cette force de trainée est
la seule force en présence, la Loi de Newton
dv
ma = m = −αv . (8.5)
dt
nous indique immédiatement qu’un objet de masse m se déplaçant à une vitesse v0 verra sa vitesse
décroitre exponentiellement selon :
α
v(t) = v0 exp − t . (8.6)
m
Vous en apprendrez plus sur tout ça en ne manquant pas de vous inscrire à l’excellent cours PHY-3140,
Hydrodynamique, généralement enseigné à la session d’hiver par l’auteur de la version originale de ces
notes : Paul Charbonneau. Bon, bref, il ne s’agit plus que d’ajouter cette force au membre de droite de la
composante-θ de notre équation du mouvement. Comme ici la vitesse linéaire v ≡ Ldθ/dt êθ , on obtient
d2 θ αL dθ
L 2
= −g sin θ − , (8.7)
dt m dt
et notre séparation en deux équations différentielles ordinaires couplées d’ordre un nous donne maintenant :
dθ
=v , (8.8)
dt
dv
= −ω 2 sin θ − βv , (8.9)
dt
où on a de nouveau défini ω 2 = g/L, et également
α
β= . (8.10)
m
Ces deux équations ne diffèrent des équations (7.43) et (7.44) pour le pendule non-linéaire que par l’appa-
rition d’un deuxième terme (l’amortissement) dans l’équation différentielle pour v. En outre, il est trivial
θ0 = π−0.1, ω =1
1.0 non amorti, β =0
amorti, β =0.05
amorti, β =0.3
0.5
θ(t)/θ0
0.0
0.5
1.0
0 5 10 15 20
ωt/2π
Figure 8.2 – Solutions numériques du mouvement du pendule nonlinéaire (à tige rigide) amorti obtenues
avec la méthode de Heun. Les deux traits en couleur ont la même condition initiale (θ0 , v0 ) = (π − 0.1, 0),
mais deux valeurs différentes de β représentant un faible ou un fort amortissement. Pour référence, le trait
noir montre la solution pour le pendule nonlinéaire non-amorti avec la même condition initiale.
de modifier notre algorithme précédent pour résoudre ces équations. La Figure 8.2 montre les solutions
numériques, pour l’angle θ en fonction de t, obtenues à l’aide de la méthode de Heun (§7.5) pour deux
valeurs distinctes de β mais la même condition initiale (θ0 , v0 ) = (π − 0.1, 0). Ces solutions correspondent
à une oscillation du pendule à partir de sa position de départ, avec une amplitude décroissant exponen-
tiellement avec le temps et une fréquence croissant avec le temps. La Figure 8.3 illustre ces deux mêmes
solutions mais cette fois dans l’espace de phase [θ, v] du modèle. Avec β = 0 (non montré), ces solutions sui-
vraient une trajectoire ayant la forme d’une orbite fermée collant de près la séparatrice (trait noir). Ici, en
présence d’amortissement, les trajectoires deviennent des spirales convergeant vers la position d’équilibre
(θ, v) = (0, 0).
Ici, en fait, quelle que soit la condition initiale ou la valeur de β (> 0), les solutions convergent toujours
vers la solution d’équilibre (θ, v) = (0, 0). On appelle un tel point un attracteur dans l’espace de
phase puisqu’il “attire” à lui toutes les solutions, quelles que soient les conditions initiales. La vitesse
de convergence des solutions vers l’attracteur dépendra évidemment de la valeur de β, et du contenu
énergétique de la condition initiale, mais si l’on attend assez longtemps cette convergence aboutira toujours
à (0, 0) ; ou, plus précisément, à l’un ou l’autre des points situés à (θ, v) = (2nπ, 0) , n = 0, 1, 2, ...
Figure 8.3 – Trajectoires dans l’espace de phase pour les deux solutions du pendule nonlinéaire amorti
présentées à la figure 8.2. Dans les deux cas la trajectoire aboutit à (θ, v) = (0, 0).
Figure 8.4 – Géométrie du forçage pour le problème du pendule forcé (ici de manière exaggérée...).
À un tel mouvement est associé une accélération également harmonique, qui n’est que la dérivée seconde
de cette expression :
L’idée générale est illustrée sur la Figure 8.4, pour un forçage passablement vigoureux. Pour un pendule
à tige rigide, cette accélération additionnelle sera transmise à la masse m par l’intermédiaire de la tige,
ce qui fait que la composante-θ de notre équation du mouvement aura maintenant l’air de :
d2 θ
L = −(g − g0 cos(ω0 t)) sin θ . (8.13)
dt2
La décomposition en deux équations différentielles d’ordre 1 donne alors :
dθ
=v , (8.14)
dt
dv
= −ω 2 (1 − γ cos(ω0 t)) sin θ , (8.15)
dt
où on a défini un paramètre γ mesurant l’amplitude relative du forçage par rapport à la gravité :
g0
γ= . (8.16)
g
Il y a donc maintenant deux paramètres additionnels contrôlant le comportement du pendule : la fréquence
ω0 et l’amplitude relative γ du forçage harmonique. Dépendant des valeurs choisies pour ces paramètres,
le pendule peut se comporter de manière carrément bizarre. En particulier, on pourrait s’attendre que
dans la limite γ → 0 le comportement du pendule devienne identique à celui du pendule non-forcé ; comme
on le verra dans ce qui suit, celà s’avère ne pas être toujours le cas, dépendant de la valeur choisie pour
la fréquence de forçage ω0 .
Figure 8.6 – Trajectoires dans l’espace de phase pour trois solutions numériques au problème du pendule
forcé et amorti. Toutes les solutions utilisent les valeurs de paramètres ω0 = 2ω, γ = 0.25, et β = 0.03.
les conditions initiales sont indiquées par des • colorés.
dθ
=v , (8.18)
dt
dv
= −ω 2 (1 − γ cos(ω0 t)) sin θ − βv , (8.19)
dt
avec γ et β donnés par les éqs. (8.16) et (8.10), comme auparavant.
Intuitivement, on peut imaginer que même en présence de l’amortissement il puisse exister un état
stationnaire autre que (θ, v) = (0, 0), puisqu’une source d’énergie extérieure peut amplifier le mouvement
pendulaire. La Figure 8.6 montre les trajectoires dans l’espace de phase de trois solutions numériques de
l’équation (8.17) par la méthode de Heun, toutes avec ω0 = 2ω, γ = 0.25, et α = 0.03, mais différant au
niveau de la condition initiale. Quelle que soit cette dernière, les trajectoires aboutissent toutes sur l’orbite
décrite par le trait noir épais. Nous avons de nouveau affaire ici à un attracteur, mais cette fois-ci ce n’est
plus un point mais une figure géométrique plus complexe, soit une courbe fermée décrivant une oscillation
quasi-harmonique d’amplitude constante.
Figure 8.7 – Amplitude maximale de battement dans l’état stationnaire, pour une séquence de solutions
avec ω0 = 2ω et β = 0.05 (vert) et β = 0.1 (rouge), en fonction de de l’amplitude γ = g0 /g du forçage
harmonique.
8.6 Bifurcations
Si sur la Figure 8.6 la forme de l’attracteur dans l’espace de phase ne dépend pas de la condition initiale,
elle dépend cependant des paramètres physiques du problème, soit le paramètre γ mesurant l’amplitude
de forçage (éq. (8.16)), et le paramètre β mesurant l’importance de l’amortissement par la friction de l’air.
Considérons la procédure suivante. Gardant tous les paramètres du modèles fixes autres que le paramètre
de forçage γ, on produit une séquence de solutions pour des valeurs de γ de plus en plus grandes. Pour
chaque solution, on intègre jusqu’à la convergence sur l’attracteur, et on mesure le rayon de l’orbite (plus
précisément, la valeur maximale Θm atteinte par la variable angulaire (e.g., Θm = 1 rad sur la Figure 8.6).
On porte ensuite en graphique ces valeurs de Θm , en fonction de la valeur du paramètre γ. Le résultat
est illustré à la Figure 8.7, pour deux valeurs du paramètre d’amortissement β (points verts et rouge).
Certains aspects de cette Figure sont satisfaisant intuitivement. À forçage égal, les solutions avec plus
faible amortissement se retrouvent à produire des amplitudes de battement plus élevées ; et à amortisse-
ment égal, plus l’on force fort plus l’amplitude de battement est grande. Cependant le détail de la variation
de Θm avec γ est pas mal moins intuitif. Pour un β donné, il existe de toute évidence une valeur de γ
en dessous de laquelle il n’y a pas du tout de croissance de l’amplitude par excitation paramétrique, et
quand elle débute, elle débute de manière soudaine et rapide. La valeur de γ à laquelle ceci se produit est
appellée point critique, et le changement dans la dynamique globale du système se produisant quand
on croise ce point est appelé bifurcation. On notera que la solution d’équilibre demeure une solution
tout-à-fait valide même au delà du point de bifurcation ; cependant, cette solution se retrouve instable
par rapport à la solution oscillatoire, mais seulement si l’énergie injectée par le mécanisme de forçage peut
dépasser l’énergie perdue en friction contre l’air. C’est pourquoi le point critique a une valeur numérique
finie, qui croit à mesure que β augmente.
La bifurcation est un phénomène typique des systèmes nonlinéaires. Dans bien des situations que vous
avez déjà rencontrées dans vos études de la physique, il existe une relation linéaire entre une “cause” et
un “effet”. Avec F = ma par exemple, quelle que soit la valeur de F, si vous l’augmentez de 1% (disons)
vous vous attendez à une augmentation directement proportionnelle de l’accélération a. Contrastez ceci
à la situation présentée à la Figure 8.7, par exemple pour β = 0.1 (points rouge). Si γ = 0.1, le pendule
n’oscille pas, et une augmentation de γ par 1% (à γ = 0.101) ne le fera pas osciller plus. À γ = 0.4
par contre, une augmentation de 1% à γ = 0.404 produira une augmentation quasi-proportionnelle de
l’amplitude maximale d’oscillation. Mais à γ = 0.2, une petite augmentation de 1% nous fait passer d’un
système qui n’oscille pas du tout à un système qui oscille avec une amplitude substantielle, Θm ≃ 0.5 rad.
En fait, au point de bifurcation même on a dΘm /dγ → ∞. Même à un niveau purement qualitatif (osciller
ou ne pas osciller, zattize ze quèstcheune...), la réponse du système à une petite variation d’un paramètre
dépend donc de manière très sensible de la valeur exacte de ce paramètre ; quand un point de bifurcation
est traversé, le système passe à un état dynamique différent. C’est ce qui fait toute la complexité des
systèmes nonlinéaires.
8.7 Le chaos
Une orbite stable dans l’espace de phase, telle qu’illustrée à la Figure 8.6 est possible ici en présence
d’un amortissement important en raison du fait que la fréquence de forçage a été judicieusement choisie
afin de profiter de la résonance par excitation paramétrique. Qu’en est-il de l’évolution du pendule forcé
si la fréquence de forçage n’a pas une valeur permettant l’excitation paramétrique ? Le forçage transfère
toujours de l’énergie au système, donc ou pourrait s’attendre à ce que pour un amortissement pas trop
important et/ou une amplitude de forçage suffisamment élevée, le mouvement pendulaire puisse perdurer.
Quelle forme prend alors ce mouvement ?
La Figure 8.8 montre l’évolution temporelle d’un pendule forcé et amorti, avec valeurs de paramètres
ω0 = 1.3ω, γ = 0.75, et β = 0.03, et une condition intiale (θ0 , v0 ) = (5π/6, 0). La solution est obtenue ici
par la méthode de Heun, avec un pas de temps h = 0.014. Chaque image montre le pendule à 10 époques
successives équidistantes dans le temps, avec la teinte de gris codant le temps : le pendule le plus pâle
correspond au premier pas de temps du groupe de dix, et le plus foncé au dernier. Chaque image représente
la suite de la précédente, de gauche à droite et du haut vers la bas, tel que numéroté ; le pendule en noir
sur la première image est donc à la même position que celui en gris le plus pâle sur la seconde, en ainsi de
suite d’une image à l’autre 1 . Le mouvement est de toute évidence très complexe, et pas harmonique ou
quasi-harmonique du tout ! Notez en particulier comment le pendule parvient parfois à effectuer quelques
tours complets successifs autour du pivot (e.g., images 17→20).
La Figure 8.9 montre l’évolution de la même solution, cette fois dans le sous-espace de phase [θ, v] du
système. On parle de sous-espace ici parce que l’espace de phase complet inclut maintenant une troisième
dimension, soit celle de l’amplitude de forçage du pivot, qui ajouterait ici une déviation harmonique des
trajectoires hors du plan de la feuille. C’est pourquoi la trajectoire semble se croiser occasionnellement
ici, chose impossible dans l’espace de phase complet d’un système déterministe. On voit que le pendule
est parfois “capturé” par l’un ou l’autre des points fixes à (θ, v) = (2nπ, 0) pendant quelques orbites,
mais la grande amplitude de forçage utilisée ici (γ = 0.75) réussit à l’en extirper malgré l’amortissement
substantiel (β = 0.03), ce qui fait que la solution “vagabonde” d’une cellule périodique de l’espace de
phase à l’autre, d’une manière en toute apparence erratique. Un saut d’une cellulle correspond à une
révolution complète du pendule autour du pivot, dans le sens horaire (saut à droite) ou antihoraire (saut
à gauche).
Mathématiquement parlant, le mouvement du pendule est en fait ici plus qu’erratique, il est chaotique.
Le chaos est une notion qui a été apprêtée à toutes les sauces, et il est donc important de bien spécifier
(et même quantifier) ce que l’on appelle chaos ici.
Considérons deux solutions ayant des valeurs de paramètres identiques, soit β = 10−4 , ω0 = 1.3ω,
γ = 0.25, mais ne diffèrant que par leur condition initiale, et même là vraiment très peu :
On utilise encore une fois ici la méthode de Heun, avec un pas de temps h = 0.019. Au début, les deux
solutions sont évidemment indistingables “à l’oeil”... mais seulement pendant trois cycles d’oscillation ;
vers ωt/2π = 25, une différence devient notable, et à partir de ωt/2π ≃ 30 les deux solutions divergent
1. Une animation de cette solution peut être visionnée sur la page Studium du cours.
Figure 8.8 – Battement d’un pendule nonlinéaire amorti et forcé (ω0 = 1.3ω, γ = 0.75, β = 0.03).
Chaque image montre 10 positions successives du pendule espacées également dans le temps, selon une
séquence allant du gris clair vers le noir. Chaque image successive reprend là où la précédente se termine,
de la gauche vers la droite, et du haut vers le bas, tel que numéroté.
Figure 8.9 – Évolution de la même solution qu’illustrée sur la Figure 8.8, cette fois dans le sous-espace
de phase [θ, v] du système. Le codage en teintes de gris de la trajectoire suit celui de la Figure 8.8, chaque
segment successif gris→noir correspondant à une image.
complètement l’une de l’autre. Cette divergence n’est pas due à un effet d’instabilité numérique associée à
une erreur d’arrondissement, ou rien du genre ; la divergence des deux solution a lieu même si on diminue
ou augmente la taille du pas de temps, ou si on passe à Runge-Kutta, etc. L’effet est réel, et nous devons
en comprendre l’origine.
Commençons par nous définir une mesure quantitative de l’écart entre les deux solutions ; il est naturel
d’utiliser la distance entre les deux solutions dans le sous-espace de phase [θ, v], définie comme :
p
δ(t) = (θ1 (t) − θ2 (t))2 + (v1 (t) − v2 (t))2 . (8.21)
En vertu des conditions initiales adoptées ici, la distance initiale entre les deux solutions est de δ =
10−4 à t = 0. La Figure 8.10 montre l’évolution temporelle de cette quantité, avec une échelle verticale
logarithmique. On remarquera que l’écart entre les deux solutions croit exponentiellement, essentiellement
depuis t = 0, jusqu’à ce que les deux solutions divergent complètement. Quelle que soit la condition
initiale, ou l’amplitude de l’écart introduit à t = 0, pour des valeurs des paramètres β, ω0 , et γ fixes, cette
croissance exponentielle se fait toujours au même taux Λ, correspondant à la pente du trait vert sur la
Fig. 8.10. En d’autres mots, on peut écrire :
La constante Λ est appelée exposant de Lyapunov, et le fait que cet exposant soit positif définit le
système comme étant chaotique.
Il nous reste à comprendre ce qui mets fin à la phase exponentielle de croissance de l’écart entre les
deux solutions. Pour ce faire il s’avèrera utile de considérer l’évolution des deux solutions dans l’espace
de phase [θ, v], tel qu’illustré au haut de la Figure 8.11.
Figure 8.10 – Évolution de la distance dans l’espace de phase, définie selon l’éq. (8.21) entre deux
solutions au problème du pendule forcé et amorti, toutes les deux ici avec les même valeurs de paramètres
ω0 = 1.3ω, β = 10−4 , et γ = 0.25. Les deux solutions ne diffèrent que très légèrement au niveau de
leur condition initiale : (θ, v) = (π/2, 0) et (π/2 + 10−4 , 0). Le trait vert indique la phase de croissance
exponentielle de l’écart (voir texte).
Les deux trajetoires débutent sur une orbite fermée centrée sur (θ, v) = (0, 0), mais après trois orbites
le forçage les fait approcher, puis sauter la séparatrice, après quoi les solutions se dirigent vers le point
répulseur (θ, v) = (π, 0) (voir premier encadré). Les deux solutions sont redéviées le long de la séparatrice.
Cependant, la solution en rouge est maintenant “retardée” par rapport à la bleue, ayant pris plus de
temps à contourner (θ, v) = (π, 0). Les deux solutions sont donc maintenant partiellement déphasées par
rapport au forçage harmonique ; comme on peut le voir sur l’encadré du bas, elles croisent et recroisent la
séparatrice à différent temps et à différentes positions sur la séparatrice. Une fois une révolution supplé-
mentaire complétée, à l’approche du prochain point répulseur à (θ, v) = (3π, 0), la solution rouge est au
dessus de la séparatrice tandis que la bleue est en dessous ; la première repart donc vers le haut, tandis
que la deuxième continue vers le bas sur une orbite fermée autour de (θ, v) = (2π, 0). Du point de vue du
pendule dans notre “vrai” espace physique, la première solution correspond à un pendule qui, oscillant
avec une forte amplitude, approche, ralentit, mais parvient tout de même à dépasser le point d’équilibre
instable, tandis que la seconde solution décrit une solution qui ralentit au point mort tout juste avant
d’avoir atteint la verticale θ = π, et repart en sens inverse. C’est donc aux alentours du point d’équilibre
instable que se décide la décorrélation du système.
Figure 8.11 – Trajectoires dans l’espace de phase des deux solutions de la Figure 8.10, avec deux zoom
successifs sur la région où se produit la décorrélation (voir texte). Les courbes en vert correspondent aux
séparatrices dans l’espace de phase. Sur l’encadré du bas, les • colorés sont tracés à des intervalles de
temps constant ; remarquez comme la solution en rouge “ralentit” plus que la bleue, en passant plus près
du point (θ, v) = (π, 0).
prédateur-proie, certaines formes d’arythmie cardiaque, réactions chimiques, chavirement des navires par
les vagues, fluctuations dans les cavités laser, pour n’en nommer que quelques-uns. Si on avait à trouver
un point commun à la dynamique de ces systèmes si divers, ce serait leur grande sensibilité par rapport
aux conditions initiales. Une minuscule différence entre deux solutions tend à s’amplifier avec le temps. Et
comme vous pourrez le vérifier en faisant le problème 4.4 ci-dessous, cette “petite différence” peut même
être produite par le choix du schéma numérique utilisé pour solutionner le problème.
Mais comment reconnaitre le chaos dans un système naturel ou artificiel ? Un comportement “désor-
donné” (“chaotique” dans le sens colloquial du terme) est un indice mais non une preuve. Un système
évoluant selon une dynamique stochastique (par exemple la marche aléatoire que nous découvrirons dans
quelques semaines) évolue également de manière “désordonnée”, mais on est très loin du chaos. Ajoutez
à ça l’inévitable présence d’erreurs de mesure dans une observation ou une manip expérimentale, et faire
la part observationnellement entre un système stochastique et un autre étant chaotique peut devenir un
solide défi. Les références listées en bibliographie en fin de chapitre pourront vous montrer la voie si ce
genre de trucs vous intéresse.
Exercices:
1. Ce problème vise à vous faire cogiter un peu plus sur le concept d’espace de phase ;
(a) Tracez la trajectoire, dans un espace de phase hauteur-vitesse, d’une masse m lancée verticale-
ment vers le haut à partir du sol, à une vitesse initiale v0 (à gravité constante).
(b) Tracez “intuitivement” une seconde trajectoire, dans un cas ou le déplacement de la masse m
est influencé de manière appréciable par la friction de l’air.
2. Revenons au pendule nonlinéaire classique, i.e. sans amortissement ou forçage (comme au chapitre
7). Obtenez une séquence de solutions numériques à l’aide de la méthode de Heun, pour des
conditions initiales du genre v = 0 et θ0 = π − ϵ, où ϵ → 0. Calculez le temps T requis pour faire
une oscillation complète, en fonction de ϵ. Utilisez vos résultats pour vérifier que :
lim T → ∞
ϵ→0
Attention : ce problème, comme tous les problèmes de type “stabilité”, est très délicat du point de
vue numérique. Assurez-vous de travailler en double précision.
3. Passons maintenant au pendule amorti, mais non-forcé. Toujours à l’aide de la méthode de Heun,
calculez une séquence de solutions, toutes avec la condition initiale (θ, v) = (3π/4, 0), pour des
valeurs du paramètre d’amortissement β allant de 0.01 à 1.0. Pour chacune de vos solutions, mesurez
le temps T requis pour que l’amplitude d’oscillation chute et demeure sous θ = 10−2 rad. Sur la
base de vos résultats, essayer d’établir la relation mathématique décrivant la variation de T avec
β.
4. Calculez deux solutions au problème du pendule forcé et amorti dans le régime chaotique, utilisant
toutes la même condition initiale et le même pas de temps. Utilisez pour la première la méthode
d’Euler explicite, et pour la seconde la méthode de Heun. Combien de temps s’écoule avant la
décorrélation des deux solutions ?
Bibliographie:
Le chaos est sujet qui a produit une littérature gigantesque et souvent indigeste. Parmi la douzaine de
bouquins techniques que je connais sur le sujet, je recommenderais les deux suivants :
Mullin, T., The Nature of Chaos, Oxford University Press (1993),
Hilborn, R.C., Chaos and Nonliner Dynamics, 2e éd., Oxford University Press (2000).
Parmi la quasi-infinité d’ouvrages à saveur plus philosophique, j’ai bien aimé :
Prigogine, I., Les Lois du Chaos, Flammarion (1994)
Chapitre 9
Considérons non pas un, mais un ensemble d’étudiant(e)s tentant laborieusement de rejoindre la Planck,
et dénotons par des crochets ⟨...⟩ la moyenne effectuée sur l’ensemble des marcheurs ; ceci s’appelle une
où x(m) dénote la valeur de la mesure x pour le m-ième membre de l’ensemble, ici de taille M . Donc,
par exemple, ⟨Dn ⟩ représenterait le déplacement moyen du groupe sortant des toilettes. Il s’agit ici d’un
opérateur linéaire, satisfaisant les propriétés :
Dn2 = Dn−1
2
+ s2 + 2Dn−1 sn = Dn−1
2
+ s2 + 2⟨Dn−1 sn ⟩ , (9.5)
en vertu de la linéarité de notre opérateur de moyenne d’ensemble. Si la marche est vraiment aléatoire, et
qu’aucune communication n’existe entre les marcheurs leur permettant de synchroniser leurs titubations,
chaque marche est statistiquement indépendante des autres ; autrement dit, l’ensemble des valeurs ±1 du
pas sn pour l’ensemble des marcheurs a la forme d’une distribution discrète pour deux valeurs, +1 et −1,
qui sont équiprobables et ne présentent aucune corrélation avec les Dn−1 , comme l’on s’y attend d’un
processus stochastique sans mémoire. Ceci implique donc que,
Comprenez bien pourquoi, c’est vital pour tout ce qui suit. L’éq. (9.5) devient alors :
Dn2 = Dn−1
2
+ s2 . (9.7)
Si l’on suppose maintenant que D0 = 0 (i.e., l’origine du système de coordonnées est à la porte des
toilettes), alors on aura :
D12 = s2 , (9.8)
D22 = D12 +s 2 2
= 2s , (9.9)
D32 = D22 2
+ s = 3s ,2
(9.10)
... = ... (9.11)
Dn2 = n s2 . (9.12)
Maintenant, si l’on interprète le décompte n des pas comme une variable temps (genre, un pas chaque trois
secondes), cette expression nous indique que le déplacement quadratique moyen augmente linéairement
avec le temps, d’où :
p √
⟨Dn2 ⟩ = s n . (9.13)
Ceci s’appelle la moyenne quadratique du déplacement. Le point crucial de cette petite analyse est
évidemment l’éq. (9.6) ; ceci est valide pour un processus stochastique sans mémoire et seulement dans
la limite d’un nombre de marcheurs tendant vers l’infini. Et l’infini, c’est un gros chiffre ; beaucoup plus
grand que le nombre d’Avogadro, beaucoup plus grand que le nombre d’atomes d’Hydrogène dans l’univers,
beaucoup plus grand que le plus grand chiffre que vous pouvez possiblement imaginer (surtout qu’il ne
faut qu’y additionner 1 pour en produire un encore plus grand...). Bref, peut-on vraiment s’attendre à ce
que l’éq. (9.13) tienne, et si oui à quel niveau de précision ?
Nous tenterons ici de répondre à cette question en simulant la marche aléatoire unidimensionnelle, ce
qui ne requiert finalement qu’un générateur de nombre aléatoire qu’on force à produire un chiffre égal
à soit +1 ou −1. Le petit code ci-dessous montre comment faire à partir d’un générateur de nombres
aléatoires uniformes dans [0, 1), comme random().
4 import numpy as np
5
Le canevas global du code est simple : une boucle extérieure sur le nombre nM de marcheurs, et une
boucle intérieure calculant les nPas de chacun de ces marcheurs. La seule partie “corsée” est le calcul
d’un pas pouvant valoir soit +1, soit −1. Ceci est effectué à partir du générateur générique random().
Séquentiellement :
1. La fonction random() produit un nombre aléatoire réel distribué uniformément entre 0 et 1 ;
2. La fonction round() arrondi son argument à l’entier le plus près, donc round(random() est un
entier qui vaut soit zéro, soit un, de façon équiprobable ;
3. En conséquence de quoi, 2*round(random())-1 est un entier qui vaut soit −1, soit +1, de manière
équiprobable. Voilà !
La Figure 9.1 montre 10 trajectoires de 104 pas résultant d’une telle marche aléatoire unidimensionnelle,
débutant à x = 0. On peut déjà remarquer que les courbes définissent un genre de “cone” plus ou moins
symétrique par rapport à x = 0 (trait pointillé), et que certains marcheurs réussissent à se retrouver
vraiment pas loin de x = 0 même après 104 pas !
On peut mieux quantifier tout ça en construisant, à chaque pas, la distribution des déplacements as-
sociés à l’ensemble des marcheurs. Ceci est illustré à la Figure 9.2 (histogrammes) pour quatre temps
spécifiques, tel qu’indiqué, les distributions étant construites ici à l’aide de 1000 marcheurs. Les distribu-
tions sont concentrées près de x = 0 au début et s’étalent en x à mesure que le temps s’écoule. Le fait
qu’elles demeurent symétriques par rapport à x = 0 indique que le déplacement moyen ⟨Dn ⟩ est nul !. Le
fait que les distributions demeurent maximales à x = 0 même après un grand nombre de pas indique que
le déplacement le plus probable est toujours zéro !.
Les distributions de la Figure 9.2 ont une allure qui devrait vous rappeler quelque chose... (sinon voir
la Figure 5.7...). Pourrait-il s’agir d’une gaussienne ? bin oui ! les traits minces représentent une série de
fonctions gaussiennes collées aux différents histogrammes en ajustant leur amplitude et déviation standard
(σ dans l’éq. (5.2)) par minimisation du χ2 (effectivement). L’ajustement est excellent dans tous les cas !
Ayant ainsi extrait σ à différents pas de temps, on peut examiner comment σ varie avec t. Ceci est illustré
à la Figure 9.3. Le graphique est ici de type log-log, et dans ce graphique les σ se distribuent parfaitement
le long d’une droite de pente 1/2 ; autrement dit, on peut écrire :
1
log(σ) = b + log(n) = b + log(n1/2 ) , (9.14)
2
d’où
1/2 1/2
10log(σ) = 10b+log(n )
= 10b 10log(n )
, → σ(n) = σ0 n1/2 , (9.15)
Figure 9.2 – Étalement avec le temps (ici représenté par le nombre de pas n) de la distribution (histo-
gramme) des déplacements associés à 1000 marcheurs aléatoires en 1D, débutant tous leur marche à x = 0.
Les traits fins et continus sont des distributions Gaussiennes ajustées aux histogrammes correspondants.
Figure 9.3 – Augmentation de la déviation standard de la distribution des déplacements d’un groupe de
en fonction du temps, mesuré ici en nombre de pas n. Le trait pointillé
1000 marcheurs aléatoires en 1D, √
correspond à une dépendance en n collée à la première mesure à n = 100.
Ces résultats ne sont pas du tout restreints à la marche aléatoire en une dimension spatiale. En plus
d’une dimension les déplacement et pas deviennent des quantités vectorielles, et on remplacerait l’éq. (9.1)
par :
où le pas sn est de longueur unitaire mais est orienté aléatoirement dans l’espace. Le carré du déplacement
au pas n devient :
Si le pas est vraiment orienté aléatoirement, alors moyenné sur en ensemble de marches aléatoires on aura
⟨Dn−1 · sn ⟩ = 0, ce qui conduit de nouveau directement à :
Dn2 = Dn−1
2
+ s2 . (9.18)
La suite est exactement comme auparavant, et aboutit de nouveau à l’éq. (9.12). C’est une propriété
remarquable de la marche aléatoire : quelle que soit la dimension du problème, le déplacement quadratique
moyen varie toujours comme n1/2 .
Le code ci-haut pour la marche aléatoire en 1D est trivial à modifier pour passer à la marche aléatoire
2D, la boucle intérieure devenant simplement :
Figure 9.4 – Les dix-huit premiers pas d’une marche aléatoire en deux dimensions spatiales et débutant
à (x, y) = (0, 0). Le dix-neuvième pas atterrira quelque part sur le cercle de rayon unitaire centré sur la
position au pas 18, mais se fera à un angle dont la valeur est complètement aléatoire et indépendante de
la grandeur et orientation du vecteur-déplacement au dix-huitième pas (segment de droite en rouge).
où x et y sont maintenant des tableaux de type float. Évidemment le déplacement quadratique est
maintenant donné par (x**2+y**2).mean().
La Figure 9.4 montre les quelques premiers pas d’une marche aléatoire en 2D, débutant à (x, y) = (0, 0).
Le trait en tirets indique le vecteur-déplacement net après le dix-huitième pas. Ce vecteur déplacement
sous-tend ici un angle θ18 par rapport à un système de coordonnées cartésiennes dont l’origine est à
(x, y) = (0, 0). Le dix-neuvième pas de la marche fera atterrir notre marcheur quelque part sur le cercle
de rayon unitaire centré sur sa position au dix-huitième pas, à un angle aléatoire dont la valeur mesurée
par rapport à un système cartésien local peut être n’importe quoi entre 0 et 2π. Dénotons la valeur de
cet angle par α19 ; l’angle entre le vecteur-déplacement D18 et le pas s19 sera donc donné par α19 − θ18 ,
et donc on aura :
D18 · s19 = D18 s19 cos(α19 − θ18 ) . (9.19)
À ce stade l’angle θ18 est fixé, donc c’est une constante entre 0 et 2π ; et α19 est aléatoire distribué
uniformément entre 0 et 2π. Donc, et en vertu de la périodicité des fonctions trigonométriques, l’angle
α19 − θ18 sera aussi uniformément distribué entre 0 et 2π, et son cosinus a donc autant de chance d’être
positif que négatif, ce qui assure qu’une moyenne d’ensemble du produit scalaire ci-dessus sera toujours
nulle.
La Figure 9.5 illustre quelques marches aléatoires en 2D, chacune de 100 pas.
p Le cercle tracé a un
rayon égal à la taille du déplacement quadratique moyen attendu, soit ici R = ⟨D100 2 ⟩ = 10s = 10. Ca
colle !
Figure 9.5 – Quatre marches aléatoires de n = 100 pas unitaires, en deux dimensions spatiales.
√ Tous
les
√ marcheurs débutent au point noir au centre, et le cercle correspond à un déplacement D ≡ D·D=
n = 10.
x = 100 et x = −100, et les marcheurs frappant le mur “rebondissent” d’un pas dans la direction opposée.
De plus, choisissons une condition initiale où tous les marcheurs débutent à des positions distribuées de
manière aléatoire mais statistiquement uniformes dans le sous-intervalle −100 < x ≤ 0, soit dans la moitié
gauche du domaine. Tous les marcheurs font leur pas de manière synchronisée dans le temps, mais sinon
de manière totalement découplée l’un de l’autre.
La Figure 9.6 montre une séquence temporelle d’histogrammes décrivant la distribution des positions
des marcheurs à mesure que la marche aléatoire progresse. La distribution, initialement très raide à x = 0,
s’étale graduellement avec le temps, mesuré en pas de marche aléatoire. Cet étalement résulte du fait
que plus de marcheurs traversent de la moitié gauche du domaine vers la moitié droite que dans le sens
inverse, en raison du simple fait qu’il y a initialement beaucoup plus de marcheurs du coté gauche. Ce
n’est que lorsqu’il y aura autant de marcheurs de chaque coté de x = 0 que le système pourra devenir
statistiquement stationnaire.
La Figure 9.7 montre l’évolution en fonction du temps du nombre de marcheurs situés dans les in-
tervalles [−100, 0] (trait noir) et [0, +100] (trait gris). Au début de la simulation le premier diminue
rapidement tandis que le second augmente évidemment tout aussi rapidement puisque leur somme doit
demeurer constante (ici 1000 marcheurs). Après quelques dizaines de milliers d’itérations, les nombres de
marcheurs de chaque coté de x = 0 sont devenus essentiellement égaux, mis à part des petites fluctuations
par rapport à la valeur moyenne (500 marcheurs). J’espère que vous commencez à voir une similitude
avec la situation considérée à la §5.5 dans le contexte de notre modélisation Monte Carlo de l’approche à
l’équilibre... et que vous pouvez conséquemment anticiper que si la simulation était effectuée avec un plus
grand nombre de marcheurs, ces fluctuations diminueraient d’un facteur proportionnel à la racine carrée
du nombre total de marcheurs...
Cet étalement graduel d’une concentration de “particules” décrivant chacune une marche aléatoire est
représentatif des processus dits de diffusion. Ce terme a déjà été introduit à la §5.5 dans le contexte de
notre discussion de l’approche à l’équilibre d’un système de deux boites contiguës communiquant via un
petit trou. On y avait mentionné que ce qui fait que des particules réussissent à traverser d’une boite à
l’autre, ce sont les fréquentes collisions inter-particules et/ou avec les parois des boites.
Comment réconcilier cet énoncé avec le fait que dans notre discussion de la marche aléatoire à date,
les marcheurs se déplacent complètement indépendamment les uns des autres, i.e., sans collisions ou
autres interactions ? Il faut revenir à l’image facétieuse des molécules de Brut 45 Magnum répandant
leur odeur dans une pièce ; si les molécules nauséabondes diffusent dans la pièce, c’est par une forme de
marche aléatoire où la direction de chaque pas est déterminée par les aléas des collisions inter-particules,
leur longueur par la distance interparticule moyenne dans la pièce, et la durée du pas par le temps moyen
s’écoulant entre deux collisions successives. Mais, et c’est là la clef de notre paradoxe apparent, puisque les
molécules d’air (essentiellement N2 et O2 ) sont beaucoup plus nombreuses que celles de Brut 45 Magnum
(heureusement d’ailleurs), ces dernières feront des collisions presqu’uniquement avec O2 ou N2 , et presque
jamais avec une autre molécule de Brut 45. Donc la marche aléatoire de chaque molécule de Brut est
complètement découplée des autres. On dirait ici du Brut 45 Magnum qu’il est un contaminant trace
diffusant dans l’air (Voir aussi Figure 9.8).
Revenons à notre marche aléatoire en 1D confinée à l’intervalle −100 ≤ x ≤ 100 (Figs. 9.6 et 9.7).
Examinons ce qui se passe à une position x0 quelconque ; seules les particules situées dans l’intervalle
x0 − |s| < x < x0 + |s| ont une chance de traverser x au prochain pas de marche, mais seulement si le
pas se fait dans la bonne direction (s = +1 pour les particules situées dans x0 − |s| < x < x0 , et s = −1
pour celles dans dans x0 < x < x0 + |s|). Pour des directions équiprobables, la moitié des particules dans
chaque intervalle feront ce pas dans la “bonne” direction. Donc le nombre net δN de particules traversant
la position x0 vers la droite sera donné par quelque chose comme
1 1
δN (x0 ) = N (x0 − |s|) − N (x0 + |s|) . (9.20)
2 2
Considérons maintenant l’intervalle x0 − |s| < x < x0 + |s| dans son ensemble, et dénotons par N (x0 , t)
Figure 9.7 – Variation temporelle du nombre de marcheurs situés dans la région x < 0 (trait noir) et
x > 0 (trait gris). Pas besoin de l’oeil du lynx pour deviner ici une tendance exponentielle vers un état
d’équilibre ou les marcheur sont distribués uniformément en fonction de x, et où leurs nombres de chaque
coté de x = 0 deviennent égaux, à quelques fluctuations statistiques près (et qui décroitront comme la
racine carrée du nombre de marcheur, comme vous l’aurez déjà deviné). Ne manquez pas de comparer
ceci à la Fig. 5.10.
le nombre de particules contenues dans cet intervalle au temps t. Le nombre au temps t + ∆t sera donné
par le nombre au temps t plus ce qui est entré du coté gauche (à x0 − |s|) moins ce qui est sorti du coté
droit (à x0 + |s|). Évaluant l’éq. (9.20) à x0 − |s| et x0 + |s|, on arrive à :
(2s)2
N (x, t + ∆t) − N (x, t) 1 N (x + 2s) − 2N (x) + N (x − 2s)
= × . (9.22)
∆t 2 ∆t (2s)2
La seconde parenthèse au membre de droite ressemble à quelque chose que vous avez déjà vu au chapitre
2, soit une formule de différence finie centrée pour la dérivée seconde par rapport à x pour un pas spatial
h ≡ 2s (voir l’éq. (4.16)), tandis que le membre de gauche ressemble tout autant à une formule de différence
finie avant pour la dérivée temporelle, pour un pas temporel h ≡ ∆t (voir l’éq. (4.12)). Si l’on accepte
ces interprétations, l’éq. (9.22) représenterait alors une discrétisation par différences finies de l’équation
Figure 9.8 – Représentation schématique d’un fluide à deux composantes, ici des molécules d’air (com-
posante 1, en gris) et de Brut 45 Magnum (composante 2, en noir), vu ici à l’échelle microscopique.
La composante (2) agit comme un contaminant trace en autant que son nombre de particules N (2)
est ≪ N (1) ; dans une telle situation, les paramètres thermodynamiques du gaz (densité, température,
pression, etc.) sont complètement déterminés par la composante 1, et la diffusion de la composante 2
s’apparente à une marche aléatoire où les collisions avec la composante 1 réorientent aléatoirement la
direction de déplacement des constituants microscopiques de la composante 2.
différentielle suivante :
∂N (x, t) ∂ 2 N (x, t)
=D , (9.23)
∂t ∂x2
où on a défini le coefficient numérique,
1 (2s)2
D= , (9.24)
2 ∆t
qu’on appelle coefficient de diffusion. C’est une quantité qui mesure le taux auquel se disperse le
contaminant trace dans le milieu ambiant ; remarquez que D varie comme s2 /∆t, donc un gaz peu dense
(grande distance interparticule) conduira à une diffusion plus rapide. De même, dans un gaz chaud les
constituants microscopiques se déplacent plus rapidement, et donc (à densité égale) le temps inter-collisions
est plus court, ce qui nous ferait prédire que D augmente avec la température. Ces deux attentes s’avèrent
conformes à l’expérience. L’équation (9.23) est le prototype d’une équation de diffusion, qui s’avère
décrire autant le transport de chaleur par conduction que la diffusion de polluants. Ceux et celles qui
choisiront de suivre PHY-3140 auront l’occasion de traiter ces sujets en plus de profondeur.
Si on accepte que l’éq. (9.23) est une représentation continue valide de notre problème de marche
aléatoire en 1D contrainte, alors on devrait pouvoir solutionner directement cette équation et retomber
sur les mêmes résultats. Vérifions donc ça. L’idée générale est la même qu’au chapitre 7, c’est-à-dire qu’on
introduit des formules de différences finies pour approximer les dérivées sur un maillage. On a cependant
ici affaire à une équation aux dérivées partielles, où la variable dépendante N (x, t) dépend de deux
variables indépendantes, ici x et t. On a donc deux maillages distincts à définir, soir un pour la dérivée
temporelle au membre de gauche de l’éq. (9.23), et un maillage spatial pour la dérivée seconde en x au
membre de droite :
où l’indice temporel m est placé en exposant pour bien le distinguer de l’indice spatial. Il ne reste plus
qu’à introduire une différence finie avant pour la dérivée temporelle évaluées à xk , et une différence centrée
pour la dérivée spatiale évaluée au temps tm :
Nkm+1 − Nkm N m − 2Nkm + Nk−1
m
= D k+1 , (9.28)
∆t (∆x)2
ce qui conduit immédiatement à l’algorithme explicite :
m
Nk+1 − 2Nkm + Nk−1
m
Nkm+1 = Nkm + D∆t . (9.29)
(∆x)2
Cet algorithme est l’équivalent direct de la méthode d’Euler explicite considérée à la §7.2, dans le sens
que le membre de droite est évalué à tk , et est donc supposé “connu”, soit du pas de temps précédent soit
à partir de la condition initiale.
Considérons maintenant les conditions initiales et limites suivantes :
(
1 x≤0
N (x, t = 0) = (9.30)
0 x>0
∂N ∂N
= =0, (9.31)
∂x x=−100 ∂x x=+100
qui devraient encore éveiller en vous un certain sentiment de déjà vu... La Figure 9.9 montre une solution
numérique à ce problème, avec ∆x = 1, ∆t = 0.25, et D = 2. Comparez maintenant ceci à la Figure 9.6.
La solution a été tracée aux même temps équivalents, et avec le même code de couleur pour les différents
traits. L’accord est excellent ! Il est clair que l’éq. (9.23) est une excellente représentation de la marche
aléatoire en 1D, dans la limite d’un nombre de marcheurs tendant vers l’infini.
Il faut remarquer comment le coefficient de diffusion a été calculé ici ; dans l’éq. (9.24), le pas de temps
∆t correspond à un pas de la marche, qui définit en quelque sorte notre unité de temps ; et la quantité s
mesure effectivement notre unité de longueur. Donc, dans ces unités, le coefficient de diffusion doit valoir
D = 2. C’est donc la valeur utilisée pour le calcul de la solution de la Figure 9.9. Rendu au stade de la
discrétisation numérique de l’éq. (9.23), on doit choisir un pas spatial et temporel pour la discrétisation,
qui n’ont aucune raison particulière d’être identiques aux “unités naturelles” de la marche aléatoire. En
fait, du point de vue strictement numérique, dans le cas d’une équation aux dérivées partielles de type
diffusion, comme l’éq. (9.23), on peut montrer que le pas de discrétisation temporel doit satisfaire au
critère :
(∆x)2
∆t ≤ (∆t)C ≡ , (9.32)
D
afin de garantir la stabilité numérique de l’algorithme d’Euler explicite, pour un pas de discrétisation
spatiale ∆x. La dérivation de ce critère (appelé critère de Courant) est un superbe exercice en analyse
numérique, mais qui dépasse les bornes de ce cours, donc prenons simplement pour acquis qu’il doit être
satisfait. Ici, avec ∆x = 1 et D = 2 on a (∆t)C = 0.5, donc la solution de la Figure 9.9, avec ∆t = 0.25
satisfait le critère de Courant par un facteur deux. Prendre un pas de discrétisation spatiale ∆x plus grand
rend le critère moins contraignant sur la taille du pas de temps, mais on risque alors de perdreau niveau
de la précision à laquelle est discrétisée la dérivée seconde au membre de droite de l’éq. (9.23). Comme
diraient nos collègues anglophones, “there is no such thing as a free lunch”...
Le critère de Courant est un truc à prendre au sérieux. La Figure 9.10 montre ce qui se passe quand
on ne le fait pas. Il s’agit d’une simulation en tout point identique à celle de la Figure 9.9, sauf que cette
fois le pas de temps ∆t = 0.6 a été choisi légèrement plus grand que la grandeur limite (∆t)C = 0.5
dictée par le critère de Courant. Les profils de N (x, t) portés en graphique couvrent ici seulement les
premiers cinq pas de temps. On y note une oscillation noeud-à-noeud se développant rapidement autour
de la position de la discontinuité initiale à x = 0. Ces oscillations gagnent en amplitude avec le temps, et
se propagent dans les directions gauche et droite du domaine, à une “vitesse” d’un point de maille spatiale
par pas de temps. Donc, après seulement 100 pas de temps, la solution est complètement polluée par cette
instabilité numérique. On pourrait croire que le problème est causé par la discontinuité dans la condition
initiale utilisée ici, mais ce n’est pas le cas ; un profil raide mais lisse (comme la fonction arctangente,
par exemple) deviendra rapidement instable lui aussi. L’instabilité est directement associée au fait que le
membre de droite de l’éq. (9.23) est évalué de manière complètement explicite, soit au pas de temps m
(cf. l’algorithme (9.29)).
Figure 9.9 – Solution numérique de l’équation de diffusion (9.23) avec les conditions initiale et limites
données par les équations (9.30)—(9.31), avec ∆x = 1 et ∆t = 0.25.
Figure 9.10 – Une solution au même problème de diffusion que ci-dessus, mais cette fois pour seulement
les cinq premiers pas de temps et en zoom sur la région [−10 ≤ x ≤ 10] chevauchant la discontinuité dans
le profil initial de N (x, t). Cette solution est en tout point identique à celle ci-dessus, sauf pour l’utilisation
d’un pas de temps ∆t = 0.6.
Figure 9.11 – Les dix-huit premiers pas d’une marche aléatoire sur réseau en deux dimensions spatiales
et débutant à (x, y) = (0, 0). Le dix-neuvième pas atterrira sur l’un des 4 sites indiqués par un cercle,
mais le site choisi l’est de manière complètement aléatoire et indépendante de la grandeur et orientation
du vecteur-déplacement au dix-huitième pas (voir texte). Comparez cette Figure à la Figure 9.4 pour la
marche aléatoire classique en 2D.
Figure 9.12 – Géométrie et règles de déplacement pour la marche aléatoire sur réseau, ici un réseau
cartésien régulier de dimensions 20×20 contenant 50 marcheurs distribués aléatoirement. Les déplacements
aux sites voisins (cercles, indiqués ici pour un sous-ensemble de 6 particules) ne sont permis (traits verts)
que si le site-cible est inoccupé.
aux déplacements puisse sembler nous éloigner de la “réalité”, les pseudo-collisions qu’elle incorpore au
processus de marche tend à nous en rapprocher. Lequel des deux types de marche aléatoire est à préférer
dépendra souvent du type de problème physique que vous désirez examiner, comme l’illustre l’exemple
discuté à la section qui suit.
9.5 Agrégation
La marche aléatoire sur réseau est particulièrement appropriée pour l’étude des phénomènes d’agré-
gation. On débute avec un grand nombre de particules distribuées de manière aléatoire mais statistique-
ment uniforme sur le réseau, et effectuant chacune leur petite marche aléatoire comme expliqué à la §9.4.
Quelque part dans le réseau, on introduit une ou plusieurs particules fixes spatialement et “collantes”, dans
le sens que les particules venant à occuper un site voisin stoppent leur mouvement, et deviennent collantes
à leur tour. Ceci conduira, au fil du temps, à une agrégation de particules se collant successivement aux
particules-germes. Ça, on s’en serait bien douté ; mais on n’aurait guère anticipé les formes des agrégats
résultant de ce processus dit d’agrégation limitée par la diffusion (“diffusion-limited aggregation” en
anglais, abbrévié DLA).
La Figure 9.13 montre un exemple spécifique, où l’agrégation débute à partir d’une seule particule-
germe, située ici au centre d’un réseau cartésien de taille 1024 × 1024. L’agrégation implique ici 2 ×
104 particules initialement distribuées aléatoirement sur le réseau. Les couleurs encodent l’ordre dans
lequel les particules ont été capturées par le germe ; examinez bien l’échelle de couleur, et constatez qu’à
n’importe quel stade de la croissance de la structure, cette croissance s’opère selon une série de captures
et branchements successifs se produisant presque toujours aux extrémités de branches déjà existantes.
La formation de structure de type dendritique s’explique par le fait que la moindre aspérité se formant
par hasard sur l’agrégat en croissance tend à “capturer” plus de particules que les surfaces planes. Ceci
est illustré sur la Figure 9.14. Sur un réseau (cartésien), il n’y qu’une manière de toucher une surface
Figure 9.13 – Formation d’une structure dentritique à partir d’un germe central, par agrégation limitée
par la diffusion. Simulation basée sur la marche aléatoire de 20000 particules sur réseau 2D cartésien
(§9.4), ici de dimensions 1024 × 1024. Les couleurs indiquent l’ordre dans lequel les particules se sont
collées sur la structure : le rouge correspond aux premières 103 particules, l’orange aux 103 suivantes, et
ainsi de suite selon le code de couleur détaillé à droite du diagramme.
Figure 9.14 – Agrégation (A) sur une “surface” plane, et (B) sur une aspérité. Les points rouges reliés
par un trait indiquent les particules fixes, et les cercles rouges les sites où pourraient potentiellement
se coller une particule mobile. Dans la situation en (A), chaque particule fixe ne contrôle qu’un site de
capture (pointillé rouge), qui n’est lui-même accessible que du site situé au dessus (flèche noire). En (B)
cependant, la particule située à l’extrémité de l’aspérité contrôle cinq sites de capture, qui collectivement
peuvent être atteints par 7 pas distincts. Notons également que les deux sites indiqués par des cercles
noirs sont devenus inaccessibles aux particules mobiles, qui se colleraient à la structure avant de pouvoir
y parvenir.
plane (un pas perpendiculaire à et dirigé vers la surface ; voir Figure 9.14A), tandis qu’une aspérité peut
être habituellement atteinte de plusieurs directions. (voir Figure 9.14B). De plus, une fois deux dendrites
plus ou moins parallèles formées, l’espace entre les deux sera difficile à remplir, puisque les particules
exécutant une marche aléatoire entre les dendrites auront une plus grande probabilité de se coller à une
des dendrites qu’à atteindre leur point de branchement. Dans certains cas, comme sur la Figure 9.14B,
les sites voisins du point de branchement peuvent même devenir carrément inaccessibles. Ces effets jouent
tous dans le même sens : croissance et branchements successifs des structures dendritiques, plutôt que
remplissage homogène du volume.
Figure 9.15 – Photographies au microscope électronique d’un grain de poussière interplanétaire. Image
extraite de [Link]/[Link].
Accrochez vos tuques avec de la broche, vous vous apprêtez à franchir la ligne séparant l’idéalisation
physique de la réalité. La nature regorge de structures ayant des formes qui s’avèrent difficile à catégoriser
à l’aide de la géométrie Euclidienne si chère à Galilée (et bien d’autres avant et après lui). Comment
décrire mathématiquement de telles structures ? Notre réponse à cette question débutera par un exemple
géométriquement simple (à prime abord) mais mathématiquement plus que surprenant.
Figure 9.16 – Photographies de flocons de neige s’étant formés sous des conditions d’humidité et
température différant légèrement. Notons ici que même si l’agrégation se produit dans l’espace à
trois dimensions spatiales, l’agrégat produit est une structure bi-dimensionnelle. Ces images sont ti-
rées de la magnifique collection de photographies de flocons de neige qui se trouve sur le site Web
[Link]/snow-crystals/English_index.html.
Figure 9.17 – Invariance d’échelle dans l’agrégat de la Figure 9.13. Les deux zooms successifs représentent
chacun un agrandissement par un facteur 4. Ce n’est ici qu’au second zoom que l’on peut commencer à dis-
tinguer que cette simulation est faite sur réseau, via le fait que les particules ne collent qu’horizontalement,
verticalement, ou à 45 degrés les unes des autres.
Considérons la construction géométrique itérative illustrée à la Figure 9.18. On débute avec un germe
ayant la forme d’un segment de droite de longueur unitaire (à n = 0 sur la Figure 9.18). On subdivise
maintenant ce segment en trois sections de longueur égales, et l’on élève un triangle équilatéral dans la
section centrale ; la courbe n = 1 montre le résultat de cette opération. Puis, on répète ce processus avec
chacun des quatre segments composant la figure n = 1, ce qui conduit à la figure n = 2 ; et ainsi de suite
pour n = 3, n = 4, etc, comme sur la Fig. 9.18 (qui ne se rend que jusqu’à n = 6). La courbe résultant
de ce processus dans la limite n → ∞ s’appelle la fractale de Koch. Avec un germe ayant la forme d’un
triangle équilatéral, on obtient quelque chose qui ressemble fort à un flocon de neige...
La fractale de Koch ne pourrait être qu’une jolie forme géométrique, mais cette courbe s’avère avoir des
propriétés mathématiques intrigantes, pour ne pas dire déroutantes. Le processus de génération implique
que le nombre de segments augmente d’un facteur 4 à chaque étape du processus. Par contre la longueur
de chacun de ces segments diminue d’un facteur 3. Donc, la longueur totale L(n) de la courbe à l’itération
n sera donnée par une expression du genre :
n
n n 4
L(n) = 4 × (1/3) = . (9.33)
3
Puisque 4/3 > 1, on a clairement que la longueur de la courbe tend vers l’infini à mesure que n augmente :
lim L(n) → ∞ . (9.34)
n→∞
À examiner les longueurs des 6 premières itérations de la fractale de Koch sur la Figure 9.18, cette
augmentation peut paraitre lente, mais je vous laisse vérifier qu’à partir d’un germe de L = 3 centimètres,
déjà à n = 100 la fractale de Koch “dépliée” a une longueur suffisante pour faire le tour de la Terre 4000
fois environ.
Ce qui est remarquable ici est que du point de vue du plan de la feuille, la fractale de Koch, de longueur
infinie, a un début et une fin bien définis, soit les deux extrémités du segment de droite définissant le germe.
Comment une ligne de longueur infinie peut-elle avoir un début et une fin ? et de surcroit s’emboiter sans
problème à l’intérieur d’un périmètre de longueur finie, e.g., un rectangle. C’est déjà bizarre, mais il faut
de plus noter que cette courbe de longueur infinie ne “remplit” que très peu le rectangle. Quid ?
Helge von Koch (1870–1924) était un mathématicien diplômé et respecté, qui s’amusait dans ses temps
libres à découvrir ce qu’il appelait lui-même ses “monstres mathématiques”. La plupart de ses contempo-
rains ont jugé ses découvertes quelque part entre “amusantes” et “intéressantes”, mais disons qu’on en est
resté bien loin du prix Nobel... C’est le météorologue Lewis Fry Richardson (1881-1953) qui a le premier
montré, initialement par inadvertance et apparemment sans rien connaitre des monstres mathématiques
de Koch, que certaines structures naturelles avaient des propriétés semblables. Richardson s’intéressait
à la mesure de la longueur des côtes de sa mère-patrie l’Angleterre. Ayant judicieusement déduit qu’il
n’avait qu’à mesurer la longueur de la courbe de niveau d’altitude zéro sur une carte topographique, il
se lança dans ce processus pour arriver à un résultat en toute apparence raisonnable. Notoirement futé
et pionnier de l’approche “think different”, Richardson avait cependant très bien réalisé que son résultat
sous-estimait la longueur réelle des côtes, puisque à l’échelle de sa carte de toute l’Angleterre plusieurs
petites irrégularités, estuaires de petits cours d’eau, etc, n’étaient pas visibles. Il eu donc la géniale idée de
reprendre le processus sur une section des côtes à l’aide d’une carte à plus petite échelle, afin de calculer
un facteur de correction à appliquer à sa mesure. Ce facteur s’avérant substantiel, Richardson répéta le
processus avec une carte d’encore plus petite échelle, pour trouver un facteur de correction encore plus
grand, sans aucune indication de “convergence”. Tout comme la fractale de Koch, les côtes de l’Angle-
terre représentent une ligne ici de toute évidence fermée, mais néanmoins de longueur infinie et pouvant
s’emboiter dans un rectangle de taille finie (e.g., une carte topo de l’Europe).
Géométriquement parlant, la fractale de Koch et la courbe de niveau décrivant les côtes de l’Angleterre
sont “plus” qu’une ligne, mais “moins” qu’une surface ; autrement dit, ce sont des objets géométriques
auxquels on devrait assigner une dimension entre un et deux, soit une dimension fractionnaire (d’où
l’origine du terme “fractale”). Mais comment quantifier ceci ? La solution se trouve dans la mesure de
l’objet, et suit essentiellement la logique de Richardson.
Figure 9.18 – Les 6 premières étapes de la génération de la fractale de Koch. Le germe (n = 0) est un
segment de droite [0, 1], et à chaque étape le tier central est remplacé par un triangle équilatéral pointant
vers le coté “extérieur” de la structure. L’image du bas est un zoom par un facteur 27 de la petite région
délimitée par le tout petit cadre rouge, à peine visible, au sommet de la courbe de l’itération n = 6 ; notez
comment ce zoom est identique à la structure à l’itération n = 3. La longueur L à chaque itération est
également indiquée.
Figure 9.19 – Deux objets géométriques simples, tous deux composés au niveau microscopique d’un
agrégat de “particules”. Le premier est une structure linéaire (i.e., à une dimension spatiale), tandis que
le second est une structure à deux dimensions spatiales, ici un carré. On définit ici une masse M (R), en
fonction du rayon R, comme étant le nombre de particules contenues à l’intérieur d’un cercle de rayon R
centré sur l’objet (voir texte).
à ce qui suivra, ces objets sont considérés comme étant formés d’un certain nombre de “particules” ou
composantes microscopiques (points rouges). Mais à une échelle spatiale beaucoup plus grande que la
taille de ces particules et que la distance-interparticules, l’objet en (A) est une ligne, tandis que celui en
(B) est un carré.
Pour identifier la position d’une particule composant l’objet en (A), on n’a besoin que d’une seule
coordonnée, par exemple la distance depuis un des bouts de la ligne ; et pour celui en (B), on aurait
besoin de deux coordonnées, par exemple mesurées dans les directions horizontale et verticale à partir du
coin inférieur gauche du carré. On dit donc que la ligne est un objet de dimension un, tandis que le carré
est un objet de dimension deux. Mais cette définition “paramétrique” de la dimensionalité est incomplète
à bien des points de vues ; par exemple, chaque particule composant l’agrégat de la Figure Fig. 9.13 peut
être localisée à l’aide de deux coordonnées (cartésiennes ou autres), donc on dirait aussi de cet objet
qu’il est de dimension deux. Du point de vue géométrique cependant, cet agrégat est fondamentalement
différent d’un carré, et cette différence se doit d’être quantifiée.
Considérons la procédure suivante : à partir du centre géométrique de chacun des deux objets, on trace
une série de cercles de rayons R croissants (voir Figure 9.19). Pour chacun de ces cercles, on calcule la
“masse” M (R), soit le nombre de particules contenues à l’intérieur de chaque cercle. Il est clair ici que
dans le cas de la ligne, ce nombre croitra linéairement avec R, tandis que dans le cas du carré on aura
M ∝ R2 , et ce tant que le diamètre du cercle demeure plus petit que la taille de chaque objet. C’est en effet
ce qu’on mesure, comme le montre la Figure 9.20. Ce graphique est tracé avec des axes logarithmiques,
donc une droite de pente D indique une relation mathématique entre M et R dite en Loi de puissance,
soit :
M ∝ RD , D≥0. (9.35)
Dans le cas de la ligne, on a D = 1, mais D = 2 pour le carré. On voit bien ici que pour des objets
géométriquement simples, D correspond à la dimension habituelle de l’objet.
Il est facile d’appliquer la même procédure de calcul à notre agrégat DLA de la Figure 9.13. La Figure
9.21 en montre le résultat. La masse M varie toujours avec R selon une loi de puissance (éq. (9.35)).
Comme auparavant cette loi de puissance ne tient que pour des échelle spatiales plus petites que la taille
de l’objet, mais passablement plus grande que la distance inter-particules. Mais cette fois-ci la pente
logarithmique est D = 1.66 ; bien que l’agrégat soit défini dans un plan 2D, sa structure a une dimension
Figure 9.20 – Relation masse–rayon pour les deux objets géométriques simples de la Figure 9.19. Une
pente logarithmique de +1 correspond ici à une dimension D = 1 (objet à une dimension spatiale), tandis
qu’une pente de +2 indique D = 2 (objet bi-dimensionnel), comme on s’y serait attendu à voir la Figure
9.19. Dans les deux cas les mesures (points noirs) “décrochent” de la droite quand R devient comparable
à la grandeur de l’objet.
entre un et deux. L’agrégat est donc “plus” qu’une ligne, mais “moins” qu’une surface. C’est une structure
fractale. On doit remarquer que pour obtenir une valeur fiable de D, la structure doit être suffisamment
grande pour couvrir au moins un ordre de grandeur en rayon, de manière à ce que la pente logarithmique
puisse être déterminée avec une précision suffisante.
La relation masse-rayon devient difficile à appliquer à des objets de forme complexe, et la valeur de
D qui en résulte se retrouve à mesurer à la fois la forme globale de l’objet, ainsi que sa structure interne.
Une méthode plus robuste de la dimension fractale est basé sur le concept de la mesure. Revenons à
notre familier agrégat. Étant un objet défini dans un plan 2D, tout comme le désormais célèbre étang du
chapitre précédent, la mesure de sa “surface” peut se faire en le recouvrant de petit carrés contigus de
surface M ×M connue, et en comptant simplement le nombre N de carrés requis. Ceci s’appelle la méthode
du décompte des boites (traduction libre de “box-counting method”). Cette méthode est particulièrement
facile à utiliser pour des structures définies sur des réseaux, ou des images pixellisées.
La procédure est illustrée à la Figure 9.22, pour des carrés de coté égal à M =8, 16, 32 et 64. L’unité est
ici la distance entre deux noeuds voisins sur le réseau où se fait la marche aléatoire. Remarquez bien qu’il
n’importe pas ici qu’une boite ne recouvre qu’une seule particule ou plusieurs, dès qu’elle en couvre une
elle contribue “1” au décompte des boites. Encore une fois ici, l’utilisation de boites de taille comparable
à (ou plus petite que) la distance inter-particule n’a aucun sens physique et/ou géométrique. De plus,
comme l’agrégat a ici un diamètre d’environ 500 unités, tout décompte utilisant des boites plus grande
que cette taille produira N = 1 indépendamment de la taille de la boite.
Le résultat de cette procédure de décompte est illustré à la Figure 9.23. On y a porté en graphique
le décompte des boites en fonction de la résolution r (= 1/M ), définie ici comme l’inverse de l’échelle de
mesure M (i.e., haute résolution ≡ petite échelle de mesure), encore une fois selon des axes logarithmiques.
Figure 9.21 – Relation masse–rayon pour l’agrégat de la Fig. 9.13. La pente logarithmique vaut main-
tenant 1.66, ce qui, géométriquement parlant, place l’objet quelque part entre la “ligne” et le “carré”, en
d’autres mots, entre le unidimensionnel et le bidimensionnel. La région en gris indique l’intervalle utilisé
pour calculer la pente (voir texte).
Figure 9.22 – [Commence à la page précédente] Quatre itérations de la méthode de décompte des boites,
appliquée à l’agrégat de la Figure 9.13. Chaque itération double la dimension linéaire des carrés utilisés
pour couvrir l’objet, par rapport à l’itération précédente.
Figure 9.23 – Détermination de la dimension fractale de l’agrégat DLA de la Figure 9.13 à l’aide de
la méthode du décompte des boites. Comme auparavant, la dimension fractale correspond à la pente
logarithmique de N versus r, et est déterminée sur un intervalle de résolution (r = 1/M ) encadré par les
valeurs correspondant à la taille de la structure (r petit) et à la distance interparticule (r grand).
Exercices:
1. Démontrez que le résultat Dn2 = ns2 tient également pour une marche aléatoire en trois dimen-
sions spatiales.
2. Modifiez le code pour la marche aléatoire 1D afin d’introduire un biais systématique dans la marche,
dans le sens que le pas dans la direction positive de l’axe devient légèrement plus probable que le
pas dans la direction négative (i.e., introduisez des probabilité p(+) et p(−) telles que p(+) > p(−)
mais p(+) + p(−) = 1 !). Ensuite,
(a) Répétez l’expérience numérique décrite à la §9.2, et calculez l’équivalent des Figures 9.1 et 9.2
pour des valeurs de p(+) = 0.525, 0.55 et 0.6.
(b) À partir de vos résultats, estimez une “vitesse de dérive” vers la droite.
(c) Revenez maintenant à une simulation classique où les deux directions de pas sont équiprobables,
mais ajoutez une composante de vitesse non-aléatoire correspondant à la vitesse de dérive
estimée en (b) ; recalculez l’équivalent des Figures 9.1 et 9.2, et comparez à celles obtenues en
(a).
3. Utilisant la même procédure de discrétisation qu’à la §9.3, solutionnez numériquement l’équation
de diffusion (9.23) pour une condition initiale donnée par un profil gaussien :
2
1 −x
N (x, t = 0) = √ exp ,
2πσ 2σ 2
avec σ = 0.5, sur l’intervalle spatial x ∈ [−10, 10]. Ensuite,
(a) Vérifiez que la forme gaussienne du profil initial est préservée au cours de l’évolution ;
(b) Examinez comment la déviation quadratique moyenne σ varie avec le temps.
4. Notre procédure de solution numérique de l’équation (9.23) évalue la dérivée seconde au membre
de droite au temps tm , tandis que la dérivée temporelle utilise une formule d’ordre O(h) impliquant
tm et tm+1 ; opérationnellement, ceci est donc équivalent à la méthode d’Euler explicite (§7.2). On
avait vu au chapitre 7 que la précision de la solution pouvait être améliorée en évaluant le membre
de droite comme une moyenne entre sa valeur à tm est une extrapolation linéaire au temps tm+1 ;
c’était la méthode de Heun.
(a) Appliquez cette technique à la solution numérique de (9.23) pour le problème de mélange
considéré à la §9.3 ;
(b) Calculez des solutions numériques pour diverses valeurs du pas de temps ∆t, et déterminez
si l’utilisation de la méthode de Heun change quelque chose à la stabilité de votre approche
numérique.
Bibliographie:
La marche aléatoire est traitée dans tous les ouvrages traitant de processus stochastiques. Le chapitre
12 de l’ouvrage Gould et Tobochnik cité en bibliographie du chapitre 1 offre une bonne discussion du
sujet.
De nos jours la diffusion est le plus souvent traitée dans certains ouvrages avancés de génie, dans
le contexte de la diffusion de la chaleur ou des effets dynamiques de la viscosité dans les fluides (deux
processus décrits par une équation effectivement identique à (9.23)). Dans la littérature plus physique,
j’aime bien la discussion au chapitre 2 de :
Guyon, É., Hulin, J.-P., et Petit, L., Hydrodynamique Physique, CNRS éditions (2001).
Pour une approche encore plus physique (mais à un niveau passablement avancé), je recommenderais :
Landau, L., et Lifschitz, E., Mécanique des Fluides, Éditions MIR (2e édition 1986).
La dérivation du critère de Courant se retrouve dans la plupart des ouvrages d’analyse numérique traitant
de la solution d’équations aux dérivées partielles. La présentation au chapitre 20 du Numerical Recipes
est tout à fait correcte et accessible.
Il existe une multitude d’ouvrages traitant de géométrie fractale. Le bouquin suivant, un des premiers
sur le sujet, vaut encore bien la peine d’être lu :
Mandelbrot, B., The fractal geometry of Nature, Freeman (1982).
J’ai appris beaucoup des deux ouvrages suivants, que je me permets donc de citer ici même si je doute
qu’ils soient les références optimales sur le sujet :
Prusinkiewicz, P., & Lindenmayer, A., The algorithmic beauty of plants, Springer (1990),
Flake, G.W., The computational beauty of Nature, MIT Press (1998).
Chapitre 10
Complexité
10.1 La complexité
La complexité est un concept qui a été et continue d’êtreapprêté à absolument toutes les sauces,
autant dans les milieux scientifiques “sérieux” quedans l’imaginaire populaire ; depuis une vingtaine d’an-
nées c’est un sujet très “in” chez les intellos de toute allégiances, tout comme le chaos l’a été durant les
années 1980. Malgré toute cette agitation intellectuelle, il demeure très difficile de cerner une définition
de la complexité qui fasse l’unanimité, au delà de sa triviale redéfinition :
complexe = pas simple
ce qui ramène évidemment le problème à définir ce qui est “simple”. Néanmoins, et tout comme moi, vous
avez probablement en tête une définition plus ou moins vague et intuitive de ce qui est complexe et de ce
qui ne l’est pas. Si l’on choisit de se limiter aux systèmes physiques et/ou naturels, on pourrait commencer
par dresser une liste de systèmes “simples” et “complexes”. Le tableau 10.1 en donne deux courtes listes,
et je compte sur vos suggestions pour qu’il s’allonge au fil des années (envoyez-moi ça par courriel).
Quelles seraient les caractéristiques de ces systèmes complexes en apparence si divers ? Si l’on reprend
les deux courtes listes du tableau 10.1, on peut en extraire certains points communs, tabulés au Tableau
10.2 ci-dessous. Un des plus évident est probablement le très grand nombre de degrés de liberté (ou
composantes en interaction), mais la plus fondamentale est plus subtile à détecter, et touche au compor-
tement dynamique des systèmes. Prenons un exemple simple. Si vous savez calculer la force nécessaire
pour faire tourner une poulie à laquelle est suspendu un poids, vous savez également comment calculer
la force nécessaire pour faire tourner un systèmes de 20 poulies démultipliées et/ou interconnectées pour
soulever le même poids ; ça pourrait bien être un calcul fastidieux, mais ce n’est vraiment qu’une question
de patience, de calme et de concentration. La dynamique du système de 20 poulies se réduit effectivement
à la dynamique d’une poulie. C’est une dynamique réductioniste. Par contre, tout connaitre sur la
molécule de H2 O ne vous permet pas d’anticiper que quelques zillions de molécules de H2 O produiront
de la turbulence, tout savoir sur l’électrochimie d’un neurone (ce qui serait déjà extraordinaire) ne vous
permet certainement pas d’anticiper qu’un assemblage de plusieurs neurones puisse pondre le Ich Will
de Rammstein ou l’Odyssée d’Homère, pas plus que le calcul du coefficient de friction entre deux grains
de sable vous permet de comprendre pourquoi un tas de sable (sec) formera toujours un cône présentant
une pente de ≃ 35 degrés. Dans ces derniers systèmes, la dynamique à l’échelle globale (fluide, cerveau,
tas de sable) est qualitativement différente de la dynamique à l’échelle locale (un H2 O, un neurone, un
grain de sable). De plus, cette dynamique globale (et souvent “complexe”) émerge des interactions locales
(et habituellement “simples”) entre un très grand nombre d’éléments composant le système. C’est là la
caractéristique sine qua non des systèmes complexes, tout comme la sensibilité aux conditions initiales
définit la nature chaotique d’un système.
Dans ce chapitre nous allons étudier trois modèles numériques simples de systèmes complexes, soit
le modèle dit “Tas-de-Sable” (§10.2 ; “sandpile model” dans la littérature anglophone), le modèle “Feux-
de-Forêt” (§10.4 ; “Forest Fire model”), et le modèle embouteillage (§10.6, “traffic jam model”). Ces
trois modèles nous servirons à illustrer divers types de comportements génériques associés aux systèmes
complexes, et nous feront aussi faire connaissance avec le concept de la criticalité auto-régulé (§10.3 ;
“Self-Organized Criticality”), sans nul doute une des grandes percées conceptuelles des dernières quelques
décennies en physique statistique ; le tout assaisonné d’un petit retour sur l’invariance d’échelle (§10.5).
et que la quantité δSjn de sable déplacée par cette procédure de redistribution est donnée par
zjn
δSjn = . (10.7)
4
Mais voilà, le fait d’avoir redistribué du sable entre les noeuds j et j + 1 change la pente associée aux
paires de noeuds (j − 1, j) et (j + 1, j + 2), et peut fort bien pousser l’une ou l’autre de ces pentes au
dessus du seuil critique Zc . Si c’est le cas, la règle de redistribution est appliquée à ces nouvelles paires
instables, et on doit conséquemment ensuite vérifier la stabilité —et appliquer la règle de redistribution le
cas échéant— des paires voisines, et ainsi de suite ; ce n’est que lorsque toutes les paires sont stables qu’on
active le mécanisme de forçage qui ajoute du sable au réseau. L’effet de tout ça est une avalanche de
redistributions, déplaçant le sable vers le bas du tas jusqu’à ce que la stabilité soit restaurée sur l’ensemble
du réseau. C’est ici que les conditions limites entrent en jeu. On impose au modèle :
Ceci est équivalent à laisser tomber le sable atteignant le bout du réseau, un peu comme si notre tas de
sable se trouvait sur une table de largeur égale à la base du tas. Les conditions limites jouent ici un rôle
clef dans la dynamique du système ; puisque la règle de redistribution est conservative, et vu l’addition
lente mais continuelle de sable sur le tas par le mécanisme de forçage, il est essentiel que du sable puisse
être évacué du système si on veut avoir une chance d’atteindre un état stationnaire.
Avant même de simuler quoique ce soit, si on songe un peu à l’effet de tout ça, on en déduirait qu’après
un certain temps, le tas prendra une forme triangulaire, avec le sommet au centre du réseau, les pentes
de chaque coté égales à la pente critique Zc , et pour un réseau de taille J on prédirait alors une hauteur
maximale égale à Zc × J/2. Voyons si ces attentes sont réalisées.
Le code Python ci-dessous offre une implémentation minimale du modèle TdS. Il s’agit ici d’une
implémentation minimale dans le sens qu’on a sacrifié la vitesse d’exécution pour avoir une meilleure
clarté du code. 1 Le tableau sable contient ici la quantité de sable à chacun des J noeuds du réseau.
Code Python pour modele Tas de Sable
1 import numpy as np
2
20 if dmasse>0: # il y a eu avalanche...
21 sable+=move # on fait les transferts
22 else: # il n'y a pas eu avalanche; on force
23 j=[Link].random_integers(0,J-1)
24 sable[j]+=[Link]()*E
25
Figure 10.1 – Croissance du tas de sable produit par le code montré ci-haut, ici avec J = 101, Zc = 5
et E = 0.1. Le trait pointillé indique la forme attendue d’un tas parfaitement triangulaire, avec les pentes
droites et gauche égales à ±Zc . Chaque courbe est séparée de la précédente par 50000 itérations.
La Figure 10.2A montre l’évolution temporelle de cette quantité à partir du début de la simulation. La
masse croit initialement de manière linéaire, mais sature éventuellement à une valeur statistiquement
stationnaire, mais sujette à fluctuations. La forme que prennent ces fluctuations est particulière, comme
on peut le constater sur examen de l’encadré, montrant un zoom sur une petite partie de la séquence
dans le régime stationnaire. On y voit la masse croitre graduellement et approximativement linéairement,
mais cette croissance est épisodiquement interrompue par des baisses très rapides, manifestations d’une
avalanche ayant fait chuter une certaine quantité de sable du tas. Le pattern en dents-de-scie est une
réflexion du fait que le processus de chargement du tas opère d’une manière lente et graduelle, tandis
que le processus de vidage est lui rapide et très intermittent. La courbe décrivant la variation de M n est
auto-similaire, et on peut lui associer une dimension fractale plus grande que un, un peu comme on l’avait
fait pour la fractale de Koch (§9.7).
Une autre quantité d’intérêt est la masse déplacée à l’itération n lors d’une avalanche :
J−1
X
∆M n = δSjn , (10.10)
j=1
où δSjn est défini via l’éq. (10.7). Notez que cette dernière quantité n’est pas nécessairement égale à
M n+1 − M n , puisqu’une avalanche n’atteignant pas le bord du réseau ne changera pas la masse du tas,
bien qu’elle déplace du sable.
La Figure 10.2B montre la portion de la séquence temporelle de ∆M n correspondant à l’intervalle cou-
vert par l’encadré de la partie (A). Cette séquence est très intermittente, dans le sens que ∆M n = 0 sauf
durant de courts épisodes d’activité correspondant évidemment aux avalanches. Celles-ci se déclenchent de
manière irrégulière dans le temps, et ont des tailles pouvant varier grandement d’une avalanche à l’autre.
On remarque cependant que les plus grandes avalanches ont une amplitude maximale ∆M n ≃ 75, ce qui
correspond à une avalanche déplaçant vers le bas une quantité ∼ Zc /2 de sable à chaque noeud entre
le sommet du tas et une de ses extrémités basales. Vu la taille finie du réseau (J = 101 ici), il est tout
simplement impossible de développer une avalanche plus intense. On peut vérifier que les masses déplacées
par les avalanches sont distribuées en loi de puissance.
Figure 10.2 – Évolution temporelle du modèle TdS en une dimension spatiale, à partir d’une condition
initiale Sj0 = 0. Les paramètres de cette simulations sont J = 101, Zc = 5, et E = 0.1. La partie (A)
montre l’évolution de la masse M du tas, l’encadré offrant un zoom sur une petite portion de la séquence,
et montrant la forme self-similaire de la séquence temporelle. La partie (B) montre la séquence temporelle
de la masse déplacée ∆M , pour le même petit intervalle temporel que l’encadré de la partie (A).
1. ouvert,
2. soumis à un forçage lent,
3. sujet à une instabilité locale...
4. ...dont l’activation requiert le dépassement d’un seuil d’amplitude finie,
5. et où la restabilisation s’effectue par redistribution locale de la variable dynamique.
Le nombre de système naturels pouvant satisfaire à ces conditions est substantiel : en plus des avalanches et
autres formes de glissement de terrain, on peut y inclure les sous-orages géomagnétiques, les tremblements
de terre, les éruptions solaires, et les feux de forêts, sujet de la section qui suit.
1. Règle 1 : Si un site est inoccupé, il peut devenir occupé avec une probabilité pg ;
2. Règle 2 : Si un site est occupé mais inactif, il peut devenir actif spontanément avec une probabilité
pf
3. Règle 3 : Si un site est occupé et inactif, il devient actif si l’un de ses 8 voisins immédiats est actif.
4. Règle 4 : Si un site est occupé et actif, il devient vide à l’itération suivante.
L’inspiration initiale du modèle vient de l’écologie : un site occupé et inactif correspond à un arbre ; et un
site occupé et actif à un arbre en train de brûler ; la règle 1, c’est un arbre qui pousse ; la Règle 2, c’est
un arbre frappé par la foudre ; la Règle 3, c’est un arbre en feu enflammant les arbres voisins ; et la Règle
4 c’est la destruction d’un arbre par le feu.
Le fait qu’un arbre en feu puisse en enflammer un autre peut conduire à des effets d’avalanche sur
le réseau, dans le sens que l’activation d’un seul site peut conduire à une reconfiguration majeure du
système. Ceci est illustré à la Figure 10.3, qui montre une séquence d’instantanés chacun séparé par 10
itérations 2 , d’une simulation ayant pg = 10−3 et pf = 10−5 . Cette simulation roulait déjà depuis plusieurs
milliers d’itérations, et avait donc déjà atteint un état statistiquement stationnaire. Quelques itérations
avant le second instantané la foudre a frappé un peu en haut et à gauche du centre du réseau. L’activité se
propage de site en site, sous la forme d’un front de combustion approximativment circulaire initialement,
mais développant une forme de plus en plus convoluée à mesure qu’il se propage dans le réseau, en raison
du fait que la densité d’arbres dans la configuration pré-activité varie substantiellement à travers le réseau.
Cette hétérogénéité est elle-même une conséquence de feux ayant balayé le réseau dans un passé plus ou
moins rapproché (voir, e.g., le dernier instantané au bas de la Figure 10.3). Bien que le déclencheur soit
ici un processus stochastique (cf. Règle 2), l’activité passée affecte donc fortement l’activité présente.
La Figure 10.3 illustre bien la disparité des échelles temporelles implicite au modèle FdF. La plus
courte échelle de temps est l’échelle “dynamique” de la propagation de l’activité d’un site occupé à un
autre. L’échelle suivante est celle associée à l’occupation des sites vides. La dernière rangée d’instantanés
au bas de la Figure 10.3 montre bien la lenteur de ce processus ; il faut vraiment y regarder de près pour
réaliser que de nouveaux arbres sont apparus ici et là durant les 40 itérations couvertes par ces images. La
probabilité d’activation spontanée définit habituellement la plus longue échelle de temps dans le modèle,
l’intervalle moyen entre deux activations étant donnée par (p × N 2 × pf )−1 , où N est la grandeur du
réseau dans chaque dimension et p la probabilité moyenne d’occupation. Ici, avec N = 100, p ≃ 0.3 et
pf = 10−5 , on s’attend à une activation tous les 33 itérations en moyenne.
Le code ci-bas représente une implémentation minimale du modèle FdF, minimale encore une fois dans
le sens qu’on aurait pu écrire une version plus efficace du point de vue numérique, mais un peu moins
lisible.
2. Des animations en format mpeg de cette simulation, et d’autres pour différentes valeurs des paramètres pg et pf sont
disponible sur la page web du cours.
Figure 10.3 – Séquence d’instantanés de l’état du réseau pour le modèle FdF avec pg = 10−3 et pf = 10−5 ,
séparés chacun par 10 itérations. Les sites vides sont en blanc, les sites actifs en rouge, et les sites occupés
mais inactifs en vert. Au second instantané la foudre vient de frapper un arbre près du centre du réseau ;
un “front” de combustion balaye par la suite une grande partie du réseau. Simulation sur un réseau de
100 × 100.
La structure globale du code est semblable à celle du modèle TdS (§10.2). Toute l’action est incluse
à l’intérieur d’une boucle inconditionnelle contrôlant l’itération temporelle. Remarquez encore une fois
qu’on identifie d’abord quels sites doivent s’enflammer, se vider ou se remplir, via le premier bloc de
doubles boucles for, et que la mise à jour du réseau se fait ensuite de manière synchrone, à la fin de
chaque itération temporelle. Les modifications au réseau sont accumulées dans un tableau (evol) de
taille identique à celle du réseau. Notez finalement que le tableau grille inclue un “tampon” de noeuds
fantômes sur sa périphérie, qui demeurent toujours vides mais permettent de ne pas avoir à traiter les bords
véritables du réseau de manière différente afin d’éviter les débordements de tableaux. C’est pourquoi, à
l’intérieur de l’itération temporelle, les boucles sur le réseau commencent à 1 et se terminent à N.
Les règles contrôlant l’évolution du modèle sont très simples, et la seule règle de couplage (la 3)
est purement locale, dans le sens qu’elle n’implique que les voisins immédiats sur le réseau. De plus, le
modèle ne compte que deux “paramètres” ajustables, soit la probabilité (à chaque itération temporelle) de
croissance d’un arbre (pg ), et la probabilité (pf ) d’être frappé par la foudre. Malgré tout, ce modèle peut
produire une vaste gamme de comportements qui sont fort difficiles à anticiper sur la base de ses règles
d’opération. Ceci est illustré aux Figures 10.4 et 10.5, montrant l’évolution du nombre de sites occupés
inactifs (Na , trait noir) et actifs (Nf , traits rouge) pour différentes combinaisons de valeurs de pg et pf .
Si pf ∼ pg (Fig. 10.4, en haut, avec pg = pf = 10−4 ), les arbres sont frappés par la foudre à une
fréquence comparable à celle à laquelle ils poussent. Le nombre d’arbres sur le réseau demeure à peu près
constant, et de petits feux brûlent toujours quelquepart, sans jamais cependant prendre trop d’ampleur
car la densité d’arbres est trop faible, ce qui implique que très peu d’arbres ont un de leur 8 sites voisins
occupés par un autre arbre. Si on augmente pg à 10−2 (Fig. 10.4, en bas), la densité d’arbre est plus
élevée, et les arbres repoussent très rapidement ; si rapidement en fait qu’une fois un feu déclenché, il ne
s’éteint jamais, étant continuellement alimenté par le “reboisement” rapide s’effectuant derrière le front
de combustion. Ceci conduit à des oscillations passablement régulières dans le nombre de site actifs et
inactifs, les deux en antiphase, avec la période d’oscillation déterminée par le temps requis pour balayer
le réseau, ici de l’ordre de 100 itérations.
Si on laisse maintenant chuter la probabilité d’activation pf à une très faible valeur (pf = 10−6 pour les
deux solutions de la Figure 10.5), alors le réseau a la chance de se remplir beaucoup plus avant qu’un site
ne devienne actif. Mais quand celà se produit, presque chaque arbre a au moins un voisin, et on déclenche
alors un feu majeur qui brûle presque tout le réseau. Ceci se traduit en un cycle quasi-périodique dit de
“charge-décharge”, où à intervalles semi-régulier un feu de grande amplitude rase la plus grande partie de
la forêt. (Fig. 10.5, en haut). Avec une probabilité d’occupation ici de p ≃ 0.5 la quasi-totalité des arbres
ont un autre arbre à au moins un de leurs sites voisins, et donc le feu se propage rapidement. Le fait que le
nombre d’arbres ne tombe pas à zéro ici est en partie une conséquence des effets de bord, les arbres étant
situés sur les bords du réseau ayant moins de voisins se retrouvant plus difficile à enflammer. À bas pf , ce
cycle ne peut être brisé que si la croissance des arbres est elle-même suffisamment lente, de manière à ne
pas toujours remplir le réseau entre deux activations par la foudre. Ceci est illustré au bas de la Fig. 10.5,
pour une solution toujours avec pf = 10−6 mais où l’on a baissé à pg = 10−4 . Bien que des feux de grande
amplitude rasent une bonne partie du réseau de temps en temps, ceci se produit maintenant de manière
beaucoup plus irrégulière, et les tailles des feux couvrent maintenant un intervalle beaucoup plus large
en terme du nombre d’arbres détruits par feu. Notons la forme en dents-de-scie-fractale de la séquence
temporelle pour le nombre de sites occupés N , qui n’est pas sans rappeler la séquence temporelle de la
masse dans le modèle Tas-de-Sable (cf. Fig. 10.2A, encadré).
Ces différences deviennent frappantes si l’on calcule, à partir des résultats des simulations, les fonctions
de densité de probabilité de la taille des feux, en d’autre mots la fonction histogramme f (Nf ) mesurant
la probabilité d’occurrence d’un feu brûlant entre Nf et Nf + dNf arbres. Ces distributions sont portées
en graphique à la Figure 10.6, pour les deux simulations de la Figure 10.5. On y voit que dans le régime
pf ≪ 1, baisser de pg = 10−3 à pg = 10−4 produit une transition d’une distribution de type Gaussienne,
caractérisée par une valeur moyenne bien définie, à une distribution en loi de puissance de la forme :
f (N ) = f0 N −α , α>0, (10.11)
où f0 est une constante de normalization ; ici α = 1.07. Ce qui est remarquable est que dans le régime
pg ≪ 1, pf ≪ pg , la valeur de α est universelle, i.e., ne dépend essentiellement pas des valeurs exactes des
paramètres du modèle.
Quelle que soit la forme de la distribution f (N ), la quantité f (N )dN mesure la probabilité qu’un feu
détruise entre N et N + dN arbres. La quantité moyenne – ou la quantité attendue – d’arbres détruits
par un feu quelconque est alors donnée par l’intégrale :
Z Nmax
S= f (N )N dN, (10.12)
Nmin
où Nmin et Nmax représentent les quantités minimale et maximale d’arbres pouvant être détruits, res-
pectivement ici 1 et 104 , soit le nombre de sites occupables sur le réseau. Substituant l’éq. (10.11) dans
l’éq. (10.12), on obtient :
f0 2−α 2−α
S= Nmax − Nmin . (10.13)
2−α
Si Nmin ≪ Nmax (et ce sera habituellement le cas pour des systèmes impliquant un très grand nombre de
degrés de liberté), on se retrouve dans la situation suivante :
Figure 10.4 – Évolution temporelle du nombre de sites occupés inactifs (traits noir) et actifs (traits rouge)
dans le modèle FdF avec diverses combinaisons de valeurs de pg et pf , dans le régime où la probabilité
d’activation est relativement élevée. Toutes les simulations sont effectuées sur un réseau de taille 100×100.
Figure 10.5 – Suite de la précédente, maintenant dans le régime où la probabilité d’activation pf est très
faible. Notez bien que les échelles horizontale et verticale des graphiques ne sont pas toujours les mêmes
d’un graphique à l’autre.
Figure 10.6 – Fonctions de densité de probabilité des tailles des feux balayant le réseau, pour les deux
simulations de la Figure 10.5. En (A), la distribution est tolérablement bien représentée par une gaussienne,
tandis qu’en (B) on observe une loi de puissance, ici avec pente logarithmique −1.07.
1. α < 2 ; l’exposant 2 − α est alors positif, et donc la quantité d’arbres détruits est dominée par les
rares grands feux, via la contribution de Nmax à l’évaluation de l’intégrale :
2−α
f0 Nmax
S≃ , [α < 2] . (10.14)
2−α
2. α > 2 ; l’exposant 2 − α est alors négatif, et donc la quantité d’arbres détruits est dominée par les
nombreux petits feux, via la contribution de Nmin à l’évaluation de l’intégrale :
f0
S≃ α−2 , [α > 2] . (10.15)
(α − 2)Nmin
Dans le cas du modèle FdF en régime pg ≪ 1, pf ≪ pg , on est dans la première de ces situations, et ce
sont donc les très rares plus grands feux qui dominent l’évolution de l’écosystème. On observe le même
comportement avec, par exemple, les tremblements de terre, où la relaxation des stress tectoniques est
dominée par les plus intenses séismes.
Il s’avère que dans la double limite
pf ≪ pg , pg ≪ 1 , (10.16)
le modèle FdF opère en régime critique auto-régulé ; la forçage, c’est la croissance des arbres ; l’instabilité,
c’est qu’il y ait une densité d’arbre suffisante pour que chaque arbre ait en moyenne un arbre voisin ; et
la redistribution, c’est la destruction des arbres par le feu.
Ceci s’avère caractéristique des systèmes en état d’autorégulation critique. Considérons ce qui arrive si l’on
décide soudainement de changer l’échelle de mesure de la variable x (par exemple, on décide de mesurer
la taille d’une avalanche de sable en tonnes plutôt qu’en kilos). Ceci revient à appliquer un changement
d’échelle à la variable x, dont on peut représenter le résultat par la définition d’une nouvelle variable y
telle que :
y = x/a , (10.18)
où a est une simple constante numérique (e.g., a = 103 si on décide de mesurer x en tonnes métriques
plutôt qu’en kilos). L’éq. (10.17) devient alors :
On voit que la distribution f (y) demeure une loi de puissance, avec la même valeur de l’exposant. Donc,
que la variable x soit mesurée en centimètres ou en années-lumière, sa distribution est toujours une loi
de puissance avec pente logarithmique égale à −α. On dit donc que la distribution est invariante par
rapport à un changement d’échelle. Contrastons ceci avec ce qui se passe, sous la même transformation
d’échelle, avec la distribution exponentielle :
−1
−1 λ
f (x) = f (ay) = λ exp(−(ay)/λ) = a exp (−y)/(λ/a)) ; (10.20)
a
ici, la forme même de la distribution change, dans le sens que la valeur numérique du paramètre d’échelle λ
se retrouve effectivement divisée par un facteur a. La distribution exponentielle n’est donc pas invariante
par rapport à un changement d’échelle. Il en irait de même avec la distribution gaussienne, pour laquelle
et la valeur moyenne et la variance se retrouve à être affectées par un changement d’échelle.
L’invariance d’échelle caractérisant les systèmes en autorégulation critique vient du fait que la dy-
namique du système n’introduit aucune échelle caractéristique entre la distance internodale et la taille
globale du système. Une avalanche, qu’elle n’implique qu’un seul site ou toute la pente, opère toujours via
l’interaction d’un site avec ses voisins immédiats ; un feu, qu’il soit petit ou grand, se propage toujours
via l’allumage d’un arbre par un voisin déjà en feu. Une des conséquences les plus intriguantes de cette
invariance d’échelle apparait quand on examine les forme géométriques des avalanches dans le modèle
TdS, des fronts de combustion ou des clairières qu’ils forment dans le modèle FdF : ces structures sont
toutes des fractales !
xn+1
k = xnk + vkn × ∆t .
Dans tout ce qui suit, on posera ∆t = 1 sans aucune perte de généralité. Le code ci-bas implémente
cet “algorithme” de déplacement du trafic, avec une importante addition discutée plus bas.
Code Python pour modele embouteillage
1 import numpy as np
2
Figure 10.7 – Évolution de la position des voitures (axe vertical) en fonction du temps (axe horizontal).
On note deux phases distinctes, la première durant laquelle la condition initiale relaxe, par une série
d’embouteillages majeurs, vers un état où la vitesse de toutes les voitures est approximativement constante,
mais où des embouteillages peuvent néanmoins se produire. Le trait orange indique la trajectoire d’une
voiture spécifique, située au trois quart du peloton. Les lignes pointillées indiquent la pente associée à
une voiture se déplaçant à la vitesse maximale v = 10. L’encadré montre un zoom sur un embouteillage ;
on y constate que les voitures ne se croisent jamais (comme il se doit puisque les dépassements sont
interdits !). Cette simulation, impliquant 300 voitures, a été effectuée avec la condition initiale et les
valeurs de paramètres tels que spécifiés dans le code de la Figure ??.
Figure 10.8 – La même simulation que sur la Figure 10.7, mais cette fois couvrant un plus grand intervalle
temporel et montrant la distribution spatiotemporelle des embouteillages. Chaque point bleu correspond
à une voiture au repos ou presque (v ≤ 1). Le carré inférieur gauche définit l’intervalle couvert sur la
Figure 10.7, et les lignes pointillées indiquent encore une fois la pente d’une trajectoire à vitesse maximale
v = 10. Les trajectoires des première et dernière voitures sont tracée en noir, et la voiture-test en orange.
(je vous laisse le soin de démontrer la seconde égalité dans cette dernière expression). La densité moyenne
de voitures (ρ ; nombre de voitures par unité de distance) est simplement l’inverse de cette expression :
N −1
ρ= (10.23)
xN − x1
Connaissant ces deux quantités, le flux (Φ) de voitures, soit le nombre moyen de voiture traversant une
position x∗ quelconque par unité de temps, se calcule facilement :
Φ = ρ × ⟨v⟩ ; (10.24)
La Figure 10.9 montre l’évolution temporelle de ⟨v⟩, ρ et Φ, pour la simulation de la Figure 10.7. On y
remarque que les valeurs numériques de ces deux quantités varient rapidement jusqu’à t ∼ 1300, ce qui
correspond au changement marqué dans la structure des embouteillages visibles sur la Fig. 10.7, cependant
la densité de voitures —et donc aussi le flux— ne se stabilise vraiment que vers t ≃ 2000.
La série d’embouteillages monstres caractérisant la phase “solide” en début de simulation suggère que
la condition initiale choisie ici n’est peut-être pas la meilleure, du point de vue de la fluidité du trafic.
Jusqu’à quel point cette condition initiale influence-t-elle l’évolution de la simulation ? Est-elle “oubliée”
rapidement ou lentement ? En fait, concoctez la condition initiale que vous voulez, ou changez le nombre
de voitures (tant que N demeure grand), le système stabilisera toujours aux valeurs statistiquement
stationnaires de ⟨v⟩, ρ et Φ de la Figure 10.9 ! Cette forme de trafic, avec ses embouteillages intermittent,
s’avère être un attracteur de la dynamique du système.
L’évolution vers une vitesse moyenne “robuste” pourrait laisser croire que toutes les voitures se dé-
placent à une vitesse ne différant pas trop de cette valeur moyenne, e.g. la distribution des vitesses a l’air
d’une gaussienne centrée sur ⟨v⟩. Ce n’est en fait pas du tout le cas, comme on peut le constater sur
examen de la Figure 10.10. La fonction de densité de probabilité est ici calculée par échantillonnage des
vitesses de toutes les voitures à tous les pas de temps dans la phase fluide (t > 3500). La distribution n’est
en rien symmétrique par rapport à la vitesse moyenne (tiret vertical à v ≃ 8.6), mais couvre l’intervalle
complet [0, 10] avec un maximum à v = 10, un pic secondaire à v = 0 correspondant aux embouteillages,
et un autre à v = 7. Ce second pic secondaire est une conséquence directe de la procédure de freinage
aléatoire, qui réduit la vitesse de 3 unités, agissant sur le pic de la distribution à v = 10. On constate
également que les voitures passent un peu plus de 60% du temps à rouler à la vitesse maximale v = 10
et environ 4% du temps coincées dans un embouteillage quelconque, ce qui n’est pas si mal finalement
(même si le niveau de stress généré serait évidemment hors de proportion par rapport à cette réalité). On
voit qu’ici la vitesse moyenne n’est pas une quantité particulièrement utile pour caractériser les vitesses
des voitures, même si elle a un sens mathématique incontestable au niveau du flux global de l’ensemble
des voitures.
Figure 10.9 – Variation temporelle de la vitesse moyenne ⟨v⟩, de la densité de voitures ρ, et du flux Φ,
pour la simulation des Figures 10.7 et 10.8, maintenant poussée jusqu’à 5000 pas de temps.
Vous aurez probablement déjà saisi que la formation d’un embouteillage représente une forme d’ava-
lanche de freinages successifs. Si l’analogie tient, on pourrait s’attendre à ce que ces “avalanches” soient
caractérisées par une invariance d’échelle. La taille d’un embouteillage devrait normalement être définie
comme le nombre de voitures bloquée (vkn ≤ 1) sommé sur la durée d’arrêt de chaque voiture impliquée.
Autrement dit, le nombre de points bleus dans chaque “amas” distinct sur la Figure 10.8. Une alternative
plus facile consiste à choisir quelques voitures-test (genre, une dizaine) suffisamment séparées l’une de
l’autre, et de comptabiliser les durées des phases où chaque voiture est arrêtée ou presque (dans le sens
vkn ≤ 1). On sait déjà (voir Figures 10.7 et 10.8) que ces phases sont plus courtes que la durée de l’embou-
teillage dans son ensemble, mais elles devraient tout de même montrer les même distributions statistiques.
Dans les deux cas, on obtient, devinez quoi, une loi de puissance, indiquant une invariance d’échelle au
niveau des embouteillages.
Globalement, ce qui compte au niveau du déplacement efficace du traffic, c’est de maximiser le flux de
voitures, soit le produit de la densité moyenne de la vitesse moyenne. On pourrait imaginer imposer un
grand espacement entre chaque voiture, de manière telle à ce qu’un freineur aléatoire ait assez de temps
pour ré-accélérer à la vitesse maximale avant de forcer la voiture le suivant à ralentir. Cependant, une telle
configuration serait caractérisée par une densité moyenne de voiture très faible, donc un flux très faible
même si toutes les voitures roulent à vitesse maximale. Inversement, forcer toutes les voitures à rouler
très près les unes des autres, de manière à avoir une très haute densité et donc un très haut flux, garantit
que le moindre freinage aléatoire causera un embouteillage monstre qui forcera l’arrêt complet d’un très
grand nombre de voitures, diminuant ainsi drastiquement le flux. Mais voici le truc vraiment subtil :
l’état bordellique du trafic simulé ici, avec ses embouteillages spatiotemporellement intermittents et de
toutes tailles, représente l’état qui maximise le flux de voitures, en présence de freineurs aléatoires. Et cet
état est auto-régulé, dans le sens qu’il émerge naturellement des interactions locales entre chaque voiture
et ses deux voisines. On pourrait même déclarer que le système est dans un état critique, dans les sens
qu’une petite perturbation spatialement localisée —un freinage aléatoire quelquepart— a une probabilitée
Figure 10.10 – Fonction de distribution de probabilité des vitesses, construite à partir des mesures de
vitesses de toutes les voitures à chaque pas de temps pour la simulation des Figs. 10.7, 10.8, et 10.9, dans
la phase “fluide” t > 3500. Le trait vertical noir indique la vitesse moyenne, et le pic secondaire à v = 7
est produit par le freinage aléatoire (v → v − 3) de voitures se déplaçant à vitesse maximale (voit texte).
définitivement non-nulle de causer un changement global dans tout le système —un embouteillage stoppant
la quasi-totalité des voitures. On pourrait donc dire qu’on a encore ici affaire à un système en état
d’autorégulation critique.
s1 s2 s3 s4 s5 s6
00000 00002 00001 00001 06464 11009
00001 00003 00002 00004 23945 31788
00000 00004 00003 00005 72118 85928
00001 00005 00005 00008 79626 59284
00000 00006 00008 00009 64242 22379
00001 00007 00013 00014 90967 39022
00000 00008 00021 00003 32538 47769
00001 00009 00034 00020 86925 22116
00000 00010 00055 00025 45004 52061
00001 00011 00089 00007 98011 34803
00000 00012 00144 00003 07716 16195
00001 00013 00233 00024 28198 97786
00000 00014 00377 00025 80178 39746
00001 00015 00610 00004 62934 09349
00000 00016 00987 00017 92374 09128
00001 00017 01597 00046 27894 29219
00000 00018 02584 00021 79649 03260
00001 00019 04181 00025 64187 26513
00000 00020 06765 00049 91028 37855
00001 00021 10946 00023 32339 87804
pourrait être judicieux d’emmagasiner un programme générant 00000 et 00001 en alternance, 20 millions
de fois. De même, la seconde séquence pourrait être emmagasinée dans un programme qui débute à 00001,
et ajoute 1 à ceci, 2 millions de fois. La séquence s3 est plus difficile à déchiffrer ; elle croit de manière
monotone mais définitivement pas linéaire ou quadratique, mais il semble avoir un pattern ici également.
La quatrième séquence semble encore plus irrégulière, dans le sens que globalement elle semble croitre
comme s2 et s3 , mais avec des baisses occasionnelles. Les séquences s5 et s6 , quant à elles, ne présentent
rien qui ressemble à un pattern pouvant s’encoder dans une instruction simple. “À l’oeil”, on pourrait
donc être tenté de classer les six séquence par ordre de complexité croissante, de la manière suivante
(schématiquement) :
[s1 ] < [s2 ] < [s3 ] < [s4 ] < [s5 ] ≃ [s6 ] .
Donc, même si toutes les séquences sont définies par le même nombre total d’entiers, on serait tenté de
dire que les deux premières sont les plus “simples” et les deux dernières sont les plus “complexes”, parce
que les premières peuvent clairement être “compressées” en quelques instruction de production, mais
(apparemment) pas les autres.
Cette idée est formalisée sous l’appellation de complexité algorithmique, qui est définie comme la
longueur (mesurée en octets, par exemple) du programme le plus court pouvant produire une séquence
donnée. Si il n’y a vraiment pas de pattern dans la séquence (comme apparamment ici avec s5 et s6 ), un
tel programme ne pourra qu’énumérer les membres de la séquences, dans lequel cas le programme aura
essentiellement la même longueur que la séquence même. Mais parfois, comme dans le cas des séquences
s1 er s2 , le programme sera beaucoup plus court que la séquence.
En fait, les six séquences du Tableau 10.3 ont été produite par le petit bout de code montré ci-dessous.
Les séquences s1 , s2 , s3 (séquence de Fibonnacci) et s5 (séquence chaotique produite par carte logistique)
s’avèrent à avoir une complexité algorithmique comparable, dans le sens qu’elles sont toutes produites par
une instruction séquentiellement répétée, soit la ligne de code à l’intérieur de la boucle. Ici ces instructions
n’impliquent que des opérations arithmétiques simples, comme l’addition ou la multiplication. La séquence
s4 est également produite par une seule ligne de code, mais utilise l’opérateur “%”, qui calcule le reste
de la division du chiffre le précédant par celui le suivant ; opération à prime abord plus compliquée que
l’addition ou la multiplication, mais qui du point de vue de la manière dont elle est calculée par l’ordi,
ne l’est pas vraiment. La séquence s6 , quant à elle, résulte d’une manipulation simple d’une séquence de
nombres pseudo-aléatoires, de complexité algorithmique plus élevée parce qu’il y a pas mal d’instructions
cachées dans la fonction [Link](). Les trois systèmes complexes étudiés dans ce chapitre
peuvent tous être définis par un code long d’une page et qui utilise [Link](), donc on devra
assigner à leur extrant (e.g., séquence temporelle de sable déplacé ; d’arbres brulés ; vitesse d’une voiture)
une complexité algorithmique encore plus élevée.
Code Python pour calculer les six séquences du Tableau 10.3.
1 #Calcul six sequences plus ou moins complexes de nombres
2 import numpy as np
3 NN=21
4
5 x1=[Link](NN)
6 x2=[Link](NN)
7 x3=[Link](NN)
8 x4=[Link](NN)
9 x5=[Link](NN)
10 x6=[Link](NN)
11
12 # Sequence 1: bitflip
13 for k in range(NN): x1[k]=(k+1)%2
14
19 # Sequence 3: Fibonnacci
20 x3[0]=1.0
21 x3[1]=1.
22 for k in range(2,NN): x3[k]=x3[k-1]+x3[k-2]
23
24 # Sequence 4: a mod k
25 for k in range(NN): x4[k]=12345%(3*k+1)
26
37 # Sortie
38 for n in zip(x1[1:],x2[1:],x3[1:],x4[1:],x5[1:],x6[1:]):
39 print(("{:05n} "*6).format(*n))
Donc, du point de vue de la complexité algorithmique, on devrait donc reclasser nos six séquences,
ainsi que les trois systèmes étudiés dans ce chapitre, de la manière suivante (toujours schématiquement) :
[s1 ] ≃ [s2 ] ≃ [s3 ] ≃ [s5 ] ≃ [s4 ] < [s6 ] < [TdS] ≃ [FdF] ≃ [A15] .
À moins d’avoir l’esprit vraiment tordu, ceci n’est définitivement pas conforme à l’impression “intuiti-
ve” produite par un premier examen du tableau 10.3, telle que reflétée dans notre premier classement
préliminaire.
déclencheur dans les modèles TdS, FdF, et A15, on s’attendrait à juste titre qu’il soit impossible de
prédire leur comportement dans le détail (e.g., étant donné l’état du système à l’itération n, où et quand
se déclenchera la prochaine avalanche ou le prochain feu). Cependant il ne faut surtout pas en conclure que
ces systèmes se comportent de manière véritablement stochastique, puisque l’état du système à l’itération
n a une très forte influence sur l’état du système à l’itération n + 1. Il est certainement vrai que les
systèmes stochastiques et complexes impliquent habituellement un grand nombre de degrés de liberté.
Mais, même si la trajectoire d’un seul marcheur aléatoire peut paraitre très “complexe”, comme on l’a vu
l’évolution de la distribution d’un très grand nombre de marcheurs est, elle, très simple, et exprimable
sous la forme d’une équation différentielle pouvant même parfois être solutionnée analytiquement. Ce
n’est certainement pas le cas des systèmes complexes en état d’autorégulation critique, qui ne se portent
absolument pas à ce genre de réduction statistique. À mesure que l’on augmente le nombre de degrés de
liberté dans le système, la possibilité de produire une grande “fluctuation” par rapport à l’état moyen (i.e.,
une avalanche de taille comparable à l’échelle globale du système) reste la même ! Tout le contraire de la
convergence statistique des systèmes stochastiques, où dans la limite N → ∞ les fluctuations diminuent
comme N −1/2 .
La relation avec les systèmes chaotiques est encore plus difficile à établir. Ces derniers impliquent
habituellement un nombre relativement bas de degrés de liberté, et leur évolution est (habituellement)
régie par des règles complètement déterministes qui font que l’état du système au temps t + ∆t est
complètement fixé par l’état du système au temps t. L’antithèse même des processus stochastiques, mais
il peut sembler y avoir un lien ici avec les systèmes complexes, puisque ceux-ci évoluent également de
manière purement déterministe, sauf pour le déclenchement des avalanches. Pourrait-on imaginer un
système complexe comme un système chaotique ayant un grand nombre de degrés de liberté, et sujet à un
faible forçage stochastique ? La réponse s’avère être non, et, ultimement, est associée à la manière dont
les décorrélations s’effectuent dans ces systèmes. Dans les systèmes chaotiques, la décorrélation de deux
solutions différant très peu se produit lors de l’approche de points critiques dans l’espace de phase du
système (retournez voir la Fig. 8.11). Ces points critiques peuvent être identifiés ab initio si la dynamique
de chaque degré de liberté est connue. Dans un système complexe en régime SOC, deux noeuds du réseau
décorrèlent au passage d’une avalanche, dont les caractéristiques ne peuvent absolument pas être anticipée
sur la base des règles d’évolution locales.
L’émergence d’une dynamique globale qualitativement distincte de la dynamique locale semble donc
être la condition sine qua non requise pour déclarer qu’un système est complexe.
Exercices:
1. Vérifiez que l’éq. (10.7) résulte bien de l’application de la règle de redistribution définie par
l’éq. (10.4).
2. Élaborez et codez une version 2D du modèle TdS de la §10.2. Mesurez les tailles et durée des
avalanches une fois le système dans son état SOC, et montrez que ces deux quantités se distribuent
comme des lois de puissance.
3. Modifiez le code C pour le modèle FdF (Fig. ??) de manière à opérer en mode “stop-and-go”,
c’est-à dire qu’aucun arbre ne peut pousser tant qu’un feu brûle quelquepart sur le réseau. Dans
quel(s) régions(s) de l’espace des paramètres celà change-t-il le comportement du modèle ?
4. Modifiez le code C pour le modèle FdF (Fig. ??) de manière à ce que seul les quatres voisins
haut+bas+gauche+droite puissent enflammer un arbre. Celà change-t-il le comportement global
du modèle ?
5. L’équation (10.13) se comporte de manière pathologique (→ ∞) pour une loi de puissance ayant
α = 2. Comment vous y prendriez vous pour calculer le nombre d’arbres détruits par l’ensemble
des feux dans cette situation ?
Bibliographie:
Plusieurs bouquins traitant de complexité à saveur grand public ont été écrits, et sont devenus des
“best-sellers”. Il pourrait être instructif d’en lire au moins un. Je doit bien être la seule personne que je
connaisse ( !) à ne pas avoir été particulièrement épaté à la lecture du best-seller The Quark and the Jaguar,
par Murray Gell-Mann (Prix Nobel de Physique 1969 !), mais c’est définitivement un incontournable sur
le sujet, dans la catégorie non-technique. Plusieurs ouvrages passablement plus techniques sont également
disponibles. Ils font cependant souvent dans la superficialité, en partie en raison de la vaste gamme
d’applications du sujet, et aussi en raison du fait qu’il n’existe pas à l’heure actuelle de théorie de la
complexité. Dans cette catégorie, ma préférence (en date de la mi-novembre 2009) va à
Érdi, P., Complexity Explained, Springer (2008).
Une exception notable à cette tendance à la superficialité est ce qui est rapidement devenu le Manifeste
du (maintenant défunt) Santa Fe Institute, soit l’ouvrage :
Kauffman, S.A., The Origin of Order, Oxford University Press (1993),
mais l’emphase y est nettement mise sur les systèmes biochimiques. Je suggérerais également l’éclectique
bouquin
Hofstadter, D.R., Gödel, Escher, Bach, Basic Books (1979),
qui, bien que datant de maintenant 30 ans, demeure une lecture des plus stimulantes intellectuellement.
Les chapitres 15 à 19 de l’ouvrage de Flake cité au chapitre 7 représentent également une intéressante
introduction au sujet. Tout ce que vous voudriez savoir sur les automates cellulaires se trouve fort proba-
blement dans la plus récente brique de Stephen Wolfram (l’inventeur du logiciel Mathematica) :
Wolfram, S., A new kind of science, Wolfram Media Inc. (2002).
Cependant, préparez vous psychologiquement à devoir endurer l’égo cosmologique de l’auteur, suintant
abondamment de chaque page souvent au point de rendre la lecture irritante. Sur la criticalité auto-régulée,
commencez par
Bak, P., Quand la nature s’organise (trad. How Nature Works), Flammarion (1999),
et passez ensuite à la vision plus cartésienne du sujet telle que développée dans :
Jensen, H.J., Self-organized Criticality, Cambridge (1998).