Installation

Python ≥ 3.10 requis. Trois niveaux d'installation selon les besoins.

1

Installation complète (recommandée)

Installe tous les extras d'un coup : géométrie, visualisation, solveur.

$ pip install -e .[full] # tout d'un coup
2

Installation sélective

Installez uniquement les extras nécessaires pour votre usage.

$ pip install -e .[geo,viz,solve] # extras séparés
3

Vérification de l'installation

Lancez les tests unitaires pour vérifier que tout fonctionne.

$ python -m unittest discover -s tests -v Ran 53 tests in 2.4s — OK
💡
Socle sans dépendances — Les modules io et core.geometry sont écrits en stdlib pur — aucune dépendance externe. Vous pouvez charger et analyser des pièces sans installer Shapely, numpy ou matplotlib.
ExtraDépendancesModules débloqués
geoshapely, numpyGéométrie avancée, AABB, inclusion polygonale
vizmatplotlibRendus PNG, visualisation des fronts de Pareto
solveortoolsCP-SAT pour le packing exact (M3)
fullTous les ci-dessusInstallation complète

Charger une pièce

Le point d'entrée du pipeline : transformer un JSON Revit en objet Room manipulable.

from bikeoptim.io.rooms import load_room # Charger une pièce depuis un fichier JSON room = load_room('data/rooms/piece_46.json') # Attributs disponibles room.boundary # List[Tuple[float, float]] — contour en mètres room.doors # List[Door] — portes avec position, largeur, sens room.columns # List[Column] — poteaux avec centre et rayon
📂
Format JSON — Le fichier JSON est exporté depuis Revit via le script revit_dump.py. Les coordonnées sont en pieds impériaux et converties automatiquement en mètres SI par load_room().
🔍 Structure du JSON attendu +
{ "name": "Piece_46", "boundary": [[0.0, 0.0], [26.24, 0.0], ...], "doors": [{ "position": [13.12, 0.0], "width": 3.38, "direction": "inward" }], "columns": [] }

Les coordonnées boundary sont en pieds. load_room() multiplie par 0.3048 pour convertir en mètres.

Implantation simple

Lancer le constructeur avec une configuration manuelle — sans passer par NSGA-II.

from bikeoptim.solve.constructor import implanter_piece from bikeoptim.catalog.systems import ConfigImplantation # Configuration manuelle cfg = ConfigImplantation( systems=['ratelier_perp'], mix_enabled=False, ) # Lancer l'implantation result = implanter_piece(room, cfg) # Résultats result.bikes # List[Bike] — emprises positionnées result.capacity # int — nombre total de vélos placés result.capacity_accessible # int — nombre de vélos accessibles
Valide par construction — Le résultat est garanti sans chevauchement et sans emprise hors polygone. Aucune étape de réparation n'est nécessaire.

ConfigImplantation

Tous les paramètres de configuration avec leur rôle et leurs valeurs par défaut.

📋 systems — Liste des systèmes à tester +

Type : List[str]

Défaut : ['ratelier_perp']

Liste des identifiants de systèmes à utiliser. Le constructeur teste chacun et retient le meilleur. Valeurs possibles :

ratelier_perp ratelier_resserre arceau_perp longi_sol double_etage
cfg = ConfigImplantation(systems=['ratelier_perp', 'longi_sol'])
🔀 mix_enabled — Mélange de systèmes par bande +

Type : bool

Défaut : False

Si activé, différentes bandes de la même pièce peuvent utiliser des systèmes différents. Par exemple, une bande en râtelier perpendiculaire et une autre en longitudinal.

⚠️
Le mix augmente le nombre de combinaisons testées. Avec 3 systèmes et 4 bandes, ça fait 3⁴ = 81 combinaisons.
🎯 exact_zone — Packing exact (M3) vs glouton +

Type : bool

Défaut : True

Active le packing exact par programmation dynamique (DP) ou CP-SAT au lieu du glissement glouton. Résultat optimal garanti mais légèrement plus lent (~15 ms vs ~5 ms).

Glouton (exact_zone=False)

Rapide (~5 ms)
Résultat approché
Filet « jamais pire »

Exact (exact_zone=True)

Plus lent (~15 ms)
Résultat optimal
DP linéaire + CP-SAT

enforce_access — Garantir l'accessibilité (M4) +

Type : bool

Défaut : True

