Optimisation des performances pour les solveurs Python PDE suit une règle simple : Profil d’abord, optimisez plus tard. Utilisez des outils tels que cProfile et line_profiler pour identifier les goulots d’étranglement réels (généralement des opérations matricielles clairsemées, l’allocation de mémoire ou la complexité algorithmique) avant d’appliquer des correctifs ciblés. Les gains courants proviennent de : choisir des formats de matrice clairsemés optimaux (CSR pour les lectures, CSC pour les écritures), tirer parti du NUMBA JIT pour les boucles serrées, réduire les copies de mémoire et paralléliser avec MPI via mpi4py. Validez toujours les optimisations par rapport aux mesures de base et évitez l’optimisation prématurée qui augmente la complexité du code sans avantage mesurable.
Introduction : Le défi de performances dans les simulations PDE
Les solveurs d’équations aux dérivées partielles (PDE) sont l’épine dorsale de la simulation scientifique, permettant aux chercheurs de modéliser le transfert de chaleur, la dynamique des fluides, l’électrochimie et les phénomènes de champ de phase. Cependant, la nature interprétée par Python et les abstractions de haut niveau peuvent conduire à des simulations qui exécutent des ordres de grandeur plus lents que théoriquement possibles. Que vous utilisiez fipy pour les calculs de volumes finis ou pour créer des solveurs personnalisés, l’optimisation des performances est essentielle pour la recherche productive.
Ce guide couvre une approche systématique du profilage et de l’optimisation des solveurs Python PDE. Vous apprendrez à identifier les goulots d’étranglement, à appliquer des techniques d’optimisation éprouvées et à faire des compromis éclairés entre la clarté du code et la vitesse d’exécution. Les principes s’appliquent à FIPY, FENICS, DEAL.II et à tout cadre de simulation numérique basé sur Python.
Pourquoi le profilage est important : le cadre d’optimisation basé sur les données
L’erreur la plus courante dans le travail de performance est optimisation basée sur des suppositions. Les développeurs expérimentés perdent souvent des semaines à optimiser le code qui contribue à moins de 1 % du temps d’exécution total. La solution est simple : mesurez avant de changer.
Le cycle d’optimisation en cinq étapes
- Établissez une ligne de base – Mesurez les performances actuelles avec des données de type production. Enregistrez l’heure de l’horloge murale, l’utilisation du processeur et la consommation de mémoire.
- Profil systématique – Utilisez les profileurs de CPU pour localiser les fonctions « chaudes » et les profileurs de mémoire afin de trouver les goulots d’étranglement d’allocation.
- Diagnostiquer la cause profonde – Déterminez si le goulot d’étranglement est une complexité algorithmique, une inefficacité de la structure des données ou une surcharge d’interpréteur.
- Appliquez des correctifs ciblés – apportez des modifications minimales et ciblées pour remédier au goulot d’étranglement spécifique.
- Validez et test de régression – Réexécutez la même charge de travail pour confirmer l’amélioration. Assurez-vous que les résultats numériques restent identiques dans la tolérance.
Ce processus itératif, souvent appelé « mesure, optimiser, répéter », empêche le piège optimisation prématurée. Comme Donald Knuth l’a fait remarquer, « l’optimisation prématurée est la racine de tous les maux », mais cela ne signifie pas que vous devez ignorer entièrement les performances, simplement que vous devez optimiser en fonction des données et non de l’intuition.
Goulots d’étranglement courants dans les solveurs Python PDE
Les solveurs PDE présentent des modèles de performances caractéristiques. La compréhension de ces éléments vous aide à interpréter correctement la sortie du profileur.
1. Opérations matricielles clairsemées
Les méthodes de volumes finis et d’éléments finis génèrent de grands systèmes linéaires clairsemés. Les opérations dominantes sont généralement :
- Matrix-Vector Multiplication (
A @ x) – se produit à chaque itération de solveurs linéaires - Matrix Assemblage – Construction de la matrice de rigidité globale à partir des contributions des éléments
- Opérations de solveurs – Factorisation, préconditionnement, résolution itérative
Un mauvais choix du format de matrice clairsemée peut ralentir ces opérations de 2 à 10 ×. SciPy’s Sparse Module propose plusieurs formats : CSR (compressé par ligne sparse) est optimal pour Produits Matrix-Vector, tandis que CSC (compressé par la colonne sparse) excelle dans les tranches de tranches et certains algorithmes de factorisation. LIL et DOK sont efficaces pour la construction incrémentale, mais doivent être convertis en CSR/CSC avant la résolution.
2. Allocation de mémoire et copie
La gestion de la mémoire de Python peut dominer le temps d’exécution dans des boucles serrées. Problèmes courants :
- Création de tableaux excessifs – La création de tableaux temporaires à l’intérieur des boucles force la surcharge de Garbage Collection.
- Copies inutiles – Passer des tableaux par valeur au lieu de vue/référence.
- Fragmentation de la mémoire – Répliques répétées dans les boucles internes.
Des outils comme memory_profiler révèlent les points d’accès d’allocation. Souvent, la réécriture pour utiliser des opérations sur place (a += b au lieu de a = a + b) ou la pré-allocation de tableaux de sorties génère des accélérations importantes.
3. Overhead de la boucle Python
Les boucles Python naïves sur des mailles ou des pas de temps peuvent être 100 × plus lentes que les opérations NumPy vectorisées. Par exemple :
# Slow: Python loop
for i in range(n_cells):
result[i] = a[i] * b[i] + c[i]
# Fast: Vectorized
result = a * b + c
Lorsque les boucles ne peuvent pas être éliminées (par exemple, une logique conditionnelle complexe), compilation JIT de Numba peut les accélérer de 10 à 1 000 × en compilant le code machine.
4. Complexité algorithmique
Le choix d’un algorithme O(n²) où existe une alternative O(n log n) dominera toutes les autres optimisations. Dans les contextes d’EDP :
- Génération de mailles – Delaunay Triangulation vs. Grilles structurées
- Choix de solveur linéaire – Factorisation directe (O(N³)) vs. Méthodes itératives (O(N²) par itération mais souvent moins d’opérations en pratique)
- Steping de temps – explicite (conditionnellement stable, bon marché par pas) ou implicite (inconditionnellement stable, coûteux par étape)
Profilez tôt pour confirmer que vous utilisez des algorithmes appropriés avant de vous optimiser.
Outils de profilage essentiels pour Python scientifique
Profilage du processeur
Cprofile (intégré) – Fournit une synchronisation au niveau des fonctions, indiquant les fonctions qui consomment le plus de temps. Utilisez-le pour obtenir une vue de haut niveau :
import cProfile
cProfile.run('solver.solve()')
La sortie affiche le nombre d’appels et le temps cumulé. Concentrez-vous sur les fonctions avec un temps cumulé élevé et un nombre élevé d’appels.
LINE_PROFILER – Pour une analyse ligne par ligne dans une fonction. Installez via pip install line_profiler, décorez les fonctions cibles avec @profile et exécutez kernprof. Cela révèle quelles lignes spécifiques sont des goulots d’étranglement, inestimables pour les noyaux numériques serrés.
PyInstrument – Un profileur statistique qui échantillonne la pile d’appels, fournissant des graphiques de flamme avec une surcharge minimale. Utile pour les simulations à long terme où le profilage déterministe perturberait les performances.
Profilage de la mémoire
Memory_Profiler – Suivi de l’utilisation de la mémoire ligne par ligne. Aide à identifier la création d’objets inattendue et les fuites de mémoire.
Tracemalloc (intégré) – Suivi des allocations de mémoire et peut identifier l’endroit où des objets ont été alloués. Particulièrement utile pour trouver des copies cachées :
import tracemalloc
tracemalloc.start()
# run simulation
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
Visualisation
Flammes graphiques – Convertir la sortie du profileur en visualisations interactives. Des outils tels que gprof2dot transforment les données de Cprofile en graphes d’appels qui rendent les points d’accès évidents d’un coup d’œil. Les techniques de graphe de flamme de Brendan Gregg sont largement adoptées en ingénierie de la performance.
Techniques d’optimisation qui offrent
Une fois le profilage identifie les goulots d’étranglement, appliquez ces stratégies ciblées.
1. Optimisation du format de matrice clairsemée
Règle du pouce : utilisez CSR pour les opérations de lecture lourde (produits Matrix-Vector), CSC pour l’écriture lourde (mises à jour de la colonne) et convertir pendant l’assemblage si nécessaire.
Exemple : FIPY utilise en interne la RSE pour la plupart des opérations. Si vous assemblez des matrices, intégrez LIL ou DOK, puis convertissez :
from scipy.sparse import lil_matrix, csr_matrix
A_lil = lil_matrix((n, n))
# ... assembly ...
A_csr = A_lil.tocsr() # Convert before solving
Formats de référence avec votre modèle de parcimonie réel : les performances varient selon la forme et la densité de la matrice. Documentation de Scipy Sparse fournit des détails sur les compromis de format.
2. Compilation Numba JIT
numba compile des fonctions décorées dans le code machine à l’aide de LLVM, atteignant souvent des vitesses de type C avec des changements de code minimes. Pour les solveurs PDE, ciblez :
- Boucles intérieures étroites (p. ex., calcul de rigidité des éléments)
- L’arithmétique personnalisée que NumPy ne peut pas vectoriser
- Conditions et branches qui empêchent la vectorisation
from numba import jit, prange
@jit(nopython=True, parallel=True)
def compute_element_matrix(coords, material_props):
# element-level calculations
return ke # stiffness matrix
# Parallel loop over elements
for i in prange(num_elements):
Ke = compute_element_matrix(elements[i], props[i])
assemble_into_global(A, Ke, connectivity[i])
Caveats : Numba fonctionne mieux avec les tableaux NumPy et les boucles simples. Il peut ralentir le code avec des objets Python, des structures de données complexes ou de fréquentes interactions avec les interpréteurs. Toujours profiler les versions compilées et non compilées—Numba ajoute des frais généraux de compilation qui peuvent ne pas être payants pour de petits problèmes.
3. Optimisation de la mémoire
Réduisez le trafic de mémoire, ce qui est souvent le véritable goulot d’étranglement :
- Utiliser les opérations sur place (
np.multiply(a, b, out=a)) - Pré-allouer des tableaux Boucles extérieures ; Évitez
np.appenddans les boucles serrées - Choisir les types de DTypes appropriés –
float32VSfloat64: compromis de précision, 2 réseaux de mémoire - Mappage de mémoire pour les grands ensembles de données qui dépassent la RAM (
np.memmap) - Expressions de générateur pour la diffusion de données au lieu de créer des listes complètes
Pour les utilisateurs de FIPY, l’objet mesh stocke les données de cellule et de sommet. L’accès à plusieurs reprises à des tableaux maillés peut déclencher une surcharge Python. Références de cache :
# Instead of repeatedly calling mesh properties:
faces = mesh.faces # cache once
areas = mesh.faceAreas # cache
4. Parallélisation avec MPI
Pour les simulations à grande échelle, mpi4py active le parallélisme à mémoire distribuée. La décomposition du domaine est mise à l’échelle des nœuds de cluster. Modèles typiques :
- Partitionnement de maillage parallèle – Diviser le domaine à l’aide d’outils tels que
scipy.sparse.csgraphou les bibliothèques externes (Métis, Scotch) - Communication des cellules fantômes – échange de données de limites entre les rangs voisins
- Solveurs linéaires parallèles – PETSC, Trilinos ou oreillons via PETSC4PY/SLEPC4PY
Exemple de modèle :
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
# Each rank owns a subdomain
local_mesh = partition_mesh(global_mesh, rank, size)
# Solve locally
local_solution = solve_local(local_mesh)
# Gather results
solution = comm.gather(local_solution, root=0)
La parallélisation MPI ajoute de la complexité : mesurez l’accélération avec des tests de mise à l’échelle solides et faibles pour confirmer que cela en vaut la peine. Pour un nœud multicœur à un seul nœud, un threading ou numba.prange peut suffire.
5. Améliorations algorithmiques
Aucune quantité de réglage de bas niveau ne compense un algorithme médiocre. Pensez à :
- Raffinement de maillage adaptatif – Concentrez les degrés de liberté au besoin. FIPY n’a pas de AMR intégré, mais des outils externes comme GMSH peuvent générer des maillages adaptés, ou envisager de passer à des codes tels que Moose ou Prisms-PF qui prennent en charge AMR de manière native.
- Multigrid Solvers – Pour les PDE elliptiques, le multi-grille géométrique ou algébrique peut réduire les itérations de solveur de O(n) à O(n log N).
- Adaptation du pas de temps – Ajustez le pas de temps en fonction de l’erreur de troncature locale, en évitant les étapes fixes trop petites.
- Méthodes sans matrice – Évitez d’assembler la matrice globale ; Calculer les produits Matrix-Vector à la volée. Utile lorsque la bande passante de mémoire est le goulot d’étranglement.
6. Accélération du GPU
Pour les problèmes de parallélisme de données massif, les GPU offrent des accélérations de 10 à 100 ×. Options :
- Cupy – Remplacement de NumPy drop-in qui s’exécute sur les GPU NVIDIA. Idéal si votre code est déjà ennuyeux et les opérations correspondent proprement à CUDA.
- Numba CUDA – Écrivez des noyaux personnalisés avec une syntaxe de type Python.
- Kokkos ou SYCL – Performances portables sur CPU/GPU.
Attention : l’accélération du GPU n’est pas gratuite. Le transfert de données entre l’hôte et l’appareil peut dominer, sinon minimisé. Profilez à la fois le code CPU et le GPU pour vous assurer que le GPU est en fait le goulot d’étranglement.
Erreurs courantes qui tuent les performances
Sur la base d’une expérience de profilage dans des projets scientifiques, ces anti-modèles apparaissent à plusieurs reprises :
- Boucles non vecteurs – Utilisation de
forBoucles sur des tableaux au lieu d’opérations NumPy. Demandez toujours : « Cela peut-il être exprimé comme une opération vectorielle? » Les conférences Python scientifiques mettent l’accent sur la vectorisation comme première étape d’optimisation. - Format clairsemée incorrecte – par défaut sur COO ou en utilisant la RSE pour des insertions fréquentes. Le COO est inefficace pour l’arithmétique ; LIL/DOK sont meilleurs pour la construction mais doivent être convertis.
- Ignorer la localité du cache – Accéder aux tableaux dans des ordres non contigus entraîne des échecs de cache. Dans les codes d’éléments finis, assurez-vous que les boucles d’éléments accèdent aux données dans l’ordre de mémoire.
- Des surcharges Python excessives dans les boucles internes – appeler les fonctions Python ou accéder aux attributs d’objet dans les boucles étroites. Déplacez un tel travail à l’extérieur ou utilisez Numba pour le compiler.
- Optimisation prématurée sans mesure – Passer du temps sur des « optimisations » qui donnent une amélioration de 5 % tout en ignorant le goulot d’étranglement réel de 80 %.
- Oublier la précision numérique – le passage à
float32peut accélérer le calcul, mais peut affecter la convergence ou la précision du solveur. Vérifiez toujours que les tolérances sont toujours respectées. - Paralléliser trop tôt – Ajout de threads MPI avant que le code ne soit correct et efficace en série. Le code parallèle est plus difficile à déboguer ; Corrigez les goulots d’étranglement en premier.
Cadre de décision : quand choisir quelle optimisation
Lorsque vous avez identifié un goulot d’étranglement, comment choisissez-vous la bonne solution ? Utilisez cet organigramme :
Is the hotspot in a Python loop?
├─ Yes → Can it be vectorized with NumPy?
│ ├─ Yes → Rewrite vectorized (big win, clean code)
│ └─ No → Use Numba JIT or Cython
└─ No → Is it a NumPy/SciPy operation?
├─ Yes → Check arguments (dtype, format, order)
│ └─ Still slow? Consider algorithmic change
└─ No → Memory allocation?
├─ Yes → Reduce copies, pre-allocate, in-place ops
└─ No → Re-profile; maybe wrong hotspot identified
Pour les décisions spécifiques à l’EDP :
| Problème | Goulot probable | Première vérification | Correction recommandée |
|---|---|---|---|
| Résoudre linéaire lentement | Algorithme de résolution | Nombre d’itérations, numéro de condition | Meilleur préconditionneur, multi-grille ou solveur direct pour les petits problèmes |
| Assemblage lent | Boucles Python | CProfile ligne par ligne | Numba Jit sur les boucles d’éléments ; Cython ; ou utilisez des bibliothèques optimisées |
| Mémoire épuisée | Tableaux denses ou copies | Mémoire_profileur | passer à parcimonieux ; utiliser float32 ; mappage de mémoire ; réduire la précision |
| Mise à l’échelle MPI pauvre | La communication | Profil avec les profileurs MPI (HPCToolkit, Score-P) | chevaucher la communication/le calcul ; améliorer l’équilibrage de la charge ; Réduire la fréquence des messages |
Un workflow d’optimisation pratique pour les projets PDE
Suivez ce processus étape par étape dans votre code de recherche :
Étape 1 : Créez un benchmark reproductible
Avant de changer quoi que ce soit, écrivez un script qui exécute une simulation représentative avec des paramètres fixes. Cela devient votre harnais de référence. Inclure :
- Correction de la graine aléatoire ou des conditions initiales déterministes
- Taille de maillage et physique de type production
- Calendrier des principales phases (assemblage, résolution, post-traitement)
import time
start = time.perf_counter()
solver.solve()
elapsed = time.perf_counter() - start
print(f"Total time: {elapsed:.2f}s")
Étape 2 : Profil avec cProfile
Exécutez le benchmark sous cprofile pour voir la situation dans son ensemble :
python -m cProfile -o profile.out benchmark.py
Analysez avec pstats ou visualisez :
import pstats
p = pstats.Stats('profile.out')
p.sort_stats('cumulative').print_stats(20) # Top 20 functions
Recherchez des fonctions avec un temps cumulé élevé et un nombre élevé d’appels. Remarque : cProfile lui-même ajoute des frais généraux (généralement 5 à 20 %), mais les délais relatifs sont toujours valides.
Étape 3 : Explorez avec LINE_Profiler
Pour les 1 à 2 meilleurs points d’accès, utilisez line_profiler pour voir les contributions ligne par ligne. Installer :
pip install line_profiler
Ajoutez @profile Décorateur à la fonction et exécutez :
kernprof -l -v benchmark.py
La sortie affiche le temps par ligne, les coups et le temps par coup. Cela vous indique exactement quelles opérations à l’intérieur de la boucle sont coûteuses.
Étape 4 : Appliquer une optimisation ciblée
Basé sur le profileur de ligne, choisissez la technique appropriée :
- Expression Numpy → Remplacer la boucle par une opération vectorisée.
- Element Computation → Ajouter
@jit(nopython=True)et corriger les incompatibilités de Numba. - Copie mémoire → Utilisez
out=le ou les vues. - Format de matrice parcimonie → Convertir en CSR/CSC avant une utilisation intensive.
Apportez un changement à la fois et réexécutez l’indice de référence pour mesurer l’impact. Conservez un journal des modifications et de leurs effets.
Étape 5 : Valider l’exactitude
Les optimisations numériques peuvent subtilement modifier les résultats. Toujours :
- Vérifiez que la norme finale ou la norme d’erreur reste inchangée dans la tolérance.
- Sorties clés de contrôle spot (valeurs maximales, grandeurs intégrales).
- Exécutez les tests unitaires existants si disponibles.
Étape 6 : Répétez
Après la première optimisation, de nouveaux points d’accès peuvent émerger (loi d’Amdahl). Revenez à l’étape 2 et profilez à nouveau. La plupart des codes bénéficient de 2 à 4 passes d’optimisation avant que les retours décroissants n’aient été définis.
Quand ne pas optimiser
L’optimisation a des coûts : une complexité accrue du code, une lisibilité réduite, une charge de maintenance et un risque de problèmes numériques. Considérez ces garde-corps :
- Le code sera exécuté une fois ou rarement – l’effort d’optimisation peut dépasser les économies d’exécution.
- La taille des problèmes est faible – pour les mailles avec des inconnues 10 ⁴, les frais généraux python peuvent être acceptables ; Concentrez-vous sur l’algorithme en premier
- La justesse est primordiale – certaines « optimisations » (par exemple, réduire la précision, le parallélisme agressif) peuvent introduire des bogues subtils. Peser le risque par rapport à la récompense.
- Votre prototypage – écrivez d’abord un code clair et correct. Optimiser uniquement après le profilage confirme un goulot d’étranglement.
Une heuristique utile : optimisez uniquement si la simulation prend plus de temps que le temps nécessaire pour prendre un café. Pour le code de recherche, la vitesse de développement est souvent supérieure à la vitesse brute, à moins que vous ne parcouriez la conception, auquel cas la rétroaction rapide est importante.
Intégration avec Fipy et d’autres frameworks PDE
Les utilisateurs de FIPY sont confrontés à des opportunités d’optimisation spécifiques :
- Utilisez la vectorisation intégrée – les équations de FIPY sont déjà vectorisées dans les cellules. Évitez d’ajouter des boucles Python sur les cellules ; Utilisez plutôt l’arithmétique
CellVariable. - Choisissez la discrétisation appropriée – les schémas au vent sont moins chers que les méthodes d’ordre supérieur ; Sélectionnez en fonction des besoins de précision.
- Tirer parti de la structure matricielle clairsemée – FIPY utilise la RSE. Si vous extrayez des matrices (
var.matrix), maintenez le format CSR. - Considérez
numbapour les termes personnalisés – Si vous écrivez unTermouEquation, compilez le calcul du coefficient.
Pour les problèmes PDE plus importants, la combinaison de FIPY avec MPI via mpi4py nécessite une décomposition minutieuse du domaine. Fipy n’a pas de support parallèle intégré, mais vous pouvez partitionner le maillage et résoudre les sous-domaines avec un échange de limites.
D’autres cadres comme fenics offrent une génération de code automatisée (UFL) qui peut produire du code C++ optimisé. Envisagez de changer si les plafonds de performance Python deviennent un bloqueur fondamental.
Test et régression : assurer la tenue des optimisations
Les optimisations ne doivent pas casser les résultats numériques. Mettre en œuvre ces garanties :
- Comparaison de base – Stockez les sorties de référence (par exemple, les valeurs finales de champ, les résidus) de la version non optimisée. Après optimisation, les différences d’assertion sont dans la tolérance (par exemple,
np.allclose(result, reference, rtol=1e-6)). - Tests de régression des performances – Ajoutez des travaux CI qui exécutent des benchmarks et échouent si l’exécution dépasse le seuil. Approche simple : les fonctions critiques temporelles et affirment qu’elles ne dépassent pas 1,2 × base de ligne.
- Profilage en CI – Exécuter périodiquement le profilage sur un exemple de problème et archiver les statistiques. Comparez les engagements pour détecter les ralentissements inattendus.
- Composition des documents – Si une optimisation réduit la précision ou limite les classes de problèmes, documentez-la clairement dans les commentaires de code et la documentation de l’utilisateur.
Résumé et étapes suivantes
L’optimisation des solveurs Python PDE nécessite une approche disciplinée et axée sur les données :
- Profiler d’abord en utilisant
cProfileetline_profilerpour trouver de vrais goulots d’étranglement. - spots d’accès cibles avec les techniques appropriées : vectorisation, numba jit, accordage de format clair, optimisation de la mémoire, parallélisation.
- Valider Correcteur numérique et mesurer objectivement la vitesse.
- Itérer : la plupart des codes s’améliorent sur plusieurs passes.
- Savoir quand s’arrêter – Équilibrez les gains de performances par rapport aux coûts de complexité et de maintenance.
Commencez par un benchmark unique, profilez-le, appliquez une optimisation et mesurez l’impact. La Scientific Python Community offre des ressources étendues sur l’ingénierie de la performance. Pour les questions spécifiques à FIPY, consultez les Documentation FIPY et Suivi des problèmes.
Guides connexes
- Qu’est-ce que la simulation scientifique et pourquoi ça compte – Concepts fondamentaux
- Des équations aux simulations : le pipeline de modélisation – contexte de flux de travail de bout en bout
- Introduction à la modélisation des matériaux pour les débutants – Premiers pas avec la simulation
- Gestion des problèmes de PDE à grande échelle : stratégies, solveurs et études de cas HPC – Considérations de mise à l’échelle
- Conditions aux limites : théorie et implémentation dans FIPY – Détails de l’implémentation spécifique à FIPY
- résolution des équations de diffusion avec Fipy
- Utilisation de FIPY pour la modélisation de champ de phase
Citations et lectures complémentaires
- Real Python, profilage en python : comment trouver des goulots d’étranglement de performance
- Documentation Numba, Conseils de performances
- Documentation Scipy, matrices d’aspect
- Wiki Python, Pythonspeed/performanceTips
- Conférences scientifiques sur Python, Optimisation du code
- Documentation MPI4Py, Programmation parallèle avec Python
- Documentation de Fipy Design, Méthode de volume fini