Pipeline Python de modélisation de niche bioclimatique (SDM) pour les espèces endémiques pyrénéennes, migré depuis un script R monolithique et adapté à l'exécution parallèle puis HPC.
ANTICI'PYR étudie l'évolution de la niche bioclimatique de 59 espèces végétales endémiques des Pyrénées sous quatre scénarios de changement climatique (SSP 126/245/370/585), à quatre horizons temporels (2030/2050/2070/2090), à partir de 11 modèles climatiques globaux (GCM) CMIP6.
Ce dépôt est la traduction Python du script R original "Projecting spatiotemporal bioclimatic niche dynamics of endemic Pyrenean plant species under climate change: how many will we lose?" (N. Collette, S. Pinel, V. Delorme-Hinoux, J.A.M. Bertrand). La migration a été réalisée dans le cadre d'un stage (Master CHPS / Espace-Dev, UMR 228), avec pour objectif secondaire de faire passer le pipeline du séquentiel au parallèle puis au HPC (SLURM) sans changer la logique métier.
Pour chaque espèce, le pipeline enchaîne : acquisition des occurrences (GBIF + iNaturalist) → sélection de variables bioclimatiques → ajustement de 5 algorithmes SDM (GLM, GAM, RF, GBM, MaxEnt) avec validation croisée spatiale, répété sur n_loops itérations → filtrage par Boyce Index → ensemble médian → projections futures → post-traitement (cartes, courbes de réponse, surfaces favorables).
Le pipeline est organisé en 8 modules sous src/ :
| Module | Rôle |
|---|---|
utils.py |
Configuration (config.yaml), logger par espèce, résolution des chemins, timer |
data_occurrences.py |
Téléchargement GBIF/iNaturalist, nettoyage, fusion des sources, carte des occurrences |
data_environment.py |
Téléchargement/découpage WorldClim (actuel + futur), sélection de variables par corrélation |
sdm_modeling.py |
Table de modélisation, blocs spatiaux, ajustement + grid-search des 5 algorithmes, orchestrateur run_sdm_for_species |
sdm_metrics.py |
Métriques maison sans équivalent Python direct : favorabilité (Real et al. 2006), Boyce Index, seuil maxSSS, AUC-PR |
ensemble.py |
Filtrage des modèles par Boyce Index, ensemble médian, résumé des métriques retenues |
projections.py |
MESS (Multivariate Environmental Similarity Surface), surface favorable, projection sur les climats futurs |
postprocessing.py |
Courbes de réponse, binarisation + surfaces favorables, PDF de projections |
figures.py (figures multi-espèces agrégées, ex. hotspots, recouvrement spatial) est un squelette documenté mais non implémenté — prévu en fin de stage une fois toutes les espèces traitées.
Les écarts méthodologiques volontaires entre le R et le Python (AUC-PR, découpage spatial, importance des variables, mapping GAM) sont documentés dans docs/migration_notes.md.
git clone <url-du-depot>
cd Script_Pyrenees_Python
conda create -n anticipyr python=3.12
conda activate anticipyr
# Bibliothèques géospatiales : toujours via conda-forge (jamais pip seul)
conda install -c conda-forge geopandas rasterio shapely numpy pandas scipy \
scikit-learn statsmodels matplotlib pyyaml requests
# Bibliothèques SDM spécifiques : via pip
pip install pygbif pyinaturalist pygam elapid joblib
# Vérification
python3 -c "import rasterio, geopandas, sklearn, pygam, elapid; print('OK')"Données à placer avant le premier lancement (voir LISEZMOI.txt) :
data/polygon_PYRENEES.shp(+.shx,.dbf,.prj,.cpg) — zone d'étudedata/ATLAS_OF_PYRENEAN_FLORA_-_ENDEMIC.xlsxoutputs/layers_cut_current_1km.tifetoutputs/future_median_layers_1km/si déjà disponibles (évite un téléchargement WorldClim de ~3,5 Go)
Le pipeline s'exécute à trois échelles, sans changement de logique métier entre elles.
python3 run_species.py "Ramonda myconi"
python3 run_species.py "Delphinium montanum" --config config/config.yamlUnité de travail indivisible : occurrences → modélisation → ensemble → post-traitement. Gère SIGTERM proprement (arrêt entre étapes, pas de fichier partiel).
python3 run_parallel.py # toutes les espèces (mode config)
python3 run_parallel.py --n_jobs 8 # 8 espèces en parallèle
python3 run_parallel.py --n_jobs 4 --subset 10 # 4 en parallèle, 10 premières
python3 run_parallel.py --species "Esp1,Esp2" # espèces ciblées
python3 run_parallel.py --dry-run # afficher la liste sans lancer
python3 run_parallel.py --resume # reprendre après coupure
python3 run_parallel.py --lpt --force --species "Esp1,Esp2" --report rapport.md| Option | Effet |
|---|---|
--config FILE |
Chemin de config.yaml (défaut : config/config.yaml) |
--n_jobs N |
Workers parallèles (défaut adaptatif : 8 sur Apple Silicon, nproc - 1 sinon) |
--subset N |
Limiter aux N premières espèces |
--species "A,B" |
Espèces ciblées, séparées par virgules |
--force |
Recalculer les espèces ciblées même si déjà OK |
--lpt |
Logger l'ordonnancement LPT (Longest Processing Time first) |
--report FILE |
Générer un rapport Markdown avant/après |
--dry-run |
Afficher la liste des espèces sans lancer |
--resume |
Ignorer les espèces déjà traitées (status=OK) |
Chaque espèce tourne dans un processus isolé (multiprocessing, contexte spawn), OMP/OPENBLAS/MKL/NUMEXPR_NUM_THREADS=1 pour éviter la sur-souscription.
sbatch --array=1-59 run_slurm.sh # 59 espèces
sbatch --array=1-59%16 run_slurm.sh # 59 espèces, 16 simultanées max
sbatch --array=53 run_slurm.sh # une seule espèce
sbatch --array=1-2682 run_slurm.sh species_all.csv # flore complète (CSV en argument)Chaque tâche du job array traite une espèce indépendamment. Variables à définir dans l'environnement : PROJECT_DIR (chemin du dépôt cloné, obligatoire), CONDA_ENV (nom de l'environnement conda), CONFIG (chemin de config.yaml).
Analyse et optimisation détaillées dans rapport/Rapport_Stage_M1chps_Michelle_Meyou.pdf.
Réduction de la grille GBM. Le grid-search GBM représentait ~80 % du temps de calcul par loop (18 min sur 27). La grille exhaustive (3 valeurs × 5 hyperparamètres = 243 combinaisons) a été réduite à 2 valeurs par paramètre (bornes extrêmes uniquement), soit 32 combinaisons — un gain de ×5 sur le temps GBM (18 min → 3 min 51 s par loop), pour un impact négligeable sur le score de performance (< 0,005).
Ordonnancement parallèle. Sur la base des temps mesurés sur des espèces de validation (de 15 à 985 occurrences), une simulation d'ordonnancement sur les 59 espèces endémiques avec tri par coût décroissant (nombre d'occurrences) et distribution dynamique (imap_unordered) donne, sur 8 cœurs :
| Métrique | Valeur |
|---|---|
| Temps séquentiel total | 75 h |
| Temps parallèle (8 cœurs) | 9,7 h |
| Speedup | ×7,7 (maximum théorique : ×8) |
| Efficacité | 96 % |
| Écart de charge max/min | < 10 % |
Un run réel des 59 espèces ((Intel® Core™ i9-10900, 10 cœurs physiques, 20 threads, Ubuntu Linux),8 workers) a confirmé cet ordre de grandeur : ~10-11 h, 55/59 espèces OK au premier passage, les 4 échecs restants (rate-limit GBIF, taxon key GBIF trop large) corrigés en session suivante pour atteindre 59/59 (voir docs/RAPPORT_FIX.md).
- Seed fixée :
modeling.seed(config.yaml, valeur 123) propagée à la génération des pseudo-absences (seed + loop_index), à l'assignation des blocs spatiaux et aux algorithmes stochastiques (Random Forest, GBM). La variabilité inter-loops provient uniquement du tirage des pseudo-absences. - Idempotence : une loop est ignorée si ses 5 modèles
.pklexistent déjà ; les rasters coûteux (WorldClim actuel/futur, élévation) sont réutilisés s'ils sont déjà présents dansoutputs/. Aucun recalcul inutile en cas de relance. - Reprise après coupure : les agrégats CSV (
raw_evaluation_all_loop.csv,parameters_best_model_all_loop.csv,variable_importance_all_loop.csv) sont réécrits après chaque loop, pas seulement en fin de run.run_parallel.py --resumerelitpipeline_results.csvet ne retraite que les espèces qui ne sont pas encoreOK.run_species.pyintercepteSIGTERMpour s'arrêter proprement entre deux étapes plutôt qu'en cours d'écriture.