Python

Version

La version 3.10 est la version de référence actuellement.

Toute version supérieure ne devrait pas être choisie sans une excellente raison.

Installation

Il est possible d’installer une version via le Windows Store ou choisir la distribution Anaconda ou bien encore de l’installer en même temps que Visual Studio, ce qui revient à choisir la version officielle du Python.

Il utile de vérifier que la distribution de Python est appelable depuis une ligne de commande “cmd.exe” ou Powershell. Pour cela il faut souvent modifier le PATH Windows en y ajoutant une ou plusieurs clés. Les clés PYTHONPATH (vers le sous-répertoire “lib”) et PYTHONHOME (vers le répertoire d’installation général) sont utiles pour modifier la PATH de Windows car Python va les ajouter au moment de l’exécution.

Une autre voie est l’utilisation d’environnements virtuels

ou encore la modification temporaire du PATH avant d’utiliser Python avec une commande “set”

set PATH=%PATH%;C:\your\path\here\

(“setx” modifie le PATH de façon permanente - !!! droits d’administrateur requis  au moment de l’exécution de cmd.exe !!!)

Exemple :

setPYTHONHOME=c:\Python39 setPYTHONPATH=c:\Python39\Lib setPATH=%PYTHONHOME%;%PATH%

La manière de modifier le PATH est parfois spécifique à une version de Windows.

PYTHONPATH et PYTHONHOME

PYTHONHOME Change the location of the standard Python libraries. By default, the libraries are searched in prefix/lib/pythonversion and exec_prefix/lib/pythonversion, where prefix and exec_prefix are installation-dependent directories, both defaulting to /usr/local.

When PYTHONHOME is set to a single directory, its value replaces both prefix and exec_prefix. To specify different values for these, set PYTHONHOME to prefix:exec_prefix.

PYTHONPATH Augment the default search path for module files. The format is the same as the shell’s PATH: one or more directory pathnames separated by os.pathsep (e.g. colons on Unix or semicolons on Windows). Non-existent directories are silently ignored.

In addition to normal directories, individual PYTHONPATH entries may refer to zipfiles containing pure Python modules (in either source or compiled form). Extension modules cannot be imported from zipfiles.

The default search path is installation dependent, but generally begins with prefix/lib/pythonversion (see PYTHONHOME above). It is always appended to PYTHONPATH.

An additional directory will be inserted in the search path in front of PYTHONPATH as described above under Interface options. The search path can be manipulated from within a Python program as the variable sys.path.

IDE

L’interface de développement de la version Intel est Visual Studio (choisir la version Community). Il est également possible d’utiliser VS Code qui est plus “léger” et, à l’utilisation, plus robuste. VisualStudio peut en effet parfois poser des problèmes (utilisation de mémoire en augmentation, plantage…)

La distribution anaconda propose Spyder comme outil par défaut mais peut poser des problèmes d’installation de paquets.

A côté des outils de programmation comme VS Studio, Code ou Spyder, il est possible de programmer du script via un navigateur web au travers de Jupyter Notebook. Les notebooks permettent d’exécuter du code par bloc, de disposer de blocs de texte pour commentaires et de visualiser les graphiques dans l’ordre d’écriture.

En fonction des versions, il est parfois nécessaire d’installer les extensions Notebook via “conda install notebook” ou “pip install notebook”.

Savoir quelle version de Python est en cours d’utilisation

Vous pouvez exécuter “which python” dans une fenêtre de commande ou dans un powershell.

Vous pouvez également exécuter “python –version”

Aide du Python sur le Web

Attention que beaucoup de sites fournissent de l’aide pour la version 2 de Python.

La version actuelle est la version 3 qui est la seule supportée.

Certaines commandes ont changé !! Il faut donc vérifier avant de s’énerver. :-)

Environnements virtuels

Comme plusieurs versions du Python peuvent coexister sur une même machine, il peut être intéressant de se créer un environnement virtuel isolé.

Plus d’infos ici :

Dans une fenêtre de commande ou un powershell, il faut se placer dans le répertoire souhaité et lancer :

