Non-dominated sorting, crowding distance, and ranking with per-objective min/max directions.
NumPy-only core. Fully typed. No framework lock-in.
demo.mp4
Pareto takes an array of multi-objective data and sorts it into Pareto fronts. Each objective can independently minimise or maximise. It handles ranking, diversity selection, and weighted scoring without pulling in an optimisation framework.
import numpy as np
from pareto import non_dominated_sort, ParetoRanker
candidates = np.array([
[0.95, 120, 0.02], # accuracy, latency_ms, cost
[0.91, 45, 0.08],
[0.88, 30, 0.15],
[0.93, 80, 0.05],
[0.80, 200, 0.25],
])
fronts = non_dominated_sort(
candidates,
senses=["max", "min", "min"],
)
ranker = ParetoRanker(senses=["max", "min", "min"])
top_3 = ranker.select(candidates, k=3)fronts[0] = [0, 1, 2, 3] # non-dominated set
fronts[1] = [4] # dominated
top_3 = [0, 2, 1] # best trade-offs by rank + crowding distance
pip install pareto-front
For visualisation helpers:
pip install pareto-front[viz]
from pareto import non_dominated_sort, crowding_distance
fronts = non_dominated_sort(objectives)
distances = crowding_distance(objectives[fronts[0]])Every function accepts an optional senses parameter. Pass "min" or "max" per objective, or use the Sense enum:
from pareto import Sense
fronts = non_dominated_sort(objectives, senses=[Sense.MIN, Sense.MAX])Rankers store senses on construction:
from pareto import ParetoRanker, WeightedRanker
ranker = ParetoRanker(senses=["min", "max", "min"])
result = ranker.rank(objectives)
top_k = ranker.select(objectives, k=5)
weighted = WeightedRanker(weights=[1.0, 2.0, 0.5], senses=["min", "max", "min"])
scores = weighted.scores(objectives)Every existing tool is either a full optimisation framework that happens to include sorting, or a narrow utility that only extracts the non-dominated set. Nothing sits in between with typed APIs, per-objective directions, and ranking out of the box.
| Tool | Sorting | Crowding | Ranking | Senses | Typed | Dependencies |
|---|---|---|---|---|---|---|
| pymoo | Yes | Yes | NSGA-II only | Framework config | Partial | scipy, matplotlib, many |
| pygmo | Yes | Yes | sort_population_mo |
All-minimise | No (C++ bindings) | pagmo2 (C++) |
| DEAP | Yes | Yes | NSGA-II selection | All-minimise | No | None heavy, but no types |
| Optuna | Yes | Partial | Internal only | Framework config | Partial | sqlalchemy, many |
| paretoset | Pareto set only | No | No | Yes | No | numpy |
| fast-pareto | Pareto set only | No | No | No | Partial | numpy |
| pareto | Yes | Yes | ParetoRanker + WeightedRanker | Per-objective | Full (py.typed) | numpy |
Where pareto is stronger. Single dependency (numpy). Full typing with py.typed marker. Per-objective min/max on every function. Combined ranking (front + crowding distance) and weighted scoring as first-class features, not buried inside an optimiser. O(N log N) fast path for 2D problems. Clean API that works directly with numpy arrays.
Where pareto is weaker. pymoo and pygmo have far more algorithms (NSGA-III, reference-point methods, hypervolume indicators, decomposition). DEAP has evolutionary operators. Optuna handles the entire hyperparameter loop. Pareto has none of these and is not trying to. If you need a full multi-objective optimiser, use one of those.
The closest alternative is paretoset. It extracts the Pareto-optimal set from a DataFrame with per-objective directions. But it stops there: no crowding distance, no front ranking beyond the first front, no weighted scoring, no visualisation. Pareto picks up where paretoset leaves off.
non_dominated_sort on random uniform data (median of 5 runs, seed 42).
2D uses an O(N log N) sweep; 3D+ uses vectorised pairwise comparison.
| Points | Objectives | Time (ms) |
|---|---|---|
| 100 | 2 | 0.8 |
| 1,000 | 2 | 11.6 |
| 5,000 | 2 | 42.1 |
| 100 | 5 | 14.1 |
| 1,000 | 5 | 203.4 |
| 5,000 | 5 | 4,018.2 |
73 tests. Pure Python + NumPy. Zero compiled extensions.
Not yet implemented:
- pandas/polars DataFrame integration (accepts via
__array__protocol, but no native returns) - Hypervolume indicator
- Epsilon-dominance
- NSGA-III reference-point sorting
- Parallel/threaded sorting for large N
Apache-2.0