Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cookbook

Practical patterns for common graph analysis tasks. Each recipe is self-contained — paste it into a file and run with cargo run --example <name>, or use the snippets directly in your project.

Find the most important nodes

Combine multiple centrality measures to identify key vertices:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, pagerank, betweenness, harmonic_centrality};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,2),(1,3),(2,3),(3,4),(4,5),(5,6),(6,4),(6,7),(7,8),(8,9),(9,7)],
    false, None
).unwrap();

let pr = pagerank(&g).unwrap();
let bc = betweenness(&g).unwrap();
let hc = harmonic_centrality(&g).unwrap();

// Rank vertices by PageRank
let mut ranked: Vec<(usize, f64)> = pr.iter().copied().enumerate().collect();
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());

println!("Top vertices by PageRank:");
for &(v, score) in ranked.iter().take(3) {
    println!("  v{v}: PR={score:.4}  betweenness={:.2}  harmonic={:.4}",
        bc[v], hc[v]);
}
}

Compare community detection algorithms

Run multiple algorithms and measure agreement:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, louvain, leiden, label_propagation,
                  compare_communities, CommunityComparison};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,2),(3,4),(3,5),(4,5),(2,3),(6,7),(6,8),(7,8),(5,6)],
    false, None
).unwrap();

let c_louvain = louvain(&g).unwrap();
let c_leiden = leiden(&g).unwrap();
let c_lpa = label_propagation(&g).unwrap();

println!("Louvain:  {} communities, modularity {:.4}",
    *c_louvain.membership.iter().max().unwrap() + 1, c_louvain.modularity);
println!("Leiden:   {} communities, modularity {:.4}",
    *c_leiden.membership.iter().max().unwrap() + 1, c_leiden.modularity);

// Compare partitions using Normalized Mutual Information
let nmi = compare_communities(
    &c_louvain.membership,
    &c_leiden.membership,
    CommunityComparison::NormalizedMutualInformation,
).unwrap();
println!("NMI(Louvain, Leiden) = {nmi:.4}");

let ari = compare_communities(
    &c_louvain.membership,
    &c_lpa.membership,
    CommunityComparison::AdjustedRand,
).unwrap();
println!("ARI(Louvain, LPA) = {ari:.4}");
}

Build a weighted graph and find shortest paths

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, dijkstra_distances, dijkstra_paths};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,3),(2,3),(3,4),(2,4)],
    false, None
).unwrap();

let weights = vec![1.0, 4.0, 2.0, 1.0, 3.0, 2.0];

// Distance vector from source 0
let dist = dijkstra_distances(&g, 0, &weights).unwrap();
for (v, d) in dist.iter().enumerate() {
    match d {
        Some(d) => println!("  0 -> {v}: distance {d:.1}"),
        None => println!("  0 -> {v}: unreachable"),
    }
}

// Full path reconstruction
let paths = dijkstra_paths(&g, 0, &weights).unwrap();
if let Some(parent) = paths.parents[4] {
    println!("Vertex 4 reached via vertex {parent}");
}
}

Analyze graph connectivity

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, connected_components, articulation_points, bridges};

let g = Graph::from_edges(
    &[(0,1),(1,2),(2,0),(2,3),(3,4),(4,5),(5,3)],
    false, None
).unwrap();

let cc = connected_components(&g).unwrap();
println!("Components: {}", cc.count);

let ap = articulation_points(&g).unwrap();
println!("Articulation points: {:?}", ap);

let br = bridges(&g).unwrap();
println!("Bridges: {:?}", br);
}

Generate and analyze random graphs

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, density, connected_components,
                  erdos_renyi_gnp, barabasi_game_bag, watts_strogatz_game};

// Erdos-Renyi: G(n, p)
let er = erdos_renyi_gnp(100, 0.05, false, false, 42).unwrap();
let er_cc = connected_components(&er).unwrap();
println!("ER(100, 0.05): {} edges, {} components",
    er.ecount(), er_cc.count);

