This release collects every change since 1.1.0 (CRAN, 2026-08-07). Versions 1.2.1 and 1.3.0 were internal and never submitted.
partition_stability(): a reliability report for a tracked partition
built from a bootstrap_multilayer() result. It returns (i) the stability
score s, the mean NMI (or ARI) between each bootstrap replicate's
meta-partition and the point estimate; (ii) a calibrated accuracy floor,
the 5th percentile of accuracy against the planted partition among
simulated fits with the same stability, so that accuracy exceeded the floor
in 95 percent of held-out fits; (iii) node-level Jaccard stability with its
own floor (reported only above stability 0.9); and (iv) decided node pairs
(co-assignment share below/above user thresholds). The calibration table
ships in inst/extdata/stability_calibration_table.csv and can be
overridden with calibration_table.
bootstrap_multilayer() now records stability_samples (replicate x
layer NMI and ARI to the point estimate) and node_jaccard_stability
(per-node mean Jaccard overlap with the point-estimate community), which
partition_stability() summarises.
allow_unequal_nodes argument for every fit_multilayer_*() function.
Layers may now contain different node sets (nodes entering and exiting the
system); nodes are matched across layers by vertex name and only nodes
present in both layers of a linked pair contribute to the interlayer
similarity. Default FALSE keeps the previous requirement of a shared node
universe.
fit_multilayer_identity_ties() now optimises Mucha et al. (2010)
multislice modularity with a generalized Louvain (local moving plus
aggregation), using each slice's own configuration null model
k_is k_js / 2 m_s and null-free interlayer identity ties. Previously the
function ran single-graph modularity on the stacked supra-graph, whose
pooled null model is roughly T times too small and penalises cross-slice
co-membership; at omega <= 1 that returned each slice as one community.
algorithm is kept for API compatibility but the optimiser is always the
generalized Louvain. The optimiser uses 10 random restarts (3 in the
internal 1.3.0); on small change-point series joint NMI varied by up to 0.2
across runs at 3.
weighted_jaccard_similarity() and weighted_overlap_similarity()
used layer-wide node strengths, so a node's strength counted toward a
community it was not a member of. Two disjoint communities could score 1.0
whenever their members had non-zero strength in both layers. A node's
strength now counts toward community a only when the node is a member of
a (and likewise for b).
weighted_overlap_similarity() errored (subscript out of bounds)
when a community contained a node with no recorded strength in that layer
(e.g., an isolate). Missing strengths now contribute 0, matching
weighted_jaccard_similarity() and the Python implementation. This
affected fit_multilayer_weighted_overlap() on layers with isolates.
co_assignment_ci() is now documented as a descriptive Monte Carlo
interval for the bootstrap co-assignment share, not a calibrated confidence
interval. The simulation study found pair-level intervals are not
calibratable for ambiguous pairs; use partition_stability() for a
calibrated reliability measure of the tracked partition. Function signature
and output are unchanged.
Documentation regenerated with roxygen2 7.3.3; partition_stability help
page added; cross-references synced.
Regression tests added for the weighted similarity fixes, the multislice
null model, and partition_stability().
Uncertainty-quantification overhaul, motivated by a large-scale simulation study of empirical CI coverage (planted-partition multilayer networks; n = 50-400 nodes, 3-10 communities, 5-15 layers, 5 switching rates, 3 density regimes, Louvain and Leiden, all similarity couplings).
New method: fit_multilayer_hungarian(). A two-stage
snapshot-and-match tracker that detects communities independently per layer
and then aligns their labels across consecutive layers with the Hungarian
(optimal linear-sum assignment) algorithm on the community overlap matrix
(clue::solve_LSAP; SciPy's linear_sum_assignment in the Python package).
This is the standard "independent detection + optimal label matching"
baseline, now exported as a first-class method with the same
multilayer_community_fit interface (layer_communities,
meta_communities, extract_meta_membership()). It complements the
coupling-based fits: those jointly optimise on a coupled supra-graph, this
one matches post hoc. interlayer_ties is NULL for this method.
Cross-layer community tracking (second-stage detection). The
fit_multilayer_*() functions now run a second community detection on the
interlayer/community graph (the similarity ties plus self-loops) and return
meta_communities: the tracked partition grouping per-layer communities into
cross-layer meta-communities. This is what makes custom layer_links and the
interlayer coupling actually affect the returned membership; previously the
output equalled independent per-layer detection. Access it with the new
exported extract_meta_membership(). The per-layer layer_communities are
unchanged and still returned. For the Jaccard/overlap specifications the
second stage runs on the community-similarity graph; for the identity
specification it is Mucha (2010) multislice modularity — the layers are
stacked into one supra-graph (intra-layer edges = each layer's own adjacency)
with interlayer identity edges, and a single detection is run on the whole
supra-graph. Custom layer_links set the coupling in both cases.
Multislice omega / resolution-limit controls.
fit_multilayer_identity_ties() gains an omega argument (Mucha's
interlayer coupling strength) alongside resolution_parameter, both
forwarded to the multislice supra-graph detection. omega scales the
interlayer identity-edge weights: small values decouple toward independent
per-layer detection, omega = 1 recovers the planted partition on
well-separated networks, and large values drive the known multislice
degeneracy (temporal threads fragment or slices collapse). Together with
resolution_parameter (Mucha's modularity resolution) these expose the
omega-sensitivity and resolution-limit behavior of multislice modularity as
tunable knobs. Mirrored in the Python package.
Uncertainty is now quantified on the meta-communities.
bootstrap_multilayer(), co_assignment_ci(), and community_est() operate
on the cross-layer meta-communities rather than the independent per-layer
partitions. Co-assignment now answers "do two nodes belong to the same
persistent community," and the community count is the number of
meta-communities per layer. (Coverage of these meta-community intervals must
be re-validated in simulation; the earlier per-layer coverage numbers do not
transfer.)
seed argument for reproducible detection. All fit_multilayer_*()
functions gain a seed argument. When supplied, the global RNG state is
saved, the RNG is seeded for the duration of the call, and the previous
state is restored on exit, so seeded detection never disturbs the caller's
random number stream (e.g. bootstrap_multilayer() resampling). The default
(NULL) preserves the previous behavior: detection inherits the caller's
RNG stream, so existing scripts using set.seed() are unaffected. (In the
Python package the default is seed=123, matching its previous internally
fixed detection seed.)
Node-universe validation. Layers must now share the same node set
(compared on vertex names when present, on vertex indices otherwise);
unequal universes error with instructions instead of silently misaligning
interlayer ties. fit_multilayer_identity_ties() gains
allow_unequal_nodes = TRUE to retain its documented support for nodes
entering or exiting the system.
Layers with different node sets are now rejected by all fit functions
(previously assumed aligned without checking). For identity ties, opt back
in with allow_unequal_nodes = TRUE.
animate_multilayer_gif() no longer has a default output_file
(previously "multilayer_animation.gif" in the working directory). An
explicit path is now required, per CRAN policy that functions must not
write to the user's filespace by default; use
tempfile(fileext = ".gif") for a temporary location.
community_ci() is renamed community_est() and no longer returns a
community-count confidence interval. The percentile community_count_ci
(estimate/lower/upper) is replaced by a community_count data frame that
reports, per layer, the observed-network estimate and its bootstrap
reproducibility (the share of replicates whose community count equals
the estimate), plus a plain-language report. Reason: the interval
covered the truth at ~0.99 on well-specified planted-partition networks
but collapsed to ~0.62 under strong community-size skew, and no
observable diagnostic separated the reliable cases from the rest. The
reproducibility summary is descriptive and makes no coverage claim; the
package's validated interval is co_assignment_ci(). The raw per-replicate
community counts are no longer exposed: bootstrap_multilayer() now returns
community_count_reproducibility (the per-layer share of replicates
matching the observed count) in place of the old community_count_samples
vector.
community_ci() no longer returns modularity_ci. The study showed its
empirical coverage is never close to the nominal 95% at any network size
(~0.40 at n = 50, 0.00 at n = 100, vacuously 1.00 at n = 200): community
detection maximizes modularity, so the bootstrap interval concentrates
around an optimized, upwardly biased estimate. An interval that is
anti-conservative when detection is imperfect and uninformative when it
is exact is not a confidence interval; it has been removed rather than
documented around. Raw bootstrap draws remain available in
bootstrap_multilayer()$modularity_samples for descriptive use.
bootstrap_multilayer() now uses a parametric network bootstrap:
within/between-community edge probabilities and edge-weight pools are
estimated from the observed network using the point-estimate partition,
and each replicate redraws the full edge set. The previous scheme
(Bayesian bootstrap on edge weights with fixed topology) was REMOVED
rather than kept as an option: because it never varies which edges
exist, it understates the variability of fresh data, and simulation
studies showed intervals built from it undercover badly (~45-48% at
nominal 95% for pairwise co-assignment) with no recovery as networks
grow. Under the project's design principle, a misleading option is
worse than no option.
co_assignment_ci(): Wilson score intervals for node-pair co-assignment
propensities, computed from the bootstrap co-assignment probabilities.
Label-invariant, so it avoids the label-switching problem that makes
per-node membership intervals ill-defined.
community_est() no longer issues the small-network community-count CI
warning. With the count interval removed there is no coverage claim to
qualify; the reproducibility summary is descriptive at any network size.
The co_assignment_ci() guidance to interpret cautiously below 100 nodes
remains.
directed = TRUE with algorithm = "leiden" previously errored
(igraph's cluster_leiden() supports undirected graphs only). Directed
layers are now collapsed to weighted undirected graphs — matching the
existing Louvain behavior — with a single warning per fit pointing users
who need directed-aware detection to the Python package (leidenalg).
Initial CRAN release.
fit_multilayer_jaccard(), fit_multilayer_overlap(),
fit_multilayer_weighted_jaccard(), fit_multilayer_weighted_overlap(),
fit_multilayer_identity_ties()).layer_links, enabling adjacent-only
temporal coupling.objective
argument; negative edge weights are rejected for modularity-based methods.bootstrap_multilayer(), community_ci()).simulate_and_fit_multilayer()).plot_multilayer_series()),
alluvial diagrams (plot_multilayer_alluvial()), and animated GIFs
(animate_multilayer_gif()).