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

rust-igraph

A pure-Rust port of igraph, the network-analysis library. Targets full API parity with igraph C v1.0.x (≈ 850 public functions), validated continuously against three official implementations:

  • igraph Ctests/unit/*.c + *.out
  • python-igraphtests/test_*.py
  • R-igraph (rigraph) — tests/testthat/test-*.R

Status: 308 algorithm work units complete across Phases 1–7 and 9. 1,297 public APIs, 8,577 tests (unit + integration + doctest + proptest). WASM-compatible (wasm32-unknown-unknown). See the master plan for the roadmap, and the algorithm tracker for per-algorithm progress.

What's implemented

CategoryHighlights
TraversalBFS, DFS, shortest paths (Dijkstra, Bellman-Ford, Johnson, Floyd-Warshall, A*), widest paths
CentralityPageRank, betweenness, closeness, harmonic, eigenvector, HITS, constraint
CommunityLouvain, Leiden, Infomap, Spinglass, label propagation, fluid communities, fast greedy, edge betweenness, walktrap, leading eigenvector, Voronoi
ConnectivityComponents, articulation points, bridges, biconnected components, cohesive blocks
FlowMax flow (Dinic), min cut, Gomory-Hu tree, vertex/edge connectivity, all s-t cuts
IsomorphismVF2 (graph + subgraph), BLISS canonical labeling, LAD subgraph isomorphism
ColoringGreedy vertex coloring (CN + DSatur), chordality, bipartite matching
Constructors40+ graph generators (Erdős-Rényi, Barabási-Albert, Watts-Strogatz, SBM, lattices, famous graphs, ...)
I/O8 formats: GML, GraphML, DOT, Pajek, NCOL, LGL, DL, LEDA — all attribute-aware
OperatorsUnion, intersection, difference, complement, simplify, permute, line graph

Why another Rust graph library?

petgraph is excellent for general-purpose graph work, but its API does not express what igraph_t expresses (rich attributes, vertex/edge selectors, full C API parity). Users coming from igraph's C, Python, or R bindings need a Rust home where:

  • function names mirror igraph_*
  • numerical results match python-igraph within tight tolerance
  • the build is WASM-friendly by default (no unsafe, no system deps)

License

GPL-2.0-or-later, matching upstream igraph. The architecture decision record explains why.

How this site is built

mdBook builds the prose sections; cargo doc builds the API rustdoc; CI publishes both to GitHub Pages. To build locally:

cargo install mdbook
mdbook serve --open

Quick start

Installation

Add rust-igraph to your Cargo.toml:

[dependencies]
rust-igraph = "0.6"

Your first graph

use rust_igraph::{Graph, bfs, pagerank, louvain};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build a small social network
    let g = Graph::from_edges(
        &[(0,1), (0,2), (1,2), (1,3), (2,3), (3,4), (4,5), (5,6), (6,4)],
        false, // undirected
        None,  // auto-infer vertex count
    )?;

    println!("{g}");
    // => Undirected graph with 7 vertices and 9 edges

    // BFS traversal from vertex 0
    let order = bfs(&g, 0)?;
    println!("BFS order: {order:?}");

    // PageRank centrality
    let pr = pagerank(&g)?;
    let (top_v, top_score) = pr.iter()
        .enumerate()
        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
        .unwrap();
    println!("Most central vertex: {top_v} (PageRank = {top_score:.4})");

    // Community detection
    let communities = louvain(&g)?;
    println!("Communities: {:?}", communities.membership);
    println!("Modularity: {:.4}", communities.modularity);

    Ok(())
}

Method-style API

The same operations are available as methods on Graph:

use rust_igraph::Graph;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let g = Graph::from_edges(
        &[(0,1), (0,2), (1,2), (1,3), (2,3), (3,4), (4,5), (5,6), (6,4)],
        false, None,
    )?;

    // All of these work directly on the graph
    let pr = g.pagerank()?;
    let bc = g.betweenness()?;
    let communities = g.louvain()?;
    let connected = g.is_connected()?;
    let diameter = g.diameter()?;

    println!("Connected: {connected}");
    println!("Diameter: {diameter:?}");
    println!("Modularity: {:.4}", communities.modularity);

    Ok(())
}

Graph construction options

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

// From a flat edge list
let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();

// Fluent builder pattern
let g = GraphBuilder::undirected()
    .vertices(5)
    .edges(&[(0,1), (1,2), (2,3), (3,4), (4,0)])
    .build()
    .unwrap();

// Classic generators
let er = Graph::erdos_renyi(1000, 0.01, 42).unwrap();    // random
let ba = Graph::barabasi_albert(1000, 3, 42).unwrap();    // scale-free
let ws = Graph::watts_strogatz(1000, 6, 0.1, 42).unwrap(); // small-world
}

Running the examples

The repository includes 116 runnable examples:

# Clone and run
git clone https://github.com/Totoro-jam/rust-igraph
cd rust-igraph

cargo run --example quickstart
cargo run --example social_network_demo
cargo run --example community_detection_demo
cargo run --example method_api_demo
cargo run --example layout_demo
cargo run --example file_io_demo
  • Tutorial — walks through all major features with runnable code snippets.
  • API documentation — full rustdoc reference for every function, struct, and enum.
  • Examples directory — 116 self-contained programs covering every algorithm category.

Tutorial

This chapter walks through the core features of rust-igraph by building a small network analysis pipeline. Every code block is a self-contained snippet you can paste into a Rust file with use rust_igraph::prelude::*;.

Creating graphs

The simplest way to create a graph is from an edge list:

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

// Undirected triangle
let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false, None).unwrap();
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 3);
}

For more control, use the GraphBuilder:

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

let g = GraphBuilder::undirected()
    .vertices(5)
    .edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
    .build()
    .unwrap();
}

There are also 40+ named constructors for common graph families:

#![allow(unused)]
fn main() {
use rust_igraph::{full_graph, ring_graph, star_graph, StarMode, erdos_renyi_gnp};

let complete = full_graph(5, false, false).unwrap();                    // K_5
let cycle = ring_graph(10, false, false, false).unwrap();              // C_10
let hub = star_graph(8, StarMode::Undirected, 0).unwrap();             // star with center 0
let random = erdos_renyi_gnp(100, 0.05, false, false, 42).unwrap();   // G(100, 0.05)
}

Basic properties

#![allow(unused)]
fn main() {
use rust_igraph::{Graph, density, is_connected, ConnectednessMode, diameter};

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

println!("Vertices: {}", g.vcount());            // 6
println!("Edges: {}", g.ecount());               // 6
println!("Directed: {}", g.is_directed());        // false
println!("Density: {:.4}", density(&g).unwrap().unwrap_or(0.0));
println!("Connected: {}", is_connected(&g, ConnectednessMode::Weak).unwrap());
println!("Diameter: {:?}", diameter(&g).unwrap());
}

Centrality measures

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

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

let pr = pagerank(&g).unwrap();
let bc = betweenness(&g).unwrap();
let cl = closeness(&g).unwrap();

// Find the most central vertex
let (top_v, top_score) = pr.iter()
    .enumerate()
    .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
    .unwrap();
println!("Highest PageRank: vertex {} ({:.4})", top_v, top_score);
}

Community detection

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

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

let result = louvain(&g).unwrap();
println!("Communities: {:?}", result.membership);
println!("Modularity: {:.4}", result.modularity);
}

Available community detection algorithms: Louvain, Leiden, label propagation, fluid communities, fast greedy, edge betweenness, walktrap, and leading eigenvector.

Shortest paths

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

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

// Unweighted distances from vertex 0
let dist = distances(&g, 0).unwrap();
println!("Distances from 0: {:?}", dist);  // [Some(0), Some(1), Some(2), Some(1)]

// Weighted shortest paths
let weights = vec![1.0, 2.0, 1.0, 5.0, 1.0];
let wdist = dijkstra_distances(&g, 0, &weights).unwrap();
println!("Weighted distances from 0: {:?}", wdist);
}

Graph attributes

Vertices, edges, and the graph itself can carry typed attributes:

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

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

// Vertex attributes
g.set_vertex_attribute("name", 0, AttributeValue::String("Alice".into())).unwrap();
g.set_vertex_attribute("name", 1, AttributeValue::String("Bob".into())).unwrap();
g.set_vertex_attribute("name", 2, AttributeValue::String("Carol".into())).unwrap();

// Edge attributes
g.set_edge_attribute("weight", 0, AttributeValue::Numeric(1.5)).unwrap();
g.set_edge_attribute("weight", 1, AttributeValue::Numeric(2.3)).unwrap();

// Graph-level attributes
g.set_graph_attribute("title", AttributeValue::String("My Network".into()));

// Read back
if let Some(name) = g.vertex_attribute("name", 0) {
    println!("Vertex 0: {}", name);  // "Alice"
}
}

File I/O

The easiest way to read and write graphs is with from_file / to_file, which auto-detect the format from the file extension:

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

// Auto-detects GML from the extension
let g = Graph::from_file("network.gml").unwrap();
println!("{g}");

// Write as GraphML — extension determines format
g.to_file("network.graphml").unwrap();
}

Supported extensions: .gml, .graphml/.xml, .dot/.gv, .net/.pajek, .ncol, .lgl, .leda/.lgr, .dl, .edges/.edgelist/.txt/.csv.

For more control (e.g. writing to a buffer), use the stream-based functions:

#![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();
g.set_graph_attribute("name", AttributeValue::String("triangle".into()));

// Write to an in-memory buffer
let mut buf = Vec::new();
write_gml(&g, &mut buf).unwrap();

// Read back — attributes survive the round-trip
let g2 = read_gml(buf.as_slice()).unwrap();
assert_eq!(g2.vcount(), 3);
assert_eq!(
    g2.graph_attribute("name").and_then(AttributeValue::as_str),
    Some("triangle")
);
}

Supported formats: GML, GraphML, DOT (Graphviz), Pajek, NCOL, LGL, DL (UCINET), and LEDA.

Graph isomorphism

#![allow(unused)]
fn main() {
use rust_igraph::{full_graph, ring_graph, isomorphic};

let g1 = full_graph(4, false, false).unwrap();
let g2 = full_graph(4, false, false).unwrap();
let g3 = ring_graph(4, false, false, false).unwrap();

assert!(isomorphic(&g1, &g2).unwrap());   // K_4 ≅ K_4
assert!(!isomorphic(&g1, &g3).unwrap());  // K_4 ≇ C_4
}

BLISS canonical labeling

The BLISS engine provides canonical forms, automorphism counting, and vertex-color-aware isomorphism:

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

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

// Canonical permutation — same for isomorphic graphs after relabeling
let p1 = canonical_permutation(&g1, None).unwrap();
let p2 = canonical_permutation(&g2, None).unwrap();
let c1 = permute_vertices(&g1, &p1).unwrap();
let c2 = permute_vertices(&g2, &p2).unwrap();
assert_eq!(c1.ecount(), c2.ecount());

// Automorphism group order: |Aut(C_4)| = 8
let aut = count_automorphisms(&g1, None).unwrap();
assert!((aut - 8.0).abs() < 1e-9);

// BLISS-backed isomorphism with mapping
let result = isomorphic_bliss(&g1, &g2, None, None).unwrap();
assert!(result.iso);
}

Graph operators

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

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

let u = &a | &b;  // union
let i = &a & &b;  // intersection

println!("Union: {} vertices, {} edges", u.vcount(), u.ecount());
println!("Intersection: {} vertices, {} edges", i.vcount(), i.ecount());
}

Graph layouts

rust-igraph includes 16 layout engines for 2D and 3D graph visualization:

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

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

// Force-directed layout (Fruchterman-Reingold)
let coords = layout_fruchterman_reingold(&g, &FrParams::default()).unwrap();
for (i, &(x, y)) in coords.iter().enumerate() {
    println!("v{i}: ({x:.2}, {y:.2})");
}

// Circle layout (deterministic)
let circle = layout_circle(&g, None);
assert_eq!(circle.len(), g.vcount() as usize);

// Kamada-Kawai (energy-based)
let n = g.vcount() as usize;
let kk = layout_kamada_kawai(&g, None, &KkParams::default_for(n), None).unwrap();
assert_eq!(kk.len(), n);
}

Available engines: Fruchterman-Reingold, Kamada-Kawai, DrL, Sugiyama, GEM, Davidson-Harel, GraphOpt, MDS, LGL, UMAP, Reingold-Tilford, circle, star, grid, random, and sphere.

Iterating over a graph

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

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

// Iterate over edges
for (src, tgt) in &g {
    println!("{} -- {}", src, tgt);
}

// Iterate over vertex ids
for v in g.vertex_ids() {
    let deg = g.degree(v).unwrap();
    println!("Vertex {}: degree {}", v, deg);
}
}

Method API vs free functions

Most algorithms are available both as free functions and as methods on Graph:

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

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

// Free function style
let pr1 = pagerank(&g).unwrap();

// Method style
let pr2 = g.pagerank().unwrap();

// Same result
assert_eq!(pr1, pr2);
}

Next steps

  • Browse the API documentation for the full list of functions
  • Run cargo run --example social_network_demo for a comprehensive demo
  • Check the 116 examples in the examples/ directory

Using rust-igraph in the Browser (WASM)

rust-igraph compiles to WebAssembly, letting you run graph algorithms entirely client-side — no server needed. This chapter walks through the full path from building the WASM module to integrating it into your web application.

Prerequisites

You need:

  • Rust (1.85+) with the wasm32-unknown-unknown target
  • wasm-pack (the build tool that wraps wasm-bindgen)
  • Node.js (18+) for your frontend toolchain
# Install the WASM target
rustup target add wasm32-unknown-unknown

# Install wasm-pack
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# or via cargo:
cargo install wasm-pack

Building the WASM module

The igraph-wasm crate in crates/igraph-wasm/ provides the browser bindings. Build it with wasm-pack:

# Clone the repo (if you haven't already)
git clone https://github.com/Totoro-jam/rust-igraph
cd rust-igraph

# Build for browser (ES module output)
wasm-pack build crates/igraph-wasm \
  --target web \
  --out-dir ../../pkg \
  --out-name igraph_wasm

This produces a pkg/ directory containing:

FilePurpose
igraph_wasm.jsJavaScript glue (ES module)
igraph_wasm_bg.wasmCompiled WebAssembly binary
igraph_wasm.d.tsTypeScript type definitions
package.jsonnpm package metadata

Build targets

wasm-pack supports different output targets:

# For ES module usage (Vite, webpack, browsers with <script type="module">)
wasm-pack build crates/igraph-wasm --target web

# For bundlers (webpack, Rollup — uses import())
wasm-pack build crates/igraph-wasm --target bundler

# For Node.js (CommonJS)
wasm-pack build crates/igraph-wasm --target nodejs

For most web projects, --target web is the right choice.

Quick start — vanilla HTML

The simplest way to use the WASM module is directly in a <script> tag:

<!DOCTYPE html>
<html>
<head>
  <title>rust-igraph WASM Demo</title>
</head>
<body>
  <pre id="output"></pre>
  <script type="module">
    // Import and initialize the WASM module
    import init, { WasmGraph } from './pkg/igraph_wasm.js';

    async function main() {
      // Must call init() before using any WASM functions
      await init();

      // Create a graph from edge pairs: [src0, dst0, src1, dst1, ...]
      const edges = new Uint32Array([0,1, 1,2, 2,3, 3,0, 0,2, 1,3]);
      const graph = WasmGraph.fromEdges(edges, false); // undirected

      console.log(`Vertices: ${graph.vcount()}, Edges: ${graph.ecount()}`);

      // Run PageRank
      const prJson = graph.pagerank();
      const pr = JSON.parse(prJson);
      document.getElementById('output').textContent =
        `PageRank scores: ${pr.scores.map(s => s.toFixed(4)).join(', ')}`;

      // Run community detection
      const commJson = graph.louvain();
      const comm = JSON.parse(commJson);
      console.log('Communities:', comm.membership);
      console.log('Modularity:', comm.modularity.toFixed(4));

      // Always free the graph when done
      graph.free();
    }

    main();
  </script>
</body>
</html>

Serve this with any static file server (e.g. python3 -m http.server) and open it in your browser.

Integration with Vite + React

For a production-quality setup with Vite and React:

1. Build the WASM module into your project

wasm-pack build crates/igraph-wasm \
  --target web \
  --out-dir ../../my-app/public/wasm \
  --out-name igraph_wasm

Placing the output in public/wasm/ ensures Vite copies the files as-is without hashing (WASM files need stable paths for dynamic import).

2. Create a Web Worker

Running graph algorithms in a Web Worker keeps the UI responsive. Create src/worker.ts:

// TypeScript interface matching the WASM exports
interface WasmGraphInstance {
  bfs(root: number): string;
  dfs(root: number): string;
  pagerank(): string;
  louvain(): string;
  betweenness(): string;
  closeness(): string;
  connectedComponents(): string;
  layoutFr(niter: number): string;
  layoutKamadaKawai(): string;
  layoutCircle(): string;
  layoutRandom(seed: number): string;
  layoutGrid(width: number): string;
  layoutStar(center: number): string;
  coreness(): string;
  eccentricity(): string;
  density(): string;
  radius(): string;
  meanDistance(): string;
  meanDegree(): string;
  assortativityDegree(): string;
  constraint(): string;
  reciprocity(): string;
  vcount(): number;
  ecount(): number;
  free(): void;
}

let WasmGraph: {
  fromEdges(edges: Uint32Array, directed: boolean): WasmGraphInstance;
} | null = null;

async function initWasm(): Promise<boolean> {
  try {
    const workerUrl = self.location.href;
    const root = workerUrl.replace(/\/[^/]*$/, '');
    const wasmModule = await import(
      /* @vite-ignore */ `${root}/wasm/igraph_wasm.js`
    );
    await wasmModule.default();
    WasmGraph = wasmModule.WasmGraph;
    return true;
  } catch (e) {
    console.error('WASM init failed:', e);
    return false;
  }
}

// Message handler
self.onmessage = async (e: MessageEvent) => {
  const { type, ...params } = e.data;

  if (type === 'init') {
    const ok = await initWasm();
    self.postMessage({ type: 'ready', ok });
    return;
  }

  if (type === 'run' && WasmGraph) {
    const edges = new Uint32Array(params.edges);
    const graph = WasmGraph.fromEdges(edges, params.directed);

    try {
      const t0 = performance.now();
      const resultJson = graph.pagerank(); // or any algorithm
      const elapsed = performance.now() - t0;

      self.postMessage({
        type: 'result',
        data: JSON.parse(resultJson),
        elapsed,
      });
    } finally {
      graph.free();
    }
  }
};

3. Use the worker from React

import { useEffect, useRef, useState } from 'react';

function App() {
  const workerRef = useRef<Worker | null>(null);
  const [ready, setReady] = useState(false);
  const [result, setResult] = useState<any>(null);

  useEffect(() => {
    const worker = new Worker(
      new URL('./worker.ts', import.meta.url),
      { type: 'module' }
    );

    worker.onmessage = (e) => {
      if (e.data.type === 'ready') setReady(e.data.ok);
      if (e.data.type === 'result') setResult(e.data.data);
    };

    worker.postMessage({ type: 'init' });
    workerRef.current = worker;

    return () => worker.terminate();
  }, []);

  const runPageRank = () => {
    workerRef.current?.postMessage({
      type: 'run',
      edges: [0,1, 1,2, 2,3, 3,0, 0,2],
      directed: false,
    });
  };

  return (
    <div>
      <button onClick={runPageRank} disabled={!ready}>
        Run PageRank
      </button>
      {result && <pre>{JSON.stringify(result, null, 2)}</pre>}
    </div>
  );
}

Integration with Node.js

For server-side or CLI usage with Node.js:

wasm-pack build crates/igraph-wasm --target nodejs --out-dir ../../pkg
const { WasmGraph } = require('./pkg/igraph_wasm.js');

const edges = new Uint32Array([0,1, 1,2, 2,0, 2,3, 3,4]);
const graph = WasmGraph.fromEdges(edges, false);

const pr = JSON.parse(graph.pagerank());
console.log('PageRank:', pr.scores);

graph.free();

WasmGraph API reference

All algorithm methods return JSON strings. Parse them with JSON.parse() to get the result objects described below.

Construction

MethodDescription
WasmGraph.fromEdges(edges: Uint32Array, directed: boolean)Create from flat edge pairs [u0,v0, u1,v1, ...]
new WasmGraph(directed: boolean)Create an empty graph
graph.addEdge(u: number, v: number)Add a single edge

Graph generators (static constructors)

MethodDescription
WasmGraph.erdosRenyi(n, p, seed)Erdos-Renyi G(n,p) random graph
WasmGraph.fullGraph(n)Complete graph K_n
WasmGraph.cycleGraph(n)Cycle graph C_n
WasmGraph.ringGraph(n, circular)Ring (cycle if circular=true, path if false)
WasmGraph.barabasiAlbert(n, m, seed)Barabasi-Albert preferential attachment
WasmGraph.wattsStrogatz(n, k, p, seed)Watts-Strogatz small-world

