Shi-MOCD (MOCD-Q / MOCD-D)¶
Re-implemented baseline Multi-objective
Shi-MOCD is the multi-objective community detection method of
Shi, Yan, Cai & Wu (2012). It
minimizes the two terms into which Newman's modularity \(Q\) splits,
\(\mathrm{intra}\) and \(\mathrm{inter}\), with the PESA-II evolutionary algorithm
over a locus-based adjacency encoding, and keeps the non-dominated partitions
in an external archive. The paper proposes two rules for picking one partition
from that front, and pymocd exposes each as its own function:
mocd_q returns the front member of highest modularity
(MOCD-Q), and mocd_d returns the member that lies furthest
from the fronts of Erdős–Rényi control graphs (MOCD-D). Both return a single
partition; the front itself is not exposed.
| Entry points | mocd_q (MOCD-Q), mocd_d (MOCD-D) |
| Objectives | \(\mathrm{intra}\) and \(\mathrm{inter}\) (Shi et al., 2012, Eqs. 3.5 and 3.6), both minimized; \(Q = 1 - \mathrm{intra} - \mathrm{inter}\) (Eq. 3.7) |
| Search | PESA-II (Corne et al., 2001) with an adaptive hyper-grid, over the locus-based adjacency encoding (Park & Song, 1998) |
| Selection rule | MOCD-Q: maximum \(Q\) (Eq. 3.8). MOCD-D: maximum of the minimum distance to Erdős–Rényi control fronts (Eqs. 3.9–3.11) |
| Front accessor | None; there is no mocd_fronts |
| Deterministic | No: there is no seed parameter, and the random generator is seeded from the operating system (module README, Determinism) |
| Original code | The first author's publications page links a C++ archive, MOGAForClustering28.zip, as the code of this paper. The port was written from the paper text. Its variation operators, parent selection and grid resolution, which the paper leaves open, follow that code (see Divergences from the paper) |
| Paper | Shi et al. (2012), Applied Soft Computing 12(2):850–859 — DOI; the same publications page links an article-in-press copy |
The paper's rates, a per-graph budget
mocd_q and mocd_d default to
cross_rate=0.6 and mut_rate=0.4, the crossover and mutation rates of
Shi et al. (2012), which "are fixed for all experiments" (§4.3). The paper
sets the internal population, the archive size and the number of
generations per graph in its Table 1, so to reproduce one of its runs, set
pop_size and num_gens to the values listed in
The paper's settings. pop_size sets both the internal
population and, capped at 100, the archive size (see
Parameters); every column of Table 1 follows that rule.
Up to 6.1.0 the defaults were cross_rate=0.9 and mut_rate=0.1, the
setting of the MOCD baseline in the HP-MOCD benchmark of
Santos et al. (2025); pass
them explicitly for that setting.
Search¶
Representation. Shi-MOCD uses the locus-based adjacency encoding of Park & Song (1998), which Shi et al. adopt in their §3.1.3. A genome holds one gene per node; gene \(i\) names one neighbor of node \(i\), read as "link node \(i\) to that neighbor". The communities are the connected components of the graph formed by these links. A node without neighbors points at itself and becomes a singleton. Initialization and mutation draw every gene from the node's neighbors, and crossover copies each gene from one parent at the same position, so the module README notes that every genome is valid by construction and no repair step exists. Decoding is a union-find over the \(n\) links followed by a relabeling of the communities in order of first appearance.
PESA-II. The search is PESA-II
(Corne, Jerram, Knowles & Oates, 2001), reimplemented inside the
module. It keeps an internal population of size pop_size that is
regenerated every generation and an external archive of capacity
min(pop_size, 100) that holds the non-dominated partitions found
so far. The archive is both the result and the mating pool.
- Seeding.
pop_sizerandom genomes are decoded and evaluated; each is offered to the archive, which is then truncated to its capacity. - Generations. For each of the
num_gensgenerations (stopping early if the archive is empty):- The archive is binned into a hyper-grid with 10 divisions per objective (100 cells), each axis normalized to the range the archive currently spans. The occupancy of a cell is its squeeze factor.
pop_sizechildren are bred as the authors' implementation breeds them. Each child starts as a copy of an archive member chosen by region-based selection (below) and is mutated once. A mutation redraws a fixed number of genes, each uniformly from its node's neighbors (which may return the same neighbor); the number depends only on the node count \(n\): \(\lfloor n/2 \rfloor\) below 20 nodes, \(\lfloor n/3 \rfloor\) below 50, \(\lfloor n/5 \rfloor\) below 100, \(\lfloor n/50 \rfloor\) below 1,000, \(\lfloor n/200 \rfloor\) below 10,000 and \(\lfloor n/1000 \rfloor\) above (11 genes on karate, 2 on a 128-node graph, 24 on the power grid). Then \(\lceil P/2 \rceil\) variation steps follow, with \(P\) =pop_size: with probabilitymut_ratea step mutates one random child again; otherwise, with probabilitycross_rate, it applies uniform crossover to two distinct random children in place, swapping each gene between them with probability 1/2, so that both offspring are kept.- Each child is evaluated and offered to the archive. It is rejected only if an archive member dominates it; archive members it dominates are removed.
- If the archive exceeds its capacity, it is truncated.
- Result. The final archive is the Pareto front.
Region-based selection. Parents are chosen as in the authors' implementation: a uniformly random occupied cell, then a uniformly random member of it. Selection is over cells, not members: a cell with one member is drawn as often as a cell with twenty, which spreads the parents along the front. (Corne et al. (2001) instead hold a binary tournament between two occupied cells on their squeeze factors.)
Truncation. While the archive is over capacity, a most crowded cell is chosen (ties uniformly at random), a uniformly random member of it is removed, and the grid is rebuilt before the next removal. Since only dominated candidates are rejected, members with identical objective values can accumulate; truncation is what bounds them.
Selecting a partition¶
Both functions run the PESA-II search above on the input graph and apply one of Shi's two model-selection rules to the resulting front \(F\). Write \(f(C) = (\mathrm{intra}(C), \mathrm{inter}(C))\) for the objective point of a partition.
MOCD-Q (mocd_q, Shi Eq. 3.8) returns the front member
of highest modularity. Since \(Q = 1 - \mathrm{intra} - \mathrm{inter}\), this
is
MOCD-D (mocd_d, Shi Eqs. 3.9–3.11) compares the front
with fronts evolved on random graphs. It generates \(r\) = rand_networks
Erdős–Rényi \(G(n, m)\) control graphs with the same node set and the same
number of edges as the input, evolves a full front \(R_1, \dots, R_r\) on each,
and returns the real front member that is furthest from all of them:
The module README explains the idea: a random graph's front shows what "no
community structure" looks like in objective space, so the real point that
deviates most from it is taken as the most meaningful one. The equations above
describe the rule as implemented (selection.rs); the module README gives the
same rule in words and attributes it to Shi Eqs. 3.9–3.11.
The result is a dict[int, int] mapping each node to a community numbered
from 0. Isolated nodes are reported as community -1.
Each call is a new search
Every call runs the search from scratch, and the search is not seeded. A
call to mocd_q and a call to mocd_d on the same graph therefore select
from the fronts of two independent searches, and mocd_d also draws new
control graphs on every call. The module README reports that over 20 runs
on the Florentine families graph, mocd_d returned between 2 and 4
communities while mocd_q returned 3 every time, and attributes the
spread to the control fronts rather than to the search.
No front accessor
The Rust class behind both functions can produce the whole front, but the
module README notes that it is not registered with the Python module, and
there is no mocd_fronts. To study the \((\mathrm{intra}, \mathrm{inter})\)
front itself, HP-MOCD optimizes the same pair and exposes its
front through hpmocd_fronts and
HpMocd.generate_pareto_front; see
Working with Pareto fronts.
Parameters¶
One table covers both functions; rand_networks belongs to mocd_d only.
| Parameter | Default | Description |
|---|---|---|
pop_size |
100 |
PESA-II internal population size: the number of random genomes in the seed population and of children bred per generation. Also sets the archive capacity to min(pop_size, 100). Values below 1 are raised to 1. |
num_gens |
100 |
Number of generations. 0 returns a selection from the seeded archive. |
cross_rate |
0.6 |
Probability that a variation step that does not mutate applies uniform crossover to two random children. Must lie in \([0, 1]\). Shi et al.'s value. |
mut_rate |
0.4 |
Probability that a variation step mutates one random child, on top of the mutation every child receives when it is copied from the archive. A step does nothing with probability \(\max(0, 1 - \texttt{cross\_rate} - \texttt{mut\_rate})\). Must lie in \([0, 1]\). Shi et al.'s value. |
rand_networks |
3 |
mocd_d only. Number of Erdős–Rényi control graphs; each one costs a full evolutionary run with the same budget. The module README attributes the value 3 to Shi §3.2, which uses three control fronts for the artificial networks (and one for the timed real-network runs). |
The archive capacity (epsize in PESA-II) is not a parameter: it is derived
as min(pop_size, 100) with a floor of 1. The module README states that the
cap of 100 encodes the largest archive in Shi's Table 1, and that the archive
of 200 used in the paper's §4.1.2 cannot be reached through the public API.
The number of grid divisions per objective is fixed at 10.
The cross_rate and mut_rate defaults are the paper's; pop_size and
num_gens are per graph in the paper (below).
The paper's settings¶
Shi et al. (2012) compare MOCD-Q and MOCD-D with other detectors on ten real
networks, with the per-graph settings of their Table 1 (§4.2.2), reproduced
below together with the node and edge counts the table gives. Every run uses
\(p_c = 0.6\) and \(p_m = 0.4\), and on these networks the Max–Min Distance rule
uses one random control front (§4.2.2), that is rand_networks=1. The archive
size ep equals min(ip, 100) in every column, the rule pymocd uses to
derive it, so pop_size = ip and num_gens = gen reproduce each column.
| Network | Nodes | Edges | ip (pop_size) |
ep |
gen (num_gens) |
|---|---|---|---|---|---|
| Karate (P1) | 34 | 78 | 50 | 50 | 50 |
| Lesmis (P2) | 77 | 254 | 50 | 50 | 50 |
| Polbooks (P3) | 105 | 441 | 50 | 50 | 100 |
| Adjnoun (P4) | 112 | 425 | 50 | 50 | 100 |
| Football (P5) | 115 | 613 | 50 | 50 | 100 |
| Celegansneural (P6) | 297 | 2,345 | 100 | 100 | 100 |
| Celegansmetabolic (P7) | 453 | 2,025 | 100 | 100 | 100 |
| Netscience (P8) | 1,589 | 2,742 | 200 | 100 | 200 |
| Power (P9) | 4,941 | 6,594 | 300 | 100 | 300 |
| Hepth (P10) | 8,361 | 15,751 | 400 | 100 | 400 |
The table spells P6 "Celegansnearal". For example, the paper's MOCD-D run on
Netscience corresponds to
pymocd.mocd_d(G, pop_size=200, num_gens=200, cross_rate=0.6, mut_rate=0.4, rand_networks=1).
The paper's other experiments give their budgets in the text: ip = ep =
100 with 800 generations for the hierarchical network (§4.1.1), 200 and 500
for the overlapping network (§4.1.2), and 100 and 200 for the four-group
artificial networks (§4.2.1).
Degenerate inputs¶
The module README records the first two behaviors below as known issues that are left unfixed on purpose. Every row was checked against the current package.
| Input | Result |
|---|---|
| A graph with no edges (including an empty or single-node graph) | mocd_q raises RuntimeError("MOCD-Q produced an empty front"). mocd_d panics with "Real Pareto front is empty."; in Python this is pyo3_runtime.PanicException, which derives from BaseException, so except Exception does not catch it. |
rand_networks=0 |
There are no control fronts, every member gets the same score, and mocd_d returns the first member of the archive: an arbitrary partition, not a selected one. |
cross_rate or mut_rate outside \([0, 1]\) |
Both functions panic (pyo3_runtime.PanicException, message p=… is outside range [0.0, 1.0]). |
num_gens=0 |
The seeded archive is returned as the front, and the selection rule is applied to it. |
Complexity¶
The module README states that both functions run single-threaded, with no
parallelism anywhere in the module, so the runtime of this baseline tracks the
published single-threaded method rather than the repository's optimizations.
It also states that mocd_d costs rand_networks + 1 full searches: the real
one plus one per control graph, each with the same pop_size and num_gens.
Below, \(n\) and \(m\) are the numbers of nodes and edges, \(P\) is pop_size,
\(E = \min(P, 100)\) is the archive capacity, \(G\) is num_gens and \(r\) is
rand_networks. The hyper-grid has a fixed 100 cells. File names are relative
to src/core/algorithms/mocd/ unless given in full.
Time of this implementation. One generation of evolutionary_phase
(pesa2/engine.rs) breeds and evaluates \(P\) children. Breeding them
(breed) copies \(P\) archive members, each found by one draw
(select_member, pesa2/grid.rs) that scans the archive, and runs at most
\(\lceil P/2 \rceil\) crossovers over all \(n\) genes; mutations
(operators.rs) redraw at most \(n/2\) genes each. One child costs
\(O(n + E)\). Evaluating it
(evaluate, pesa2/solution.rs) decodes the genome with a union-find over
the \(n\) links (decode, locus.rs) and computes both objectives by visiting
every node and both directions of every edge (calculate_objectives,
objectives.rs), so it costs \(O(n + m)\). Offering each child to the archive
(insert_nondominated, pesa2/archive.rs) scans at most \(E + P\) members,
and truncate removes at most \(P\) members, rebuilding the grid and rescanning
the archive after each removal; together these cost \(O(P(E + P))\) per
generation and never read the graph. Seeding the archive costs about one
generation, and indexing the graph (NodeIndex::build, locus.rs) costs
\(O(n + m)\) once. Since \(E \le P\), one search costs
mocd_q runs one search, converts every front member to a node-to-community
map (generate_pareto_front, api.rs), which costs \(O(E\,n)\), and returns
the member of maximum \(Q\) (mocd_q_fn, src/api/detectors.rs). mocd_d runs
\(r + 1\) searches. Drawing each Erdős–Rényi control graph (null_model.rs)
takes an expected \(O(m)\) edge draws on a sparse graph, and the selection rule
(min_max_selection, selection.rs) compares every real front member with
every control-front member, \(O(r E^2)\), so mocd_d takes about \(r + 1\) times
as long as mocd_q.
Memory of this implementation. Every child and every archive member holds
an \(n\)-gene genome (Vec<usize>, 8 bytes per gene) and, once evaluated, an
\(n\)-entry label vector (Vec<i32>, 4 bytes per node): about \(12n\) bytes. At
most \(E + P\) of them are alive at once, next to the \(O(n + m)\) graph and node
index, so one search needs \(O(P\,n + m)\) memory, and the front conversion of
mocd_q another \(O(E\,n)\). mocd_d also holds all \(r\) control graphs and the
label vectors of \(r + 1\) fronts, \(O(r\,(n + m + E\,n))\) more.
What the paper states. Shi et al. (2012, §4.3) count the evaluation of one
individual as \(O(m + n)\) (\(O(m)\) for the objectives, \(O(n)\) for the decode),
the PESA-II process as \(O(g s^2)\), citing Corne et al. (2001), and the whole
method as \(O(g s^2 (m + n))\), "which grows linearly with the scale of the
network", where \(g\) is the number of generations and \(s\) is both ipsize and
epsize. They give Max Q as \(O(1)\) and Max–Min Distance as
\(O(g s^2 (m + n))\), and report that MOCD-D, with one control front, takes
nearly twice as long as MOCD-Q (§4.2.2). This implementation has the paper's per-individual cost, except that
its decode is a union-find with path halving and no union by rank instead of
the paper's linear-time backtracking (§3.1.3). Its overall bound is tighter
than the paper's, because the archive work and the graph work add rather than
multiply: the archive operations compare objective pairs and grid cells and
never read the graph. Both bounds are linear in \(n + m\) at fixed \(P\) and \(G\).
The paper states no memory bound.
Measured. The bound was checked on jackal (48 cores, shared; Python
3.10.12; pymocd 6.1.0 from PyPI). Each call ran in a fresh process on the LFR
graph
pymocd.lfr_graph(n, 2.5, 1.5, 0.3, average_degree=10, max_degree=50, min_community=20, max_community=100, seed=1),
and each time below is the median of three calls. Memory is the rise of the
process's peak resident set (ru_maxrss) over its resident set just before
the call, in MB of \(10^6\) bytes.
mocd_q sweep |
Wall time | Log–log slope |
|---|---|---|
| \(n\) = 1,000 → 32,000 (\(m\) = 4,887 → 160,335), defaults | 0.380 s → 13.5 s | 1.02 against \(n + m\) |
pop_size = 25 → 800, \(n\) = 2,000, num_gens=100 |
0.189 s → 6.38 s | 1.03 |
num_gens = 25 → 400, \(n\) = 2,000, pop_size=100 |
0.189 s → 3.28 s | 1.03 |
Across the \(n\) sweep, the time per bred and evaluated child stayed between 6.2
and 6.9 ns per unit of \(n + m\). The time per unit of pop_size stayed between
7.4 and 8.4 ms up to pop_size=800, so the \(P^2\) archive term did not show at
these sizes. A num_gens=0 call, which converts the graph, seeds the archive
and converts the front, took under 2% of the time of the default run at every
\(n\). At \(n\) = 2,000, mocd_d took 1.88, 4.00 and 7.16 times as long as
mocd_q with rand_networks = 1, 3 and 6, and at \(n\) = 8,000 it took 4.26
times as long with the default 3. Process CPU time matched wall time to within
2.4% in every call, as expected of a single-threaded engine. Peak memory grew
linearly with \(n\), from 7.8 MB at \(n\) = 4,000 to 60.9 MB at \(n\) = 32,000 (1.9
to 2.0 kB per node at pop_size=100; below \(n\) = 4,000 the rise was as small
as its run-to-run spread), and at \(n\) = 2,000 each unit of
pop_size between 200 and 800 added 11.4 bytes per node, close to the 12
bytes per node of one genome and its labels.
When to use it¶
- As a published baseline. Shi-MOCD is one of the re-implemented baselines (ports of other authors' published methods), and the two functions implement Shi's two selection rules on the same search. For new analyses, start from Choosing a detector.
- To follow the paper, pass its budget. The rates default to Shi's; the population and the number of generations are per graph in the paper (see the note at the top of this page).
- MOCD-Q or MOCD-D. MOCD-Q needs one search and selects by modularity.
MOCD-D selects by distance from a random-graph reference rather than by
modularity, but costs
rand_networks + 1searches, and the module README reports it as the noisier of the two rules (see Selecting a partition). - Not when you need the front. Neither function returns it. HP-MOCD optimizes the same objective pair and exposes its front.
- Not when you need reproducible output. There is no seed parameter, so repeated calls can return different partitions. RIMPSO, one of the library's own methods, returns identical output on repeated calls; see Performance and reproducibility.
- Not on graphs without edges, where both functions fail (see Degenerate inputs).
Divergences from the paper¶
The port was written from the paper text, not from the authors' code (see the Original code row at the top of this page), and the module README lists the following as deliberate readings of, or departures from, that text:
- Variation operators from the authors' code. The paper describes
mutation only as "randomly select some genes and assign them with other
randomly selected adjacent nodes" and calls \(p_m\) "the ratio of mutation".
Up to 6.1.0 the port read \(p_m\) as a per-gene probability, which at the
paper's \(p_m = 0.4\) redraws 40% of every child and keeps the search from
converging. The breeding step now follows the authors' C++ implementation
(see Original code): the number of genes a mutation redraws, the copy and
mutate step, the order of the mutation and crossover steps, uniform
selection over occupied cells and a 10 × 10 grid. On the paper's
four-group benchmark (§4.2.1: 128 nodes, \(z_{\mathrm{in}} +
z_{\mathrm{out}} = 16\);
mocd_qwith \(p_c = 0.6\), \(p_m = 0.4\),pop_size=100,num_gens=200; 50 graphs per point), the mean fraction of vertices identified correctly rose from 0.80 to 0.98 at \(z_{\mathrm{out}} = 4\) and from 0.70 to 0.92 at \(z_{\mathrm{out}} = 5\), where the paper reports 1.0. On the hierarchical network of §4.1.1, the maximum-\(Q\) member rose from \(Q = 0.634\) to \(0.696\) (paper: \(0.706\) with four communities; mean over 10 graphs). - Uniform crossover. Shi names the operator "uniform two-point crossover" but describes it as an independent per-gene choice between the two parents, and the authors' code implements uniform crossover. A gene inherited at its own position is valid in either parent, so genomes stay valid.
- PESA-II's archive, not the authors' archive update. The authors' code rejects a candidate only if a member is better in both objectives and, when the archive is full, lets a candidate replace a member of a more crowded cell. The port keeps the archive of Corne et al. (2001): a candidate is rejected if a member dominates it, and an overfull archive is truncated from its most crowded cell. On the benchmark above the two archives gave the same accuracy.
- Union-find decoding instead of the paper's backtracking. The connected components, and therefore the partition, are the same.
- Erdős–Rényi \(G(n, m)\) null model. MOCD-D's control graphs are uniformly random simple graphs with the same node set and edge count, Shi's "random networks with the same scale" (§3.2). The module README records that a degree-preserving null model was considered and rejected because it is not what the paper describes. All \(n\) nodes, including isolated ones, are inserted into each control graph, so its node count matches the input.
- Full budget for the control fronts. Each control search uses the same
pop_sizeandnum_gensas the real one. The module README explains that an under-evolved random front never reaches the fragmented region, which makes the real front's fragmented extreme look spuriously deviant and leads MOCD-D to the wrong answer. - A separate, single-threaded engine. PESA-II is reimplemented inside the module instead of reusing the repository's shared NSGA-II/NSGA-III code.
- Derived archive size and benchmark rates. The archive size is not exposed (see Parameters), and the Python functions ship the HP-MOCD benchmark rates instead of Shi's.
Example¶
The example runs both selection rules on Zachary's karate club at the defaults and MOCD-Q again at the paper's rates. For each result it prints the number of communities \(k\), the two objectives computed from the partition with NetworkX, and \(1 - \mathrm{intra} - \mathrm{inter}\) next to NetworkX's own modularity, which should agree (Eq. 3.7). Edge weights are ignored, matching pymocd.
import networkx as nx
import pymocd
G = nx.karate_club_graph()
m = G.number_of_edges()
def describe(name, partition):
groups = {}
for node, community in partition.items():
groups.setdefault(community, set()).add(node)
intra = 1 - sum(G.subgraph(c).number_of_edges() for c in groups.values()) / m
inter = sum((sum(d for _, d in G.degree(c)) / (2 * m)) ** 2 for c in groups.values())
q = nx.community.modularity(G, groups.values(), weight=None)
print(f"{name:<24} k = {len(groups)} intra = {intra:.4f} inter = {inter:.4f}"
f" 1 - intra - inter = {1 - intra - inter:.4f} Q = {q:.4f}")
describe("mocd_q (defaults)", pymocd.mocd_q(G))
describe("mocd_d (defaults)", pymocd.mocd_d(G))
# Shi et al. (2012) rates; the paper's Table 1 also sets pop_size and num_gens per graph.
describe("mocd_q (pc=0.6, pm=0.4)", pymocd.mocd_q(G, cross_rate=0.6, mut_rate=0.4))
mocd_q (defaults) k = 4 intra = 0.2692 inter = 0.3110 1 - intra - inter = 0.4198 Q = 0.4198
mocd_d (defaults) k = 3 intra = 0.1795 inter = 0.4185 1 - intra - inter = 0.4020 Q = 0.4020
mocd_q (pc=0.6, pm=0.4) k = 4 intra = 0.2692 inter = 0.3110 1 - intra - inter = 0.4198 Q = 0.4198
MOCD-D draws new control graphs on every call. The next block repeats it five times and prints the number of communities of each result; the counts need not agree.
Reference¶
C. Shi, Z. Yan, Y. Cai and B. Wu, "Multi-objective community detection in complex networks", Applied Soft Computing, vol. 12, no. 2, pp. 850–859, 2012. doi:10.1016/j.asoc.2011.10.005
@article{Shi_2012,
title={Multi-objective community detection in complex networks},
volume={12},
ISSN={1568-4946},
url={http://dx.doi.org/10.1016/j.asoc.2011.10.005},
DOI={10.1016/j.asoc.2011.10.005},
number={2},
journal={Applied Soft Computing},
publisher={Elsevier BV},
author={Shi, Chuan and Yan, Zhenyu and Cai, Yanan and Wu, Bin},
year={2012},
month=Feb,
pages={850–859}
}
Other works cited on this page:
- D. W. Corne, N. R. Jerram, J. D. Knowles and M. J. Oates, "PESA-II: Region-based selection in evolutionary multiobjective optimization", in Proc. Genetic and Evolutionary Computation Conference (GECCO 2001), pp. 283–290, 2001.
- Y. Park and M. Song, "A genetic algorithm for clustering problems", in Proc. 3rd Annual Conference on Genetic Programming, pp. 568–575, 1998.
- M. E. J. Newman and M. Girvan, "Finding and evaluating community structure in networks", Physical Review E, vol. 69, no. 2, 026113, 2004. doi:10.1103/PhysRevE.69.026113
- G. O. Santos, L. S. Vieira, G. Rossetti, C. H. G. Ferreira and G. J. P. Moreira, "A high-performance evolutionary multiobjective community detection algorithm", Social Network Analysis and Mining, vol. 15, no. 1, 110, 2025. doi:10.1007/s13278-025-01519-7. Preprint: arXiv:2506.01752.
See also¶
- API:
mocd_qandmocd_d - HP-MOCD, which optimizes the same objective pair with NSGA-II and exposes its front
- Algorithms overview
- Core concepts, Choosing a detector and Working with Pareto fronts
- Input graphs and Performance and reproducibility
- Gallery: Detector tour and Comparing detectors
- The module README, with the implementation notes, known issues and file
layout:
src/core/algorithms/mocd/README.md