Skip to content

API reference

The package is used through Network: load a graph, decompose(), then visualize() (the layout is computed on the way, or explicitly with compute_layout()). Every setting is a field of LaNetConfig, the same model the CLI fills from its flags. The usage guide walks through a complete example.

Network, LaNetConfig and its sub-models, DecompositionType, ColorScheme, BackgroundColor, load_config_from_yaml and save_config_to_yaml are re-exported from the top-level lanet_vi package; everything else is imported from its module.

Network

network

Main Network class for LaNet-vi.

Network

Network(graph: Graph, config: LaNetConfig | None = None)

Main class for network analysis and visualization.

This class provides a high-level API for:

  • loading network data,
  • computing the k-core, k-dense or d-core decomposition,
  • computing the layout and rendering the picture.

Parameters:

Name Type Description Default
graph Graph

NetworkX graph

required
config LaNetConfig

Complete configuration

None

Attributes:

Name Type Description
graph Graph

The network graph; after decompose() with config.decomposition.from_layer set, the subgraph induced by the nodes of index >= that layer

input_graph Graph

The graph as given, which every decompose() starts from

config LaNetConfig

Configuration settings

decomposition Optional[DecompositionResult]

Decomposition results (None until decompose() is called)

communities Optional[CommunityResult]

Communities of graph (None until detect_communities() runs, which decompose() does when config.community.detect_communities is set)

kconnectivity Optional[Dict[int, int]]

K-connectivity of every node, 0 for the nodes that are not k-connected (None until compute_kconnectivity() runs, which decompose() does when config.decomposition.kconn is set)

node_names Dict[int, str]

Node name mappings

custom_names bool

A names file was loaded (even an empty one): only the nodes it names are labeled, never the node numbers (the C++ -names FILE)

node_colors Dict[int, Tuple[float, float, float]]

Custom node colors

Examples:

>>> config = LaNetConfig()
>>> G = nx.Graph(nx.karate_club_graph().edges())  # drop the weights
>>> net = Network(G, config)
>>> net.decompose()
>>> net.visualize("output.png")

Initialize Network with graph and configuration.

colors_by_community property

colors_by_community: bool

True when the nodes take their community's color instead of the shell color.

Communities were detected and config.community.color_by_community is set; a colors file (load_node_colors) takes precedence, as it names each node's color.

from_edge_list classmethod

from_edge_list(
    file_path: Path | str, config: LaNetConfig | None = None
) -> Network

Create Network from edge list file.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to edge list file

required
config Optional[LaNetConfig]

Configuration (uses defaults if None)

None

Returns:

Type Description
Network

Network instance

Examples:

>>> net = Network.from_edge_list("network.txt")

load_node_names

load_node_names(file_path: Path | str) -> None

Load node names from file.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to node names file

required

load_node_colors

load_node_colors(file_path: Path | str) -> None

Load custom node colors from file.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to node colors file

required

decompose

decompose(
    decomp_type: DecompositionType | None = None,
) -> DecompositionResult

Compute network decomposition.

Parameters:

Name Type Description Default
decomp_type Optional[DecompositionType]

Type of decomposition (uses config if None)

None

Returns:

Type Description
DecompositionResult

Decomposition results

Examples:

>>> result = net.decompose(DecompositionType.KCORES)

detect_communities

detect_communities() -> CommunityResult

Detect the communities of the graph with config.community.

decompose() calls this when config.community.detect_communities is set; calling it directly detects them regardless of that flag. The result is kept in communities and, from then on, compute_layout() colors the nodes by community (config.community.color_by_community) and visualize() draws the community boundaries or circles (draw_boundaries / draw_circles).

Returns:

Type Description
CommunityResult

Communities of graph, with the modularity of the partition

Examples:

>>> net.decompose()
>>> communities = net.detect_communities()
>>> net.visualize("communities.png")

compute_kconnectivity

compute_kconnectivity() -> dict[int, int]

Compute the k-connectivity of the shells (the C++ -kconn).

decompose() calls this when config.decomposition.kconn is set; calling it directly computes it regardless of that flag, with config.decomposition.kconn_type (wide or strict). The result is kept in kconnectivity and, from then on, compute_layout() paints the nodes that are not k-connected black on white / white on black (squares in the grayscale schemes), as the C++ did.

The clusters are those of the component tree the layout draws, in the same order (the tree depends on config.layout.seed), so the walk sees what the picture shows.

Returns:

Type Description
Dict[int, int]

K-connectivity of every node, 0 when not k-connected

Raises:

Type Description
ValueError

If decompose() has not run, the decomposition is not the k-cores, or the graph is directed, a multigraph or weighted (the C++ refused those too)

Examples:

>>> net.decompose()
>>> kconn = net.compute_kconnectivity()
>>> net.visualize("kconn.png")

compute_layout

compute_layout() -> VisualizationLayout

Compute visualization layout.

Returns:

Type Description
VisualizationLayout

Layout with node positions and visual properties

Raises:

Type Description
ValueError

If decompose() hasn't been called yet

visualize

visualize(
    output_path: Path | str,
    layout: VisualizationLayout | None = None,
) -> None

Generate and save visualization.

Parameters:

Name Type Description Default
output_path Union[Path, str]

Output file path

required
layout Optional[VisualizationLayout]

Pre-computed layout (computes if None)

None

Examples:

>>> net.visualize("network.png")

get_metadata

get_metadata() -> dict

Get network metadata.

Returns:

Type Description
Dict

Dictionary with network statistics

Configuration

config

Configuration models for LaNet-vi using Pydantic.

BackgroundColor

Bases: str, Enum

Background color options.

ColorScheme

Bases: str, Enum

Color scheme options for visualization.

DecompositionType

Bases: str, Enum

Type of network decomposition.

StrengthIntervalMethod

Bases: str, Enum

Method for building strength intervals in weighted graphs.

CoordDistributionAlgorithm

Bases: str, Enum

Algorithm for distributing components.

MeasureType

Bases: str, Enum

Centrality measure type.

KConnectivityType

Bases: str, Enum

How the k-connectivity walk treats the clusters it skips (-kconntype).

CommunityConfig

Bases: BaseModel

Configuration for community detection.

Attributes:

Name Type Description
detect_communities bool

Whether to detect and visualize communities

algorithm Literal['louvain', 'greedy_modularity']

Community detection algorithm to use

resolution float

Resolution parameter for Louvain algorithm (higher = more communities)

color_by_community bool

Color nodes by community membership instead of k-core

draw_boundaries bool

Draw convex hull boundaries around communities

draw_circles bool

Draw circles around communities

boundary_alpha float

Transparency of community boundaries

colormap str

Matplotlib colormap for community colors

colormap_is_known classmethod

colormap_is_known(value: str) -> str

Require a matplotlib colormap name (a short error, not the list of all names).

VisualizationConfig

Bases: BaseModel

Configuration for network visualization.

Attributes:

Name Type Description
background BackgroundColor

Background color for the visualization

color_scheme ColorScheme

Color scheme for nodes and edges

width int

Image width in pixels

height int

Image height in pixels

window tuple of four floats

(hstart, hend, vstart, vend), fractions of the full picture to render (the C++ -window): (0, 1, 0, 1) is the whole picture, (0, 0.5, 0, 0.5) its top-left quarter, at the same pixel size

epsilon float

Ring thickness as a fraction of its radius (formula (1) of NIPS 2005)

delta float

Shrink factor of sibling components (formula (5))

gamma float

Component diameter; scales the whole picture (formulas (6)-(7))

font_zoom float

Font size multiplier for node labels

legend_fontsize Optional[float]

Legend font size (auto-scales with diagram size if not set)

edges_percent float

Percentage of visible edges (0.0-1.0)

min_edges int

Minimum number of visible edges

opacity float

Edge opacity (0.0-1.0), the C++ -opacity

unit_length float

Base unit length for scaling

draw_circles bool

Whether to draw component border circles

show_degree_scale bool

Whether to show the degree (node size) legend, as the C++ -showDegreeScale

show_color_legend bool

Whether to show the shell/dense index color legend

color_scale_max_value Optional[int]

Maximum value for color scale normalization

gradient_edges bool

Whether to use gradient edge coloring

show_node_labels bool

Whether to show node labels

label_all_nodes bool

If True, label all nodes; if False, use label_kcore_range or label_node_list

label_kcore_min Optional[int]

Minimum k-core for labeling (only if label_all_nodes=False)

label_kcore_max Optional[int]

Maximum k-core for labeling (only if label_all_nodes=False)

node_edge_color Optional[str]

Border color of the nodes (the C++ drew none: border = node color)

node_size_scale float

Multiplier on the node radius (1.0 = the C++ size)

edge_alpha float

Deprecated alias of opacity

show_size_legend bool

Deprecated alias of show_degree_scale

window_is_a_sub_rectangle classmethod

window_is_a_sub_rectangle(
    value: tuple[float, float, float, float],
) -> tuple[float, float, float, float]

Require 0 <= hstart < hend <= 1 and the same vertically.

fold_deprecated_aliases classmethod

fold_deprecated_aliases(data: Any) -> Any

Map the deprecated aliases onto their current fields.