python -m venv env

avec “env” qui est le nom de l’environnement (à adapter en fonction de ses souhaits).

Il faut bien entendu que la commande “python” utilise la version souhaitée (si plusieurs coexistent).

Une fois que l’environnement est créé, il faut l’activer avec le script “env/Scripts/activate” (dans PowerShell) ou “”env/Scripts/activate.bat” (dans cmd).

On peut aussi le désactiver avec le script “env/Scripts/dectivate” (dans PowerShell) ou “”env/Scripts/deactivate.bat” (dans cmd).

Installation de paquets

En fonction de la version de Python utilisée, l’installation de paquets se fait avec “conda install xxx” ou “pip install xxx”.

La version Python d’Intel est normalement sous “Conda”.

Paquets utiles

voir les “requirements”

Mais les paquets suivants sont souvent utiles :

  • Pandas : gestion des fichiers type tableur Excel

  • Numpy : bibliothèque numérique

  • Matplotlib : créer des graphiques aussi beau qu’en Matlab :-)

  • Scipy : bibliothèque plus générale que Numpy (contient Numpy) avec notamment du calcul symbolique (Simpy)

  • Notebook : gestion du codage via navigateur web (fichier .ipynb)

  • Wxpython : intarfaçage graphique avec les WxWidgets

  • Pyopengl : possibilité de tracer des graphiques avec OpenGL

  • Json : mode de stockage de données sur disque très proche des dictionnaires Python

Parmi les modules “standards” de Python :

  • math (fonctions ceil(), floor(), fmod(), fsum(), exp(), log(), les cyclométriques sin,cos,tan…)

  • statistics (fonctions  mean(), median(), quantiles(), variance(), stdev()…)

  • os (chdir(), mkdir(), listdir()…)

  • collections (OrderedDict …)

Dans un paquet/module, il est possible d’importer tout ou seulement une partie :

  • import module_name : lecture et exécution du fichier du module

  • import module_name as mn : mn devient un alias de module_name (ex : import numpy as np)

  • from module_name import myfunc : seule la fonction myfunc est accessible

  • from module_name import myclass : seule la classe myclass est accessible

  • from module_name import myvar : seule la variable myvar est accessible (ex. : from math import pi)

  • from module_name import * : toutes les méthodes et constantes sont importées. Il n’y a pas besoin de spécifier le nom du module pour les utiliser dans la suite du script)

Informations utiles

  • Documentation officielle : https://docs.python.org/fr/3.9/

  • Python est langage orienté objet

  • en Python, tout est “objet”.

  • Python est un langage indenté (4 espaces par niveau d’indentation). Par exemple, il n’y a pas de fin de test (if: else:), le seul fait de reculer dans l’indentation indique la fin du segment de code. La plupart des IDE gère très bien les indentations automatiques dès le saut de ligne après la commande.

  • Comme les objets sont omniprésents, Python privilégie les pointeurs plutôt qu’une copie. Cela peut être assez déroutant en venant d’autres langages de programmation plus “stricts” (Fortran/C/Matlab). Il faut donc bien faire attention à gérer des instances distinctes d’objets pour éviter des effets de bord non souhaités.

  • Les parenthèses () servent principalement pour les appels de fonctions/routines mais aussi pour définir un “tuple” (collection ordonnée et non modifiable)

  • Les crochets [] servent pour les vecteurs/matrices ou encore les listes (collection ordonnée et modifiable)

  • Les accolades {​​​​}​​​​ permettent de définir des dictionnaires (permettant de stocker facilement des infos sous la forme d’une paire key:value, à clé unique) (collection ordonnée depuis version 3.7 et modifiable)

  • Dans les chemins d’accès sous Windows, il est nécessaire d’utiliser un double backslash (d:\\mydir\\myfile au lieu de d:\mydir\myfile). Si ce n’est pas le cas, la chaîne ‘\t’ serait interprétée comme une tabulation, ‘\n’ comme une nouvelle ligne… :-) Il est possible de se passer de ce double backslash si on transmet une chaîne “raw” avec l’opérateur “r” (exemple print(r’\t’) donnera \t comme résultat).

  • Python démarre sa numérotation à 0 et non à 1

