NSGA-III-KRM¶
Re-implemented baseline Multi-objective
NSGA-III-KRM, from Shaik, Ravi and Deb (2021)
(doi:10.1007/s42979-020-00382-x),
optimizes three objectives together: kernel k-means (KKM) and ratio cut (RC),
both minimized, and Newman's modularity \(Q\), maximized. Its three letters name
those three objectives. KKM is minimized by splitting the graph into
singletons and RC by merging it into one community, so the two pull in
opposite directions and are optimized against each other. 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.
krm returns the member of the final rank-1 Pareto front with
the highest modularity, and krm_fronts returns the whole
front. The same paper also defines NSGA-III-CCM, which keeps the
engine and the modularity objective but optimizes Community Score and
Community Fitness instead of KKM and RC.
| Entry points | krm, krm_fronts |
| Objectives | Kernel k-means \(\mathrm{KKM}\) and ratio cut \(\mathrm{RC}\), minimized; modularity \(Q\), 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 | krm_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; the module README reports that the authors' sites and GitHub were searched |
| Paper | Shaik, Ravi and Deb (2021), SN Computer Science 2(1):1 — doi:10.1007/s42979-020-00382-x |
Objectives¶
The module README states that the \((\mathrm{KKM}, \mathrm{RC})\) pair the paper adopts is the objective pair of MODPSO. The MMCoMO module README attributes the exact formulation of the same pair to Gong, Cai, Chen and Ma (2014), "Complex Network Clustering by Multiobjective Discrete Particle Swarm Optimization Based on Decomposition", doi:10.1109/TEVC.2013.2260862; MMCoMO optimizes the same two formulas with a different engine and without modularity as a third objective.
Let the partition be \(C = \{V_1, \dots, V_k\}\) of the \(n\) nodes of the graph, with \(A\) the adjacency matrix, \(\bar V_i\) the complement of \(V_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\). Define
\(L(V_i, V_i)\) counts each internal edge from both of its endpoints, so it is twice the number of edges inside \(V_i\), and \(L(V_i, \bar V_i) = \sum_{v \in V_i} \deg(v) - L(V_i, V_i)\) is the cut of \(V_i\). The three objectives are
Denser communities lower KKM, and fewer edges between communities lower RC. \(Q\) is the modularity of Newman and Girvan (2004), doi:10.1103/PhysRevE.69.026113. Inside the search, \(n\) counts every node of the graph; an isolated node is a singleton community there.
Opposite degenerate extremes. KKM is minimized by fragmentation: with every node in its own community, \(k = n\) and every \(L(V_i, V_i) = 0\), so \(\mathrm{KKM} = 0\). RC is minimized by coarsening: a single community has no cut, so \(\mathrm{RC} = 0\). Neither objective alone rules out its own degenerate extreme, which is why they are optimized against each other. A unit test in the module pins both extremes, and a two-community split lying between them, on a graph of two triangles joined by one edge; the example recomputes those values.
The engine minimizes, so the vector each individual stores is \((\mathrm{KKM}, \mathrm{RC}, -Q)\), and every rank, dominance test and reference-point distance is computed in that space. The sign of \(Q\) is undone only when the public function reads the selected member's modularity.
Search¶
Representation. The genome is the locus-based encoding of Pizzuti (2012),
doi:10.1109/TEVC.2011.2161090,
which the module README traces to Park and Song (1998). 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 that joins
\(u\) with the cell's value; each connected component is one community. Every
genome that an operator can produce is valid, so there is no repair step
anywhere in the module. Initialization draws each cell uniformly from
\(\{u\} \cup N(u)\), where \(N(u)\) is the set of neighbors of \(u\). Isolated nodes
have no neighbor to point at, 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, per-gene 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 translated by the ideal point and scaled by the hyperplane intercepts, each is associated with its nearest reference line by perpendicular distance, and niching picks the survivors from that front. The least crowded reference point wins, with ties among equally crowded points 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, 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 decoded and evaluated at once.
- Re-rank the population. The customizations overwrite individuals after selection has ranked them: a replacement starts unranked, and the other members' ranks were computed against the individuals it displaced.
- Breed \(N\) offspring. Two parents are drawn uniformly at random, with no
fitness-based mating selection. With probability
- Rank 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-KRM is not deterministic: two consecutive calls can return different results. The engine and the niching step draw from the thread-local generator, which is seeded from the operating system, and no seed is exposed. Every source of variation is a random draw: the initial population, both parent picks, the crossover coin flips, the mutation resamples, the replacement genomes of the two customizations, and the two random tie-breaks inside niching. The search is sequential, so the thread count is not a further source of variation.
To show the spread, krm was run 30 times with its defaults on
nx.karate_club_graph() (pymocd 6.1.0 from PyPI, Python 3.10, on a shared
48-core Linux server). It returned 4 communities in 25 runs and 5 in the other
5. Scored with ami against the two
karate ground-truth conventions,
the AMI was 0.545 ± 0.042 (mean ± sample standard deviation; range 0.431 to
0.578) against NetworkX's club attribute, and 0.649 ± 0.045 against the
convention that puts node 8 with the Officer faction. A second batch of 300
runs gave 0.533 ± 0.045 and 0.638 ± 0.046, with a mean of 4.19 communities.
See Performance and reproducibility
for how to handle nondeterministic detectors in experiments.
Selecting a partition¶
krm 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.
krm_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 krm need not be a member of the front returned by a
separate krm_fronts call. To obtain a front and its selected member from the
same run, call krm_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 |
100 |
Population size \(N\). The paper's best combination on its dataset D1, Zachary's karate club. The module README requires it to stay at or above the number of reference points, \(H = 91\) at the default divisions; the code does not check this. |
num_gens |
100 |
Number of generations. From the same combination. |
cross_rate |
0.8 |
Probability that a child is the uniform crossover of its two parents rather than a clone of one of them. From the same combination. |
mut_rate |
1/34 |
Per-gene probability of resampling a cell from \(\{u\} \cup N(u)\). From the same combination: \(1/n\) on the 34-node karate graph. |
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 krm and
krm_fronts take the same ones with the same defaults.
The budget defaults are the paper's row for one 34-node graph
The default mut_rate is the constant \(1/34 \approx 0.0294\) (the stub
prints it as 0.029411764705882353), which the module README describes as
\(1/n\) on the 34-node karate graph. It is not recomputed from the graph you
pass. 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 \(1\) on karate.
The paper tunes the parameters separately for each of its four datasets. Its Section 8.1 (Parameter Setting) gives the best NSGA-III-KRM combination for each: the one, among the sensitivity grid of its Table 3, with the highest mean over 10 runs of the product of the best modularity and the best NMI. Table 3 fixes 100 generations for every dataset. Only the D1 row is shipped as the default; to reproduce another dataset, pass its row explicitly:
| Paper dataset | Nodes | pop_size |
num_gens |
cross_rate |
mut_rate |
|---|---|---|---|---|---|
| D1, Zachary's karate club (the default) | 34 | 100 | 100 | 0.8 | \(1/34\) |
| D2, bottlenose dolphins | 62 | 200 | 100 | 0.85 | \(1/124\) |
| D3, American college football | 115 | 500 | 100 | 0.9 | \(1/230\) |
| D4, books about US politics | 105 | 400 | 100 | 0.9 | \(2/105\) |
Table 3 offers each dataset the mutation rates \(1/n\), \(2/n\) and \(1/(2n)\); the chosen rate is \(1/n\) on D1, \(1/(2n)\) on D2 and D3, and \(2/n\) on D4. The rows, the dataset names and sizes, and the section and table numbers are taken from the arXiv preprint of the paper, arXiv:2005.03181v1 (May 2020). The published article is paywalled and was not read, so its numbering may differ.
Internal constants
These are fixed in the module and are not exposed. In
src/core/algorithms/krm/nsga3/normalize.rs:
NEAR_ZERO = 1e-10 is the threshold below which a Gauss–Jordan 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, DEGENERATE_REF_NORM2 = 1e-30: a reference direction
with a smaller squared norm is treated as the origin, and the distance
becomes the point's own norm.
Degenerate inputs¶
The budget arguments are not validated. Every row was checked against the current package.
| Input | Result |
|---|---|
pop_size=0 |
krm panics with "empty Pareto front"; krm_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 | krm returns {}; krm_fronts returns pop_size empty dicts. |
| A graph with no edges | krm maps every node to -1; krm_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¶
The module README states no bound, and the arXiv preprint of the paper
(arXiv:2005.03181v1) has no complexity
analysis; the paywalled published version was not read. 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 (91 at the default divisions), 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 at least
\(H\), as the module README asks, \(NH \le 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))\).
make_individual(nsga3/engine.rs) decodes a genome withLocus::decode(locus.rs) and then callscommunity_stats(objectives.rs) twice, once for KKM and RC and once for \(Q\); each call walks every node's neighbor list once. A generation evaluates its \(N\) offspring and every individual the two customizations replace, at most \(2N - 1\) more: up to \(N - 1\) duplicates, then up to \(N\) single-community members. Breeding (operators.rs), 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::build costs \(O(n + m)\), the initial population \(N\)
evaluations and one sort, and api.rs ranks the final population once more.
Converting the result costs \(O(n)\) for krm and up to \(O(Nn)\) for
krm_fronts, which converts every rank-1 member. The decode is a union-find
with path compression but no union by rank or size, so the linear cost of an
evaluation is not guaranteed in the worst case.
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 environmental
selection: about \(24Nn\) bytes. Locus stores the \(n + 2m\) candidate 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. krm was timed on a shared 48-core Linux server
(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 krm call, the
ratio to the previous row, and the largest peak resident memory of the three
runs above the process's size 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); the pop_size rows use
Zachary's karate club, where \(n + m\) is small enough for the \(N^2\) term to
dominate.
| Varied | Graph | Setting | Median time | Ratio | Added peak memory |
|---|---|---|---|---|---|
| \(n\) | LFR, \(N = G = 100\) | \(n\) = 2,000 (\(m\) = 9,795) | 1.49 s | 5.8 MiB | |
| \(n\) = 4,000 (\(m\) = 19,667) | 3.20 s | 2.14 | 11.2 MiB | ||
| \(n\) = 8,000 (\(m\) = 40,227) | 6.91 s | 2.16 | 21.8 MiB | ||
| \(n\) = 16,000 (\(m\) = 79,847) | 14.3 s | 2.08 | 43.7 MiB | ||
| \(n\) = 32,000 (\(m\) = 160,335) | 29.3 s | 2.05 | 86.2 MiB | ||
| \(n\) = 64,000 (\(m\) = 319,998) | 66.5 s | 2.27 | 176.5 MiB | ||
| \(G\) | LFR \(n\) = 4,000, \(N = 100\) | \(G\) = 50 | 1.80 s | 11.1 MiB | |
| \(G\) = 100 | 3.24 s | 1.80 | 10.8 MiB | ||
| \(G\) = 200 | 6.36 s | 1.96 | 11.2 MiB | ||
| \(G\) = 400 | 13.4 s | 2.11 | 11.2 MiB | ||
| \(N\) | karate, \(G = 100\) | \(N\) = 100 | 0.052 s | 0.4 MiB | |
| \(N\) = 200 | 0.184 s | 3.51 | 0.5 MiB | ||
| \(N\) = 400 | 0.470 s | 2.56 | 1.2 MiB | ||
| \(N\) = 800 | 1.95 s | 4.16 | 3.0 MiB | ||
| \(N\) = 1,600 | 7.80 s | 3.99 | 10.7 MiB |
The measurements agree with the bound, with one drift. Doubling \(n\), which also doubles \(m\) on these graphs, roughly doubled the time (ratios 2.05 to 2.27), and memory grew linearly at about 2.8 KiB per node, of which the population accounts for \(24N\) = 2,400 bytes at \(N = 100\). The drift: time per unit of \(G\,N\,(n + m)\) rose from 12.7 ns at \(n\) = 2,000 to 15.3 ns at 32,000 and 17.3 ns at 64,000. The bound does not predict this, and its cause was not investigated; the server's load average also rose during the sweep, from about 12 to 26. Time was linear in \(G\) and memory did not depend on it. On karate, doubling \(N\) from 400 to 800 and from 800 to 1,600 quadrupled the time, the \(N^2\) term of non-dominated sorting, and memory grew faster than linearly, as the \(O(N^2)\) domination lists predict.
When to use it¶
- Reproducing or comparing with Shaik, Ravi and Deb (2021). The
pop_size,num_gens,cross_rateandmut_ratedefaults are the paper's best combination on karate, the paper's rows for its other three datasets are in the note above, andkrm_frontsexposes the front for the paper's best-NMI and best-modularity protocol. - Comparing engines on the same objective pair. KRM and MMCoMO optimize the same KKM and RC formulas; KRM adds modularity as a third objective and uses NSGA-III. Both return the max-modularity member of their rank-1 front.
- As a baseline, not as a default. NSGA-III-KRM 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, whose mating comparison ranks only constraint violation and so always falls through to a coin flip; there is no binary tournament.
- The ideal point and the extreme points are recomputed at every normalization, whereas pymoo carries them across generations. The README calls this a known minor difference from the reference the paper's tooling used.
divisions = 12resolves an omission in the paper; 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. The formula is the same.
- The duplicate filter compares canonical labelings, so it also removes partitions that differ only by a permutation of community labels. The README notes that without this the encoding's many genomes per partition would defeat the filter almost entirely.
- The public functions return normalized partitions in which isolated nodes
are community
-1; the paper leaves them as singletons.
Example¶
The first block evaluates KKM and RC, written directly from the formulas above, on the module's unit-test graph: two triangles joined by one edge. It shows the two degenerate extremes and the split between them.
import networkx as nx
import pymocd
def communities(partition):
groups = {}
for node, label in partition.items():
groups.setdefault(label, set()).add(node)
return list(groups.values())
def kkm_rc(G, partition):
"""KKM and RC as defined above (assumes no isolated nodes)."""
groups = communities(partition)
n, k = G.number_of_nodes(), len(groups)
within = cut = 0.0
for V in groups:
l_in = sum(1 for u in V for v in G[u] if v in V) # L(V, V)
within += l_in / len(V)
cut += (sum(d for _, d in G.degree(V)) - l_in) / len(V) # L(V, V-bar)
return 2 * (n - k) - within, cut
T = nx.Graph([(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5), (2, 3)])
cases = {
"singletons": {v: v for v in T},
"two triangles": {v: int(v >= 3) for v in T},
"one community": {v: 0 for v in T},
}
for name, p in cases.items():
kkm, rc = kkm_rc(T, p)
print(f"{name:>13}: KKM = {kkm:.3f} RC = {rc:.3f}")
singletons: KKM = 0.000 RC = 14.000
two triangles: KKM = 4.000 RC = 0.667
one community: KKM = 7.667 RC = 0.000
The second block runs krm and
krm_fronts on Zachary's karate club and prints three
members of the front: the coarsest, the max-modularity one, and the finest.
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 KRM counts edges.
G = nx.karate_club_graph()
part = pymocd.krm(G) # max-Q member of one run's rank-1 front
print("krm:", len(set(part.values())), "communities")
front = pymocd.krm_fronts(G) # the rank-1 front of a second, independent run
print("krm_fronts:", len(front), "partitions on the front")
rows = []
for p in front:
kkm, rc = kkm_rc(G, p)
q = nx.community.modularity(G, communities(p), weight=None)
rows.append((len(set(p.values())), kkm, rc, q))
coarsest = min(rows, key=lambda r: r[0])
best_q = max(rows, key=lambda r: r[3])
finest = max(rows, key=lambda r: r[0])
for name, (k, kkm, rc, q) in [("coarsest", coarsest), ("max Q", best_q), ("finest", finest)]:
print(f"{name:>8}: k = {k:2d} KKM = {kkm:7.2f} RC = {rc:6.2f} Q = {q:.4f}")
krm: 4 communities
krm_fronts: 94 partitions on the front
coarsest: k = 3 KKM = 51.75 RC = 2.64 Q = 0.3924
max Q: k = 4 KKM = 47.77 RC = 4.85 Q = 0.4172
finest: k = 24 KKM = 10.00 RC = 80.83 Q = 0.0866
Compare the KKM and RC columns across the three rows to see how members at different numbers of communities \(k\) trade the two objectives, and where the max-modularity member falls between the coarsest and the finest.
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. A preprint of the paper by the same three authors is arXiv:2005.03181v1 (May 2020); this page's parameter rows and dataset names come from it, because the published article is paywalled.
@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.
- Maoguo Gong, Qing Cai, Xiaowei Chen and Lijia Ma. "Complex Network Clustering by Multiobjective Discrete Particle Swarm Optimization Based on Decomposition." IEEE Transactions on Evolutionary Computation 18(1): 82–97, 2014. doi:10.1109/TEVC.2013.2260862.
- Park and Song. "A genetic algorithm for clustering problems." Proceedings of the 3rd Annual Conference on Genetic Programming, pp. 568–575, 1998 (as cited by the module README).
See also¶
- NSGA-III-CCM: the same paper's first variant, with Community Score, Community Fitness and modularity.
- MMCoMO: the KKM and RC pair under a macro-micro co-evolutionary NSGA-II.
- API reference:
krm,krm_fronts,nmi. - Guide: Core concepts, Working with Pareto fronts, Evaluating partitions, Choosing a detector.
- Gallery: Every detector on one graph,
which runs
krmalongside the other detectors, and Choosing a partition from a front. - Algorithms overview and the module README, which carries the full derivation and file map.