-- Introduction au Python scientifique --

Les bases du Python et de numpy

Introduction au python scientifique

-

simon.marie@lecnam.net

Exécution d'un script Python

Le langage Python est un langage précompilé ne nécessitant donc aucune compilation. Il y a 3 façons principales d'utiliser Python:

  1. Ecrire un script dans un fichier .py et le lancer en ligne de commande:
    \$> python script.py
    Pour éditer des scripts, on utilise un éditeur spécialisé qui reconnait la syntaxe par exemple notepad++, emacs, vi,gedit...
  2. Utiliser directement Python en ligne de commande avec Ipython ou Spyder
  3. Utiliser un Notebook Jupyter (C'est ce moyen qui est utilisé ici):
    \$> jupyter notebook filename.ipynb

Pour un projet large et complexe, on préfèrera la première solution, pour faire des petits tests ou pour une utilisation locale et restreinte on préfèrera la seconde solution. Pour les TP, les rapports et tous les support nécessitant une diffusion large et une utilisation de texte, le Notebook constitue la solution idéale.

Importation des modules

Les premères lignes d'un script Python sont consacrées à l'appel des modules nécessaires aux calculs. On utilise pour cela la commande import. Pour les besoins des TP de simulation numérique, les modules numpy, matplotlib et math seront nécessaires. Le début du script ressemblera donc à:

In [2]:
import numpy as np # contitent des fonctions utiles pour le traitement des matrices
import matplotlib.pyplot as plt # contitent des fonctions utiles pour la representation graphique
import time # Pour évaluer les temps de calcul
%matplotlib inline

Les lignes ci-dessus importent le module numpy en le nommant np. Ainsi tous les appels à des fonctions propres aux modules numpy seront précédés de np.:

In [3]:
y=np.sin(np.pi) # calcul sin(pi)
print(y)
print(np.sqrt(np.log(np.exp(4))))
1.2246467991473532e-16
2.0

Manipulation des vecteurs / matrices

In [4]:
A=[0,1,2,3,4,5,6,7,8]
B=[[0,1,2],[3,4,5],[6,7,8]]

print(A)
print(B)
print(B[1][1])
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
4

Les objets A et B sont des listes:

In [5]:
print(type(A),type(B))
<class 'list'> <class 'list'>

Le module $\textit{numpy}$ permet la manipulation des matrices dans python. Avec numpy, on peut facilement transformer ces objets en matrices:

In [6]:
D=np.array(A)
E=np.array(B)
print(type(E))
print(E[1,1])
<class 'numpy.ndarray'>
4

Pour créer des vecteurs et des matrices on pourra donc utiliser des fonction natives du module numpy. Ainsi pour initialiser et créer une matrice pleine de 0 on utilisera la fonction $\textit{zeros}$ de $\textit{numpy}$.

In [7]:
nx=10
ny=4
A=np.zeros((nx,ny)) # Matrice de 10 lignes et 4 colonnes ne contenant que des 0
print(A)
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

La fonction arange(n) de $\textit{numpy}$ sert a créer une liste d'entier allant de $0$ à $n-1$.

In [8]:
B=np.arange(nx) # Vecteur de nx elements allant de 0 à 9
print(B)
[0 1 2 3 4 5 6 7 8 9]

Attention, en python les indices commencent à 0 ! On peut ainsi sélectionner ou modifier une partie de matrice en sélectionnant ses indices comme présenté ci-dessous:

In [9]:
print(B[:]) # Tous les elements 
print(B[0]) # Le premier élément
print(B[-1]) # Le dernier
print(B[-2]) # L'avant dernier
print(B[2:5,]) # Elements 3 a 5 
print(B[0:-1]) # Attention ici on ne va que jusqu'à n-1
print(B[0:])  # Equivalent à B[:]
# B[n1:n2] # selection de n1 a n2-1 donc ligne n1+1 a n2
[0 1 2 3 4 5 6 7 8 9]
0
9
8
[2 3 4]
[0 1 2 3 4 5 6 7 8]
[0 1 2 3 4 5 6 7 8 9]

Pour créer des vecteurs contenant des suites de nombres régulièrement espacés (par exemple pour des maillages) on peut utiliser:

  • Soit la fonction $\textit{arange(start,end,step)}$ de $\textit{numpy}$ qui impose le début la fin et le pas:
In [10]:
A=np.arange(-2,2,0.5) # Vecteur contenant tous les nombres entre -2 et 1.5 par pas de 0.5
print(A)
[-2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5]
  • Soit la fonction linespace(start,end,nombre) de numpy qui impose le début, la fin et le nombre d'élément:
In [11]:
A=np.linspace(-2,2,8) # Vecteur contenant 10 élément régulièrement espacés entre -2 et 2
print(A)
[-2.         -1.42857143 -0.85714286 -0.28571429  0.28571429  0.85714286
  1.42857143  2.        ]

Pour créer une matrice diagonale on utilise la fonction $\textit{eye(nx,ny)}$ de $\textit{numpy}$:

In [13]:
B=np.eye(3,3) # Matrice identite de 3*3 elements
print(B)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Les boucles python

L’ensemble de la syntaxe python est basée sur l'indentation du texte. Ainsi toutes les lignes devront commencer au même endroit. Le début et la fin d'une boucle (for, if, while...) sera repérée par une indentation différente. C'est une syntaxe propre au Python qui permet de s'affranchir des syntaxe de type end for mais qui impose d'être rigoureux dans l'écriture du code ! Par convention on utilise une indentation de 4 espaces. Ainsi, une boucle for s'écrira de la façon suivante:

In [15]:
A=np.arange(10)
j=1
for i in A:
    j+=2 # equivalent de j=j+2
    print(j,i,i**2,i**3)
3 0 0 0
5 1 1 1
7 2 4 8
9 3 9 27
11 4 16 64
13 5 25 125
15 6 36 216
17 7 49 343
19 8 64 512
21 9 81 729

Attention a ne pas oublier le signe $:$ à la fin de la première ligne de boucle.

Notes sur les Boucles et le slicing en Python

Si l'on doit effectuer des boucles sur un très grand nombre de points, le temps de restitution risque de devenir très long. Dans ce cas on préferrera faire du "slicing" c'est à dire faire une implémentation vectorisé quand cela est possible.

L'exemple suivant illustre le même calcul réalisé en boucle puis en slicing:

In [16]:
nx=1000000
f1=np.zeros(nx)
f2=np.zeros(nx)
g=np.ones(nx)

# boucle
t0=time.time()
for i in np.arange(1,nx-1):
    f1[i]=g[i+1]+g[i]+g[i-1]
t1=time.time()-t0

# slicing
t0=time.time()
f2[1:-1]=g[2:]+g[1:-1]+g[0:-2]
t2=time.time()-t0
print(t1,t2,t1/t2)
1.000169038772583 0.006905555725097656 144.83541637895317

Ici le slicing est donc cent fois plus rapide !!

On peut vérifier que les fonctions $f_1$ et $f_2$ sont bien identique

In [17]:
plt.plot(f1,f2)
Out[17]:
[<matplotlib.lines.Line2D at 0x7fca70bef7d0>]

Utilisation des fonctions

Dans tous les langages de programmation, il est possible d'effectuer une tache répétitive à l'aide d'une fonction. En python on utilise pour cela le mot clé def suivit du nom de la fonction que l'on veut créer. Essayons par exemple de créer une fonction qui calcul la valeur d'un polynôme en x:

In [18]:
def ma_fonction(x):
    return 2.*x**2-3.*x+1

print(ma_fonction(-1.))
print(ma_fonction(0.))
print(ma_fonction(1.))
6.0
1.0
0.0
In [19]:
x=np.arange(-2.,2.,0.1)
plt.plot(x,ma_fonction(x))
Out[19]:
[<matplotlib.lines.Line2D at 0x7fca70b16910>]

On peut également créer des fonctions un peu plus évoluées comme par exemple la fonction factorielle qui utilise la récusrion (le fait d'appeller la fonction dans la définition de la fonction):

In [20]:
def fact(n):
    if n==0:
        return 1
    else:
        return n*fact(n-1)
    
fact(4)
Out[20]:
24

Représentation graphique

Le module matplotlib permet l'utilisation des fonctions graphiques de type plot() et pcolormesh():

In [21]:
x=np.arange(-10,10,0.5)
y=np.arange(-10,10,0.5)
z=np.exp(-x**2/16)
z2=np.einsum('i,j->ij',np.exp(-x**2/16),np.exp(-y**2/16))
print(z2.shape)
fig=plt.figure(1) # Creation de l'objet figure 1
plt.plot(x,z) # Courbe de y en fonction de x
plt.xlabel('On peut etiqueter les axes $\epsilon$',fontsize=16.)
plt.ylabel('Utiliser des symboles: $\sum_\infty \epsilon$',fontsize=16.)
plt.title('Titre du graphique',fontsize=16.)
(40, 40)
Out[21]:
Text(0.5, 1.0, 'Titre du graphique')
In [22]:
fig=plt.figure(2,figsize=(12,6),dpi=120) # Creation de l'objet figure 2 en specifiant la taille
fig.add_subplot(121) # On crée une subfigure dans une division de 1 ligne et 2 colonne 
plt.pcolormesh(x,y,z2.T)
# Representation 2D de la matrice A avec la colormap "spectral"
# A.T signifie que l'on affiche la transposee de A
# vmin et vmax permettent de fixer l'echelle de couleur
plt.xlim((-10,10)) # Restriction de l'affichage a un domaine particulier 
plt.ylim((-10,10))
# !! Attention en dehors des notebook il faut utiliser plt.show() pour afficher une figure
plt.xlabel(r'$x$',fontsize=18.)
plt.ylabel(r'$y$',fontsize=18.)

fig.add_subplot(122) # Sur la deuxieme subfigure on changhe les options d'affichage 
plt.pcolormesh(x,y,z2.T,shading='gouraud',cmap="jet") # on change la colormap et on lisse l'affichage
plt.xlim((-10,10)) # Restriction de l'affichage a un domaine particulier 
plt.ylim((-10,10))
plt.xlabel('x',fontsize=18.)
Out[22]:
Text(0.5, 0, 'x')

Pour enregistrer une figure, on peut utiliser la fonction savefig:

In [23]:
fig.savefig('titre.png', bbox_inches='tight',dpi=120) # Le format peut etre choisi librement (png,jpg,eps...)

Pour continuer on pourra essayer le TP suivant sur l'algoritme de la fourmis de Langton.

Conclusion

Le python est un language simple très utilisé en science et dans plein d'autres domaines. Il permet de se familiariser facilement avec les problématiques liées à la programmation scientifique, au calcul mathématique et à la simulation numérique.

Note: Vous pouvez télécharger ce notebook (fichier ipynb) et l'executer (modifier) à votre guise sur une machine disposant d'une version de Python (3.x) ou bien l'éxecuter sur le JupyterHUB du CNAM

Pour aller plus loin voici quelques liens utiles:

In [1]:
from IPython.core.display import HTML
style=open('notebooks.css', "r").read()
HTML(style)
Out[1]: