Skip to main content

AlgoGraph: Immutable Graph Library with Functional Transformers

AlgoGraph is an immutable graph library for Python that brings functional programming elegance to graph algorithms. Version 2.0.0 introduces pipe-based transformers, declarative selectors, and lazy views—achieving ~90% code reduction for common operations.

Why Immutability for Graphs?

Mutable graph libraries like NetworkX are powerful but come with hidden costs:

  • Side effects: Modifying a graph can break other code holding references
  • Debugging difficulty: Hard to track when/where a graph changed
  • Thread unsafety: Concurrent modifications cause subtle bugs

AlgoGraph takes a different approach: all operations return new graph objects. The original is never modified.

from AlgoGraph import Graph

g1 = Graph.from_edges(('A', 'B'), ('B', 'C'))
g2 = g1.add_vertex('D')  # g1 unchanged, g2 is new graph

assert 'D' not in g1.vertices()
assert 'D' in g2.vertices()

Pipe-Based Transformers

The killer feature of v2.0.0 is the transformer pipeline using Python’s | operator:

from AlgoGraph.transformers import filter_vertices, largest_component, stats

# Compose operations declaratively
result = (graph
    | filter_vertices(lambda v: v.get('active'))
    | largest_component()
    | stats())

# result: {'vertex_count': 42, 'edge_count': 156, 'density': 0.18, ...}

This replaces verbose imperative code:

# Old way (NetworkX-style)
active = graph.subgraph([v for v in graph.vertices() if v.attrs.get('active')])
components = list(connected_components(active))
largest = max(components, key=len)
subgraph = active.subgraph(largest)
stats = compute_stats(subgraph)

Available transformers:

  • filter_vertices(pred), filter_edges(pred) — Filter by predicate
  • map_vertices(fn), map_edges(fn) — Transform attributes
  • reverse(), to_undirected() — Structure transformations
  • largest_component(), minimum_spanning_tree() — Algorithm-based
  • to_dict(), to_adjacency_list(), stats() — Export operations

Declarative Selectors

Query vertices and edges with logical operators instead of filtering lambdas:

from AlgoGraph.graph_selectors import vertex as v, edge as e

# Find active users with high degree
power_users = graph.select_vertices(
    v.attrs(active=True) & v.degree(min_degree=10)
)

# Find heavy edges from admin nodes
admin_edges = graph.select_edges(
    e.source(v.attrs(role='admin')) & e.weight(min_weight=100)
)

# Complex queries with OR, NOT, XOR
special = graph.select_vertices(
    (v.attrs(vip=True) | v.degree(min_degree=50)) & ~v.attrs(banned=True)
)

This is declarative: you specify what you want, not how to find it.

Lazy Views

Views provide efficient filtering without copying data:

from AlgoGraph.views import filtered_view, neighborhood_view

# Create view without copying (O(1) space)
view = filtered_view(
    large_graph,
    vertex_filter=lambda v: v.get('active'),
    edge_filter=lambda e: e.weight > 5.0
)

# Iterate lazily
for vertex in view.vertices():
    process(vertex)

# Materialize only when needed
small_graph = view.materialize()

# Explore k-hop neighborhood
local = neighborhood_view(graph, center='Alice', k=2)

View types:

  • filtered_view() — Filter vertices/edges
  • subgraph_view() — View specific vertices
  • reversed_view() — Reverse edge directions
  • undirected_view() — View as undirected
  • neighborhood_view() — k-hop neighborhood

56+ Algorithms

AlgoGraph includes comprehensive algorithm coverage:

Traversal

from AlgoGraph.algorithms import dfs, bfs, topological_sort, find_path

visited = dfs(graph, start_vertex='A')
levels = bfs(graph, start_vertex='A')
order = topological_sort(graph)  # DAG only
path = find_path(graph, 'A', 'Z')

Shortest Paths

from AlgoGraph.algorithms import dijkstra, bellman_ford, floyd_warshall, a_star

distances = dijkstra(graph, source='A')
distances = bellman_ford(graph, source='A')  # Handles negative weights
all_pairs = floyd_warshall(graph)
path = a_star(graph, 'A', 'Z', heuristic=h)

Centrality

from AlgoGraph.algorithms import pagerank, betweenness_centrality, closeness_centrality

pr = pagerank(social_network)
bc = betweenness_centrality(network)
cc = closeness_centrality(network)

Flow Networks

from AlgoGraph.algorithms import max_flow, min_cut

flow_value = max_flow(network, 'Source', 'Sink')
cut_value, source_set, sink_set = min_cut(network, 'Source', 'Sink')

Matching

from AlgoGraph.algorithms import hopcroft_karp, maximum_bipartite_matching

matching = hopcroft_karp(bipartite_graph, left_set, right_set)
max_matching = maximum_bipartite_matching(graph, left, right)

Graph Coloring

from AlgoGraph.algorithms import welsh_powell, chromatic_number, dsatur

coloring = welsh_powell(graph)
num_colors = chromatic_number(graph)
coloring = dsatur(graph)  # Often better than greedy

Interactive Shell

Explore graphs with a filesystem-like interface:

pip install AlgoGraph
algograph network.json
graph(5v):/$ ls
Alice/  [2 neighbors]
Bob/    [3 neighbors]

graph(5v):/$ cd Alice
graph(5v):/Alice$ ls
Attributes:
  age = 30
neighbors/  [2 vertices]

graph(5v):/Alice$ path Bob Eve
Path found: Alice -> Bob -> Diana -> Eve

Relationship to AlgoTree

AlgoGraph and AlgoTree share the same design philosophy:

  • Immutable data structures
  • Pipe-based transformers
  • Declarative selectors
  • Lazy views

They also interoperate:

from AlgoTree import Tree
from AlgoGraph import tree_to_graph, graph_to_tree

# Tree to Graph
graph = tree_to_graph(tree)

# Graph to Tree (extracts spanning tree)
tree = graph_to_tree(graph, root='root')

Quick Start

pip install AlgoGraph
from AlgoGraph import Graph
from AlgoGraph.algorithms import dijkstra, pagerank
from AlgoGraph.transformers import filter_vertices, stats

# Build a graph
g = (Graph.builder()
     .add_vertex('A', age=30)
     .add_vertex('B', age=25)
     .add_edge('A', 'B', weight=5.0)
     .build())

# Run algorithms
distances = dijkstra(g, source='A')
pr = pagerank(g)

# Transform with pipes
result = g | filter_vertices(lambda v: v.get('age') > 20) | stats()

Resources


AlgoGraph: Functional programming meets graph algorithms. Immutable, composable, and elegant.

Discussion