// Barabasi-Albert: preferential attachment
let ba = barabasi_game_bag(100, 3, false, false, 42).unwrap();
println!("BA(100, m=3): {} edges, density {:.4}",
    ba.ecount(), density(&ba).unwrap().unwrap_or(0.0));

// Watts-Strogatz: small-world
let ws = watts_strogatz_game(100, 4, 0.1, false, false, 42).unwrap();
println!("WS(100, k=4, p=0.1): {} edges", ws.ecount());
}

You can also use the convenience methods on Graph:

#![allow(unused)]
fn main() {
use rust_igraph::Graph;

let er = Graph::erdos_renyi(100, 0.05, 42).unwrap();
let ba = Graph::barabasi_albert(100, 3, 42).unwrap();
let ws = Graph::watts_strogatz(100, 4, 0.1, 42).unwrap();
}

Round-trip graph with attributes through GML

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, AttributeValue, write_gml, read_gml};

let mut g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();

// Add vertex names
for (i, name) in ["Alice", "Bob", "Carol"].iter().enumerate() {
    g.set_vertex_attribute("name", i as u32,
        AttributeValue::String((*name).into())).unwrap();
}

// Add edge weights
g.set_edge_attribute("weight", 0, AttributeValue::Numeric(1.5)).unwrap();
g.set_edge_attribute("weight", 1, AttributeValue::Numeric(2.0)).unwrap();
g.set_edge_attribute("weight", 2, AttributeValue::Numeric(0.8)).unwrap();

// Serialize and deserialize
let mut buf = Vec::new();
write_gml(&g, &mut buf).unwrap();

let g2 = read_gml(buf.as_slice()).unwrap();
assert_eq!(g2.vcount(), 3);
assert_eq!(
    g2.vertex_attribute("name", 0).and_then(AttributeValue::as_str),
    Some("Alice")
);
}

Check graph isomorphism with BLISS

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, isomorphic_bliss, canonical_permutation,
                  count_automorphisms, permute_vertices};

let g1 = Graph::from_edges(&[(0,1),(1,2),(2,3),(3,0)], false, None).unwrap();
// Same graph with vertices relabeled
let g2 = Graph::from_edges(&[(2,0),(0,3),(3,1),(1,2)], false, None).unwrap();

let result = isomorphic_bliss(&g1, &g2, None, None).unwrap();
assert!(result.iso);
println!("Isomorphic: {}", result.iso);
if !result.map12.is_empty() {
    println!("Mapping g1->g2: {:?}", result.map12);
}

// Canonical form: two isomorphic graphs get the same canonical labeling
let perm1 = canonical_permutation(&g1, None).unwrap();
let perm2 = canonical_permutation(&g2, None).unwrap();
let c1 = permute_vertices(&g1, &perm1).unwrap();
let c2 = permute_vertices(&g2, &perm2).unwrap();
// c1 and c2 have the same edge set

// Count automorphisms
let aut = count_automorphisms(&g1, None).unwrap();
println!("|Aut(C_4)| = {aut}");  // 8
}

Compute a minimum spanning tree

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, minimum_spanning_tree, MstAlgorithm};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,2),(1,3),(2,3),(3,4)],
    false, None
).unwrap();
let weights = vec![4.0, 2.0, 1.0, 3.0, 5.0, 1.0];

let mst_edges = minimum_spanning_tree(&g, Some(&weights), MstAlgorithm::Automatic).unwrap();
println!("MST edges: {:?}", mst_edges);

let total_weight: f64 = mst_edges.iter()
    .map(|&e| weights[e as usize])
    .sum();
println!("Total MST weight: {total_weight}");
}

Network flow

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, max_flow_value};

// A simple flow network (directed)
let g = Graph::from_edges(
    &[(0,1),(0,2),(1,3),(2,3),(1,2)],
    true, // directed
    None,
).unwrap();

let capacity = vec![3.0, 2.0, 2.0, 3.0, 1.0];

let flow = max_flow_value(&g, 0, 3, Some(&capacity)).unwrap();
println!("Max flow from 0 to 3: {flow}");
}

