par Rémy Mozul (remy.mozul@umontpellier.fr)
Commençons par quelques recommandations générales sur la manière de programmer avec le langage Python :
import this
Pour reprendre quelques généralités déjà connus:
=
est l'opérateur d'assignation=
est la variable=
est la valeura = 12
b = -8
print( bin(a) )
print( bin(b) )
bin(a)
Première remarque... nous essayons de reprénsenter des ensembles infinis ($\mathbb{N}, \mathbb{R}$, etc) dans une mémoire RAM finie...
Il se trouve que l'on perd quelques propritétés mathématiques devenue intuitives pour beaucoup:
a = -1.
b = 1.
c = 1e-24
d1 = a + ( b + c )
d2 = (a + b) + c
print( f"d1 == d2 ? \n -> {d1==d2}" )
print( f"d1={d1} et d2={d2}" )
Illustration de:
for
en Pythontype
(très utilse pour faire des vérifications)for d in (True, 1, 1., 'c', 2.+1j):
print( type(d) )
Pour rappel, la définition d'un bloc de code est décrite par l'apparition des :
et de l'indentation à nombre d'espace égale sur tout les lignes qui appartiennent au bloc.
Donc pour faire une double boucle :
for i in range(3):
for j in range(i, i+4):
print( f"(i,j) = {i},{j}" )
print( "passage au i suivant" )
Il faut donc être très vigilant sur l'utilisation des espaces pour l'indentation. Sinon on rencontre les erreurs suivantes :
for i in range(2):
print( 'toto' )
print(i)
File "<ipython-input-28-f9e84326b9e5>", line 3
print(i)
^
IndentationError: unindent does not match any outer indentation level
for i in range(2):
for k in range(3):
print( i, k )
File "<ipython-input-10-517c91c3ba15>", line 2
for k in range(3):
^
IndentationError: expected an indented block
Rappel:
$ u(x,t) = \sum_{n=1}^{+\infty} \left[ \frac{2}{l} \int_0^l \phi(x) sin( \frac{n\pi x}{l} ) dx \cdot e^{-\frac{n^2\pi^2}{l^2} D t} sin( \frac{n\pi x}{l} ) \right] $
Pour commencer, essayons simplement de définir une fonction qui permettent de calculer : $ f_n(x) = sin( \frac{n\pi x}{l} ) $
Il va falloir utiliser un module dédié, le module math
, pour récupérer la valeur de pi ainsi que la fonction sinus. Pour avoir une liste exhaustive de ce qui est fourni par ce module, il faut aller voir ici : https://docs.python.org/3/library/math.html
La manière recommandée d'importer des sous parties de modules, est d'être le plus lisible possible. Ainsi il est fortement commandée de ne JAMAIS utiliser *
. Mais plutôt de faire:
import math
p = math.sin( math.pi )
# ou a défaut:
from math import pi, sin
p = sin( pi )
Une fonction se définit avec le mot-clef def
et ce qu'elle retourne avec return
:
def f_n(n,x, l):
result = math.sin( n * math.pi * x / l )
print(f"calcul de f_n({n},{x},{l}) = {result} ")
return result
N = 12
x0 = 1.2
L = 2*x0
f12 = f_n(N, x0, L)
# calcul pour plusieurs valeur de n
results = []
for x in range(4):
new_value = f_n(x, x0, 3*L)
results.append( new_value )
print( "Tout les résultats : ", results )
Dans l'exemple ci-dessus, une liste de résultats a été générée en plus afin de garder accès à toute les valeurs déjà calcuée précédemment.
De plus il faut faire attention au choix des variables à utiliser pour être
lisible et expressif. Utiliser la variable de boucle x
, alors qu'on le passe
comme variable de mode est un très MAUVAISE idée. Il aurait mieux value faire:
for mode in range(4):
new_v = f_n(mode, x0, 3*L)
results.append( new_v )
Utilisation d'un module : matplotlib
pour la représentation graphique
Première illustration (trop) simple : tracer une seule valeur en ordonnée:
# la ligne suivante est nécessaire uniquement
# dans l'environnement d'un notebook jupyter
%matplotlib inline
from matplotlib import pyplot as plt
plt.plot( f_n( 1, 0., L), 'b+' )
Pour afficher une courbe, il suffit de donner une liste de valeur. On remarque que l'abscisse est alors un index, decrivant la position du point dans la liste et pas une coordonnées en $x$ réelle.
results = list()
for x in range(8):
results.append( f_n(1, x, L) )
plt.plot( results )
La courbe précédente est continue uniquement parce que le graphique relie les valeurs calculée par des droites. Ce que nous avons calculé n'est bien qu'un nombre fini de points isolés les uns des autres :
plt.plot( results, '+' )
On peut modifier l'appel à plot
pour donner en plus des ordonnés à tracer,
la liste des abscisses associées. Mais d'abord modifions un peu la fonction
f_n
pour pouvoir supprimer le texte.
# redefinition de la fonction f_n pour modifier son comportement:
def f_n(n,x, l, log=True):
result = math.sin( n * math.pi * x / l )
if log:
print(f"calcul de f_n({n},{x},{l}) = {result} ")
return result
# calcul des nb_points abscisses entre 0 et 2pi
all_x = []
x_max = 2* math.pi
nb_points = 1001
for i in range(nb_points):
all_x.append( i * x_max/(nb_points-1) )
# calcul des f_n associés à all_x
results = []
for x in all_x:
results.append( f_n(1,x , x_max/2., False) )
# affichage
plt.plot(all_x, results)
Les listes sont des objets pratiques car polyvalents, mais pas du tout rapide d'accès.
C'est là que les tableaux issus du module numpy
interviennent.
def list_x(n):
res = []
for i in range(n):
res.append(i)
return res
import numpy as np
print( list_x(3) )
print( np.arange(3) )
a = list_x(4)
b = np.arange(4)
print( type(a), type(b) )
Il est possible de mesurer le temps d'execution d'une ligne de code de python
depuis les notebooks ou spyder avec l'instruction %timit
. Cette fonctionnalité
n'est pas disponible dans l'interpréteur standard.
Toutefois le résultat est assez édifiant:
N = 3000
%timeit( list_x(N) )
%timeit( np.arange(N) )
Il existe de nombreuses fonction disponible pour créer et manipuler des tableaux numpy : https://numpy.org/doc/stable/
Ici juste pour illustrer deux constructeurs proches:
print( f"L = {L}")
all_x1 = np.linspace(0,L,4)
print( all_x1 )
all_x2 = np.arange(0,L,0.8)
print( all_x2 )
L'intérêt principaux des tableux numpy, est qu'ils permettent directement des manipulations arithmétiques ; ce que ne font pas les listes :
a = list_x(3)
b = list_x(4)
print( a+b )
a = np.random.rand(3)
b = np.random.rand(3)
print(f"a = {a}")
print(f"b = {b}")
c = 2*a + 3*b/2.
print(c)
Ces opérations se font termes à termes sur les éléments des tableaux, il faut donc que les tailles des tableaux soient compatibles pour être possible.
Il est possible de vérifier quelle forme a un tableau avec l'attribut shape
:
print( all_x1.shape )
print( all_x2.shape )
mat = np.array( [[1,2,3],[3,4,5]])
print( mat.shape )
print( mat )
mat[1,2] = 0
print( mat )
On peut donc réecrire notre fonction f_n
pour s'assurer qu'elle
travaille automatiquement sur des vecteurs:
def f_n(n, x, l, log=True):
r = np.sin( n * math.pi * x / l)
if log:
print(f"calcul de f_n({n},{x},{l}) = {r} ")
return r
N = 100
all_x1 = np.linspace(0,L,N)
plt.plot( all_x1, f_n(1, all_x1, L, log=False) )