Skip to content

Detectors

Every detector takes a graph and returns a partition as dict[node, community]. Isolated nodes are assigned community -1.

Two of these are this library's own algorithms — rimpso and hpmocd. The other eight entry points re-implement published methods by other authors; see Algorithms for the paper, the selection rule and the original implementation (where the authors released one) behind each.

This library's algorithms

pymocd.rimpso

rimpso(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 100,
    inertia: float = 0.4,
    cognitive: float = 0.7,
    social: float = 0.7,
    local_rate: float = 0.35,
    archive: int = 100,
    ls_period: int = 10,
    seed: int = 0,
) -> typing.Any

rimpso — multi-objective particle swarm optimisation over the Constant Potts Model. Returns the selected partition as dict[node, community]; isolated nodes get -1.

CPM, H(gamma) = sum_c [e_c - gamma * C(n_c,2)], is split the way HP-MOCD splits modularity, into a cut fraction and a pair coverage. Every resolution gamma is a weighted sum of that same pair, so the Pareto front the swarm builds is the graph's whole resolution profile and gamma stops being a parameter the caller has to guess.

Selection is label-free and has no parameter: of the archive's members, the one a degree-corrected assortative block model fits best once its own free densities are paid for. Both degenerate partitions carry no evidence and pay the penalty anyway, so there is no degeneracy filter and no fallback stage.

Deterministic: the same graph and the same parameters, seed included, give the same partition on any number of threads.

Parameters:

Name Type Description Default
pop_size int

particles in the swarm, one per rung of the resolution ladder.

100
num_gens int

generations to fly; the search always runs all of them.

100
inertia float

fraction of a node's instability carried to the next iteration.

0.4
cognitive float

pull toward the particle's own best partition.

0.7
social float

pull toward a leader drawn from the archive by binary tournament on crowding distance.

0.7
local_rate float

per-node rate of the resolution-directed CPM local move, applied on the iterations the full local search does not run.

0.35
archive int

capacity of the external Pareto archive.

100
ls_period int

run the full local search — drive the particle back to a local optimum of CPM at its own resolution, then sweep for community merges — every ls_period iterations; 0 turns it off. This is what makes the flight a search: without it, 100 generations of 100 particles improve a particle's own objective between 0 and 9 times in total and the net effect on the LFR grid is negative.

10
seed int

run seed. The default, 0, contributes nothing to the random stream, so it reproduces the single trajectory this searched before the seed was a parameter; any other value flies an independent one.

0

seed is the random seed, not a seeding budget: there is no seeding local search and no seed_rounds. Every particle starts at a raw scatter and the flight does all of the optimisation. Driving each particle to a CPM local optimum first was measured to be worth only a handful of iterations, and asymptotically to cost quality, because a particle already at a local optimum must be dragged out of it before it can move.

pymocd.hpmocd

hpmocd(graph: Any) -> builtins.dict[builtins.int, builtins.int]

Run HP-MOCD (NSGA-II) with its published defaults.

Returns dict[node, community]. Isolated nodes get -1.

Tunable HP-MOCD

hpmocd takes the graph and nothing else: it runs at the published configuration (pop_size=100, num_gens=100, cross_rate=0.7, mut_rate=0.5). The pymocd.HpMocd class exposes the same search with those four as constructor arguments, plus set_objectives for plugging in your own Python objective functions and set_on_generation for a per-generation callback.

Re-implemented baselines

pymocd.cdrme

cdrme(
    graph: Any,
    alpha_walk: float = 1.0,
    n_walk: int = 50,
    pop_size: int = 300,
    elite_size: int = 300,
    alpha_mut: float = 0.5,
    mut_sweeps: int = 10,
) -> builtins.dict[builtins.int, builtins.int]

Run CDRME (Dabaghi-Zarandi, Afkhami & Ashoori, "Community Detection method based on Random walk and Multi objective Evolutionary algorithm in complex networks", Journal of Network and Computer Applications 234:104070, 2025) — softmax-weighted random walks seeded at degree-weighted centres compose a primary community set, a population of stochastic agglomerative merge chains diversifies it under the paper's linkage objective (Eq. 12), and a similarity-driven mutation repairs the weakly attached nodes.

