NSGA-III-CCM¶
Re-implemented baseline Multi-objective
NSGA-III-CCM, from Shaik, Ravi and Deb (2021)
(doi:10.1007/s42979-020-00382-x),
maximizes three objectives together: Community Score and Community Fitness,
the objective pair of MOGA-Net, and Newman's modularity \(Q\).
Its three letters name those three objectives. The search is the
reference-point-based NSGA-III of Deb and Jain (2014)
(doi:10.1109/TEVC.2013.2281535),
run over a locus-based genome in which every genome decodes to a valid
partition, so no repair step is needed. ccm returns the member of the final rank-1 Pareto
front with the highest modularity, and ccm_fronts
returns the whole front. The same paper also defines
NSGA-III-KRM, which keeps the engine and the modularity objective but
replaces Community Score and Community Fitness with kernel k-means and ratio
cut.
| Entry points | ccm, ccm_fronts |
| Objectives | Community Score \(\mathrm{CS}\), Community Fitness \(\mathrm{CF}\) and modularity \(Q\), all three maximized |
| Search | NSGA-III (Deb and Jain, 2014) over a locus-based genome, in a private, sequential engine |
| Selection rule | Highest modularity \(Q\) on the rank-1 front |
| Front accessor | ccm_fronts, a list[dict[int, int]] |
| Deterministic | No. The engine draws from an OS-seeded generator and the API has no seed parameter (module README) |
| Original code | None released (module README) |
| Paper | Shaik, Ravi and Deb (2021), SN Computer Science 2(1):1 — doi:10.1007/s42979-020-00382-x |
Objectives¶
Community Score, Community Fitness and the genome come from Pizzuti (2012), doi:10.1109/TEVC.2011.2161090, the paper behind MOGA-Net. Let \(S\) range over the communities of a partition, \(|S|\) be the number of nodes in \(S\), \(k_{\mathrm{in}}(i)\) the number of neighbors of node \(i\) that lie in \(i\)'s own community, \(\deg(i)\) the degree of \(i\), \(m\) the number of edges, \(l_c\) the number of edges inside community \(c\) (each counted once) and \(d_c\) the sum of the degrees of the nodes of \(c\).
- \(v_S\) is twice the number of edges inside \(S\).
- \(M(S)\) is the plain mean of \(\mu_i^{\,r}\), with no outer \(1/r\) root. This follows Pizzuti's definition. At the default \(r = 1\) the two readings coincide.
- A node of degree 0 contributes 0 to \(\mathrm{CF}\).
- \(\mathrm{CF}\) is largest when no edge leaves a community, which on a connected graph happens only for the single-community partition, so maximizing it favors partitions with few edges between communities.
- \(Q\) is the modularity of Newman and Girvan (2004), doi:10.1103/PhysRevE.69.026113.
- The exponents \(r\) and \(\alpha\) are the parameters
randalpha. The module README states that the paper fixes \(r = 1\), which weights all nodes equally, and \(\alpha = 1\), following MOGA-Net.
The paper maximizes all three objectives. The engine minimizes, so the vector it receives is \((-\mathrm{CS}, -\mathrm{CF}, -Q)\), and every rank, dominance test and reference-point distance is computed in that sign-flipped space.
Search¶
Representation. The genome is Pizzuti's locus-based encoding. It has one
cell per node, in the graph's stable node order. Cell \(u\) holds either \(u\)
itself or the position of one of \(u\)'s neighbors. Decoding is a union-find
over the cells: each connected component is one community. Every genome that
an operator can produce is valid, so the module has no repair step.
Initialization draws each cell uniformly from \(\{u\} \cup N(u)\), where \(N(u)\)
is the set of neighbors of \(u\). Isolated nodes decode to singletons and are
reported as community -1.
Loop. With pop_size \(= N\):
- Draw \(N\) random genomes, decode and evaluate them, and sort them into non-dominated fronts.
- Build the structured reference points of Das and Dennis (1998),
doi:10.1137/S1052623496307510,
for \(M = 3\) objectives and \(p\) =
divisionssubdivisions: \(H = \binom{M + p - 1}{p}\) points, which is 91 at the default \(p = 12\). - For each of
num_gensgenerations:- Breed \(N\) offspring. Two parents are drawn uniformly at random, with no
fitness-based mating selection. With probability
cross_ratethe child is their uniform crossover; otherwise it is a clone of one of the two, chosen with probability 1/2 each. Every gene of the child is then independently resampled from \(\{u\} \cup N(u)\) with probabilitymut_rate. - Run NSGA-III environmental selection over the \(2N\) parents and offspring (Deb and Jain, 2014, Algorithms 1 to 4). Whole non-dominated fronts are kept while they fit. The remaining places go to members of the first front that does not fit: the kept members and that front together (\(S_t\)) are normalized onto a hyperplane, each is associated with its nearest reference line, and niching picks the survivors from that front. The least crowded reference point wins, with ties broken uniformly at random; an empty niche takes its closest member and a non-empty one takes a random member.
- Apply the paper's two customizations (Sec. 4), in this order: any individual whose canonical labeling repeats one already seen is replaced by a fresh random genome, and then so is any individual that puts every node in one community. Replacements are evaluated at once.
- Re-rank the population, because the customizations replaced members after selection had ranked them.
- Breed \(N\) offspring. Two parents are drawn uniformly at random, with no
fitness-based mating selection. With probability
- Sort the final population once more. Its rank-1 members are the result.
The engine is that of Deb and Jain (2014), doi:10.1109/TEVC.2013.2281535, with the fast non-dominated sort of Deb et al. (2002), doi:10.1109/4235.996017.
Randomness¶
NSGA-III-CCM is not deterministic: two consecutive calls can return different results. The engine draws from the thread-local generator, which is seeded from the operating system, and no seed parameter is exposed, so the initial population, both parent picks, every crossover, clone and mutation draw, the replacement genomes of the two customizations and the random tie-breaks inside niching change from run to run. The search is sequential, so the thread count is not a further source of variation.
On Zachary's karate club the runs do not spread. ccm was run
30 times with its defaults on nx.karate_club_graph() (pymocd 6.1.0 from
PyPI, Python 3.10, on a shared Linux server with 48 hardware threads), and
all 30 calls returned the same 4-community partition, with modularity
\(Q = 0.4198\) counted on edges (weight=None). Scored with
ami against the two
karate ground-truth conventions,
its AMI is 0.5667 ± 0.0000 against NetworkX's club attribute and
0.6712 ± 0.0000 against the convention that puts node 8 with the Officer
faction; its nmi is 0.5878 and 0.6873. The module README
attributes the absence of spread to that graph being degenerate, and reports
a rank-1 front of roughly 110 ± 30 members on a 250-node LFR graph.
See Performance and reproducibility
for how to handle nondeterministic detectors in experiments.
Selecting a partition¶
ccm returns the rank-1 member with the highest modularity \(Q\).
The docstring calls this the paper's recommended decision rule when no ground
truth is available.
ccm_fronts returns every rank-1 member as a list of
partitions. The paper reports both the best-NMI and the best-modularity member
of the front (its Tables 1 and 2), and reproducing that protocol needs the
whole candidate set; score each member against the ground truth with
nmi.
Each call runs its own search. Because the search is not deterministic, the
partition returned by ccm need not be a member of the front returned by a
separate ccm_fronts call. To obtain a front and its selected member from the
same run, call ccm_fronts and apply the max-modularity rule yourself, as the
example below does. See
Working with Pareto fronts for more on fronts.
Parameters¶
| Parameter | Default | Description |
|---|---|---|
pop_size |
200 |
Population size \(N\). The module README requires it to stay above the number of reference points, \(H = 91\) at the default divisions; the code does not check this. The README notes that Sec. 8.1 of the paper says 20 for karate, while the paper's Table 3 grid and its Fig. S.22 use 200. |
num_gens |
100 |
Number of generations. The paper's best-reported combination on Zachary's karate club. |
cross_rate |
0.8 |
Probability that a child is the uniform crossover of its two parents rather than a clone of one of them. The paper's best combination. |
mut_rate |
1/68 |
Per-gene probability of resampling a cell from \(\{u\} \cup N(u)\). The paper's best combination on the 34-node karate graph. |
r |
1.0 |
Exponent \(r\) applied to each \(\mu_i\) in \(M(S)\) (there is no outer \(1/r\) root, so \(M(S)\) is not a power mean). Fixed by the paper at \(r = 1\). |
alpha |
1.0 |
Exponent \(\alpha\) of the degree in Community Fitness. Fixed by the paper at \(\alpha = 1\), following MOGA-Net. |
divisions |
12 |
Das and Dennis subdivisions \(p\); \(H = \binom{p + 2}{2}\) reference points for the three objectives, 91 at \(p = 12\). The paper does not state a value; the module README takes 12 from pymoo's convention for three objectives. |
Every parameter after graph is optional, and ccm and
ccm_fronts take the same ones with the same defaults.
The mut_rate default is tuned to a 34-node graph
The default is the constant \(1/68 \approx 0.0147\) (the stub prints it as
0.014705882352941176). It is not rescaled for other graphs. Because
mutation acts on each gene independently, a child of an \(n\)-node graph has
on average \(n \cdot\) mut_rate resampled genes, which is \(0.5\) on karate.
To use another rate, pass mut_rate explicitly.
The paper ties the rates it tries to \(n\) but picks no single rule. Its Table 3 is the sensitivity grid, one row per dataset and shared by both variants: D1 is karate (\(n = 34\)), D2 the dolphins (62), D3 college football (115) and D4 the political books (105). Each row tries 100 generations, crossover rates 0.8, 0.85 and 0.9, and mutation rates \(1/n\), \(2/n\) and \(1/(2n)\). The best NSGA-III-CCM settings are given in the text of Sec. 8.1, not in Table 3: population sizes 20, 200, 450 and 500 and mutation rates \(1/68\), \(1/62\), \(1/230\) and \(2/105\) for D1 to D4, which are \(1/(2n)\), \(1/n\), \(1/(2n)\) and \(2/n\). The same sentence lists only three crossover rates, 0.8, 0.85 and 0.9, for the four datasets, so the crossover rate of each dataset cannot be read from it. The default is the D1 rate. These numbers were read from the arXiv preprint (see Reference).
Internal constants
These are fixed in src/core/algorithms/ccm/nsga3/normalize.rs and are not
exposed: NEAR_ZERO = 1e-10 is the threshold below which a pivot, an
intercept or a fallback scale counts as zero; MIN_INTERCEPT = 1e-6 is the
smallest accepted hyperplane intercept, below which a fallback scaling is
used; and ASF_OFF_AXIS_WEIGHT = 1e-6 is the off-axis weight of the
achievement scalarizing function that picks the extreme points. In
nsga3/niching.rs, a reference direction whose squared norm is below
1e-30 is treated as the origin.
Degenerate inputs¶
The budget arguments are not validated. Every row was checked against the current package.
| Input | Result |
|---|---|
pop_size=0 |
ccm panics with "empty Pareto front"; ccm_fronts returns []. |
cross_rate or mut_rate outside \([0, 1]\) |
Both functions panic with p=… is outside range [0.0, 1.0]. |
| An empty graph | ccm returns {}; ccm_fronts returns pop_size empty dicts. |
| A graph with no edges | ccm maps every node to -1; ccm_fronts returns pop_size copies of that partition. |
A panic reaches Python as pyo3_runtime.PanicException, which derives from
BaseException, so except Exception does not catch it.
Complexity¶
Neither the module README nor the paper states a bound: the arXiv preprint
(arXiv:2005.03181v1) has no complexity
analysis. The bounds below are derived from the source of this
implementation. Write \(N\) for pop_size, \(G\) for num_gens, \(H\) for the
number of reference points (\(\binom{p + 2}{2}\) for divisions \(= p\), so 91 at
the default), and \(n\) and \(m\) for the numbers of nodes and edges.
Time. This implementation takes \(O(N(n + m) + N^2 + NH)\) time per
generation and \(O(G\,[N(n + m) + N^2 + NH])\) per run. With pop_size above
\(H\), as the module README asks, \(NH < N^2\) and the bound per run is
\(O(G\,[N(n + m) + N^2])\). The search runs on one thread. Three steps make up
a generation:
- Evaluation, \(O(N(n + m))\).
new_individual(nsga3/individual.rs) decodes a genome withlocus::decode(locus.rs), a union-find with path compression and union by size, which costs \(O(n\,\alpha(n))\); the inverse Ackermann factor \(\alpha(n)\) is treated as a constant here.evaluate(objectives.rs) then callscommunity_objectivesandmodularity_labels, each of which walks every node's neighbor list once. A generation evaluates its \(N\) offspring and every individual the two customizations replace (apply_customizationsinnsga3/engine.rs), at most \(2N - 1\) more: up to \(N - 1\) duplicates, then up to \(N\) single-community members. Breeding (breed, withlocus::uniform_crossoverandlocus::mutate, which draws once per gene whatevermut_rateis), the canonical labelings the duplicate filter hashes, and the single-community test add \(O(Nn)\). - Non-dominated sorting, \(\Theta(N^2)\).
fast_non_dominated_sort(nsga3/individual.rs) tests every ordered pair of individuals for dominance. It runs on the \(2N\) parents and offspring insideenvironmental_selection(nsga3/survival.rs), and again on the \(N\) survivors after the customizations. - Niching, \(O(NH)\).
niche_select(nsga3/niching.rs) measures the distance from each of the at most \(2N\) members of \(S_t\) to all \(H\) reference lines, and each of its at most \(N\) picks scans all \(H\) niche counts. The normalization innsga3/normalize.rsis linear in the size of \(S_t\) for three objectives.
Outside the loop, locus::neighbor_positions costs \(O(n + m)\), the initial
population \(N\) evaluations and one sort, and api.rs sorts the final
population once more. ccm then computes the modularity of every rank-1
member, at most \(O(N(n + m))\), and converts one partition in \(O(n)\);
ccm_fronts converts every rank-1 member, up to \(O(Nn)\).
Memory. \(O(Nn + m + N^2 + H)\). Each individual stores its genome (\(n\)
usize) and its labels (\(n\) i32), 12 bytes per node on a 64-bit build, and
the \(2N\) parents and offspring are alive together during breeding and
environmental selection: about \(24Nn\) bytes. The neighbor table holds the
\(2m\) neighbor positions. The domination lists of the sort hold at most one
entry per pair of individuals, under \(2N^2\) for the \(2N\)-member pool, and the
duplicate filter's hash set holds up to \(N\) canonical labelings (\(4Nn\) bytes)
after the pool is cut back to \(N\). Nothing accumulates across generations.
Measured scaling. ccm was timed on a shared Linux server with 48
hardware threads (pymocd 6.1.0 from PyPI, Python 3.10), three runs per
setting, each run in a fresh process. The table gives the median wall time of
the ccm call, the ratio to the previous row, and the added peak memory: the
median peak resident memory of the three runs minus the median size of the
process just before the call. The LFR rows use
pymocd.lfr_graph(n, 2.5, 1.5, 0.3, average_degree=10, max_degree=50,
min_community=20, max_community=100, seed=1); every parameter not named in
the table is at its default.
| Varied | Graph | Setting | Median time | Ratio | Added peak memory |
|---|---|---|---|---|---|
| \(n\) | LFR, \(N = 200\), \(G = 100\) | \(n\) = 500 (\(m\) = 2,393) | 0.884 s | 2.6 MiB | |
| \(n\) = 1,000 (\(m\) = 4,887) | 1.54 s | 1.75 | 5.2 MiB | ||
| \(n\) = 2,000 (\(m\) = 9,795) | 3.37 s | 2.18 | 10.5 MiB | ||
| \(n\) = 4,000 (\(m\) = 19,667) | 6.31 s | 1.87 | 20.6 MiB | ||
| \(n\) = 8,000 (\(m\) = 40,227) | 13.7 s | 2.17 | 41.4 MiB | ||
| \(G\) | LFR \(n\) = 1,000, \(N = 200\) | \(G\) = 25 | 0.433 s | 5.2 MiB | |
| \(G\) = 50 | 0.907 s | 2.09 | 4.9 MiB | ||
| \(G\) = 100 | 1.87 s | 2.06 | 5.6 MiB | ||
| \(G\) = 200 | 3.50 s | 1.87 | 4.9 MiB | ||
| \(N\) | LFR \(n\) = 2,000, \(G = 100\) | \(N\) = 100 | 1.68 s | 6.0 MiB | |
| \(N\) = 200 | 3.46 s | 2.07 | 10.1 MiB | ||
| \(N\) = 400 | 7.36 s | 2.13 | 20.6 MiB | ||
| \(N\) = 800 | 15.0 s | 2.04 | 42.8 MiB | ||
| \(N\) | karate, \(G = 100\) | \(N\) = 100 | 0.063 s | 0.8 MiB | |
| \(N\) = 200 | 0.179 s | 2.84 | 1.5 MiB | ||
| \(N\) = 400 | 0.734 s | 4.10 | 3.0 MiB | ||
| \(N\) = 800 | 2.29 s | 3.12 | 9.8 MiB | ||
| \(N\) = 1,600 | 8.28 s | 3.62 | 33.4 MiB |
The measurements agree with the bound. Doubling \(n\), which also doubles \(m\) on these graphs, multiplied the time by 1.75 to 2.18; the time per unit of \(G\,N\,(n + m)\) stayed between 13.1 and 15.3 ns with no trend from \(n\) = 500 to 8,000. Memory grew by about 5.3 KiB per node at \(N = 200\), close to the \(24N = 4{,}800\) bytes (4.7 KiB) per node the population accounts for. Time was linear in \(G\), and memory did not depend on it. At \(n\) = 2,000, where \(n + m\) is about 11,800, doubling \(N\) multiplied the time by 2.04 to 2.13 and the memory by 1.68 to 2.08: the \(N(n + m)\) term dominates and the \(N^2\) term is not visible up to \(N = 800\). On karate, where \(n + m = 112\), doubling \(N\) multiplied the time by 2.8 to 4.1, the \(N^2\) term of non-dominated sorting, and memory grew faster than linearly from \(N = 400\) on, as the \(O(N^2)\) domination lists predict. The server was shared, so repeated timings vary: the setting \(n\) = 1,000, \(N = 200\), \(G = 100\) took 1.54 s in the \(n\) sweep and 1.87 s in the \(G\) sweep, run at a different time.
When to use it¶
- Reproducing or comparing with Shaik, Ravi and Deb (2021). The
num_gens,cross_rateandmut_ratedefaults are the paper's best-reported combination on Zachary's karate club, andccm_frontsexposes the front for the paper's best-NMI and best-modularity protocol. - Comparing with MOGA-Net. CCM keeps MOGA-Net's two objectives, adds
modularity as a third, and replaces MOGA-Net's NSGA-II-ranked genetic
algorithm with NSGA-III. Both detectors
return the max-modularity member of their rank-1 front. Note that
moga_netdefaults tor=2.0whileccmdefaults tor=1.0. - As a baseline, not as a default. NSGA-III-CCM is one of the library's re-implemented baselines, which exist for papers and benchmarks. For general use, start from Choosing a detector; RIMPSO is the library's recommended default.
Divergences from the paper¶
The module README lists these deliberate differences:
- The NSGA-III engine is private to the module and sequential, instead of a shared engine, and uses no thread parallelism, so that the baseline's cost and behavior track the paper's rather than this library's infrastructure.
- Mating draws two parents uniformly at random, with no fitness-based selection. The README identifies this as pymoo's NSGA-III for unconstrained problems, which the paper adapted; there is no binary tournament.
- \(M(S)\) is the plain mean of \(\mu_i^{\,r}\) with no outer \(1/r\) root, following Pizzuti's definition. This is not a dropped operation.
pop_size = 200resolves a contradiction in the paper anddivisions = 12an omission; see Parameters.- Modularity is computed inside the module rather than by the library's shared modularity function, so the inner loop never builds a partition object. A test asserts that the two agree to within \(10^{-12}\).
- The public functions return normalized partitions in which isolated nodes
are community
-1; the paper leaves them as singletons. - The duplicate filter compares canonical labelings, so it also removes partitions that differ only by a permutation of community labels.
The code has one more difference that the CCM README does not list but the
KRM README records for the same engine design (see
NSGA-III-KRM): the ideal point and the
extreme points are recomputed from \(S_t\) at every normalization
(nsga3/normalize.rs), whereas pymoo carries them across generations.
Example¶
The block below runs ccm and ccm_fronts
on Zachary's karate club, then applies the max-modularity rule to the front by
hand. The two calls are independent runs. The modularity is computed with
weight=None because nx.karate_club_graph() carries edge weights, while the
modularity inside CCM counts edges.
import networkx as nx
import pymocd
G = nx.karate_club_graph()
part = pymocd.ccm(G) # max-Q member of one run's rank-1 front
print("ccm:", len(set(part.values())), "communities")
front = pymocd.ccm_fronts(G) # the rank-1 front of a second, independent run
print("ccm_fronts:", len(front), "partitions on the front")
def communities(partition):
groups = {}
for node, label in partition.items():
groups.setdefault(label, set()).add(node)
return list(groups.values())
# ccm's decision rule, applied to this front
scored = [
(nx.community.modularity(G, communities(p), weight=None), p) for p in front
]
q_best, best = max(scored, key=lambda item: item[0])
sizes = sorted(len(set(p.values())) for p in front)
print(f"communities per front member: {sizes[0]} to {sizes[-1]}")
print(f"max-Q member: {len(set(best.values()))} communities, Q = {q_best:.4f}")
ccm: 4 communities
ccm_fronts: 42 partitions on the front
communities per front member: 2 to 10
max-Q member: 4 communities, Q = 0.4198
The first line is the number of communities ccm selected. The last line is
the member the same rule picks from the second run's front; the range above it
shows how far the front spreads in the number of communities.
Reference¶
Tanveerulhuq Shaik, Vadlamani Ravi and Kalyanmoy Deb. "Evolutionary Multi-Objective Optimization Algorithm for Community Detection in Complex Social Networks." SN Computer Science 2(1), article 1, 2021 (published online 7 November 2020). doi:10.1007/s42979-020-00382-x.
The volume, issue, article number and dates follow the Crossref record of the DOI.
The published article is not open access. The paper's parameter grid and best
settings quoted in the mut_rate note, and the absence of a
complexity bound, were read from the open preprint instead: Shaik Tanveer ul
Huq, Vadlamani Ravi and Kalyanmoy Deb, arXiv:2005.03181v1, 7 May 2020,
arXiv:2005.03181v1. There, Sec. 8.1 is
on p. 10 and Table 3 on p. 13. The "Sec. 4" cited for the two customizations
under Search follows the module README; in the preprint, Sec. 4
lists the duplicate filter among the contributions and Sec. 6.5 describes
both customizations.
@article{Shaik_2020,
title = {Evolutionary Multi-Objective Optimization Algorithm for Community Detection in Complex Social Networks},
volume = {2},
ISSN = {2661-8907},
url = {http://dx.doi.org/10.1007/s42979-020-00382-x},
DOI = {10.1007/s42979-020-00382-x},
number = {1},
journal = {SN Computer Science},
publisher = {Springer Science and Business Media LLC},
author = {Shaik, Tanveerulhuq and Ravi, Vadlamani and Deb, Kalyanmoy},
year = {2020},
month = Nov,
pages = {1}
}
The entry is the publisher's record from doi.org, which dates the article by
its online publication (2020); the journal issue is dated 2021. The pages
field is added: it holds the article number 1 from Crossref, which the doi.org
record leaves out.
Works the implementation builds on:
- Clara Pizzuti. "A Multiobjective Genetic Algorithm to Find Communities in Complex Networks." IEEE Transactions on Evolutionary Computation 16(3): 418–430, 2012. doi:10.1109/TEVC.2011.2161090.
- Kalyanmoy Deb and Himanshu Jain. "An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based Nondominated Sorting Approach, Part I: Solving Problems With Box Constraints." IEEE Transactions on Evolutionary Computation 18(4): 577–601, 2014. doi:10.1109/TEVC.2013.2281535.
- Kalyanmoy Deb, Amrit Pratap, Sameer Agarwal and T. Meyarivan. "A fast and elitist multiobjective genetic algorithm: NSGA-II." IEEE Transactions on Evolutionary Computation 6(2): 182–197, 2002. doi:10.1109/4235.996017.
- Indraneel Das and J. E. Dennis. "Normal-Boundary Intersection: A New Method for Generating the Pareto Surface in Nonlinear Multicriteria Optimization Problems." SIAM Journal on Optimization 8(3): 631–657, 1998. doi:10.1137/S1052623496307510.
- M. E. J. Newman and M. Girvan. "Finding and evaluating community structure in networks." Physical Review E 69(2): 026113, 2004. doi:10.1103/PhysRevE.69.026113.
See also¶
- NSGA-III-KRM: the same paper's second variant, with kernel k-means, ratio cut and modularity.
- MOGA-Net: Community Score and Community Fitness under NSGA-II ranking with elitist generational replacement.
- API reference:
ccm,ccm_fronts,nmi. - Guide: Core concepts, Working with Pareto fronts, Evaluating partitions, Choosing a detector.
- Gallery: Every detector on one graph, which
runs
ccmalongside the other detectors, and Choosing a partition from a front. - Algorithms overview and the module README, which carries the full derivation and file map.