Detect communities with Infomap and Spinglass

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, infomap, spinglass,
                  compare_communities, CommunityComparison};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,2),(3,4),(3,5),(4,5),(2,3),(6,7),(6,8),(7,8),(5,6)],
    false, None
).unwrap();

// Infomap — information-theoretic (map equation)
let im = infomap(&g).unwrap();
println!("Infomap: {} modules, codelength {:.4}",
    *im.membership.iter().max().unwrap() + 1, im.codelength);

// Spinglass — Potts model simulated annealing
let sp = spinglass(&g, None).unwrap();
println!("Spinglass: {} communities, modularity {:.4}",
    *sp.membership.iter().max().unwrap() + 1, sp.modularity);

// Compare the two partitions
let nmi = compare_communities(
    &im.membership, &sp.membership,
    CommunityComparison::NormalizedMutualInformation,
).unwrap();
println!("NMI(Infomap, Spinglass) = {nmi:.4}");
}

Analyze triadic structure (motif census)

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, triad_census, dyad_census};

// Directed graph for triad analysis
let g = Graph::from_edges(
    &[(0,1),(1,2),(2,0),(0,3),(3,4),(4,0),(1,4)],
    true, None
).unwrap();

let tc = triad_census(&g).unwrap();
println!("Triad census (16 types):");
for (i, &count) in tc.counts.iter().enumerate() {
    if count > 0 {
        println!("  Type {:02}: {count}", i);
    }
}

let dc = dyad_census(&g).unwrap();
println!("Dyad census: mutual={}, asymmetric={}, null={}",
    dc.mutual, dc.asymmetric, dc.null_count);
}

Build spatial proximity graphs

#![allow(unused)]
fn main() {
use rust_igraph::{delaunay_graph, gabriel_graph};

// 2D point cloud
let points = vec![
    vec![0.0, 0.0],
    vec![1.0, 0.0],
    vec![0.5, 0.866],
    vec![2.0, 0.5],
    vec![1.5, 1.5],
];

// Delaunay triangulation — connects nearest point triples
let dt = delaunay_graph(&points).unwrap();
println!("Delaunay: {} vertices, {} edges", dt.vcount(), dt.ecount());

// Gabriel graph — subset of Delaunay, no third point inside
// the diametral circle of any edge
let gg = gabriel_graph(&points).unwrap();
println!("Gabriel:  {} vertices, {} edges", gg.vcount(), gg.ecount());
}

K-core decomposition and trussness

Identify the dense backbone of a network:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, coreness, trussness};

let g = Graph::from_edges(
    &[(0,1),(0,2),(1,2),(1,3),(2,3),(3,4),(3,5),(4,5),(4,6),(5,6),(6,7)],
    false, None
).unwrap();

// Coreness: each vertex gets its maximum k such that it belongs to a k-core
let cores = coreness(&g).unwrap();
println!("Vertex coreness: {:?}", cores);

let max_core = *cores.iter().max().unwrap();
let core_members: Vec<usize> = cores.iter().enumerate()
    .filter(|&(_, &c)| c == max_core)
    .map(|(i, _)| i)
    .collect();
println!("{max_core}-core members: {core_members:?}");

// Trussness: edge-based analog (each edge in a k-truss has >= k-2 triangles)
let trusses = trussness(&g).unwrap();
println!("Edge trussness: {:?}", trusses);
}

Clique analysis

Find cliques and measure their structure:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, clique_number, largest_cliques, maximal_cliques};

let g = Graph::from_edges(
    &[(0,1),(0,2),(0,3),(1,2),(1,3),(2,3),(3,4),(4,5),(5,6),(4,6)],
    false, None
).unwrap();

// Clique number: size of the largest clique
let omega = clique_number(&g).unwrap();
println!("Clique number (ω): {omega}");

// All largest cliques (there may be several of size ω)
let big = largest_cliques(&g).unwrap();
println!("Largest cliques:");
for c in &big {
    println!("  {:?}", c);
}

