Generate Pareto Front
Walk the Pareto front of multi-resolution modularity trade-offs found by PRISM.
Pareto Front Generation
Unlike HP-MOCD — whose front trades intra-density against inter-separation — PRISM’s front sweeps community resolution. Each non-dominated member of the archive is (locally) optimal for a different granularity of community structure, from fine splits (γ = 2.0) through standard Newman modularity (γ = 1.0) down to coarse super-communities (γ = 0.5).
A single .generate_pareto_front() call gives you the whole spectrum, so you can pick the partition whose resolution matches your domain — no rerunning with different resolution parameters.
Instantiate the Algorithm
Same setup as the Basic Usage page:
import networkx as nx
from pymocd import Prism
G = nx.karate_club_graph()
alg = Prism(
graph=G,
debug_level=1,
swarm_size=100,
num_gens=100,
)
Generate the Pareto Front
alg.run() # evolve the swarm
frontier = alg.generate_pareto_front() # read the archive
Return value:
list[tuple[dict[int, int], list[float]]]First element: partition dict —
{node_id: community_id}Second element: objective values in the order PRISM minimizes them
[0] = -Q_0.5— negated modularity at coarse resolution (γ = 0.5)[1] = -Q_1.0— negated classical Newman modularity (γ = 1.0)[2] = -Q_2.0— negated modularity at fine resolution (γ = 2.0)
-0.65 at index 1 means Q_1.0 = 0.65. Flip the sign when comparing to typical modularity reports.Custom Objectives: When using the objectives= parameter, the second element of each tuple matches your objective list instead of the default [-Q_0.5, -Q_1.0, -Q_2.0] triple. See Custom Objectives.
Inspect a Solution
labels, objs = frontier[0]
neg_q05, neg_q10, neg_q20 = objs
print(f"Communities: {len(set(labels.values()))}")
print(f"Q(γ=0.5) = {-neg_q05:.4f} # coarse")
print(f"Q(γ=1.0) = {-neg_q10:.4f} # standard")
print(f"Q(γ=2.0) = {-neg_q20:.4f} # fine")
Selecting Your Preferred Solution
The front is ordered arbitrarily — pick by objective, not by index:
| Goal | How |
|---|---|
| Standard modularity (classical Newman Q) | Minimize objs[1] (equivalently, maximize -objs[1]). |
| Coarse partitions (fewer, larger communities) | Minimize objs[0]. γ = 0.5 escapes the resolution limit upward. |
| Fine partitions (many small communities) | Minimize objs[2]. γ = 2.0 splits aggressively. |
| Knee point / balanced | Pick the member closest to the origin in normalized objective space. |
| Domain metric | Compute your metric (conductance, NMI vs ground truth, etc.) on each labels dict and pick the best. |
Example — grab the partition with the best classical modularity:
best_idx = min(range(len(frontier)), key=lambda i: frontier[i][1][1])
best_labels, best_objs = frontier[best_idx]
print(f"Best Q_1.0 = {-best_objs[1]:.4f}")
print(f"k = {len(set(best_labels.values()))} communities")
The Convenience Accessor
If you just want the single scalar best Q score from the archive (after negation), use:
alg.best_q() # float — max over Pareto members of (−min(objs[1]))
This is handy for quick comparisons against Louvain/Leiden without iterating the front yourself.
Why a resolution-diverse front helps
Classical modularity has a well-known resolution limit: it cannot detect communities smaller than a threshold set by the total edge count. PRISM’s multi-γ front sidesteps this by optimizing three resolutions simultaneously — the fine-resolution member (γ = 2.0) will split a Q-merged super-community whenever doing so is justified under γ > 1, and the coarse member (γ = 0.5) will merge small-but-related communities when that is justified under γ < 1. You get to choose the resolution after seeing the data, not before.
Last updated 18 Apr 2026, 22:19 -0300 .