Claude
feat(migration): Phase 2 — statistics (8/8 modules migrés)
b0b1bdf unverified
Raw
History Blame
2.97 kB
"""Frontière de Pareto multi-objectifs (Sprint 19).
Algorithme générique sur N objectifs (CER, coût, vitesse, CO₂…).
Renvoie les noms des points non-dominés.
"""
from __future__ import annotations
from typing import Optional
def compute_pareto_front(
points: list[dict],
objectives: tuple[str, ...] = ("cer", "cost"),
name_key: str = "engine",
minimize: Optional[tuple[bool, ...]] = None,
) -> list[str]:
"""Calcule la frontière de Pareto sur ``len(objectives)`` dimensions.
Un point ``p`` est Pareto-dominant si aucun autre point n'a, pour TOUS
les objectifs, une valeur au moins aussi bonne ET au moins une valeur
strictement meilleure.
Parameters
----------
points:
Liste de dicts. Chaque dict doit contenir ``name_key`` et toutes les
clés de ``objectives``. Les points dont une valeur d'objectif est
``None`` sont ignorés (pas de comparaison possible).
objectives:
Clés des objectifs à minimiser/maximiser.
name_key:
Clé identifiant le point (par défaut ``"engine"``).
minimize:
Pour chaque objectif, ``True`` = minimiser (ex. CER, coût),
``False`` = maximiser (ex. ancrage). Doit avoir la même longueur
que ``objectives``.
Returns
-------
Liste des ``name`` des points sur le front Pareto, ordre stable depuis
``points``.
"""
if minimize is None:
minimize = tuple(True for _ in objectives)
if len(minimize) != len(objectives):
raise ValueError("`minimize` doit avoir la même longueur que `objectives`")
valid = []
for p in points:
try:
vals = tuple(float(p[k]) for k in objectives)
except (KeyError, TypeError, ValueError):
continue
valid.append((p[name_key], vals))
front: list[str] = []
for name_a, vals_a in valid:
dominated = False
for name_b, vals_b in valid:
if name_a == name_b:
continue
# B domine A si B est ≥ aussi bon partout ET strictement meilleur quelque part
better_or_equal_everywhere = True
strictly_better_somewhere = False
for va, vb, mini in zip(vals_a, vals_b, minimize):
if mini:
if vb > va:
better_or_equal_everywhere = False
break
if vb < va:
strictly_better_somewhere = True
else: # maximiser
if vb < va:
better_or_equal_everywhere = False
break
if vb > va:
strictly_better_somewhere = True
if better_or_equal_everywhere and strictly_better_somewhere:
dominated = True
break
if not dominated:
front.append(name_a)
return front
__all__ = ["compute_pareto_front"]