An alias only applies when the current field itself is absent, so a file that sets the current field is never overridden by the old one.

DecompositionConfig

Bases: BaseModel

Configuration for network decomposition.

Attributes:

Name Type Description
decomp_type DecompositionType

Type of decomposition to apply

measure MeasureType

Name of the k-dense measure in the legend: mcore labels a k-dense as k - 2 (its m-core number, the C++ default) and reads color_scale_max_value in the same units; kdense labels it k

from_layer int

Consider graph induced from this layer upward

granularity int

Number of groups in weighted graphs (-1 for max degree)

strength_intervals StrengthIntervalMethod

Method for building strength intervals

maximum_strength Optional[float]

Upper limit for strength intervals

strength_intervals_file Optional[Path]

Boundaries, one per line, for strength_intervals = custom

no_cliques bool

Whether to omit cliques in central core

kconn bool

Compute the k-connectivity of the shells (the C++ -kconn; k-cores of an undirected, unweighted, simple graph only): nodes that are not k-connected are drawn black on white / white on black, as squares in the grayscale schemes

kconn_type KConnectivityType

wide (the C++ default) gives the clusters skipped by the walk another chance at every lower index; strict drops them

granularity_is_sentinel_or_positive classmethod

granularity_is_sentinel_or_positive(value: int) -> int

-1 means "maximum degree"; any other value must be at least 1.

custom_intervals_need_a_file

custom_intervals_need_a_file() -> DecompositionConfig

strength_intervals = custom is only meaningful with a boundaries file.

kconn_needs_kcores

kconn_needs_kcores() -> DecompositionConfig

Refuse kconn outside the k-cores: it is defined on their clusters.

GraphConfig

Bases: BaseModel

Configuration for graph construction.

Attributes:

Name Type Description
multigraph bool

Allow repeated edges

weighted bool

Support edge weights

directed bool

Whether graph is directed

LayoutConfig

Bases: BaseModel

Configuration for layout algorithm.

Attributes:

Name Type Description
coord_distribution CoordDistributionAlgorithm

Placement of the components: classic (concentric rings, the C++ default) or the pow / log circle packing of sibling components

alpha float

Constant of the disc area law of the circle packing (pow / log only; the C++ default 0.3)

beta float

Exponent of the disc area law (pow / log only)

seed int

Random seed for reproducibility

ratio_constant Optional[float]

Node radius factor of the pow / log modes (the C++ -ratioConstant); None is the C++ auto-adjustment (1 for k-cores, from the top cores for k-dense)

min_component_size int

Minimum component size to visualize (filters small components for performance)

LaNetConfig

Bases: BaseModel

Complete LaNet-vi configuration.

Attributes:

Name Type Description
graph GraphConfig

Graph construction settings

decomposition DecompositionConfig

Decomposition algorithm settings

visualization VisualizationConfig

Visualization appearance settings

layout LayoutConfig

Layout algorithm settings

community CommunityConfig

Community detection settings

kconn_needs_a_simple_unweighted_graph

kconn_needs_a_simple_unweighted_graph() -> LaNetConfig

Refuse kconn on weighted, directed or multigraph inputs, as the C++ did.

Results

graph

Graph data models for LaNet-vi.

NodeData

Bases: BaseModel

Node attributes and metadata.

Attributes:

Name Type Description
node_id int

Unique node identifier

name Optional[str]

Human-readable node name

color Optional[Tuple[float, float, float]]

RGB color values (0.0-1.0 range)

shell_index Optional[int]

k-core shell index

dense_index Optional[int]

k-dense index

degree Optional[int]

Node degree

strength Optional[float]

Node strength (sum of edge weights)

coordinates Optional[Tuple[float, float]]

2D visualization coordinates (x, y)

EdgeData

Bases: BaseModel

Edge attributes and metadata.

Attributes:

Name Type Description
source int

Source node ID

target int

Target node ID

weight float

Edge weight

visible bool

Whether edge should be rendered

Component

Component(**data: Any)

Bases: BaseModel

Connected component in the network.

Attributes:

Name Type Description
component_id int

Unique component identifier

nodes List[int]

List of node IDs in this component

shell_index Optional[int]

k-core shell index for this component

dense_index Optional[int]

k-dense index for this component

size int

Number of nodes in component

center Optional[Tuple[float, float]]

Component center coordinates

radius Optional[float]

Component radius for visualization

Initialize component and compute size.

DecompositionResult

Bases: BaseModel

Results from k-core or k-dense decomposition.

Attributes:

Name Type Description
decomp_type str

Type of decomposition ('kcores' or 'kdenses')

node_indices Dict[int, int]

Mapping from node ID to shell/dense index

max_index int

Maximum shell/dense index

min_index int

Minimum shell/dense index

components List[Component]

List of connected components at each level

p_function Optional[List[float]]

Strength interval boundaries for weighted graphs

metadata Dict[str, Any]

Extra, decomposition-specific data (e.g. d_cores pairs for d-cores)

VisualizationLayout

Bases: BaseModel

Complete layout information for visualization.

Attributes:

Name Type Description
node_positions Dict[int, Tuple[float, float]]

Mapping from node ID to (x, y) coordinates

node_colors Dict[int, Tuple[float, float, float]]

Mapping from node ID to RGB color

node_sizes Dict[int, float]

Mapping from node ID to visual size/radius

visible_edges List[Tuple[int, int]]

List of edges to render (source, target)

edge_colors Dict[Tuple[int, int], Tuple[Tuple[float, ...], Tuple[float, ...]]]

Mapping from edge (u,v) to pair of RGB colors (color1, color2) for gradient rendering

edge_widths Dict[Tuple[int, int], float]

Mapping from edge (u,v) to line width

components List[Component]

Component layout information

bounds Tuple[float, float, float, float]

Layout bounds (xmin, xmax, ymin, ymax)

frame float

