Skip to content

Pareto fronts

The evolutionary search optimizes competing objectives at once, so it ends with a Pareto front: a set of partitions where no member is better than another on every objective — coarse trades against fine, tight communities against well-separated ones.

Every detector already resolves this — each applies the selection rule published with its algorithm (max-modularity for most; the label-free best-fitting assortative block model for rimpso; Shi's max-min distance to random-graph control fronts for mocd_d; see Algorithms) and returns a single partition. The front accessors exist for making that choice yourself: ground truth, a known expected community count, or your own quality metric.

The *_fronts functions

Six of the ten detector entry points pair with a *_fronts function exposing the candidate set as a plain list[dict] of partitions: rimpso, hpmocd, mmcomo, ccm, krm and moga_net. Each list is exactly what the corresponding detector selects from:

import networkx as nx
import pymocd

G = nx.karate_club_graph()
front, points, selected = pymocd.rimpso_fronts(G)

for partition in front:
    k = len(set(partition.values()))
    print(f"{k} communities")

Picking a member yourself

Max modularity

def modularity(G, partition):
    comms = {}
    for node, c in partition.items():
        comms.setdefault(c, set()).add(node)
    return nx.community.modularity(G, comms.values())

best = max(front, key=lambda p: modularity(G, p))

Against ground truth

Score every member with pymocd.gt_metrics (or ari, nmi, ami, f1 individually). Karate club, using the club attribute as ground truth:

gt = {v: int(G.nodes[v]['club'] != 'Mr. Hi') for v in G}

best = max(front, key=lambda p: pymocd.ari(p, gt))
nmi, ami, ari, f1 = pymocd.gt_metrics(best, gt)
print(f"NMI={nmi:.3f} AMI={ami:.3f} ARI={ari:.3f} F1={f1:.3f}")

The oracle may not be on the front

Even the best front member can fall short of ARI = 1.0: the ground-truth partition may be dominated under the search objectives and never survive to the final front. The front bounds what selection can recover.

Target community count

target = 2
best = min(front, key=lambda p: abs(len(set(p.values())) - target))

gdpso and cdrme optimize a single scalar — Newman-Girvan modularity and the CDRME paper's Eq. (12) linkage sum respectively — so they have no Pareto front and no gdpso_fronts / cdrme_fronts. mocd_q and mocd_d are multi-objective but expose no front accessor.

Each *_fronts function takes the same kwargs as its detector. rimpso_fronts returns (partitions, points, selected) rather than a bare list: every member, its (cut, pair) point, and the index the selector picked.

rimpso also offers rimpso_select, which runs its label-free selection rule over a candidate set this library did not produce — the control that separates the search's contribution from the selector's:

pick, points = pymocd.rimpso_select(G, candidates)
print(f"selected k={len(set(candidates[pick].values()))}")

The baseline fronts exist because the original papers report the best-NMI solution of the front, not the max-modularity one their detectors return — reproducing those tables needs the full candidate set:

front = pymocd.moga_net_fronts(G, r=2.0)   # Pizzuti Table 1 protocol (TEVC 2012 Sec. VI-C)
best_nmi = max(pymocd.nmi(p, gt) for p in front)

See also

  • Plotting — visualize the trade-off the front spans and draw the selected partition.
  • Fronts API — full signatures for the *_fronts functions.