// All maximal cliques (cannot be extended by adding a vertex)
let all = maximal_cliques(&g).unwrap();
println!("{} maximal cliques found", all.len());
}

Graph algebra: products, union, complement

Build complex graphs from simple building blocks:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, cartesian_product, tensor_product,
                  complementer, line_graph, union, intersection};

let path3 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
let k2 = Graph::from_edges(&[(0,1)], false, None).unwrap();

// Cartesian product: P3 □ K2 gives a ladder graph (6 vertices, 7 edges)
let ladder = cartesian_product(&path3, &k2).unwrap();
println!("Ladder: {} vertices, {} edges", ladder.vcount(), ladder.ecount());

// Tensor product (categorical product)
let tp = tensor_product(&path3, &k2).unwrap();
println!("Tensor product: {} vertices, {} edges", tp.vcount(), tp.ecount());

// Complement: edges become non-edges and vice versa
let comp = complementer(&path3, false).unwrap();
println!("Complement of P3: {} edges (K3 minus P3 = 1 edge)", comp.ecount());

// Line graph: vertices are edges of the original, adjacent if they share a vertex
let lg = line_graph(&path3).unwrap();
println!("Line graph of P3: {} vertices, {} edges", lg.vcount(), lg.ecount());

// Union: overlay two graphs on the same vertex set
let g1 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
let g2 = Graph::from_edges(&[(2,3),(3,4)], false, None).unwrap();
let combined = union(&g1, &g2).unwrap();
println!("Union: {} vertices, {} edges", combined.vcount(), combined.ecount());
}

Random walks

Simulate a random walk on a graph:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, random_walk, DijkstraMode};

let g = Graph::from_edges(
    &[(0,1),(1,2),(2,3),(3,4),(4,0),(0,2),(1,3),(2,4)],
    false, None
).unwrap();

// Unweighted random walk: 15 steps from vertex 0
let (vertices, edges) = random_walk(&g, None, 0, DijkstraMode::Out, 15, 42).unwrap();
println!("Walk: {:?}", vertices);

// Weighted random walk (biases toward heavier edges)
let weights = vec![1.0, 1.0, 1.0, 1.0, 1.0, 5.0, 5.0, 5.0];
let (v2, _) = random_walk(&g, Some(&weights), 0, DijkstraMode::Out, 15, 7).unwrap();
println!("Weighted walk: {:?}", v2);
}

Distance metrics: eccentricity, radius, diameter

Understand graph-level distance structure:

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, eccentricity, diameter, radius, girth};

let g = Graph::from_edges(
    &[(0,1),(1,2),(2,3),(3,4),(4,5),(5,0),(0,3),(1,4),(2,5)],
    false, None
).unwrap();

// Eccentricity: max distance from each vertex to any other
let ecc = eccentricity(&g).unwrap();
for (v, &e) in ecc.iter().enumerate() {
    println!("  v{v}: eccentricity = {e}");
}

// Diameter: max eccentricity (longest shortest path)
let d = diameter(&g).unwrap();
println!("Diameter: {:?}", d);

// Radius: min eccentricity (tightest center)
let r = radius(&g).unwrap();
println!("Radius: {:?}", r);

// Girth: length of the shortest cycle
let girth_val = girth(&g).unwrap();
println!("Girth: {:?}", girth_val);
}

Layout for visualization export

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, FrParams, layout_fruchterman_reingold, layout_circle};

let g = Graph::from_edges(
    &[(0,1),(1,2),(2,3),(3,4),(4,0),(0,2),(1,3)],
    false, None
).unwrap();

// Force-directed layout
let coords = layout_fruchterman_reingold(&g, &FrParams::default()).unwrap();

// Output as CSV for use in external tools
println!("vertex,x,y");
for (v, &(x, y)) in coords.iter().enumerate() {
    println!("{v},{x:.6},{y:.6}");
}

// Circle layout (deterministic, good for small graphs)
let circle = layout_circle(&g, None);
for (v, &(x, y)) in circle.iter().enumerate() {
    println!("v{v}: ({x:.4}, {y:.4})");
}
}