Network radius in layout units (gamma * u * R of the C++, the outermost component's radius); the legends are placed relative to it

weighted bool

Node radii are strength-based (the degree legend then shows strengths)

radius_law (RadiusLaw, optional)

The node radius law of the picture (mode, ratioConstant), so the degree legend draws its samples with the radii the nodes have

square_nodes Set[int]

Nodes drawn as squares instead of circles: the nodes that are not k-connected in the grayscale schemes (the C++ addBlock)

NetworkMetadata

Bases: BaseModel

Metadata about the network.

Attributes:

Name Type Description
num_nodes int

Total number of nodes

num_edges int

Total number of edges

is_weighted bool

Whether graph has edge weights

is_multigraph bool

Whether graph allows parallel edges

is_directed bool

Whether graph is directed

max_degree int

Maximum node degree

min_degree int

Minimum node degree

avg_degree float

Average node degree

density float

Graph density

Decomposition

kcores

K-core decomposition (degree-based and, for weighted graphs, strength-based).

compute_kcores

compute_kcores(
    graph: Graph,
    config: DecompositionConfig | None = None,
    weighted: bool | None = None,
    p_function: list[float] | None = None,
) -> DecompositionResult

Compute k-core decomposition of a graph.

For unweighted graphs, uses NetworkX's built-in k-core algorithm. For weighted graphs, computes strength-based decomposition using p-function.

Parameters:

Name Type Description Default
graph Graph

Input graph

required
config Optional[DecompositionConfig]

Decomposition configuration

None
weighted Optional[bool]

Force the weighted (strength-based) or unweighted algorithm. None (default) picks the weighted one if any edge has a weight attribute.

None
p_function Optional[list[float]]

Strength interval boundaries to reuse instead of building them from this graph (the C++ findCores(&pf), used when re-decomposing the subgraph of from_layer). Ignored for unweighted graphs.

None

Returns:

Type Description
DecompositionResult

K-core decomposition results with shell indices

Examples:

>>> G = nx.karate_club_graph()
>>> result = compute_kcores(G)
>>> print(f"Max core: {result.max_index}")

read_custom_intervals

read_custom_intervals(path: Path | None) -> list[float]

Read the p-function boundaries of strength_intervals = custom from a file.

One boundary per line, as the C++ -strengthsIntervalsFile. A positive strength in (0, p1] gets index 1, (p(i-1), pi] index i, and anything above pn the last index n; a strength of exactly 0 (isolated node) gets index 0, like every other interval method.

find_components_by_shell

find_components_by_shell(
    graph: Graph, decomposition: DecompositionResult
) -> DecompositionResult

Find connected components for each shell index.

Parameters:

Name Type Description Default
graph Graph

Original graph

required
decomposition DecompositionResult

K-core decomposition result

required

Returns:

Type Description
DecompositionResult

Updated result with component information

kdenses

K-dense (m-core) decomposition by triangle-pair peeling.

This ports graph_kdenses.cpp / graph_triangled_kcores.cpp from the C++ LaNet-vi. The C++ builds a dual graph whose vertices are the edges of the input graph and whose edges join the three sides of every triangle, then peels it: whenever a dual vertex is removed, the two other sides of each triangle it belonged to lose one triangle. That is the k-truss decomposition of the input graph. The k-dense index of an edge is its trussness (2 for edges in no triangle, 3 for edges in a triangle whose sides are in no other triangle, ...) and the k-dense index of a vertex is the maximum over its incident edges.

compute_kdenses

compute_kdenses(graph: Graph) -> DecompositionResult

Compute the k-dense decomposition of a graph.

Parallel edges and self-loops are ignored, as in the C++ reader.

Parameters:

Name Type Description Default
graph Graph

Input graph (must be undirected)

required

Returns:

Type Description
DecompositionResult

node_indices maps every node to its k-dense index (>= 2); metadata["edge_indices"] maps every simple edge (u, v), u < v, to its k-dense index, which the C++ uses to color edges.

Notes

The k-dense index of an edge is 2 + s where s is its truss support number: the largest s such that the edge belongs to a subgraph in which every edge closes at least s triangles. The k-dense index of a vertex is the maximum k-dense index of its incident edges, or 2 if it has none.

Examples:

>>> G = nx.karate_club_graph()
>>> result = compute_kdenses(G)
>>> print(f"Max dense index: {result.max_index}")
Max dense index: 5

find_components_by_dense

find_components_by_dense(
    graph: Graph, decomposition: DecompositionResult
) -> DecompositionResult

Find connected components for each dense index.

Parameters:

Name Type Description Default
graph Graph

Original graph

required
decomposition DecompositionResult

K-dense decomposition result

required

Returns:

Type Description
DecompositionResult

Updated result with component information

dcores

D-core decomposition for directed graphs.

D-cores extend k-cores to directed graphs by considering both in-degree and out-degree. Each node is assigned a (k_in, k_out) pair indicating its core membership based on incoming and outgoing edges separately.

Each direction is peeled independently: k_in is the largest k such that the node belongs to the subgraph where every node has in-degree >= k, and k_out likewise for out-degree. compute_dcore_table gives the full (k, l)-core table of Giatsidis et al. (for every out-degree threshold l, the largest k with the node in the (k, l)-core), the dcores_list.txt of the C++ 4.0.0 driver.

compute_dcores

compute_dcores(
    graph: DiGraph,
    config: DecompositionConfig | None = None,
) -> DecompositionResult

Compute d-core decomposition for directed graphs.

Each node receives a (k_in, k_out) core number pair based on: - k_in: maximum k such that node has in-degree >= k in the k-in-core - k_out: maximum k such that node has out-degree >= k in the k-out-core

Self-loops are ignored (they count for neither degree).

Parameters:

Name Type Description Default
graph DiGraph

Directed input graph

required
config Optional[DecompositionConfig]

Decomposition configuration

None

Returns:

Type Description
DecompositionResult

D-core decomposition results with (k_in, k_out) pairs

Raises:

Type Description
ValueError

If graph is not directed

Examples:

>>> G = nx.DiGraph()
>>> G.add_edges_from([(0, 1), (1, 2), (2, 0)])
>>> result = compute_dcores(G)
>>> result.node_indices[0]  # max(k_in, k_out), used for layout/coloring
1
>>> result.metadata["d_cores"][0]  # full (k_in, k_out) pair for node 0
(1, 1)
Notes

For undirected graphs, use compute_kcores instead.

compute_dcore_table

compute_dcore_table(
    graph: DiGraph,
) -> dict[int, dict[int, int]]

Compute the (k, l)-core table of a directed graph.

The (k, l)-core (Giatsidis, Thilikos & Vazirgiannis, 2011) is the largest subgraph in which every node has in-degree >= k and out-degree >= l. For every l from 0 up to the last non-empty (0, l)-core, the table gives each node of that core the largest k such that the node belongs to the (k, l)-core. Row 0 is the in-core number (k_in of compute_dcores); a node absent from a row is not in the (0, l)-core.

Parameters:

Name Type Description Default
graph DiGraph

Directed input graph

required

Returns:

Type Description
Dict[int, Dict[int, int]]

{l: {node: k}}

Raises:

Type Description
ValueError

If the graph is not directed

Examples:

>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 0), (2, 3)])
>>> table = compute_dcore_table(G)
>>> sorted(table[0].items())  # in-core numbers
[(0, 1), (1, 1), (2, 1), (3, 1)]
>>> sorted(table[1].items())  # node 3 has out-degree 0: not in the (0, 1)-core
[(0, 1), (1, 1), (2, 1)]
Notes

This is what the C++ 4.0.0 driver computed for a directed graph (-directed): it wrote the table to dcores_list.txt as node k l lines and drew nothing. That code kept a node in the in-degree peeling after its out-degree had dropped below l, so it reported a larger k than the definition for such nodes; this implementation follows the definition (a node leaves the (k, l)-core as soon as either degree fails), which is checked against a brute-force one in the tests.

find_components_by_dcore

find_components_by_dcore(
    graph: DiGraph, decomposition: DecompositionResult
) -> DecompositionResult

Find weakly connected components within each d-core level.

Nodes are grouped by max(k_in, k_out), the same value stored in decomposition.node_indices and used by the layout, so the resulting components line up with the rings drawn for the other decompositions.

Parameters:

Name Type Description Default
graph DiGraph

Directed input graph

required
decomposition DecompositionResult

D-core decomposition result

required

Returns:

Type Description
DecompositionResult

Updated decomposition with component information

kconnectivity

K-connectivity of the k-core clusters (the C++ -kconn).

Port of computeKConnectivityWide / computeKConnectivityStrict and their conditions from graph_kcores_components.cpp, the method of Beiró, Alvarez-Hamelin & Busch, A low complexity visualization tool that helps to perform complex systems analysis, New J. Phys. 10 (2008) 125003: a lower bound of the edge connectivity of the nodes of each shell, built from the clusters of the nested component tree.

A cluster is a connected piece of a shell inside a component (the ones the layout draws, in the same order). The walk grows a k-connected set C from the top shell down:

  1. Seed. From the top shell down, the first cluster Q whose induced subgraph has diameter at most 2 (wide: or a minimum edge cut of at least the shell index; strict: and a minimum degree of at least the shell index) joins C with k-connectivity equal to its shell index.
  2. Growth. For every remaining shell k from the top down, a cluster Q joins C with k-connectivity k when the graph of Q with C contracted to a single vertex has diameter at most 2 (skipped for k = 1) and either at least k nodes of Q touch C, or all of them do, or phi(Q) >= k with phi the sum over the nodes of Q of min(max(1, |N(v) ∩ NotB2|), |N(v) ∩ C|), where NotB2 holds the nodes of Q with fewer than two neighbors in C. Each shell's clusters are examined once, in order, with C growing as clusters are accepted.

The two types differ in what happens to the clusters that are skipped: wide (the C++ default) keeps them pending and tries them again at every lower k (so a node can end up with a k-connectivity below its shell index); strict drops them.

Nodes that never join C have k-connectivity 0 and are drawn black on white / white on black (as squares in the bw and bwi schemes), as the C++ did.

Deliberate differences from the C++: shells with no clusters are skipped (the C++ dereferenced an empty list there); the shell == 77 escape in the strict seed is a debugging leftover and is not reproduced; self-loops are ignored.

diameter_at_most_two

diameter_at_most_two(
    graph: Graph, nodes: Iterable[int]
) -> bool

Whether the subgraph induced by nodes has diameter at most 2 (connected).

cluster_conditions

cluster_conditions(
    graph: Graph,
    cluster: list[int],
    k: int,
    kconnectivity: Mapping[int, int],
) -> bool

Test whether cluster may join the k-connected set at k (conditions).

Parameters:

Name Type Description Default
graph Graph

The network

required
cluster List[int]

Nodes of the cluster

required
k int

The shell index being processed (k_conditions)

required
kconnectivity Mapping[int, int]

Current k-connectivity of every node; non-zero means "in C"

required

Returns:

Type Description
bool

Whether the contracted diameter and the frontier / phi conditions hold

compute_kconnectivity

compute_kconnectivity(
    graph: Graph,
    clusters_by_shell: Mapping[int, list[list[int]]],
    kind: str = "wide",
) -> dict[int, int]

K-connectivity of every node from the clusters of the k-core component tree.

Parameters:

Name Type Description Default
graph Graph

The network (undirected, simple, unweighted: the C++ refused anything else)

required
clusters_by_shell Mapping[int, List[List[int]]]

The clusters of each shell index in the order of the component tree walk (:func:lanet_vi.visualization.lanet_layout.clusters_by_index)

required
kind str

"wide" (the C++ default) or "strict"

'wide'

Returns:

Type Description
Dict[int, int]

K-connectivity of every node of the graph; 0 for the nodes that are not k-connected

Raises:

Type Description
ValueError

If kind is not "wide" or "strict"

Examples:

>>> root = build_component_tree(G, cores, edge_index, rng)
>>> kconn = compute_kconnectivity(G, clusters_by_index(root))

Layout

lanet_layout

The LaNet-vi placement: nested components, neighbor-based rho, clique sectors.