Properties

MethodReturn typeDescription
graph.vcount()numberNumber of vertices
graph.ecount()numberNumber of edges

Centrality algorithms

MethodResult fieldsDescription
graph.pagerank(){ scores: number[] }PageRank centrality
graph.betweenness(){ scores: number[] }Betweenness centrality
graph.closeness(){ scores: number[] }Closeness centrality
graph.eigenvectorCentrality(){ scores: number[] }Eigenvector centrality
graph.harmonicCentrality(){ scores: number[] }Harmonic centrality
graph.katzCentrality(){ scores: number[] }Katz centrality
graph.hubAndAuthorityScores(){ hub: number[], authority: number[] }HITS algorithm
graph.edgeBetweenness(){ scores: number[] }Edge betweenness centrality

Community detection

MethodResult fieldsDescription
graph.louvain(){ membership: number[], modularity: number }Louvain modularity
graph.leiden(){ membership: number[], quality: number, nb_clusters: number }Leiden algorithm
graph.infomap(){ membership: number[], codelength: number }Infomap
graph.spinglass(){ membership: number[], modularity: number, nb_clusters: number }Spinglass
graph.labelPropagation(){ membership: number[], nb_clusters: number }Label propagation
graph.walktrap(){ membership: number[], nb_clusters: number, modularity: number }Walktrap
graph.fastGreedy(){ membership: number[], nb_clusters: number, modularity: number }Fast greedy
graph.leadingEigenvector(){ membership: number[], modularity: number }Leading eigenvector
graph.edgeBetweennessCommunity(){ membership: number[], nb_clusters: number }Edge betweenness community
graph.fluidCommunities(k: number){ membership: number[], nb_clusters: number }Fluid communities

