GraphMini is a high-performance graph pattern-matching system for subgraph enumeration on arbitrary patterns. It now provides both:
- a Python API for interactive use in Python scripts and Jupyter notebooks
- the original CLI for preprocessing, code generation, and benchmarking
The Python API is the easiest entry point for most users, so this README starts there.
The main branch is the actively maintained GraphMini implementation. It
includes the modernized build, scheduler tooling, and Python API.
The exact PACT 2023 artifact repository—including the bundled GraphPi and Dryadic comparison systems—is preserved at:
- branch:
artifact/pact-2023 - immutable tag:
pact-2023-artifact
Use the tagged artifact for reproducing the published PACT 2023 evaluation;
use main for current development and the Python API.
- Repository Versions
- Python Quick Start
- Python API Overview
- Example: Run on a Preprocessed Graph
- Example: Build a Graph from NumPy CSR Arrays
- Example: Reuse a Compiled Plan
- CLI Workflow
- Requirements
- Tested Graph Data
- Citation
python3 -m venv venv
source venv/bin/activate
pip install numpy cmake ninja clang-formatFrom the repository root:
python scripts/install_python.pyThis is a source-tree install. The script:
- configures CMake for the active Python interpreter
- builds
pygraphminiand the generated plan-module target - writes a
.pthfile into the environment soimport pygraphminiresolves to this repository's build output
To remove that installation later:
python scripts/uninstall_python.pyYou can override the interpreter or build directory if needed:
python scripts/install_python.py --python venv/bin/python --build-dir buildOn Unix-like systems, the shell wrappers still work:
./scripts/install_python.sh
./scripts/uninstall_python.shThe extension module is named pygraphmini.
pygraphmini.GraphGraph.from_preprocessed(graph_dir, reorder_by_degree=False)Graph.from_csr(indptr, indices, offsets=None, triangles=None, reorder_by_degree=False)
pygraphmini.CompiledPlan- returned by
pygraphmini.compile_plan(...) - executes a compiled shared module in-process
- returned by
pygraphmini.RunResult- structured result object returned by
CompiledPlan.run(...)
- structured result object returned by
graph_dir- type:
str - path to a preprocessed GraphMini graph directory
- the directory is expected to contain GraphMini's binary graph files such as
meta.txt,indptr_u64.bin,offset_u64.bin, triangle counts, and the compiledindicesfile matching the current vertex-id width
- type:
reorder_by_degree- type:
bool - default:
False - when
True, GraphMini reloads the graph and remaps vertex ids so higher-degree data-graph vertices receive smaller ids
- type:
indptr- type: 1-D NumPy array of
np.uint64 - shape:
num_vertices + 1 - CSR row-pointer array
indptr[0]must be0indptr[-1]must equallen(indices)- must be non-decreasing
- type: 1-D NumPy array of
indices- type: 1-D NumPy integer array compatible with GraphMini vertex ids
- current build expects 32-bit vertex ids in practice
- stores all adjacency lists concatenated together in CSR order
- every value must be in
[0, num_vertices) - adjacency lists must be sorted
- adjacency lists must not contain duplicates
- self loops are rejected
- the graph must be undirected, so if
u -> vappears thenv -> umust also appear
offsets- type: optional 1-D NumPy array of
np.uint64 - shape:
num_vertices - if omitted, GraphMini computes it
offsets[v]is the split position inside vertexv's adjacency list where neighbors stop being< v- equivalently:
indices[indptr[v] : indptr[v] + offsets[v]]are the neighbors smaller thanvindices[indptr[v] + offsets[v] : indptr[v + 1]]are the neighbors greater than or equal tov
- this is used by GraphMini's bounded-neighbor and canonicality logic
- type: optional 1-D NumPy array of
triangles- type: optional 1-D NumPy array of
np.uint64 - shape:
num_vertices - if provided, it must follow GraphMini's raw preprocessing convention:
- each entry is the per-vertex triangle count without canonicality constraints
- the total sum over all vertices must therefore be divisible by
6for an undirected graph
- if omitted, GraphMini estimates the graph-wide triangle statistic used by scheduling:
- it selects the top-100 highest-degree vertices
- computes their triangle counts without canonicality constraints
- uses the sample average to estimate the graph-wide triangle count
- for the
graphpischeduler, it also uses those sampled vertices' degrees to derive the average-degree term in the cost model
- this estimate can be larger than the true global average, but it is intentionally biased toward the high-degree region that dominates runtime
- type: optional 1-D NumPy array of
reorder_by_degree- type:
bool - default:
False - applies the same degree-based graph reordering as the preprocessed-graph path
- type:
compile_plan(graph, query_adjmat, query_type, pruning_type="eager", parallel_type="nested_rt", scheduler="graphpi")
graph- type:
pygraphmini.Graph - the loaded graph object to compile against
- type:
query_adjmat- type:
str - flattened adjacency matrix for the query graph
- length must be a perfect square
- for a query with
kvertices, the string length must bek * k - example:
- triangle:
"011101110" - 4-clique:
"0111101111011110"
- triangle:
- type:
query_type- type:
str - required
- allowed values:
vertexedgeedge_iep
- type:
pruning_type- type:
str - default:
eager - Python API currently supports:
noneeagercostmodel
- type:
parallel_type- type:
str - default:
nested_rt - allowed values:
openmptbb_topnestednested_rt
- type:
scheduler- type:
str - default:
graphpi - allowed values:
graphpigraphminigraphzero
- type:
graph- type:
pygraphmini.Graph - graph to execute the compiled plan on
- in normal usage this should be the same graph that was used during compilation
- type:
num_threads- type:
int - default:
0 - if
<= 0, GraphMini uses the machine's hardware concurrency - otherwise it uses the requested positive thread count
- type:
compile_plan(...) requires:
graphquery_adjmatquery_type
The remaining options are optional:
query_type:vertex,edge,edge_ieppruning_type: defaults toeager; Python API supportsnone,eager,costmodelparallel_type: defaults tonested_rtscheduler: defaults tographpi
GraphMini preserves its current performance model:
- schedule the query
- generate specialized C++ code
- compile that code into a shared module
- load the module back into the current Python process
Compiled plan modules are cached under build/python_plan_cache, so repeated use of the same generated code avoids recompiling.
Preprocess the graph once:
./build/bin/prep --path_to_graph=./dataset/wikiThen use it from Python:
import pygraphmini as gm
graph = gm.Graph.from_preprocessed(
"./dataset/GraphMini/wiki",
reorder_by_degree=True,
)
plan = gm.compile_plan(
graph=graph,
query_adjmat="011101110",
query_type="vertex",
)
result = plan.run(graph, num_threads=32)
print("pattern_size:", plan.pattern_size)
print("module_path:", plan.module_path)
print("result:", result.result)
print("execution_time_seconds:", result.execution_time_seconds)
print("throughput:", result.throughput)print(graph.num_vertices)
print(graph.num_edges)
print(graph.num_triangles)Graph.from_csr(...) is intended for notebook use and in-memory workflows.
Requirements:
indptrmust be a 1-Dnp.uint64arrayindicesmust be a 1-D integer array compatible with GraphMini vertex ids- adjacency lists must be sorted
- the graph must be undirected, so if
u -> vappears thenv -> umust also appear - self loops and duplicate neighbors are rejected
offsetsis optional; if omitted, GraphMini computes ittrianglesis optional- if
trianglesis provided, it must follow GraphMini's raw preprocessing convention: per-vertex counts without canonicality constraints, so the total sum is divisible by 6
If triangles is omitted, GraphMini estimates the triangle statistic needed by the scheduler:
- select the top-100 highest-degree vertices
- compute their triangle counts without canonicality constraints
- use that sample average to estimate the graph-wide triangle count for schedule generation
- if the scheduler is
graphpi, use those same sampled vertices' degrees as the average-degree signal in its cost model
This estimate can be larger than the true graph-wide average, but the high-degree vertices dominate the expensive parts of the search, so their statistics are used to drive scheduling.
Example:
import numpy as np
import pygraphmini as gm
indptr = np.array([0, 2, 4, 6], dtype=np.uint64)
indices = np.array([1, 2, 0, 2, 0, 1], dtype=np.uint32)
graph = gm.Graph.from_csr(
indptr=indptr,
indices=indices,
offsets=None,
triangles=None,
reorder_by_degree=False,
)
plan = gm.compile_plan(
graph=graph,
query_adjmat="011101110",
query_type="vertex",
)
result = plan.run(graph, num_threads=1)
print(result.result)If you want to compile once and run multiple times:
import pygraphmini as gm
graph = gm.Graph.from_preprocessed("./dataset/GraphMini/wiki")
plan = gm.CompiledPlan(
graph=graph,
query_adjmat="011101110",
query_type="vertex",
)
for threads in (1, 8, 32):
result = plan.run(graph, num_threads=threads)
print(threads, result.execution_time_seconds, result.result)plan.run(...) returns a RunResult object with:
resultexecution_time_secondsthroughputnum_threadsvertex_allocatedminigraph_allocatedthread_min_time_secondsthread_mean_time_secondsthread_max_time_secondsthread_time_std_seconds
The CLI remains available for preprocessing and benchmark-style runs.
mkdir -p build
cd build
cmake ..
cmake --build . -jbash dataset/download.sh
bash dataset/prep.shTo preprocess a graph manually:
./build/bin/prep --path_to_graph=./dataset/wiki./build/bin/run \
--graph_name=wiki \
--path_to_graph=./dataset/GraphMini/wiki \
--query_name=P1 \
--query_adjmat=0111101111011110 \
--query_type=vertex \
--pruning_type=costmodel \
--parallel_type=nested_rt \
--scheduler=graphmini \
--num_threads=32 \
--graph_reordering=truePrimary CLI options:
--graph_name: graph nickname--path_to_graph: path to the preprocessed graph directory--query_name: query nickname--query_adjmat: flattened adjacency matrix string--query_type:vertex,edge,edge_iep--pruning_type:none,static,eager,online,costmodel--parallel_type:openmp,tbb_top,nested,nested_rt--scheduler:graphpi,graphzero,graphmini--num_threads: execution thread count--graph_reordering: enable or disable degree-based graph reordering
- 128GB of free RAM to preprocess Friendster correctly
- 180GB of free disk space to store preprocessed graphs
Current status:
- Ubuntu 22.04: supported
- Ubuntu 24.04: supported
- WSL on Windows: known to work
- macOS: portability changes have been added, but this remains untested
- native Windows: portability changes have been added, but this remains untested
Platform outlook:
- Linux support is already in place.
- macOS should be closer now because the project already had Apple-specific CMake handling, and the Python installer is no longer Linux-only.
- native Windows is more plausible now because shared-module loading, process helpers, and graph memory mapping no longer assume POSIX-only APIs.
Remaining caveats:
- macOS and Windows builds are still untested
- the dataset helper scripts are shell scripts aimed at Unix-like environments
- the build and runtime paths are still validated only on Linux in this repository
- CMake >= 3.20
- GCC >= 7
- Python >= 3.10
numpyfor the Python CSR APIclang-formatoptional
- Wiki
- YouTube
- Patents
- LiveJournal
- Orkut
- Friendster
If GraphMini is helpful in your work, please consider citing the paper:
@inproceedings{Liu_2023,
title={GraphMini: Accelerating Graph Pattern Matching Using Auxiliary Graphs},
url={http://dx.doi.org/10.1109/PACT58117.2023.00026},
DOI={10.1109/pact58117.2023.00026},
booktitle={2023 32nd International Conference on Parallel Architectures and Compilation Techniques (PACT)},
publisher={IEEE},
author={Liu, Juelin and Polisetty, Sandeep and Guan, Hui and Serafini, Marco},
year={2023},
month=oct,
pages={211--224}
}