Port of kcores_component.cpp / graph_kcores_components.cpp from the C++ LaNet-vi (-coordDistributionAlgorithm classic, the default), following the formulas of Alvarez-Hamelin, Dall'Asta, Barrat & Vespignani, k-core decomposition: a tool for the visualization of large scale networks (NIPS 2005):

  1. The graph is split recursively into nested components: the connected components of the subgraph with index > k inside a component of index k. A component of index k owns the clusters of its nodes with index exactly k (connected inside the shell) and its children (index k + 1).
  2. Each component gets a center, a radius (ratio) and a scale (u): the top core of a branch has a radius proportional to the root of the sum of its squared log-degrees, every enclosing shell adds one unit (part 1); children are placed inside their parent at rho = 1 - size / siblings, phi = 2 pi (previous / siblings)^2 and scaled by sqrt(size / siblings) / delta (formulas (3)-(5), part 2).
  3. A node of index k sits at rho = ratio (1 - eps) + eps ratio * average, where average measures how deep its higher-index neighbors are (formula (1)), and at the circular average of the angles of those neighbors (already placed). Top cores are split into cliques laid along U-shaped paths in angular sectors (formula (2)).

K-dense and d-core results go through the same placement with their own edge index for the component tree (an edge belongs to the inner component when its index is above the component's: for k-cores the minimum of its endpoints' indices, for k-dense the edge's own dense index, as kdenses_component.cpp walks it).

The pow and log coordinate distributions (findCoordinatesModern) replace step 2: the whole network is a disc of radius 1; inside a component of radius ratio the children share a disc of radius R ((sqrt((T - S) / T))^0.25 ratio capped at 0.96 ratio for pow, sqrt((size - shell) / size) ratio for log, with T and S the sums of squared log-degrees of the component and of its own shell), packed as non-overlapping discs whose areas follow their sum(log(1 + d)^(2 / beta)) weight (:func:lanet_vi.visualization.layout.distribute_components); the nodes of the shell sit on the ring between R and ratio by formula (1). The k-dense variant of kdenses_component.cpp (the only placement the C++ had for k-dense) shrinks by a fixed 0.92, counts the neighbors of the same index in formula (1), scales epsilon by tau = (ratio - R) / ratio and auto-adjusts the ratioConstant of the node radii.

LayoutComponent dataclass

LayoutComponent(
    index: int,
    parent: LayoutComponent | None = None,
    clusters: list[list[int]] = list(),
    children: list[LayoutComponent] = list(),
    size: int = 0,
    shell_cardinal: int = 0,
    rho: float = -1.0,
    phi: float = 0.0,
    x: float = 0.0,
    y: float = 0.0,
    u: float = 1.0,
    ratio: float = 1.0,
    central_core_k: int = 0,
    central_core_ratio: float = 1.0,
    end_ratio: float = 0.0,
    t_log2_degree: float = 0.0,
    shell_t_log2_degree: float = 0.0,
    t_log_beta_degree: float = 0.0,
)

A component of the nested decomposition with its placement (KCores_Component).

walk

walk() -> list[LayoutComponent]

Return this component and all its descendants, parents first.

LayoutParameters dataclass

LayoutParameters(
    epsilon: float = 0.18,
    delta: float = 1.3,
    gamma: float = 1.5,
    u: float = 1.0,
    no_cliques: bool = False,
    weighted: bool = False,
    coord_distribution: str = "classic",
    alpha: float = 0.3,
    beta: float = 1.0,
    dense: bool = False,
    ratio_constant: float | None = None,
)

The C++ parameters that shape the picture.

modern property

modern: bool

Whether the pow / log placement is used instead of classic.

LanetLayout dataclass

LanetLayout(
    positions: dict[int, tuple[float, float]],
    root: LayoutComponent,
    frame: float,
    ratio_constant: float = 1.0,
)

Result of :func:compute_lanet_layout.

RadiusLaw dataclass

RadiusLaw(
    max_degree: int,
    max_strength: float = 0.0,
    weighted: bool = False,
    modern: bool = False,
    dense: bool = False,
    ratio_constant: float = 1.0,
)

Node radius in layout units for the active placement (computeHostRatio).

classic uses :func:node_radius. The pow / log modes use 0.007 ratioConstant log(1 + d)^1.5 (graphics_kcores.cpp), or ratioConstant sqrt(log(1 + d)) for k-dense (graphics_kdenses.cpp, whose radii only ever went with that placement); on weighted graphs both use ratioConstant log(1 + s) / log(s_max) (times 0.007 for k-cores), with the same fallback to the degree law as :func:strength_radii.

circular_average

circular_average(
    a: float, weight_a: float, b: float, weight_b: float
) -> float

Weighted average of two angles along the shorter arc (Circular_average).

place_in_circular_sector

place_in_circular_sector(
    ratio: float,
    alfa: float,
    n: int,
    total: int,
    random: float,
    unique: bool,
) -> tuple[float, float]

Position n of total along a U-shaped path in a sector (placeInCircularSector).

The path goes out along the radius, along the arc of aperture alfa and back in; with unique (the clique is the whole core) only the arc is used.

build_component_tree

build_component_tree(
    graph: Graph,
    node_index: dict[int, int],
    edge_index: Callable[[int, int], int],
    rng: Generator,
) -> LayoutComponent

Nested components and clusters of a decomposition (computeComponents).

Parameters:

Name Type Description Default
graph Graph

The network

required
node_index Dict[int, int]

Shell / dense index of every node

required
edge_index Callable[[int, int], int]

Index of an edge: the component of index k is connected through edges of index > k and its clusters through edges of index == k

required
rng Generator

Source of the random cluster order

required

Returns:

Type Description
LayoutComponent

The root (index 0, all nodes)

clusters_by_index

clusters_by_index(
    root: LayoutComponent,
) -> dict[int, list[list[int]]]

Collect the clusters of every index in tree-walk order (buildClustersMap).

Parameters:

Name Type Description Default
root LayoutComponent

The component tree of :func:build_component_tree

required

Returns:

Type Description
Dict[int, List[List[int]]]

Clusters keyed by their component's index, parents' clusters before children's; indices without clusters are absent

component_tree

component_tree(
    graph: Graph,
    node_index: dict[int, int],
    edge_index: Callable[[int, int], int] | None = None,
    seed: int = 0,
) -> tuple[LayoutComponent, Generator]

Build the component tree exactly as :func:compute_lanet_layout starts.

Parameters:

Name Type Description Default
graph Graph

The network

required
node_index Dict[int, int]

Shell / dense index of every node

required
edge_index Callable[[int, int], int]

Index of an edge; defaults to the minimum of its endpoints' indices (k-cores)

None
seed int

Seed of the random generator (the C++ -seed)

0

Returns:

Type Description
Tuple[LayoutComponent, Generator]

The root and the generator after the tree's draws, to hand both to :func:compute_lanet_layout as tree so the placement (which keeps drawing from it) is the one the seed gives

compute_lanet_layout

compute_lanet_layout(
    graph: Graph,
    node_index: dict[int, int],
    params: LayoutParameters,
    seed: int = 0,
    edge_index: Callable[[int, int], int] | None = None,
    tree: tuple[LayoutComponent, Generator] | None = None,
) -> LanetLayout

Place every node with the LaNet-vi algorithm.

Parameters:

Name Type Description Default
graph Graph

The network (undirected view is used for neighborhoods)

required
node_index Dict[int, int]

Shell / dense index of every node

required
params LayoutParameters

epsilon, delta, gamma, u, no_cliques, weighted

required
seed int

Seed of the random generator (the C++ -seed)

0
edge_index Callable[[int, int], int]

Index of an edge; defaults to the minimum of its endpoints' indices (k-cores)

None
tree Tuple[LayoutComponent, Generator]

A tree already built by :func:component_tree for this graph, indices and seed, with its generator; the placement continues from it instead of building the tree again. The root is modified in place.

None

Returns:

Type Description
LanetLayout

Node positions, the component tree (centers, radii, scales) and the frame size

strength_radii

strength_radii(weighted: bool, max_strength: float) -> bool

Whether node radii follow the strength law (weighted and a usable maximum).

node_radius

node_radius(
    degree: int,
    max_degree: int,
    strength: float = 0.0,
    max_strength: float = 0.0,
    weighted: bool = False,
) -> float

Node radius in layout units (computeHostRatio, classic mode).

The strength law needs log(max_strength) > 0; when every strength is at most 1 (the C++ would divide by zero or a negative number) the degree law is used instead, see :func:strength_radii.

layout

Circle packing of sibling components (the C++ distribute_components.cpp).

Used by the pow / log coordinate distributions of :mod:lanet_layout: the child components of a component are packed as non-overlapping discs inside its disc, each with an area that grows with its weight, and the discs are inflated step by step until they no longer fit.

packing_radii

packing_radii(
    radius: float,
    weights: ndarray,
    alpha: float,
    beta: float,
    log_mode: bool,
) -> ndarray

Radii of the discs for normalized weights: R sqrt(alpha w^beta).

In log mode the weight enters as log(1 + w).

distribute_components

distribute_components(
    x0: float,
    y0: float,
    radius: float,
    weights: ndarray,
    alpha: float,
    beta: float,
    log_mode: bool,
    seed: int,
) -> tuple[ndarray, ndarray, ndarray]

Pack len(weights) discs inside the disc of radius radius at (x0, y0).

Port of distribute_components: every disc starts with radius R sqrt(alpha w^beta) (w the normalized weight) at a random spot; discs outside the container or overlapping another are moved to a random free spot (up to 10 N tries each, the smaller of an overlapping pair first); while every disc could be settled, alpha grows by 1 % and the discs are inflated in place (pulled towards the center by the radius increase); when a disc cannot be settled the last round is undone by dividing alpha by 1.1.

The C++ re-seeded its generator with -seed on every call, so every packing of the same weights is the same; that is reproduced with a fresh generator per call.

Deliberate deviations in the inflation step: the C++ pulled the discs towards the origin of the picture, not the container center (harmless for the root, wrong for nested components, which the next round pushed back at random), and it overwrote x before computing y from it, so the pull skewed the angle; here the discs keep their angle and are pulled towards (x0, y0).

Parameters:

Name Type Description Default
x0 float

Center of the container

required
y0 float

Center of the container

required
radius float

Radius of the container

required
weights ndarray

One positive weight per disc (normalized here)

required
alpha float

Constant and exponent of the disc area law

required
beta float

Constant and exponent of the disc area law

required
log_mode bool

log coordinate distribution (log(1 + w) instead of w)

required
seed int

Seed of the random generator

required

Returns:

Type Description
(x, y, r) : tuple of np.ndarray

Centers and radii of the discs

Rendering

matplotlib_renderer

Matplotlib-based renderer for network visualization.

The picture is laid out as the C++ SVG writer did (svg.cpp, graphics_kcores.cpp generateNetworkFile): the viewport is the layout's frame, scaled uniformly to the requested pixel size and centered; edges are drawn under the nodes in increasing index order; the color and degree legends are drawn in layout units in the margins of the frame so they scale with the picture.

render_network

render_network(
    graph: Graph,
    layout: VisualizationLayout,
    decomposition: DecompositionResult,
    config: VisualizationConfig,
    output_path: Path | str,
    node_names: dict[int, str] | None = None,
    *,
    custom_colors: bool = False,
    measure: MeasureType = MCORE,
    communities: CommunityResult | None = None,
    community_config: CommunityConfig | None = None,
) -> None

Render network visualization using matplotlib.

Parameters:

Name Type Description Default
graph Graph

Network graph

required
layout VisualizationLayout

Layout with node positions and visual properties

required
decomposition DecompositionResult

Decomposition results

required
config VisualizationConfig

Visualization configuration

required
output_path Union[Path, str]

Output file path (.png, .pdf, .svg)

required
node_names Optional[Dict[int, str]]

Node names for the labels (config.show_node_labels): a mapping labels the nodes it names (an empty one labels nothing); None labels every node with its number (the C++ -names with no file)

None
custom_colors bool

Nodes were colored from a colors file: the color legend is not drawn (the C++ hid it with -colorsFile)

False
measure MeasureType

Labels of the k-dense legend: mcore prints k - 2, kdense prints k

MCORE
communities Optional[CommunityResult]

Communities to outline under the network: a translucent convex hull (community_config.draw_boundaries) and/or circle (draw_circles) per community, in the community's color

None
community_config Optional[CommunityConfig]

Which overlays to draw and how (defaults when None)

None

Examples:

>>> render_network(G, layout, decomp, config, "output.png")

select_visible_edges

select_visible_edges(
    graph: Graph,
    config: VisualizationConfig,
    seed: int | None = None,
) -> list[tuple[int, int]]

Select the edges to draw: an independent Bernoulli draw per edge, as the C++ did.

Parameters:

Name Type Description Default
graph Graph

Network graph

required
config VisualizationConfig

edges_percent and min_edges

required
seed int | None

Seed of the draw, so the same seed gives the same picture

None

Returns:

Type Description
List[Tuple[int, int]]

Edges to render, in the graph's order

Notes

Every edge is kept with probability max(edges_percent, min_edges / E) (graphics_kcores.cpp addCluster), so about that fraction of the edges is drawn, spread over all shells in proportion to their edge counts.

colors

Color scales of the C++ LaNet-vi (types.cpp, graphics_kcores.cpp).

The rainbow runs magenta -> blue -> cyan -> green -> yellow -> red, so the maximum index is red; the black-and-white scale runs white -> gray -> black, so the maximum index is black. The stop lists are the C++ ones verbatim: some stops lie outside [0, 1] (white is 2.0, the blackwhite positions are -0.75 and 1.23) so that the extreme indices saturate; the results are clamped as the SVG writer did.

get_color_scale

get_color_scale(
    scheme: ColorScheme,
) -> list[tuple[RGB, float]]

Get the color stops of a color scheme.

Parameters:

Name Type Description Default
scheme ColorScheme

Color scheme to use

required

Returns:

Type Description
list[tuple[RGB, float]]

(color, position) stops; bw and bwi share the same list (the interlacing happens in the position, not in the stops)

interpolate_color

interpolate_color(
    color1: RGB, color2: RGB, alpha: float
) -> RGB

Linearly interpolate between two colors.

Parameters:

Name Type Description Default
color1 RGB

First color (R, G, B)

required
color2 RGB

Second color (R, G, B)

required
alpha float

Interpolation factor (0.0 = color1, 1.0 = color2)

required

Returns:

Type Description
RGB

Interpolated color

clamp_color

clamp_color(color: RGB) -> RGB

Clamp every channel to [0, 1] (the SVG writer saturated at 255).

scale_color

scale_color(color: RGB, factor: float) -> RGB

Multiply every channel by factor and clamp (edge and luminosity shading).

default_node_color

default_node_color(background: BackgroundColor) -> RGB

Color of a node absent from the colors file: white on black, black on white.

compute_shell_color

compute_shell_color(
    shell_index: int,
    max_shell_index: int,
    color_scheme: ColorScheme,
    color_scale_max: int | None = None,
    *,
    background: BackgroundColor = BLACK,
    dense: bool = False,
) -> RGB

Compute the color of a shell or dense index (computeHostColorByShellIndex).

Parameters:

Name Type Description Default
shell_index int

Shell or dense index of the node

required
max_shell_index int

Maximum shell/dense index in the network

required
color_scheme ColorScheme

Color scheme to use

required
color_scale_max int | None

Index shown with the last color of the scale (-colorScaleMaxValue); higher indices get the same color. Defaults to max_shell_index.

None
background BackgroundColor

Background of the picture; only k-dense colors depend on it

BLACK
dense bool

Apply the k-dense rules of graphics_kdenses.cpp: on a black background the luminosity alternates as for k-cores, on a white background it is a constant 0.9

False

Returns:

Type Description
RGB

RGB color, every channel in [0, 1]

Notes

Position on the scale is (min(i, max) - 1) / (max - 1) for col and bw; bwi interlaces even and odd indices over the two halves of the scale. A single shell (max == 1) is red. With col, consecutive shells alternate a luminosity of 0.7 and 1.2 so neighboring rings stay distinguishable.

Examples:

>>> compute_shell_color(5, 5, ColorScheme.COLOR)  # top shell: red, luminosity 1.2
(1.0, 0.24, 0.24)

create_matplotlib_colormap

create_matplotlib_colormap(
    color_scheme: ColorScheme, n_colors: int = 256
) -> LinearSegmentedColormap

Create a matplotlib colormap from a color scheme.

Parameters:

Name Type Description Default
color_scheme ColorScheme

Color scheme to use

required
n_colors int

Number of discrete colors in the colormap

256

Returns:

Type Description
LinearSegmentedColormap

Matplotlib colormap over [0, 1]; stops outside that range are clipped to it, so the map starts and ends on the saturated colors

Input and output

readers

Input/output functions for reading network data.

read_edge_list

read_edge_list(
    file_path: Path | str,
    weighted: bool = False,
    directed: bool = False,
    multigraph: bool = False,
    delimiter: str | None = None,
    comment: str = "#",
) -> Graph

Read an edge list file and create a NetworkX graph.

Lines hold source target [weight] separated by whitespace (or by delimiter if given). Behavior follows the C++ LaNet-vi reader: an unused third column is ignored, a missing weight on a weighted graph counts as 1.0, and self-loops are dropped (with a warning) because the decompositions do not accept them.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to the edge list file (supports .txt, .gz, .bz2)

required
weighted bool

Whether edges have weights (third column)

False
directed bool

Whether to create a directed graph

False
multigraph bool

Whether to allow parallel edges

False
delimiter Optional[str]

Column delimiter; None (default) accepts any run of whitespace

None
comment str

Comment character to skip lines

'#'

Returns:

Type Description
Graph

NetworkX graph constructed from the edge list

Raises:

Type Description
ValueError

If the file has fewer than two columns or non-integer node ids

Examples:

>>> g = read_edge_list("network.txt", weighted=True)
>>> g = read_edge_list("network.txt.bz2", weighted=False, directed=True)

read_caida_snapshot

read_caida_snapshot(
    url: str, timeout: int = 30
) -> tuple[Graph, DataFrame]

Fetch and parse CAIDA AS-Relationships data.

This function downloads a CAIDA AS-relationships snapshot in bz2 format, decompresses it, and creates both a NetworkX graph and a pandas DataFrame.

Parameters:

Name Type Description Default
url str

URL to the CAIDA .as-rel.txt.bz2 file

required
timeout int

Request timeout in seconds

30

Returns:

Name Type Description
graph Graph

NetworkX graph with AS relationships

dataframe DataFrame

DataFrame with columns: provider, customer, relationship_type

Raises:

Type Description
RequestException

If the download fails

ValueError

If decompression or parsing fails

Examples:

>>> url = "https://publicdata.caida.org/.../20251001.as-rel.txt.bz2"
>>> graph, df = read_caida_snapshot(url)

read_node_names

read_node_names(
    file_path: Path | str,
    delimiter: str | None = None,
    comment: str = "#",
) -> dict[int, str]

Read node names from a file.

Each line is node_id name; the name is everything after the first separator, so it may contain spaces. Surrounding quotes are removed, as in the C++ reader.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to file with node names (format: node_id name)

required
delimiter Optional[str]

Separator between the id and the name; None (default) means any run of whitespace

None
comment str

Comment character

'#'

Returns:

Type Description
Dict[int, str]

Mapping from node ID to node name

Examples:

>>> names = read_node_names("nodes.txt")
>>> names[42]
'node_name_42'

read_node_colors

read_node_colors(
    file_path: Path | str,
    delimiter: str | None = None,
    comment: str = "#",
) -> dict[int, tuple[float, float, float]]

Read node colors from a file.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to file with node colors (format: node_id r g b) RGB values should be in range [0.0, 1.0]

required
delimiter Optional[str]

Column delimiter; None (default) accepts any run of whitespace

None
comment str

Comment character

'#'

Returns:

Type Description
Dict[int, Tuple[float, float, float]]

Mapping from node ID to (r, g, b) tuple

Examples:

>>> colors = read_node_colors("colors.txt")
>>> colors[42]
(1.0, 0.0, 0.0)  # Red

writers

Output functions for writing decomposition results and visualizations.

write_decomposition_csv

write_decomposition_csv(
    result: DecompositionResult, output_path: Path | str
) -> None

Export decomposition results to CSV.

Parameters:

Name Type Description Default
result DecompositionResult

Decomposition result to export

required
output_path Union[Path, str]

Output file path

required

Examples:

>>> write_decomposition_csv(decomp_result, "cores.csv")

write_dcore_table

write_dcore_table(
    table: dict[int, dict[int, int]], file_path: Path | str
) -> None

Write a (k, l)-core table as the C++ dcores_list.txt.

One node k l line per node and out-degree threshold l, after a # node k l header, sorted by l then node.

Parameters:

Name Type Description Default
table Dict[int, Dict[int, int]]

{l: {node: k}} from compute_dcore_table

required
file_path Union[Path, str]

Output path

required

Examples:

>>> write_dcore_table(compute_dcore_table(G), "dcores_list.txt")

write_kconnectivity

write_kconnectivity(
    kconnectivity: dict[int, int],
    node_indices: dict[int, int],
    file_path: Path | str,
) -> None

Write the k-connectivity of every node as the C++ log/kconn.log.

One node shell_index k_connectivity line per node after a # node shell_index k_connectivity header, sorted by shell index then node, as the C++ walked its cores list; 0 marks a node that is not k-connected.

Parameters:

Name Type Description Default
kconnectivity Dict[int, int]

{node: k_connectivity} from compute_kconnectivity

required
node_indices Dict[int, int]

Shell index of every node (DecompositionResult.node_indices)

required
file_path Union[Path, str]

Output path

required

Examples:

>>> write_kconnectivity(net.kconnectivity, net.decomposition.node_indices, "kconn.txt")

write_decomposition_json

write_decomposition_json(
    result: DecompositionResult,
    output_path: Path | str,
    include_components: bool = True,
    include_metadata: bool = True,
) -> None

Export decomposition results to JSON with enhanced details.

Parameters:

Name Type Description Default
result DecompositionResult

Decomposition result to export

required
output_path Union[Path, str]

Output file path

required
include_components bool

Include component information (default: True)

True
include_metadata bool

Include metadata (default: True)

True

Examples:

>>> write_decomposition_json(decomp_result, "cores.json")
>>> write_decomposition_json(decomp_result, "cores_full.json", include_components=True)

write_node_attributes

write_node_attributes(
    node_data: dict[int, dict], output_path: Path | str
) -> None

Export node attributes to CSV.

Parameters:

Name Type Description Default
node_data Dict[int, Dict]

Mapping from node ID to attribute dictionary

required
output_path Union[Path, str]

Output file path

required

Examples:

>>> attrs = {1: {"shell": 3, "x": 0.5, "y": 0.3}, 2: {"shell": 2, "x": 0.2, "y": 0.1}}
>>> write_node_attributes(attrs, "nodes.csv")

write_graph_json

write_graph_json(
    graph: Graph,
    output_path: Path | str,
    include_node_attrs: bool = True,
    include_edge_attrs: bool = True,
) -> None

Export NetworkX graph to JSON using node-link format.

Parameters:

Name Type Description Default
graph Graph

NetworkX graph to export

required
output_path Union[Path, str]

Output file path

required
include_node_attrs bool

Include node attributes (default: True)

True
include_edge_attrs bool

Include edge attributes (default: True)

True

Examples:

>>> import networkx as nx
>>> G = nx.karate_club_graph()
>>> write_graph_json(G, "karate.json")
Notes

Uses NetworkX's node-link JSON format, which is compatible with D3.js and other visualization libraries.

write_community_json

write_community_json(
    community_result: CommunityResult,
    output_path: Path | str,
) -> None

Export community detection results to JSON.

Parameters:

Name Type Description Default
community_result CommunityResult

Community detection result

required
output_path Union[Path, str]

Output file path

required

Examples:

>>> from lanet_vi.community import detect_communities_louvain
>>> communities = detect_communities_louvain(G)
>>> write_community_json(communities, "communities.json")

write_edge_list

write_edge_list(
    graph: Graph,
    output_path: Path | str,
    include_weights: bool = True,
    delimiter: str = " ",
) -> None

Write graph to edge list file using pandas.

Parameters:

Name Type Description Default
graph Graph

NetworkX graph

required
output_path Union[Path, str]

Output file path

required
include_weights bool

Include edge weights if available (default: True)

True
delimiter str

Column delimiter (default: space, the format read_edge_list and the C++ LaNet-vi expects)

' '

Examples:

>>> write_edge_list(G, "network.txt")

config_loader

Configuration loading from YAML files.

load_config_from_yaml

load_config_from_yaml(file_path: Path | str) -> LaNetConfig

Load LaNet-vi configuration from a YAML file.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to YAML configuration file

required

Returns:

Type Description
LaNetConfig

Validated configuration object

Raises:

Type Description
FileNotFoundError

If the configuration file doesn't exist

YAMLError

If the YAML file is malformed

ValidationError

If the configuration values are invalid

Examples:

>>> config = load_config_from_yaml("config.yaml")
>>> net = Network(graph, config)
>>> net.decompose()
>>> net.visualize("output.png")

read_config_yaml

read_config_yaml(file_path: Path | str) -> dict[str, Any]

Read a YAML configuration file into a plain dictionary without validating it.

Used by the CLI to merge the file with explicit command-line flags before a single validation pass.

Parameters:

Name Type Description Default
file_path Union[Path, str]

Path to the YAML file

required

Returns:

Type Description
Dict[str, Any]

Nested dictionary as written in the file (empty if the file is empty)

Raises:

Type Description
FileNotFoundError

If the file does not exist

ValueError

If the top level of the file is not a mapping

save_config_to_yaml

save_config_to_yaml(
    config: LaNetConfig, file_path: Path | str
) -> None

Save LaNet-vi configuration to a YAML file.

Parameters:

Name Type Description Default
config LaNetConfig

Configuration object to save

required
file_path Union[Path, str]

Path where YAML file should be saved

required

Examples:

>>> config = LaNetConfig()
>>> config.visualization.width = 1920
>>> save_config_to_yaml(config, "my_config.yaml")

Generators

generators

Random graph generation utilities.

This module provides functions for generating various types of random graphs for testing, benchmarking, and demonstration purposes.

generate_barabasi_albert

generate_barabasi_albert(
    n: int, m: int, seed: int | None = None
) -> Graph

Generate Barabási-Albert scale-free network.

Creates a random graph using preferential attachment. The graph exhibits a power-law degree distribution, common in real-world networks.

Parameters:

Name Type Description Default
n int

Number of nodes

required
m int

Number of edges to attach from a new node to existing nodes

required
seed Optional[int]

Random seed for reproducibility

None

Returns:

Type Description
Graph

Generated scale-free graph

Examples:

>>> G = generate_barabasi_albert(n=1000, m=3, seed=42)
>>> G.number_of_nodes()
1000
Notes

The Barabási-Albert model produces graphs with: - Power-law degree distribution P(k) ~ k^(-γ) where γ ≈ 3 - High clustering coefficient - Short average path length (small-world property)

This model is useful for simulating social networks, the internet, and citation networks.

generate_erdos_renyi

generate_erdos_renyi(
    n: int,
    p: float | None = None,
    m: int | None = None,
    seed: int | None = None,
    directed: bool = False,
) -> Graph

Generate Erdős-Rényi random graph.

Creates a random graph using either G(n,p) or G(n,m) model: - G(n,p): n nodes, each edge exists with probability p - G(n,m): n nodes, exactly m edges

Parameters:

Name Type Description Default
n int

Number of nodes

required
p Optional[float]

Probability of edge creation (for G(n,p) model). Must be in [0,1]. Either p or m must be specified, but not both.

None
m Optional[int]

Number of edges (for G(n,m) model). Either p or m must be specified, but not both.

None
seed Optional[int]

Random seed for reproducibility

None
directed bool

If True, generate directed graph (default: False)

False

Returns:

Type Description
Graph or DiGraph

Generated random graph

Raises:

Type Description
ValueError

If neither p nor m is specified, or if both are specified

Examples:

>>> # G(n,p) model: 100 nodes, 10% edge probability
>>> G = generate_erdos_renyi(n=100, p=0.1, seed=42)
>>> G.number_of_nodes()
100
>>> # G(n,m) model: 100 nodes, exactly 500 edges
>>> G = generate_erdos_renyi(n=100, m=500, seed=42)
>>> G.number_of_edges()
500
Notes

Uses NetworkX's fast_gnp_random_graph and gnm_random_graph functions. For large dense graphs, G(n,m) model may be faster.

generate_powerlaw_cluster

generate_powerlaw_cluster(
    n: int, m: int, p: float, seed: int | None = None
) -> Graph

Generate Holme-Kim powerlaw cluster graph.

Creates a scale-free graph with higher clustering than Barabási-Albert, using triangle formation mechanism.

Parameters:

Name Type Description Default
n int

Number of nodes

required
m int

Number of random edges to add for each new node

required
p float

Probability of adding a triangle after adding a random edge (0 ≤ p ≤ 1)

required
seed Optional[int]

Random seed for reproducibility

None

Returns:

Type Description
Graph

Generated powerlaw cluster graph

Examples:

>>> G = generate_powerlaw_cluster(n=1000, m=3, p=0.5, seed=42)
>>> G.number_of_nodes()
1000
Notes

The Holme-Kim model extends Barabási-Albert to include: - Power-law degree distribution (like BA) - Higher clustering coefficient (via triangle formation)

This model better represents real networks where clustering matters, such as social networks and collaboration networks.

generate_watts_strogatz

generate_watts_strogatz(
    n: int, k: int, p: float, seed: int | None = None
) -> Graph

Generate Watts-Strogatz small-world network.

Creates a random graph with small-world properties: high clustering and short average path length.

Parameters:

Name Type Description Default
n int

Number of nodes

required
k int

Each node is connected to k nearest neighbors in ring topology

required
p float

Probability of rewiring each edge (0 ≤ p ≤ 1) - p=0: regular ring lattice - p=1: random graph - 0<p<1: small-world network

required
seed Optional[int]

Random seed for reproducibility

None

Returns:

Type Description
Graph

Generated small-world graph

Examples:

>>> G = generate_watts_strogatz(n=1000, k=6, p=0.3, seed=42)
>>> G.number_of_nodes()
1000
Notes

The Watts-Strogatz model interpolates between: - Regular lattice (p=0): high clustering, long path length - Random graph (p=1): low clustering, short path length - Small-world (intermediate p): high clustering AND short path length

This model is useful for modeling social networks and neural networks.

Metrics

metrics

Network metrics and information theory measures.

This module provides various metrics for analyzing network structure, including mutual information, entropy, and similarity measures.

compute_mutual_information

compute_mutual_information(
    partition1: dict[int, int], partition2: dict[int, int]
) -> float

Compute mutual information between two partitions.

Mutual information I(X;Y) measures how much knowing one partition tells us about the other: I(X;Y) = Σᵢ Σⱼ p(i,j) log₂(p(i,j) / (p(i)p(j)))

Parameters:

Name Type Description Default
partition1 Dict[int, int]

First partition (node ID -> cluster ID)

required
partition2 Dict[int, int]

Second partition (node ID -> cluster ID)

required

Returns:

Type Description
float

Mutual information in bits

Raises:

Type Description
ValueError

If partitions have different node sets

Examples:

>>> p1 = {0: 0, 1: 0, 2: 1, 3: 1}
>>> p2 = {0: 0, 1: 0, 2: 1, 3: 1}  # Identical partition
>>> mi = compute_mutual_information(p1, p2)
>>> mi  # Should equal entropy of either partition
1.0
>>> p2 = {0: 1, 1: 0, 2: 1, 3: 0}  # Completely different
>>> mi = compute_mutual_information(p1, p2)
>>> mi  # Should be close to 0
0.0
Notes
  • MI = 0 when partitions are independent
  • MI = H(X) = H(Y) when partitions are identical
  • MI ≤ min(H(X), H(Y))

compute_normalized_mutual_information

compute_normalized_mutual_information(
    partition1: dict[int, int],
    partition2: dict[int, int],
    method: str = "arithmetic",
) -> float

Compute normalized mutual information (NMI) between two partitions.

NMI normalizes MI to [0, 1] range by dividing by entropy: - NMI = 0: partitions are independent - NMI = 1: partitions are identical

Parameters:

Name Type Description Default
partition1 Dict[int, int]

First partition

required
partition2 Dict[int, int]

Second partition

required
method str

Normalization method (default: "arithmetic") - "arithmetic": NMI = 2*I(X;Y) / (H(X) + H(Y)) - "geometric": NMI = I(X;Y) / sqrt(H(X) * H(Y)) - "min": NMI = I(X;Y) / min(H(X), H(Y)) - "max": NMI = I(X;Y) / max(H(X), H(Y))

'arithmetic'

Returns:

Type Description
float

Normalized mutual information in [0, 1]

Examples:

>>> p1 = {0: 0, 1: 0, 2: 1, 3: 1}
>>> p2 = {0: 0, 1: 0, 2: 1, 3: 1}  # Identical
>>> nmi = compute_normalized_mutual_information(p1, p2)
>>> nmi
1.0
>>> p2 = {0: 0, 1: 1, 2: 2, 3: 3}  # Different
>>> nmi = compute_normalized_mutual_information(p1, p2)
>>> nmi < 0.5
True
Notes

The choice of normalization affects the score: - Arithmetic mean is most common in literature - Geometric mean is symmetric - Min/max provide bounds on the score

compute_partition_entropy

compute_partition_entropy(
    partition: dict[int, int],
) -> float

Compute Shannon entropy of a partition.

The entropy H(X) measures the uncertainty in the partition: H(X) = -Σ p(x) log₂ p(x)

Parameters:

Name Type Description Default
partition Dict[int, int]

Mapping from node ID to cluster/community ID

required

Returns:

Type Description
float

Entropy in bits

Examples:

>>> partition = {0: 0, 1: 0, 2: 1, 3: 1}  # 2 clusters, balanced
>>> entropy = compute_partition_entropy(partition)
>>> entropy  # Should be close to 1.0 bit
1.0
Notes
  • Maximum entropy occurs when all clusters have equal size
  • Minimum entropy (0) occurs when all nodes in one cluster
  • Uses log base 2, so entropy is measured in bits

compare_decompositions

compare_decompositions(
    decomp1: DecompositionResult,
    decomp2: DecompositionResult,
) -> dict[str, float]

Compare two network decompositions.

Compares decompositions as partitions where each k-core/k-dense level is treated as a cluster.

Parameters:

Name Type Description Default
decomp1 DecompositionResult

First decomposition

required
decomp2 DecompositionResult

Second decomposition

required

Returns:

Type Description
Dict[str, float]

Similarity metrics (see compare_partitions)

Examples:

>>> from lanet_vi.decomposition import compute_kcores
>>> decomp1 = compute_kcores(G1)
>>> decomp2 = compute_kcores(G2)
>>> metrics = compare_decompositions(decomp1, decomp2)
Notes

Useful for comparing: - k-core vs k-dense on same graph - Same decomposition on different graphs - Community detection vs structural decomposition

compare_partitions

compare_partitions(
    partition1: dict[int, int], partition2: dict[int, int]
) -> dict[str, float]

Compare two partitions using multiple similarity metrics.

Computes several metrics for comprehensive comparison: - Normalized Mutual Information (NMI) - Adjusted Rand Index (ARI) - Variation of Information (VI)

Parameters:

Name Type Description Default
partition1 Dict[int, int]

First partition

required
partition2 Dict[int, int]

Second partition

required

Returns:

Type Description
Dict[str, float]

Dictionary with similarity metrics: - "nmi": Normalized Mutual Information [0, 1] - "ari": Adjusted Rand Index [-1, 1] - "vi": Variation of Information (lower is better) - "num_clusters_1": Number of clusters in partition 1 - "num_clusters_2": Number of clusters in partition 2

Examples:

>>> p1 = {0: 0, 1: 0, 2: 1, 3: 1}
>>> p2 = {0: 0, 1: 0, 2: 1, 3: 1}
>>> metrics = compare_partitions(p1, p2)
>>> metrics["nmi"]
1.0
>>> metrics["ari"]
1.0

compute_adjusted_rand_index

compute_adjusted_rand_index(
    partition1: dict[int, int], partition2: dict[int, int]
) -> float

Compute Adjusted Rand Index (ARI) between two partitions.

ARI measures similarity between two clusterings, adjusted for chance: - ARI = 1: partitions are identical - ARI = 0: partitions are independent (random) - ARI < 0: worse than random

Parameters:

Name Type Description Default
partition1 Dict[int, int]

First partition (node ID -> cluster ID)

required
partition2 Dict[int, int]

Second partition (node ID -> cluster ID)

required

Returns:

Type Description
float

Adjusted Rand Index in [-1, 1]

Raises:

Type Description
ValueError

If partitions have different node sets

Examples:

>>> p1 = {0: 0, 1: 0, 2: 1, 3: 1}
>>> p2 = {0: 0, 1: 0, 2: 1, 3: 1}  # Identical
>>> ari = compute_adjusted_rand_index(p1, p2)
>>> ari
1.0
>>> p2 = {0: 0, 1: 1, 2: 0, 3: 1}  # Different
>>> ari = compute_adjusted_rand_index(p1, p2)
>>> ari < 1.0
True
Notes

Uses sklearn's implementation. ARI is widely used for comparing clustering algorithms and community detection methods.

Community detection

community

Community detection algorithms for network analysis.

This module provides community detection functionality including: - Louvain algorithm for modularity optimization - Greedy modularity maximization - detect_communities, which runs the algorithm named in a CommunityConfig

The overlays that draw a CommunityResult on a picture live in lanet_vi.visualization.community_viz.

Community

Community(**data: Any)

Bases: BaseModel

Represents a single community in a network.

Attributes:

Name Type Description
id int

Unique community identifier

nodes List[int]

List of node IDs in this community

size int

Number of nodes in the community

Initialize community and compute size if not provided.

CommunityResult

CommunityResult(**data: Any)

Bases: BaseModel

Results from community detection algorithm.

Attributes:

Name Type Description
algorithm str

Name of the community detection algorithm used

communities List[Community]

List of detected communities

node_to_community Dict[int, int]

Mapping from node ID to community ID

num_communities int

Total number of communities detected

modularity float

Modularity score of the partition (if applicable)

Initialize community result and compute derived fields.

get_community

get_community(community_id: int) -> Community | None

Get a community by its ID.

Parameters:

Name Type Description Default
community_id int

Community identifier

required

Returns:

Type Description
Community | None

The community if found, None otherwise

get_node_community

get_node_community(node: int) -> int | None

Get the community ID for a given node.

Parameters:

Name Type Description Default
node int

Node identifier

required

Returns:

Type Description
int | None

Community ID if node is found, None otherwise

get_community_sizes

get_community_sizes() -> dict[int, int]

Get the size of each community.

Returns:

Type Description
Dict[int, int]

Mapping from community ID to size

detect_communities_greedy_modularity

detect_communities_greedy_modularity(
    graph: Graph,
    weight: str | None = "weight",
    resolution: float = 1.0,
) -> CommunityResult

Detect communities using greedy modularity maximization.

This is an alternative to Louvain that may be faster on some graphs but typically produces lower modularity scores.

Parameters:

Name Type Description Default
graph Graph

Input graph (undirected)

required
weight Optional[str]

Edge attribute to use as weight (default: "weight")

'weight'
resolution float

Resolution parameter for modularity (default: 1.0). Higher values lead to more communities.

1.0

Returns:

Type Description
CommunityResult

Detected communities with modularity score

Notes

Uses NetworkX's community.greedy_modularity_communities.

detect_communities_louvain

detect_communities_louvain(
    graph: Graph,
    weight: str | None = "weight",
    resolution: float = 1.0,
    seed: int | None = None,
) -> CommunityResult

Detect communities using the Louvain algorithm.

The Louvain method is a greedy optimization algorithm that attempts to maximize the modularity of a partition of the network. It works on both weighted and unweighted graphs.

Parameters:

Name Type Description Default
graph Graph

Input graph (undirected). For directed graphs, the graph will be converted to undirected first.

required
weight Optional[str]

Edge attribute to use as weight (default: "weight"). Set to None for unweighted graphs.

'weight'
resolution float

Resolution parameter for modularity (default: 1.0). Higher values lead to more communities.

1.0
seed Optional[int]

Random seed for reproducibility (default: None)

None

Returns:

Type Description
CommunityResult

Detected communities with modularity score

Examples:

>>> G = nx.karate_club_graph()
>>> result = detect_communities_louvain(G)
>>> result.num_communities
4
>>> result.modularity > 0.4
True
Notes

This function uses NetworkX's community.louvain_communities which implements the algorithm from: Blondel, V.D. et al. "Fast unfolding of communities in large networks." Journal of Statistical Mechanics (2008).

detect_communities

detect_communities(
    graph: Graph,
    config: CommunityConfig,
    seed: int | None = None,
) -> CommunityResult

Run the community detection algorithm named in config.

Parameters:

Name Type Description Default
graph Graph

Input graph; directed graphs are analyzed as their undirected version

required
config CommunityConfig

algorithm and resolution are read; detect_communities is not (the caller decided to detect)

required
seed Optional[int]

Random seed of the Louvain algorithm (greedy modularity is deterministic)

None

Returns:

Type Description
CommunityResult

Detected communities with modularity score

Raises:

Type Description
ValueError

If config.algorithm is not a known algorithm.

community_viz

Community visualization utilities.

This module provides functions for visualizing network communities, including: - Coloring nodes by community membership - Drawing community boundaries - Creating community-based color palettes

get_community_colors

get_community_colors(
    num_communities: int, colormap: str = "tab20"
) -> list[tuple[float, float, float]]

Generate distinct colors for communities.

Parameters:

Name Type Description Default
num_communities int

Number of communities to generate colors for

required
colormap str

Matplotlib colormap name (default: "tab20", 20 distinct colors)

'tab20'

Returns:

Type Description
List[Tuple[float, float, float]]

List of RGB color tuples, one per community id (0-based)

Notes

A qualitative colormap (tab10, tab20, Set3, ...) is used entry by entry, so up to its size no two communities share a color; with more communities than entries the colors are spread evenly over hsv instead. A continuous colormap (viridis, hsv, ...) is sampled evenly over its range. The default tab20 alternates a dark and a light shade of each hue, so up to 10 communities take the dark shades (tab10) and stay telling apart.

assign_node_colors_by_community

assign_node_colors_by_community(
    community_result: CommunityResult,
    node_positions: dict[int, tuple[float, float]],
    colormap: str = "tab20",
) -> dict[int, tuple[float, float, float]]

Assign colors to nodes based on their community membership.

Parameters:

Name Type Description Default
community_result CommunityResult

Community detection result

required
node_positions Dict[int, Tuple[float, float]]

Node positions (used to determine which nodes to color)

required
colormap str

Matplotlib colormap name

'tab20'

Returns:

Type Description
Dict[int, Tuple[float, float, float]]

Mapping from node ID to RGB color tuple

Examples:

>>> from lanet_vi.community import detect_communities_louvain
>>> import networkx as nx
>>> G = nx.karate_club_graph()
>>> communities = detect_communities_louvain(G)
>>> positions = nx.spring_layout(G)
>>> colors = assign_node_colors_by_community(communities, positions)

draw_community_boundaries

draw_community_boundaries(
    ax: Axes,
    community_result: CommunityResult,
    node_positions: dict[int, tuple[float, float]],
    alpha: float = 0.2,
    linewidth: float = 2.0,
    colormap: str = "tab20",
    zorder: float = 0.0,
) -> None

Draw convex hull boundaries around communities.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axes to draw on

required
community_result CommunityResult

Community detection result

required
node_positions Dict[int, Tuple[float, float]]

Node positions in 2D space

required
alpha float

Transparency of the boundary fill (default: 0.2)

0.2
linewidth float

Width of the boundary line (default: 2.0)

2.0
colormap str

Matplotlib colormap name

'tab20'
zorder float

Drawing order of the hulls (default 0: behind the edges and nodes)

0.0
Notes

Only draws boundaries for communities with 3 or more nodes (required for convex hull).

draw_community_circles

draw_community_circles(
    ax: Axes,
    community_result: CommunityResult,
    node_positions: dict[int, tuple[float, float]],
    padding: float = 0.1,
    alpha: float = 0.15,
    linewidth: float = 2.0,
    colormap: str = "tab20",
    zorder: float = 0.0,
) -> None

Draw circles around communities based on their bounding box.

This is an alternative to convex hulls that works better for small communities.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axes to draw on

required
community_result CommunityResult

Community detection result

required
node_positions Dict[int, Tuple[float, float]]

Node positions in 2D space

required
padding float

Extra padding around community as fraction of radius (default: 0.1)

0.1
alpha float

Transparency of the circle fill (default: 0.15)

0.15
linewidth float

Width of the circle line (default: 2.0)

2.0
colormap str

Matplotlib colormap name

'tab20'
zorder float

Drawing order of the circles (default 0: behind the edges and nodes)

0.0