Exemple : lecture/écriture d’un fichier texte

Pour lire un fichier texte en Python :

with open('myfile.txt') as f:
   mycontent = f.readlines()
   f.close()
   print(mycontent)

Cette commande va lire le contenu du fichier et créer une liste avec autant de valeurs que lignes. Chaque valeur se terminera par le caractère ‘\n’ indiquant que c’est bien une fin de ligne.

Si ce caractère ‘\n’ n’est pas souhaitable (et c’est souvent le cas) on peut utiliser la fonction strip() pour l’éliminer.

A la lecture, il est également possible d’utiliser une commande f.read().splitlines() qui va lire en une ligne l’entièreté du fichier dans une variable et ensuite scinder la variable sur base de ce caractère ‘\n’. Dès lors, plus besoin de nettoyer la chaîne lors de l’utilisation.

Il est aussi possible de lire le fichier ligne par ligne :

for line in open('myfile.txt'):
   print(line)
   print(line.strip())

A l’écriture, il ne faut pas oublier d’ajouter ‘\n’ si on souhaite changer de ligne.

file.write(your_string +'\\n')

ou par formattage :

lines = ['hello','world']
with open('filename.txt', "w") as fhandle:
   for line in lines:
      fhandle.write(f'{line}\n')

Commentaires dans un script Python

Une ligne peut être commentée avec le caractère #.

Il est également possible d’ajouter un commentaire en fin de ligne avec le même #.

Des commentaires multilignes sont également possibles en encadrant les lignes avec des triples “ ou ‘.

'''
   linecomment1

   linecomment2
'''

ou
"""
   linecomment1

   linecomment2
"""

Aide sur une fonction

Il est possible d’avoir de l’aide sur un module/fonction/méthode via help().

Dans les scripts personnels, une documentation est automatiquement générée sur base des commentaires utilisant les triples “ ou ‘. Elle est accessible via la fonction .__doc__ (attention au double underscore avant et après doc).

Typage des variables et Conversion de types

Même si les variables ne sont pas fortement typées, un type est attribué dynamiquement à l’exécution.

Exemple :

a=1 donnera le type int à a

a=1. fonnera le type float à a

La conversion entre type est possible et même recommandée pour éviter de laisser la main à l’ordinateur.

  • float() transforme l’argument en virgule flottante

  • int() transforme l’argument en entier

  • str() transforme l’argument en texte

Dans la manipulation de chaînes de caractères, il est utile de connaître la fonction f-string qui permet de concaténer des variables.

exemples :

print(f'{5*5}')

qui donnera 25

a=20
print(f'My var {str(a+5)}')

qui donnera : My var 25

Ressources f2py sur le web

https://numpy.org/doc/stable/f2py/

https://www.numfys.net/howto/F2PY/

Utilisation du Fortran avec Python “f2py”

Il est possible de compiler du code Fortran pour l’utiliser sous Python.

Pour ce faire, il faut utiliser l’outil “f2py” qui est contenu dans le paquet “numpy”.

Il faut donc commencer par s’assurer que le paquet “numpy” est installé sur votre version de Pyhton.

Ensuite, si vous avez un fichier Fortran compilable “myfile.f90”, vous pouvez utiliser une instruction de ce type dans un powershell ou une fenêtre de commande:

python -m numpy.f2py -c myfile.f90 - m mypylib -L"C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\2021.1.1\\windows\\compiler\\lib\\intel64_win" --fcompiler=intelvem --build-dir "pathtowrite"