Eq. (12) adds innerLinkage (Eq. 9) and outerLinkage (Eq. 10) into one maximised scalar, so there is no Pareto front and no cdrme_fronts. The paper's own selector (Sec. 4.4.4) names three "evaluation measures"; NMI needs ground truth and Density is maximised by the single community, so the shipped rule is max-modularity, which is what the authors' own code selects on.

Written from the paper. The authors' reference implementation is a private notebook, not a published repository.

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required
alpha_walk float

Eq. (7) walk-length coefficient, the paper's 1 to 2. Since |V|/ENC is identically AvgDegree(G) (Eqs. 5-6), the length is Degree(v) + alpha_walk * AvgDegree(G). Clamped to [0, 2].

1.0
n_walk int

walks per centre (Algorithm 1); the paper gives no value.

50
pop_size int

N_p, the number of merge chains. Every chromosome starts identical, so this is how many points along the merge chain are sampled, not a breeding pool. Cost is linear in it.

300
elite_size int

N_sp <= N_p, the chromosomes that reach mutation (Sec. 4.4.1). Ranking by Eq. (12) drops the coarse chromosomes, so the default keeps them all.

300
alpha_mut float

Sec. 4.4.2 mutation threshold on the [0,1] similarity scale; a gene below it is offered a new community.

0.5
mut_sweeps int

cap on the 4.4.2 <-> 4.4.3 loop, which the paper leaves unbounded. The loop also stops on the first sweep that moves no gene.

10

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.mmcomo

mmcomo(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 50,
    cross_rate: float = 0.1,
    mut_rate: float = 0.1,
    gap: int = 10,
    beta: float = 0.05,
) -> typing.Any

MMCoMO macro-micro co-evolutionary detector (Zhang et al.); returns the max-modularity member of the merged rank-1 front. Isolated nodes get -1.

pymocd.ccm

ccm(
    graph: Any,
    pop_size: int = 200,
    num_gens: int = 100,
    cross_rate: float = 0.8,
    mut_rate: float = 0.014705882352941176,
    r: float = 1.0,
    alpha: float = 1.0,
    divisions: int = 12,
) -> builtins.dict[builtins.int, builtins.int]