Traversal & paths

MethodResult fieldsDescription
graph.bfs(root: number){ order: number[] }Breadth-first search
graph.dfs(root: number){ order: number[] }Depth-first search
graph.dijkstra(source: number, weights: Float64Array){ distances: number[] }Dijkstra shortest paths
graph.shortestPath(source, target){ path: number[] }Shortest path between two vertices
graph.randomWalk(start, steps, seed){ vertices: number[] }Random walk from a start vertex
graph.topologicalSort(){ order: number[] }Topological sort (directed graphs)
graph.maxFlow(source: number, target: number){ value: number }Maximum flow
graph.diameter(){ diameter: number | null }Graph diameter (longest shortest path)

Structure

MethodResult fieldsDescription
graph.connectedComponents(){ membership: number[], count: number }Weakly connected components
graph.stronglyConnectedComponents(){ membership: number[], count: number }Strongly connected components
graph.graphStats(){ vcount, ecount, is_directed, is_connected, diameter, girth, triangles, is_bipartite }Aggregate statistics
graph.articulationPoints(){ vertices: number[] }Cut vertices
graph.bridges(){ edges: [number,number][], count: number }Bridge edges
graph.degreeSequence(){ degrees: number[] }Degree of each vertex
graph.vertexColoring(){ colors: number[], chromatic: number }Greedy vertex coloring
graph.transitivity(){ value: number }Global clustering coefficient
graph.triadCensus(){ counts: number[] }16-type triad census (directed)