Cette instruction :

  • appelle f2py comme un module python, via l’option -m (et non comme une application, ce que l’on retrouve fréquemment sur internet)

  • compile le fichier myfile.f90, via -c (ATTENTION : vous devez commencer par vous placer dans le répertoire qui contient le fichier sinon, vous devez fournir le chemin complet – si plusieurs fichiers, il faut les énumérer dans le bon ordre)

  • crée la librairie mypylib, via -m, et l’écrit dans le répertoire courant

  • les fichiers intermédiaires sont écrits dans le répertoire pathtowrite, via –build-dir

  • force l’utilisation du compilateur Intel, via –fcompiler=intelvem (ATTENTION que l’application ifort doit être appelable en ligne de commande ce qui demande d’avoir ajouté manuellement le répertoire adhoc dans le PATH ou d’avoir ajouter temporairement ce chemin à votre fenêtre de commande – pour la version One API, il s’agit sans doute de “C:\Program Files (x86)\Intel\oneAPI\compiler\2021.1.1\windows\bin\intel64” avec la versiion “2021.1.1” à adapter)

  • L’option -L indique dans quel répertoire il faut aller chercehr les librairies statiques .lib déjà compilées. L’exemple illustre le strict nécessaire pour le compilateur Intel. Il faut complétr au besoin

Exemple plus complet pour l’utilisation des librairies WOLF avec utilisation des librairies MKL :

python -m numpy.f2py -c wolf_to_py.f90 -m wolfpy -I"C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\2021.1.1\\windows\\compiler\\lib\\intel64_win" -I"C:\\Program Files (x86)\\Intel\\oneAPI\\mkl\\2021.1.1\\lib\\intel64" -I"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\Unit_Tests\\NetCDF\\x64\\DebugX64_2" -I"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\LibWOLF\\LibWOLF\\x64\\DebugX64_2" -I"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\Unit_Tests\\LibExamples\\x64\\DebugX64_2" -L"C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\2021.1.1\\windows\\compiler\\lib\\intel64_win" -L"C:\\Program Files (x86)\\Intel\\oneAPI\\mkl\\2021.1.1\\lib\\intel64" -L"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\Unit_Tests\\NetCDF\\x64\\DebugX64_2" -L"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\LibWOLF\\LibWOLF\\x64\\DebugX64_2" -L"D:\\Programmation2\\wolf_oo\\Sources\\Solutions\\Unit_Tests\\LibExamples\\x64\\DebugX64_2" -l"LibWOLF" -l"NETCDF_OO" -l"LibExamples" -lmkl_core -lmkl_sequential -lmkl_intel_thread -lmkl_intel_lp64 -lmkl_intel_ilp64 -lmkl_rt -llibiomp5md --fcompiler=intelvem --build-dir "D:\\Programmation2\\wolf_oo\\Sources\\Python"

Exemple d’une fonction

La fonction add2 ajoute 2 à un entier passé en argument :

Fortran :

function add2(a) result(b)
integer a,b

b=a+2
end function

Si ce code est stocké dans un fichier nommé “add2.f90”, la commande suivante va créer une librairie python du nom “add_int”.

python -m numpy.f2py -c add2.f90 -m add_int --fcompiler=intelvem -L"C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\2021.1.1\\windows\\compiler\\lib\\intel64_win"

Une fois importée (import add_int), il est possible d’appeler la fonction “add_int.add2”:

Python :

import add_int

b=add_int.add2(2)

Exemple d’une routine

La routine mult prend 1 argument en intent(in) et 1 argument en intent(out)

Fortran :

subroutine mult(a,b)
integer, intent(in) :: a
integer , intent(out) :: b
b=a*2
end subroutine

Dans ce cas, f2py va convertir cette routine en fonction Python.

Après l’import de la librairie Python, il ne faudra pas appeler la routine comme “mult(a,b)’ mais bien comme la fonction “b=mult(a)”

Il est donc nécessaire de bien spécifier ce qui “intent(in)” et ce qui “intent(out)”.

Exemple d’une routine avec 2 “out”

Si la routine a 2 arguments intent(out), le Python attendra 2 valeurs en retour, dans l’ordre de l’énumération des arguments.

Fortran :

subroutine outtwo(c,a,b)
integer, intent(in) :: a
integer , intent(out) :: b,c
b=a*2
c=a*3
end subroutine

Python :

e,f=add_int.outtwo(1)

**Résultat:**

e=3

f=2

