A fast, directed hypergraph library written in Zig, with Python bindings included.
In a regular graph an edge connects exactly two nodes. A hyperedge connects any number of vertices at once — making hypergraphs a natural fit wherever relationships involve more than two parties:
| Domain | Vertices | Hyperedges |
|---|---|---|
| Academia | researchers | papers (all co-authors at once) |
| Biology | proteins | complexes, metabolic pathways |
| Chemistry | molecules | reactions (multiple reactants → products) |
| Finance | accounts | transactions (split payments, settlements) |
| Software | packages | commits, dependency sets |
| Networks | devices | multicast groups, subnets |
| Social | people | group chats, events, committees |
| Knowledge | concepts | ontology relations, semantic clusters |
| Logistics | locations | routes, shared shipments |
| ML / AI | features | co-occurrence sets, hypergraph neural nets |
HypergraphZ adds directed semantics on top: consecutive vertex pairs within a hyperedge define a direction, enabling shortest-path queries, topological sort, and reachability over the same structure.
Highlights
- Hyperedges can span zero, one, or many vertices — including self-loops and duplicates
- Full algorithm suite: BFS/DFS, shortest path, PageRank, centrality, cut vertices, connected components, topological sort, transitive closure
- Structural operations: dual, k-skeleton, line graph, star/clique expansion, incidence matrix, Laplacian
- Python wheels ship a precompiled Zig core — no compiler needed at install time
- Bulk insertion API (
create_vertices,create_hyperedges) cuts Python FFI overhead ~10×
The Python package is a thin binding over the same Zig core used by the native library — there is no Python reimplementation of the graph logic.
┌─────────────────────────────┐
│ Python (hypergraphz) │ high-level API — Hypergraph, VertexQuery, …
├─────────────────────────────┤
│ ctypes │ FFI bridge — no C extension, no compilation step for users
├─────────────────────────────┤
│ C ABI (hgz_c_api.zig) │ flat exported functions — hgz_add_vertex, hgz_find_shortest_path, …
├─────────────────────────────┤
│ HypergraphZ (Zig core) │ all algorithms, memory management, type-safe generics
└─────────────────────────────┘
The shared library (libhypergraphz.so / .dylib / .dll) is compiled once and shipped inside the wheel. Data crosses the boundary as JSON (vertex and hyperedge payloads) and as plain integer ID arrays (all structural operations). For bulk insertion, create_vertices and create_hyperedges serialise an entire payload list in one call, reducing FFI crossings from N to 1 — see Performance below.
pip install hypergraphz
# or with uv
uv add hypergraphzPre-built wheels are available for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (x86_64).
from hypergraphz import Hypergraph
g = Hypergraph()
# Add vertices and hyperedges
alice = g.create_vertex({"name": "alice", "age": 30})
bob = g.create_vertex({"name": "bob", "age": 25})
carol = g.create_vertex({"name": "carol", "age": 35})
collab = g.create_hyperedge({"type": "project"})
g.append_vertices(collab, [alice, bob, carol])
# Build the reverse index before querying
g.build()
# Shortest path
path = g.find_shortest_path(alice, carol)
# Fluent query builders
active = (
g.vertices()
.where(lambda d: d["age"] > 28)
.data()
)
# Structural algorithms
scores, _, _ = g.compute_page_rank()
centrality = g.compute_centrality()
components = g.get_connected_components()
# Sub-graph operations return independent Hypergraph objects
sub = g.get_vertex_induced_subhypergraph([alice, bob])
dual = g.get_dual()| Category | Methods |
|---|---|
| Lifecycle | build(), clear(), save(), load(), clone() |
| Vertices | create_vertex(), get_vertex(), update_vertex(), delete_vertex(), count_vertices(), get_all_vertex_ids() |
| Hyperedges | create_hyperedge(), get_hyperedge(), update_hyperedge(), delete_hyperedge(), count_hyperedges(), get_all_hyperedge_ids() |
| Relations | append_vertices(), prepend_vertices(), insert_vertex(), insert_vertices(), delete_vertex_from_hyperedge(), delete_vertex_at_index() |
| Degree | get_vertex_indegree(), get_vertex_outdegree() |
| Queries | get_hyperedge_vertices(), get_vertex_hyperedges(), get_vertex_neighborhood(), get_intersections(), get_hyperedges_connecting(), get_endpoints(), get_orphan_vertices(), get_orphan_hyperedges() |
| Boolean | is_connected(), is_reachable(), has_cycle(), is_k_uniform() |
| Traversal | find_shortest_path(), find_all_paths(), bfs(), dfs(), random_walk() |
| Algorithms | get_connected_components(), topological_sort(), find_cut_vertices(), compute_centrality(), compute_page_rank(), get_inclusions(), get_nestedness_profile() |
| Matrices | to_incidence_matrix(), to_incidence_matrix_coo(), to_laplacian() |
| Sub-graphs | get_dual(), get_k_skeleton(), get_vertex_induced_subhypergraph(), get_edge_induced_subhypergraph(), get_core(), expand_to_graph(), expand_to_star(), get_line_graph(), get_transitive_closure() |
| Fluent builders | vertices() → VertexQuery, hyperedges() → HyperedgeQuery |
All errors map to typed exceptions from hypergraphz:
| Exception | When raised |
|---|---|
NotBuiltError |
Query requires build() first |
VertexNotFoundError |
Vertex ID does not exist |
HyperedgeNotFoundError |
Hyperedge ID does not exist |
CycleDetectedError |
Operation requires a DAG |
NoPathError |
No directed path exists |
IndexOutOfBoundsError |
Insertion index out of range |
NotEnoughVerticesError |
Operation needs more vertices |
Benchmarks run with pytest-benchmark on an Intel Core i9-13900H, 64 GB RAM, CachyOS Linux (kernel 7.0.5), ReleaseFast shared library, Python 3.14.
| API | Mean | vs atomic |
|---|---|---|
create_vertex per vertex (atomic) |
~4,095 ms | 1× |
create_vertex per vertex + append_vertices (batch append) |
~2,554 ms | 1.6× |
create_hyperedges + create_vertices (bulk, single FFI call each) |
~415 ms | ~10× |
The dominant cost in the atomic and batch cases is ctypes call overhead (~1–2 µs per crossing). create_hyperedges and create_vertices each replace a per-item loop with a single FFI call, serialising the entire payload list as one JSON array.
| Operation | Mean |
|---|---|
find_shortest_path — chain of 1,000 vertices |
~148 µs |
find_cut_vertices — chain of 1,000 vertices |
~351 µs |
get_vertex_indegree × 100 — each vertex in 1,000 hyperedges |
~4.7 ms |
HypergraphZ currently requires Zig 0.17.0-dev.242+5d55999d2.
Add hypergraphz as a dependency to your build.zig.zon:
zig fetch --save https://github.com/yamafaktory/hypergraphz/archive/v0.1.0.tar.gzAdd hypergraphz as a dependency to your build.zig:
const hypergraphz = b.dependency("hypergraphz", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("hypergraphz", hypergraphz.module("hypergraphz"));examples/coauthorship.zig models a small research community as a directed hypergraph
where vertices are researchers and hyperedges are papers. The first listed author is
treated as the corresponding author, giving each hyperedge a natural direction.
zig build example
It walks through seventeen features of the library in sequence:
| Section | API | What it shows |
|---|---|---|
| Papers per researcher | getVertexHyperedges |
reverse-index query |
| Research communities | getConnectedComponents |
two isolated groups with no cross-group papers |
| Betweenness centrality | computeCentrality |
which researchers bridge the most collaboration paths |
| Degrees of separation | findShortestPath |
1–3 hops within a community; unreachable across communities |
| Dual: paper-centric view | getDual |
swap vertices ↔ hyperedges to see each researcher's papers |
| Shared authorship | getIntersections |
researchers present on every paper in a given set |
| Seniority ordering | topologicalSort |
hierarchy implied by the authorship direction |
| Pairwise expansion | expandToGraph |
6 papers encode 11 directed pairs in a regular graph |
| Spectral view | toLaplacian |
normalized Laplacian; block-diagonal under disjoint groups |
| PageRank | computePageRank |
citation-weighted stationary distribution; sums to 1 |
| Random walk | randomWalk |
seeded weighted walk; stays inside the start's component |
| Star expansion | expandToStar |
bipartite (researcher ↔ paper) projection; 2-uniform |
| Line graph | getLineGraph |
papers as vertices, linked when they share an author |
| K-core decomposition | getCore |
(s, t)-core peeling; trims the lone-paper researcher |
| Nestedness | getInclusions |
strict-subset pairs across hyperedges; sized profile |
| Co-author neighborhoods | getVertexNeighborhood |
undirected co-occurrence neighbors across all hyperedges |
Benchmarks run with zig build bench -Doptimize=ReleaseFast on an Intel Core i9-13900H, 64 GB RAM, CachyOS Linux (kernel 7.0.5).
| API | Time |
|---|---|
createVertex per vertex (atomic) |
77.7 ms |
createVertex per vertex + appendVerticesToHyperedge (batch append) |
70.4 ms |
createHyperedges + createVertices (bulk, single call each) |
47.6 ms |
build() — construct reverse index over all 1,000,000 vertices |
227.8 ms |
| Operation | Time |
|---|---|
getVertexIndegree × 100 — each vertex in 1,000 hyperedges |
422 µs |
findShortestPath — chain of 1,000 vertices |
232 µs |
findCutVertices — chain of 1,000 vertices |
414 µs |
The latest online documentation can be found here.