Run NSGA-III-CCM (Shaik, Ravi & Deb, SN Computer Science 2:13, 2021) — NSGA-III over the three maximized objectives (Community Score, Community Fitness, Modularity). Returns the max-modularity member of the rank-1 Pareto front (the paper's recommended ground-truth-free decision rule).

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required
r float

Community Score power-mean exponent (Shaik default 1).

1.0
alpha float

Community Fitness exponent (Shaik default 1).

1.0
divisions int

Das–Dennis reference-point granularity p (default 12 → 91 reference points for the 3 objectives).

12

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.krm

krm(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 100,
    cross_rate: float = 0.8,
    mut_rate: float = 0.029411764705882353,
    divisions: int = 12,
) -> builtins.dict[builtins.int, builtins.int]

Run NSGA-III-KRM (Shaik, Ravi & Deb, SN Computer Science 2:13, 2021) — NSGA-III over (Kernel-K-Means, Ratio-Cut, Modularity); KKM & Ratio-Cut minimized, Modularity maximized. Returns the max-modularity member of the rank-1 Pareto front (the paper's recommended ground-truth-free decision rule).

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required
divisions int

Das–Dennis reference-point granularity p (default 12 → 91 reference points for the 3 objectives).

12

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.gdpso

gdpso(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 250,
    w: float = 0.7298,
    c1: float = 1.4961,
    c2: float = 1.4961,
    mut_rate: float = 0.1,
    mut_frac: float = 0.1,
    lpa_sweeps: int = 5,
) -> builtins.dict[builtins.int, builtins.int]

Run GDPSO (Cai, Gong, Ma, Ruan, Yuan, Jiao, "Greedy discrete particle swarm optimization for large-scale social network clustering", Information Sciences 316:503–516, 2015) — a swarm of label vectors, each seeded by a short asynchronous label-propagation run, that once per generation turns a sigmoid of the velocity into a binary per-node move mask and offers every masked node an exact single-node modularity move. Returns the best position the swarm ever held; GDPSO is single-objective (Newman–Girvan modularity), so there is no Pareto front and no gdpso_fronts.

Written from a specification of the authors' public reference implementation; no reference source was copied.

Note pbest and gbest carry no label information — they enter only as two indicator bits shifting a node's move probability — so in practice this behaves as the best of pop_size LPA seeds, each polished by Louvain local-moving. lpa_sweeps, not num_gens, is the lever on seed diversity. GDPSO also inherits modularity's resolution limit whole.

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required
w float

inertia weight on the previous velocity (Clerc constant, inherited from real-valued PSO; the velocity is re-binarized every generation).

0.7298
c1 float

cognitive weight, applied to the pbest agreement indicator.

1.4961
c2 float

social weight, applied to the gbest agreement indicator.

1.4961
mut_rate float

per-node label-broadcast probability inside a mutated particle.

0.1
mut_frac float

fraction of the swarm that is mutated each generation. The reference overloads a single 0.1 for this and for mut_rate.

0.1
lpa_sweeps int

asynchronous label-propagation sweeps seeding each particle.

5

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.mocd_q

mocd_q(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 100,
    cross_rate: float = 0.9,
    mut_rate: float = 0.1,
) -> builtins.dict[builtins.int, builtins.int]

Run Shi-MOCD (Shi, Yan, Cai, Wu 2012) — PESA-II over Shi's decomposed-modularity objectives (intra/inter). Returns the max-modularity member of the Pareto front (MOCD-Q selection, Shi Eq. 3.8).

Defaults (pop=100, gen=100, C_R=0.9, M_R=0.1) are the repo's HP-MOCD-parity benchmark budget, NOT Shi's published configuration — that is pc=0.6, pm=0.4 with per-graph ip/ep/gen from Table 1; pass those via kwargs.

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.mocd_d

mocd_d(
    graph: Any,
    pop_size: int = 100,
    num_gens: int = 100,
    cross_rate: float = 0.9,
    mut_rate: float = 0.1,
    rand_networks: int = 3,
) -> builtins.dict[builtins.int, builtins.int]

Shi-MOCD with the Max-Min Distance (MOCD-D) model selector (Shi et al. 2012, Eqs. 3.9–3.11): returns the Pareto-front member whose (intra, inter) deviates most from rand_networks same-scale Erdős–Rényi control fronts.

Defaults (pop=100, gen=100, C_R=0.9, M_R=0.1) are the repo's HP-MOCD-parity benchmark budget, NOT Shi's published configuration — that is pc=0.6, pm=0.4 with per-graph ip/ep/gen from Table 1; pass those via kwargs.

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

pymocd.moga_net

moga_net(
    graph: Any,
    pop_size: int = 300,
    num_gens: int = 30,
    cross_rate: float = 0.8,
    mut_rate: float = 0.2,
    r: float = 2.0,
    alpha: float = 1.0,
) -> builtins.dict[builtins.int, builtins.int]

Run MOGA-Net (Pizzuti, IEEE TEC 16(3):418–430, 2012) — NSGA-II over the (Community Score, Community Fitness) bi-objective. Returns the max-modularity member of the rank-1 Pareto front (Pizzuti Sec. V-E).

Parameters:

Name Type Description Default
graph Any

networkx.Graph or igraph.Graph (integer node ids).

required
r float

Community Score power-mean exponent (resolution knob; higher helps at high mixing). TEVC 2012 Sec. VI-C fixes it at 2, which is the default here.

2.0
alpha float

Community Fitness exponent. It does not set a community size: in the per-node form used here CF ≤ Σ_i deg(i)^(1−alpha) for every alpha, with equality only for the single-community partition. It reweights who counts — alpha > 1 discounts high-degree nodes, so low-degree nodes' internal edges matter relatively more. Pizzuti default 1.

1.0

Returns:

Type Description
dict[int, int]

dict[node, community]. Isolated nodes get community -1.

Deprecated aliases

pymocd.mr_mocd, pymocd.mr_mocd_fronts and pymocd.mr_mocd_select are the names this detector carried before it was renamed to RIMPSO; pymocd.scale and pymocd.scale_fronts are older still. All five are the same function objects as rimpso, rimpso_fronts and rimpso_select, kept so pinned callers keep working. They emit no warning and do not appear in the type stubs. Use the new names.