Isomorphism

MethodResult fieldsDescription
graph.canonicalPermutation(){ permutation: number[] }BLISS canonical labeling
graph.countAutomorphisms(){ count: number }Automorphism group order
graph.isomorphicBliss(other){ isomorphic: boolean, mapping: number[] }BLISS isomorphism test

Graph Metrics

MethodResult fieldsDescription
graph.coreness(){ cores: number[] }k-core decomposition
graph.eccentricity(){ values: number[] }Per-vertex eccentricity
graph.density(){ density: number | null }Edge density
graph.radius(){ radius: number | null }Graph radius (min eccentricity)
graph.meanDistance(){ mean_distance: number | null }Average shortest-path length
graph.meanDegree(){ mean_degree: number | null }Average degree
graph.assortativityDegree(){ assortativity: number | null }Degree assortativity coefficient
graph.constraint(){ scores: number[] }Burt's constraint (structural holes)
graph.reciprocity(){ reciprocity: number | null }Edge reciprocity (directed)

Layout

MethodResult fieldsDescription
graph.layoutFr(niter: number){ coords: [number,number][] }Fruchterman-Reingold force-directed layout
graph.layoutKamadaKawai(){ coords: [number,number][] }Kamada-Kawai spring layout
graph.layoutCircle(){ coords: [number,number][] }Circular layout
graph.layoutRandom(seed: number){ coords: [number,number][] }Random layout
graph.layoutGrid(width: number){ coords: [number,number][] }Grid layout (0 = auto width)
graph.layoutStar(center: number){ coords: [number,number][] }Star layout with center vertex

Memory management

Always call graph.free() when you're done with a graph instance. WASM memory is not garbage-collected by JavaScript — if you don't call free(), the graph's memory will leak until the page is closed or the worker is terminated.