Cela fonctionne également avec les vecteurs…

Python :

g=np.ones(2)
g=add_int.outtwo(1)

Résultat :

g[0]=3

g[1]=2

f2py et les variables globales en Fortran

Il est possible de gérer des variables globales en Fortran.

Le but est bien entendu de maintenir en mémoire les commandes effectuées afin d’obtenir un traitement complexe via l’appel à plusieurs routines/fonctions.

Pour définir des variables globales, il faut avoir recours à un module Fortran où les variables sont déclarées. Ce module est ensuite utilisé dans les fonctions/routines.

Lors du premier appel à la librairie depuis le Python, les variables globales sont allouées et persistent en mémoire jusqu’à la fin de l’exécution. Des appels successifs à la même routine/fonction ou des appels à des routines différentes mais utilisant en interne la même variable est donc possible.

Un exemple très simple… qui utilise la variable “e” du module “mymod” comme stockage interne. L’appel successif à la routine “submod” (addition de l’argument “a” à la variable “e”) donne ainsi 2 et puis 4.

Fortran:

module mymod
integer:: e=0
end module

subroutine submod(a,b)
use mymod
integer, intent(in) ::a
integer, intent(out) :: b

e = a+e
b=e
end subroutine

Python:

f=submod(2)
f=submod(2)

Résultat :

f=2

f=4

f2py et le Fortran orienté-objets

f2py ne peut pas gérer les types/objets du Fortran. Seuls sont autorisés les types de variables implémentés dans Numpy (entiers, réels à virgule flottante, vecteur, matrices…).

Par contre, il est possible de lier une librairie déjà compilée qui utilise ce genre d’objet. C’est cette façon de procéder qui permet d’interagir avec les objets de WOLF.

Cela demande néanmoins d’utiliser une couche supplémentaire d’interfaçage.

Pour utiliser les objets et le partage de mémoire, il faut se tourner vers le paquet Ctypes qui permet l’interaction avec une DLL. Côté Fortran, il faut respecter les standards ISO-C-bindings.

f2py et WOLF

Dans les explications précédentes, on a pu voir que f2py demande à énumérer tous les fichiers Fortran à compiler. Cette énumération doit être faite du plus bas niveau (routines les plus simples) vers le plus haut niveau (routines qui appellent d’autres routines ou utilisent des variables globales). Ceci est vrai pour tout appel au compilateur et ce travail est habituellement réalisé par Visual Studio ou Code::Blocks ce qui rend généralement cette étape “transparente” pour l’utilisateur.

Une façon de laisser cette tâche (fastidieuse en présence de qq fichiers, quasi-impossible avec les dizaines de fichiers sources de WOLF) est de compiler une librairie statique (.lib) avec Visual Studio qu’il n’y a plus qu’à lier via f2py.

C’est ce qui a poussé à rassembler l’ensemble des développements Fortran en librairies statiques. Les projets de codes exécutables (Apps) étant le plus souvent limité à un “main” pratiquement vide.

Ainsi, WOLF est constitué de plusieurs librairies statiques :

  • libwolf.lib : pratiquement tous les objets

  • libexamples.lib : exemples codés directement dans le Fortran

  • libwolf_cpp.lib : code GPU en C++ et openGL

  • NetCDF_OO.lib : librairie de lecture/écriture des fichiers NetCDF

  • CoinMetis.lib, CoinMumps.lib, CoinMumpsF90.lib, IpOptFor.lib, IpOptFSS.lib, Ipopt-vc8.lib, libhsl.lib : librairies utiles pour le solveur IpOpt

Depuis mai 2021, toutes les librairies compilées se retrouvent dans le répertoire “wolf_oo\branch\Solutions\Unit_Tests\platform\config” où :

  • branch = la branche de développement

  • platform : plateforme de compilation (x64 en 64 bits)

  • config : avec option de déboggage “DebugX64_2” ou optimisé vitesse “R_X64_OpenMP”

Avant cette date, les librairies étaient principalement compilées dans chaque projet à l’exception des libraires IpOpt et libwolf_cpp.

