Le problème métier

On reçoit une pièce polygonale — potentiellement non convexe — avec des portes et des poteaux. L'objectif : y implanter le maximum d'emplacements vélo accessibles, en respectant le Décret 2022-930 et l'Arrêté du 30 juin 2022.

📥 Entrée

Un polygone (liste de sommets), une ou plusieurs portes (position + largeur sur arête), des poteaux optionnels (centres + rayons). Tout est exprimé en mètres.

📤 Sortie

Une liste d'emplacements vélo (position, orientation, système) tels que chaque vélo est joignable depuis au moins une porte via un couloir libre de 1,20 m.

⚖️ Contraintes

Cotes réglementaires par système (entraxe, profondeur, allée), dégagements de porte, circulation 1,20 m. Possibilité de réserver un emplacement cargo.

📐
Pourquoi c'est difficile ? — La difficulté n'est pas de poser une rangée de vélos (sous-problème 1D résolu exactement). C'est l'orchestration : découper la pièce, choisir l'orientation, le système, et garantir la circulation — tout en maximisant la capacité accessible.

Objets et termes

Chaque mot a un sens précis dans le moteur. Ce glossaire est la pierre de Rosette du projet.

TermeDéfinition
Emprise Le rectangle au sol occupé par un emplacement vélo. Largeur = entraxe du système, profondeur ≈ 2,00 m.
Système Un dispositif réel de stationnement avec ses cotes normées : entraxe, largeur d'allée, capacité par emprise (ex. : râtelier perpendiculaire, resserré, double étage…).
Rangée Suite d'emprises alignées le long d'un axe, desservies par une même allée de desserte.
Allée de desserte Bande libre devant une rangée permettant de manœuvrer le vélo. Largeur : 1,80 m (râtelier) ou 0,90 m (longitudinal).
Bande Rangée simple (profondeur + allée) ou rangée double (2 × profondeur + allée). Unité de pavage du constructeur.
Dégagement de porte Rectangle de 1,20 m de profondeur devant chaque porte. Aucune emprise ne peut y être placée.
Couloir / circulation Passage libre de 1,20 m minimum reliant les portes aux emplacements. Calculé par Dijkstra sur grille fine (M4).
Capacité Nombre total de vélos dans l'implantation = Σ bikes_per_place pour chaque emprise.
Capacité accessible Nombre de vélos réellement joignables depuis au moins une porte via un couloir de 1,20 m. C'est le vrai KPI.
💡
Capacité vs capacité accessible — Un glouton naïf maximise la capacité brute, mais beaucoup d'emplacements peuvent être injoignables (bloqués derrière d'autres rangées). NSGA-II optimise la capacité accessible — le seul indicateur qui a du sens pour l'usager.

Unités et repères

Mètres partout. La conversion pieds Revit se fait au chargement uniquement.

📏 Convention de conversion

Revit travaille en pieds internes. Le loader applique la conversion 1 ft = 0,3048 m une seule fois au chargement du JSON. Tout le moteur fonctionne ensuite en mètres.

1 ft = 0,3048 m  →  xm = xft × 0,3048

🔄 Repère monde (x, y) ↔ Repère local (s, t)

Le constructeur travaille dans un repère local tourné de l'angle principal θ. Les rangées sont alignées sur l'axe s, les bandes s'empilent sur l'axe t.

Monde → Local
s = x · cos θ + y · sin θ
t = −x · sin θ + y · cos θ
Local → Monde
x = s · cos θ − t · sin θ
y = s · sin θ + t · cos θ
⚠️
Attention — L'angle θ est celui de l'arête la plus longue du polygone, calculé par atan2(Δy, Δx) modulo π. Voir la page M1 — Géométrie.

Les 5 invariants non négociables

Ces règles sont vérifiées par les tests unitaires. Aucune optimisation, aucun refactoring ne peut les briser.

1. Jamais pire

Capacité ≥ golden numbers du banc legacy sur toutes les pièces de référence. Chaque étage du pipeline (M2 → M3 → M4 → M5) ne peut que maintenir ou améliorer le résultat du précédent.

2. reconnect = 0

Re-vérifier la connectivité d'un résultat final ne doit retirer aucune emprise. Le couloir 1,20 m est réellement garanti — pas simulé, pas approximé.

3. chevauchements = 0

Aucune emprise n'en recoupe une autre, aucune emprise ne sort du polygone. Test AABB en repère local + SAT sur chaque résultat.

4. Déterminisme

Tout aléa passe par un unique random.Random(seed). Deux exécutions à graine égale produisent exactement le même résultat, bit pour bit.

5. Respect de l'intention utilisateur

Un optimiseur ne désactive jamais un choix utilisateur (réserve cargo, double rangée forcée, système imposé…) pour gonfler un score. L'utilisateur a toujours le dernier mot.

🧪
Testés en CI — Chacun de ces invariants a au moins un test unitaire dédié dans tests/. Le pipeline de tests (53 tests, M1→M5) ne passe que si les 5 invariants sont vérifiés sur toutes les pièces de référence.

Le problème en un coup d'œil

Des données géométriques brutes aux vélos accessibles — le chemin complet.

Entrée : Pièce polygonale

Sommets, portes (position + largeur), poteaux (centre + rayon). Format JSON exporté de Revit.

Données

Analyse géométrique (M1)

Aire, angle principal, dégagements de porte, détection de zones. Repère local calculé.

Fondations

Construction + optimisation (M2→M5)

Pavage par bandes, packing exact, circulation réseau, NSGA-II multi-objectif.

Moteur

Sortie : Implantation optimale

Liste d'emplacements tous accessibles, PNG de visualisation, métriques de capacité.

Résultat
📂 Format JSON d'entrée (exemple) +
{ "vertices": [[0,0], [8,0], [8,5.8], [0,5.8]], "doors": [ {"position": [4.0, 0.0], "width": 1.03} ], "pillars": [], "unit": "meters" }

Les coordonnées sont en mètres. Si l'export Revit est en pieds, le loader applique automatiquement × 0,3048.

📊 Métriques de sortie +
28Capacité accessible
29Capacité brute
0Chevauchements
0Reconnections

La capacité accessible est toujours ≤ la capacité brute. L'écart mesure les emplacements « gaspillés » — placés mais injoignables.