const graph = WasmGraph.fromEdges(edges, false);
try {
  const result = JSON.parse(graph.pagerank());
  // use result...
} finally {
  graph.free(); // always free!
}

Performance tips

  1. Use Web Workers — graph algorithms can be CPU-intensive. Running them on the main thread blocks the UI. Always offload to a worker.

  2. Reuse graph instances — if you run multiple algorithms on the same graph, create the WasmGraph once and call multiple methods before freeing.

    const graph = WasmGraph.fromEdges(edges, false);
    const pr = JSON.parse(graph.pagerank());
    const bc = JSON.parse(graph.betweenness());
    const layout = JSON.parse(graph.layoutFr(300));
    graph.free();
    
  3. Use typed arrays — edges must be passed as Uint32Array, not regular arrays. Dijkstra weights use Float64Array. Typed arrays avoid serialization overhead across the JS/WASM boundary.

  4. Bundle size — the WASM binary is ~400 KB gzipped. Use wasm-opt -O3 (included in wasm-pack build --release) for production builds.

Troubleshooting

"WASM module not found" / 404 errors

The WASM files must be served with the correct MIME type (application/wasm). Most development servers handle this automatically. If you're using Vite, place the WASM output in public/ so it's served as-is:

wasm-pack build crates/igraph-wasm \
  --target web \
  --out-dir ../../my-app/public/wasm

"Cannot use import statement outside a module"

The --target web output is an ES module. Use <script type="module"> or configure your bundler to handle .js imports correctly.

CORS errors when loading WASM

If loading from file://, CORS policies block WASM. Use a local server:

python3 -m http.server 8080
# or
npx serve .

Memory issues with large graphs

WASM has a default memory limit. For graphs with millions of edges, you may need to increase it in .cargo/config.toml:

[target.wasm32-unknown-unknown]
rustflags = ["-C", "link-args=-z stack-size=8388608"]

Algorithms that require undirected graphs

Some algorithms (spinglass, fast greedy, fluid communities) require undirected graphs. If you pass a directed graph, the WASM call will throw a JavaScript Error with a descriptive message.

Live demo

Try the interactive playground to see all 64 algorithms running in the browser via WASM. The playground source code at website/playground/ serves as a full reference implementation.

Next steps

  • Browse the API documentation for the full Rust API
  • Check the playground source for a production-quality React + WASM integration
  • Run cargo check --target wasm32-unknown-unknown to verify your own code is WASM-compatible

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})");
}
}

Project layout

rust-igraph/
├── Cargo.toml                         workspace root
├── CLAUDE.md                          project-level rules for AI agents
├── CONTRIBUTING.md                    alpha-stage external policy
├── DEVELOPMENT.md                     setup + AWU workflow (maintainer notes)
├── CHANGELOG.md                       Keep a Changelog
├── SECURITY.md                        vulnerability reporting
├── LICENSE                            GPL-2.0-or-later
├── README.md                          high-level overview
├── deny.toml                          cargo-deny: license + security policy
│
├── src/
│   ├── lib.rs                         crate root + re-exports
│   ├── core/                          Graph, Vector, Matrix, error types
│   └── algorithms/                    every algorithm implementation
│
├── tests/
│   └── conformance/{c,py,r}/<algo>/   3-source fixtures (gitignored: no, tracked)
│
├── benches/                           criterion baselines
├── examples/                          runnable demos
├── fixtures/                          standard graphs (karate, dolphins, ...)
├── scripts/
│   ├── oracle.py                      live python-igraph oracle bridge
│   ├── bench_compare.py               criterion vs python-igraph diff
│   └── test_extract/                  conformance extractors (from_c/py/r)
├── templates/                         Step-3 skeletons for the AWU SOP
│
├── book/                              this mdBook site
├── docs/plans/                        planning + design docs
│
├── .codefuse/tracking/                trackers committed alongside code
│   ├── ALGORITHMS.md                    AWU status (single source of truth)
│   ├── ARCHITECTURE.md                  ADR index
│   ├── CONFORMANCE.md                   3-source coverage matrix
│   ├── AI_PROMPTS.md                    cookbook of working prompts
│   ├── RESUME.md                        session-by-session notes
│   └── perf/<ALGO-XXX>.json             criterion baselines
│
├── .claude/                           AI infrastructure (committed)
│   ├── agents/                          7 sub-agents
│   ├── skills/                          9 /awu-* skills + helpers
│   └── hooks/                           git guardrails + auto-fmt
├── .githooks/                         repo-hosted git hooks (Co-Authored-By)
│
├── references/                        gitignored: igraph C / py / R clones
└── .github/workflows/                 CI + Pages deployment