Concrètement, pour rendre les objets accessibles, il faut :

  • coder des routines dans le module Fortran “wolf_to_python”

  • ces routines devront obligatoirement se terminer par “_py”

  • ajouter les variables globales, au besoin, en tête de ce module

  • ajouter les modules utiles dans les sous-routines, sauf si nécessaire pour des variables globales

  • placer des commentaires sur des lignes séparées et pas en fin de ligne de code (sinon possible problèmes dans le code wrap_wolf2python.py qui va ignorer ces lignes)

Les routines présentes dans ce module seront analysées automatiquement par le script Python “wrap_wolf2python.py” (dans le projet “WOLF2Python” du répertoire “Python/f2py”. Il faut d’ailleurs penser à modifier le chemin d’accès des répertoires utiles (codé par défaut pour la branche “Source”).

Le script va écraser le fichier “WOLF_to_py.f90” qui contient les interfaces de routines sans le “_py” final. Le fichier “WOLF_to_py.f90” ne doit donc pas être créé manuellement.

Le script “compile.py” prépare finalement la ligne de commande à exécuter dans un powershell pour obtenir le librairie Python “wolfpy”.

L’appel à f2py comporte donc :

  • des dossiers de recherche pour les librairies : option -I (même si les librairies .lib sont regroupées dans un sous-répertoire unique, le linkage a besoin de certains fichiers .o qui sont dans les répertoires de chaque projet) et -L

  • la liste des librairies à inclure (WOLF, OneAPI et MKL) : option -l

Le fichier “compile.py” contient une section “## TO CHANGE !!” à modifier en fonction de la configuration locale (répertoire d’installation du compilateur et des mkl, version du Python à utiliser, fichier à écrire, configuration à utiliser – Debug/Release).

Les 2 premières étapes du fichier obtenu déplace l’utilisateur dans le répertoire Python de la branche. C’est de là qu’il faut exécuter f2py pour que la librairie finale soit écrite au bon endroit. Le fichier à obtenir est nommé “wolfpy.cpxx-win_amd64.pyd” où xx va dépendre de la version de Python utilisée. Ce nom de fichier permet de faire coexitser dans un même répertoire des versions pour Python 3.9, 3.10 … A l’import, seule la version adaptée à l’environnement sera réellement chargée.

Si une version ad hoc n’est pas trouvée, une erreur d’import dans le script Python apparaîtra.

Pour que la compilation puisse être réalisée, il faut que le compilateur Fortran d’Intel soit accessible.

Au besoin, il est possible d’utiliser les outils GCC pour tout autre code Fortran qui n’utilise pas les objets de WOLF.

f2py - intent(in), intent(out), intent(inout)

Valable pour Numpy <=1.20 (cela peut évoluer en fonction des futures versions)

D’expérience, les variables passées par le Python le sont BY VALUE. Cela signifie qu’il ne faut pas attendre de modification de la variable avec INTENT(IN) et INTENT(INOUT). INOUT permet juste de modifier localement la variable sans que le compilateur ne produise une erreur.

En OUT, il est possible de passer des vecteurs/matrices mais la taille doit être fixe ; pas de support de ALLOCATABLE. Par contre, la taille peut être passée en argument.

Comment accélérer un code Python

Faut-il rappeler que Python est un langage script non compilé?

L’utilisation de Numpy, SciPy… permet d’obtenir de bonnes performances car les routines de calcul sont compilées dans un langage efficient (C ou Fortran la plupart du temps).

Il est admis que l’utilisation du Fortran permet de diviser le temps de calcul d’environ un facteur 30.

Il existe cependant des alternatives telles que Cython qui permet d’obtenir des performances assez similaires tout en gardant une syntaxe très proche du Python.

D’autres approches existent et se basent sur des librairies. Une intéressante est Numba qui permet notamment d’invoquer le GPU assez facilement. MAIS il est intéressant de noter “Special care should be taken when the function which is written under the jit attempts to call any other function then that function should also be optimized with jit else the jit may produce even more slower codes.” :-)