0/ Conseils pour les TP, préparation Python

Conseils

Consigne

Les blocs “Consigne” synthétisent les étapes à faire pour mener à bien le TP.

Les TP de l’UE Projets Numériques sont construits de manière incrémentale : chaque TP s’appuie sur le travail fait lors des TP précédents. À des fins d’organisation de code, il est recommandé, lors avant chaque nouveau TP, de :

  1. Créer un nouveau fichier Python correspondant au nouveau TP, par exemple tp5.py
  2. Ajouter les fonctions que vous avez implémentées au TP précédent dans un fichier my_functions.py
  3. import sys; sys.path.append('.'); from my_functions import * dans le fichier du nouveau TP
  4. Démarrer le sujet du nouveau TP
Avertissement

De nombreux exemples sont donnés dans les sujets de TP pour illustrer des notions de code. Ne copiez-collez pas ces exemples sans réfléchir. Les fonctions (par exemple la fonction write_xyz) données telles quelles dans le sujet peuvent être copiées sans soucis.

Puisque chaque TP s’appuie sur les TP précédents, il est important de terminer chaque TP, même si les TP 1 à 4 ne sont pas évalués, car aucune correction ne sera donnée. En cas de blocage, il est donc essentiel de demander de l’aide, à vos collègues ou au responsable (moi).

Utilisation de modèles génératifs (type ChatGPT)

L’un des objectifs l’UE Projets Numériques est d’apprendre à traduire des idées mathématiques en code. Les algorithmes qu’il faut implémenter sont relativement simples et suffisamment documentés pour que ChatGPT (ou un autre modèle génératif) soit capable de générer un code fonctionnel. Cela veut aussi dire que ces algorithmes sont tout à fait à la portée d’étudiants de L3 ! Si vous voulez utiliser un modèle génératif, essayez de vous en servir comme d’une aide à l’apprentissage, et non comme un outil qui ferait le travail à votre place.

Note

Dans tous les cas, n’hésitez pas à poser des questions en cas de doute.

Python

Tous les travaux pratiques et le projet se feront en Python. Avant de commencer les TPs d’implémentation, vérifions que l’installation Python à disposition (dans les salles de TP ou sur une machine personnelle) est suffisante :

Consigne
  1. Ouvrir “Anaconda Navigator” puis “Spyder” depuis Anaconda.
  2. Vérifiez que les librairies numpy et matplotlib sont bien présentes (import numpy; import matplotlib.pyplot)

Pour la rédaction du rapport, il est recommandé d’utiliser LaTeX, le Wikibook LaTeX est une excellente ressource, et Overleaf une bonne solution d’édition, qui évite d’installer LaTeX et permet facilement la collaboration.

Afin de se refamiliariser avec Python, il est fortement recommandé de faire les exercices corrigés de code du chapitre sur l’intégration numérique des équations différentielles ordinaires.

Important

Dans Spyder, n’utilisez jamais la fonctionnalité de “cellule” ou de “notebook” (le bouton “Exécuter la cellule”). C’est une grosse source d’erreurs difficiles à tracer. À la place, toujours exécuter un script avec le bouton “Exécuter le fichier” ou le raccourci “F5”.

Installation de paquets Python

Sur les ordinateurs de TP, il est possible d’installer des paquets Python avec la commande :

pip install --proxy http://proxyweb.upmc.fr:3128 <paquet>

Dans Spyder, on pourra exécuter cette commande dans la console IPython en la précédant d’un point d’exclamation, par exemple :

In[1]: !pip install --proxy http://proxyweb.upmc.fr:3128 matscipy

Il est possible qu’il faille redémarrer le noyau IPython pour pouvoir import matscipy: dans Spyder, Console > Redémarrer le noyau, ou avec le raccourci Ctrl+..

Quelques erreurs communes en Python et Numpy

Création vs. mise-à-jour de la valeur d’une variable

N = 10

velocities = np.zeros([2, N])

velocities[:] = 1
print(f"`velocities[:] = 1` donne :\n{velocities}\n")

velocities = 2
print(f"`velocities = 2` donne :\n{velocities}")
1
On crée des vitesses 2D pour chaque particule
2
On assigne une valeur à chaque composante du vecteur velocities
3
On crée une nouvelle variable qui vient effacer la première
`velocities[:] = 1` donne :
[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

`velocities = 2` donne :
2

Le code velocities[:] = 1 permet d’assigner la valeur 1 à toutes les valeurs du tableau velocities, qui est un “Numpy array”. En revanche, quand on écrit velocities = 2, on crée en réalité une nouvelle variable qui vient détruire l’ancienne variable. velocities est donc maintenant un entier qui vaut 2.

Valeur de retour d’une fonction

def forward_euler1(position, velocity, dt):
    return position + velocity * dt

def forward_euler2(position, velocity, dt):
    position += velocity * dt

La première fonction retourne la position après une itération d’Euler explicite. On doit donc l’utiliser comme ceci, en assignant la valeur de retour de la fonction :

position, velocity = 1, 0.1
dt = 0.1

position = forward_euler1(position, velocity, dt)
print(position)
1.01

La seconde fonction ne retourne aucune valeur. À la place, elle met à jour la valeur de l’argument position, et doit être utilisée comme ceci :

positions, velocity = 1, 0.1
dt = 0.1

forward_euler2(positions, velocity, dt)
print(position)
1.01

Notez que si on utilise forward_euler2 comme forward_euler1, on change complètement la variable position, qui devient None :

position = forward_euler2(position, velocity, dt)

print(position, type(position))
None <class 'NoneType'>

Cela peut facilement causer des erreurs dans le reste du code, par exemple :

position += 1.0
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 position += 1.0

TypeError: unsupported operand type(s) for +=: 'NoneType' and 'float'

Expliciter les types de variables en Python

Depuis la version 3.5 de Python, il est possible d’annoter le type d’une variable. Contrairement à un langage comme le C, le type indiqué en Python est purement indicatif : il ne contraint en rien l’exécution du programme si le type indiqué ne correspond pas au type effectif de la variable. L’annotation se fait comme suit :

dt: float = 0.1
N: int = 100

valeurs: list[int] = [1, 2, 3]

L’intérêt d’annoter les types est de pouvoir préciser les types qu’une fonction attend pour ses arguments, par exemple :

def exponentielle(x: float):
    return np.exp(x)

On peut également préciser des types issus de Numpy. Il faut dans ce cas importer le module numpy.typing, par exemple, si une fonction attend un tableau de flottants :

import numpy.typing as npt

def forward_euler(positions: npt.NDArray[float],
                  velocity: npt.NDArray[float],
                  dt: float):
    return positions + dt * velocity