The Master Plan

The rust-igraph master plan lives on GitHub and is updated continuously:

View the Master Plan on GitHub

It covers:

  • Phase-by-phase algorithm porting roadmap
  • Release milestones and exit criteria
  • Architectural decisions and constraints
  • v0.6.0 roadmap with three workstreams: algorithms, docs/ecosystem, and engineering quality

Architecture Decisions

Architecture Decision Records (ADRs) for rust-igraph are maintained on GitHub:

View Architecture Decisions on GitHub

Key decisions include:

  • GPL-2.0-or-later license (matching upstream igraph)
  • Zero unsafe blocks policy
  • Single dependency (thiserror) constraint
  • Graph data structure design (Vec<Vec<u32>> adjacency lists)
  • Three-source conformance testing approach

Algorithm Tracker (AWU)

Every algorithm in rust-igraph is tracked as an Algorithm Work Unit (AWU). The full tracker is on GitHub:

View Algorithm Tracker on GitHub

Current status: 308 AWUs complete across traversal, centrality, community detection, connectivity, flow, isomorphism, generators, layouts, I/O, and more.

Each AWU follows a 9-step SOP: recon, interface design, skeleton, implementation, unit tests, conformance extraction, property tests, benchmarks, and documentation.

Conformance Matrix

rust-igraph is cross-validated against three official igraph implementations. The full conformance matrix is on GitHub:

View Conformance Matrix on GitHub

Conformance testing ensures that rust-igraph produces numerically identical results to:

  • igraph C (v0.10.x) — the reference implementation
  • python-igraph (v0.11.x) — Python bindings
  • R-igraph (rigraph) — R bindings

Test fixtures are extracted from upstream test suites and stored in tests/conformance/{c,py,r}/.

Contributing

rust-igraph welcomes contributions! The full contributing guide is on GitHub:

View Contributing Guide on GitHub

Quick start

  1. Fork and clone the repository
  2. Run cargo test --workspace to verify your setup
  3. Pick an unassigned AWU from the algorithm tracker
  4. Follow the 9-step AWU SOP described in the master plan
  5. Open a PR with ALGO-XXX-NNN in the title

Key rules

  • No unsafe blocks
  • No new dependencies without an ADR
  • All public APIs need rustdoc with a doctest
  • Floating-point comparisons use tolerance helpers, never ==

Development Notes

Detailed development setup and workflow notes are on GitHub:

View Development Notes on GitHub

Essential commands

# Build and test
cargo build --workspace
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check

# Full test suite (including conformance and proptests)
cargo test --workspace --all-features

# WASM compatibility check
cargo check --target wasm32-unknown-unknown

Project structure

src/core/           # Graph, Vector, Matrix, attributes
src/algorithms/     # All algorithm implementations
tests/              # Integration, conformance, property tests
fixtures/           # Standard graph datasets
scripts/            # Oracle and test extraction tools

AI Workflow

rust-igraph uses AI-assisted development with Claude Code. The workflow configuration is on GitHub:

View CLAUDE.md on GitHub

The AI workflow includes:

  • AWU skills (/awu-start, /awu-translate, /awu-test, etc.) for systematic algorithm porting
  • Sub-agents for specialized tasks (recon, translation, testing, benchmarking)
  • Three-source conformance extraction from igraph C, Python, and R test suites
  • Property-based testing with proptest for algorithmic invariants

All AI-generated code goes through the same quality gates as human-written code: clippy, fmt, full test suite, and conformance checks.

Resume After a Break

If you're picking up development after a break, the resume guide is on GitHub:

View Resume Guide on GitHub

Quick checklist

  1. git pull and check for new commits
  2. Review the algorithm tracker for current status
  3. Check .codefuse/tracking/ for any in-progress AWUs
  4. Run cargo test --workspace to verify your local build
  5. Pick up from the last incomplete AWU or start a new one

API rustdoc

The complete public API documentation is auto-generated by cargo doc and published to GitHub Pages on every push to main:

Build locally:

cargo doc --no-deps --open

v0.6.0: 1,297 public functions spanning traversal, shortest paths, centrality, community detection, connectivity, flow, isomorphism, graph generators, layouts, spatial algorithms, and I/O. See the algorithm tracker for per-algorithm status.