Si activé, le constructeur élimine les emprises inaccessibles (celles qu'aucun couloir de 1,20 m ne relie à une porte). La capacité résultante est la capacité accessible.

🔗
Quand enforce_access=True, capacity == capacity_accessible. Aucun vélo « fantôme » inaccessible ne subsiste dans le résultat.
🚛 reserve_cargo — Réserver un emplacement cargo +

Type : bool

Défaut : False

Réserve un emplacement pour vélo cargo (2,50 × 1,00 m) près d'une porte. L'optimiseur ne peut pas désactiver ce choix pour gonfler la capacité — c'est un invariant utilisateur.

cfg = ConfigImplantation( systems=['ratelier_perp'], reserve_cargo=True, ) # Un emplacement cargo est garanti dans le résultat

Optimiser (NSGA-II)

Lancer l'optimiseur multi-objectif pour explorer l'espace des structures et trouver le front de Pareto.

from bikeoptim.optimize.nsga2 import optimiser # Lancer l'optimisation results = optimiser( room, cfg, seed=0, # graine pour le déterminisme budget_s=30.0, # budget en secondes max_evals=200, # nombre max d'évaluations ) # Résultats results.pareto_front # List[Solution] — solutions non dominées results.top_n # List[Solution] — top-N variantes diverses

🎯 Objectif 1

Maximiser la capacité accessible — nombre de vélos réellement joignables via un couloir de 1,20 m.

📉 Objectif 2

Minimiser le coût de circulation — longueur totale pondérée des couloirs nécessaires.

⏱️
Budget temps — L'optimiseur s'arrête dès que le budget en secondes OU le nombre max d'évaluations est atteint (premier des deux). Avec budget_s=30 et des évaluations à ~50 ms, on atteint ~200 évaluations en ~10 s.
🎲
Déterminisme — Le paramètre seed garantit la reproductibilité. Deux exécutions avec la même graine sur la même pièce produisent le même front de Pareto, bit pour bit.

Visualiser

Générer des rendus PNG des pièces et des implantations pour l'analyse visuelle.

from bikeoptim.viz.render import rendre_piece, rendre_implantation # Rendre la pièce seule (contour, portes, poteaux) rendre_piece(room, 'output/piece.png') # Rendre une implantation (emprises, couloirs, métriques) rendre_implantation(result, 'output/layout.png')

🖼️ Contenu d'un rendu d'implantation

Polygone de la pièce (contour gris)
Emprises colorées par système de stationnement
Couloir de circulation (1,20 m minimum)
Portes avec arc de débattement
📊 Rendre le front de Pareto +
from bikeoptim.viz.render import rendre_pareto # Visualiser le front de Pareto rendre_pareto(results, 'output/pareto.png') # Axe X : -capacité_accessible # Axe Y : coût_circulation # Points verts : solutions non dominées # Points gris : solutions dominées
🏆 Rendre les top-N variantes +
# Rendre les N meilleures variantes du front for i, sol in enumerate(results.top_n): rendre_implantation(sol, f'output/top_{i}.png') print(f'Variante {i}: {sol.capacity_accessible} vélos, ' f'coût={sol.circulation_cost:.2f}')

Les top-N variantes sont sélectionnées pour maximiser la diversité structurelle : orientations, systèmes et partitionnements différents.

Bancs et tests

Scripts de benchmark par jalon et suite de tests unitaires pour valider chaque composant.

CommandeJalonDescription
python scripts/bench_m2.py M2 Parité golden numbers — vérifie que le constructeur reproduit les capacités de référence du moteur legacy
python scripts/bench_m3.py M3 Exact vs glouton — compare le packing exact (DP/CP-SAT) au glissement glouton sur toutes les pièces
python scripts/bench_m4.py M4 Accessibilité + 3 portes — vérifie le couloir 1,20 m, multi-portes, BFS sur pièces complexes
python scripts/bench_m5.py M5 NSGA-II + Pareto + top-N — lance l'optimisation complète, vérifie les gains stricts et la diversité
python -m unittest discover -s tests -v Tous 53 tests unitaires couvrant M1 → M5 — géométrie, constructeur, packing, circulation, NSGA-II

✅ Exécution typique

$ python scripts/bench_m5.py ═══ Bench M5 — NSGA-II ═══ Pièce 46 râtelier : 22 vélos (22 accessibles) — baseline Pièce 46 resserré : 29 vélos (14 accessibles) — glouton Pièce 46 NSGA-II : 28 vélos (28 accessibles) — +100% ✓ Front de Pareto : 8 solutions non dominées Top-N variantes : 5 layouts divers rendus
🧪
CI recommandé — Intégrez python -m unittest discover -s tests -v dans votre CI/CD. Les 53 tests s'exécutent en ~2,5 s et vérifient tous les invariants non négociables (zéro chevauchement, reconnect = 0, déterminisme).

Workflow complet en 5 lignes

Du JSON au PNG en un seul script — copier-coller et adapter.

from bikeoptim.io.rooms import load_room from bikeoptim.catalog.systems import ConfigImplantation from bikeoptim.optimize.nsga2 import optimiser from bikeoptim.viz.render import rendre_implantation # 1. Charger room = load_room('data/rooms/piece_46.json') # 2. Configurer cfg = ConfigImplantation( systems=['ratelier_perp', 'ratelier_resserre', 'longi_sol'], mix_enabled=True, exact_zone=True, enforce_access=True, ) # 3. Optimiser results = optimiser(room, cfg, seed=42, budget_s=30.0) # 4. Afficher le meilleur best = results.top_n[0] print(f'{best.capacity_accessible} vélos accessibles') # 5. Rendre en PNG rendre_implantation(best, 'output/best.png')