sparx (Python)
Python bindings of SparX: memory-mapped analytics for
large static sparse graphs (de Bruijn graphs in particular). Graphs are parsed once into a
memory-mapped compressed sparse row cache, and algorithms run in parallel with the GIL released,
returning numpy arrays.
pip install sparx-graph
Wheels are built for Linux (x86_64, aarch64) and macOS (Apple silicon, Intel), for Python 3.9 and later.
import sparx
with sparx.Graph("graph.mtx", index="u32") as g: # undirected
print(g) # <sparx.Graph |V|=... |E|=... index=u32>
offsets, targets = g.csr() # edge e of node u: offsets[u] <= e < offsets[u+1]
coreness = g.k_core() # per node
trussness = g.k_truss(algorithm="pkt") # per edge, in csr() order
triangles = g.triangles(width="u32") # per edge
cc = g.clustering() # {"local", "average", "transitivity"}
louvain = g.louvain() # {"communities", "count", "modularity"}
leiden = g.leiden() # the same, refined (GVE-Leiden)
hb = g.hyperball(precision=8) # {"closeness", "harmonic", "lin", ...} estimates
closeness = g.closeness() # exact
ranks = g.pagerank() # alpha=0.85, tol=1e-6 (in total, see below)
dist = g.bfs(0) # -1 where unreachable
bcc = g.biconnectivity() # {"components", "heads", "articulation_points",
# "bridges", "two_edge_components", "two_edge_count"}
blocks = g.biconnected_components() # node arrays, as networkx's biconnected_components
r = g.degree_assortativity() # Newman's (networkx's degree_assortativity_coefficient)
rho = g.degree_assortativity(method="spearman") # of the degrees' ranks
g.numeric_assortativity(values) # a number per node
g.attribute_assortativity(labels) # an integer category per node
lpa = g.label_propagation() # {"communities", "count", "modularity", ...}
sc = g.spectral_clustering(16) # 16 clusters (engine="exact", k=None: chosen)
emb = g.spectral_embedding(8) # {"eigenvalues", "vectors", "residuals", ...}
q = g.modularity(lpa["communities"]) # of any partition
local = g.hk_relax([0]) # a local community: {"nodes", "conductance", ...}
bc = g.betweenness() # {"betweenness", "exact", "samples", ...}
hubs = g.betweenness(engine="kadabra", epsilon=0.001, k=10)["top"] # sampled, top 10
with sparx.Graph("roads.mtx", weighted=True, weight_type="f32") as w: # Matrix Market values
w.edge_weights() # in csr() order
far = w.dijkstra(0, engine="delta") # weighted distances, in parallel (Δ-stepping)
w.build_landmarks(8) # for A*
path = w.shortest_path(0, 9) # {"distance", "nodes", "expanded"}
paths = w.shortest_paths([0, 1], [9, 5], paths=False) # distances only, in parallel
with sparx.DiGraph("web.mtx") as d: # directed
d.in_degree(0), d.in_neighbours(0) # and degree/neighbours: out-arcs
cycles = d.directed_triangles("cycle") # per arc, directed cycles only
flow = d.k_truss("feedforward") # trusses of feed-forward triangles
cores = d.k_core(degree="in") # "in", "out" or "total" (in + out: networkx's core_number)
cc = d.clustering() # Fagiolo's: {"local", "average"}
communities = d.louvain() # by directed modularity
refined = d.leiden() # the same, with Leiden's refinement
spread = d.label_propagation(direction="in") # labels spreading along arcs
reach = d.bfs(0, direction="in") # "out", "in" or "both" (ignoring directions)
r = d.degree_assortativity(x="out", y="in") # any of "out" and "in" at either end
scc = d.components(kind="strong") # {"components", "count", "sizes"}
u = d.to_undirected() # a sparx.Graph, valid while d is open
m = d.to_undirected(mode="each") # "collapse" (default), "each" (per arc) or "mutual"
trussness = u.k_truss()
sparx.Graphis undirected: loading a directed dataset (one with an arcu -> vwithout a reversev -> u) raisesRuntimeError.sparx.DiGraphloads any dataset;is_symmetrictells whether its arcs all have reverses.DiGraph.to_undirected(mode=...)is the underlying undirected graph:"collapse"(the default) has one edge per pair of nodes linked by an arc either way (networkx'sDiGraph.to_undirected()),"each"one edge per arc, reciprocal and parallel arcs becoming parallel edges (networkx'sMultiDiGraph.to_undirected()), and"mutual"one edge per pair linked both ways, self-loops included (to_undirected(reciprocal=True)).direction="both"runs on the collapsed graph.Graph.to_directed()andDiGraph.to_undirected()return graphs mapping their parent's cache, which the parent owns: they're usable while it's open, and raiseRuntimeErrorafter.- Inputs: Matrix Market (
.mtx), ggcat graphs (.lz4) and nodes/edges files. weighted=Truereads a Matrix Market file's values as edge weights,weight_type="f64"(the default) or"f32"(half the memory;weight_typereads it back) (patternfiles' edges weigh one; ggcat and nodes/edges files carry no weights, and fail).weight_parsingis"fast"(the default: no per-value validation, malformed values give unspecified weights) or"checked"(malformed, out of range or non-finite values raise).is_weighted,weights(u)andedge_weights()(incsr()order) read them back; unweighted graphs' weigh one. A kept weighted graph reopens withopen(cache_file, weighted=True)(and itsweight_type).modularity(communities)is a given partition's (each node's label): Newman's, or on aDiGraphLeicht and Newman's directed modularity; weighted on weighted graphs. On aGraph,hk_relax(seeds)(Kloster and Gleich's heat kernel diffusion) andhkpr(seed)(Chung and Simpson's approximate Dirichlet heat kernel PageRank, seeded byrandom_seed) find a local community by a sweep cut,sweep_cut(nodes, heat)sweeps any diffusion:{"nodes", "heat", "size", "volume", "width", "conductance"}.- On weighted graphs, algorithms follow networkx's
weight="weight": PageRank (transitions in proportion to weight), Louvain and Leiden (weighted modularity), clustering (Onnela's and Fagiolo's weighted coefficients; transitivity stays unweighted, as networkx's) and degree assortativity (of strengths), and exactcloseness,harmonicandlin(weighted distances, networkx'sdistance="weight"; withdirection="both", two nodes are as far apart as their lightest arc).dijkstra(source)gives weighted distances (infwhere unreachable; hop counts on unweighted graphs), whilebfsalways counts hops. Weights must be finite and non-negative, and aDiGraph's Louvain needs integer weights (it compares gains exactly). HyperBall (it counts hops), k-cores, k-trusses and components ignore weights. - Shortest paths:
dijkstra(source, engine=..., predecessors=False)runs Dijkstra's algorithm ("sequential") or Δ-stepping on the graph's threads ("delta",delta=its bucket width;"auto"picks it on large graphs), with the same distances bit for bit. Withpredecessors=Trueit also returns each node's parent in a shortest path tree (-1for the source and unreachable nodes), found only then, the same for every engine.shortest_path(s, t)andshortest_paths(sources, targets)(in parallel) give{"distance", "nodes", "expanded"}dicts (Nonewithout a path; no"nodes"withpaths=False, which keeps less memory), by A*: guided by landmarks oncebuild_landmarks(k)has kept some in the graph's cache (they persist withkeep_cache=True;landmarkslists them,drop_landmarks()removes them), else bidirectional Dijkstra.heuristic=andbidirectional=override that: bidirectional search pays without landmarks (hundreds of times fewer nodes on small-world graphs), one-directional with them (on road-like graphs). DiGraph.to_undirected(merge=...)weighs an edge merged from several arcs ("collapse"and"mutual": every arc between two nodes, both ways) by their"sum"(the default),"min"or"max", the same from both ends.index("u32"or"u64") is the width of node and edge indices.width("u8"to"u64") is the width of triangle counts, trussness and coreness values; a value that doesn't fit, such as a node of degree 256 in a"u8"k_core, fails with an error rather than overflowing.- Undirected edges are stored once in each direction; per-edge results follow
csr(). biconnectivityandbiconnected_componentsare onsparx.Graphonly. In"components", each node is labelled with one biconnected component it's in and doesn't head, and"heads"gives each component's head (the node it shares with the component above it). Isolated nodes, and nodes heading every component they're in, are labelled -1.biconnected_components()lists every component's nodes, heads included."bridges"is an(n, 2)array of sorted pairs, and parallel edges are never bridges.- Assortativity coefficients match networkx's, multigraphs included.
method="spearman"correlates the degrees' ranks rather than the degrees themselves: on graphs with heavy-tailed degrees, Pearson's coefficient shrinks as they grow, while Spearman's doesn't. Degree and attribute coefficients are exact, and the same whatever the number of threads; a coefficient isNaNwhere it's undefined (for example, all degrees equal).attribute_assortativitytakes integer labels: for others, passnumpy.unique(labels, return_inverse=True)[1]. label_propagation()finds communities by semi-synchronous label propagation (colors of non-adjacent nodes moving in turn):{"communities", "count", "modularity", "moves", "colors"}, the same whatever the threads, connected and numbered by their least node. On aDiGraph,direction="in"has labels spread along arcs ("out"against them,"both", the default, ignores directions) and the modularity is directed. Faster thanleiden(), and as good or better on clear structure;leiden()'s modularity is higher on graphs without it.exploration="auto"ends exploring early, once a pass moves at mosttoleranceof the nodes: faster where exploring has stopped paying, for a slightly lower modularity.spectral_clustering(k)clusters by spectral clustering, into{"communities", "count", "modularity", "lambda_k"}, the same whatever the threads.engine="compressive"(the default) filters random signals by a low-pass graph filter;engine="exact"finds the eigenvectors themselves (Chebyshev-filtered subspace iteration, with locking;"eigenvalues"and"converged"in the dict), and withk=Nonechooseskby the largest eigengap amongmax_k.matrix="laplacian"(the default, regularized by the mean degree; any graph) or"bethe"(the Bethe Hessian: unweighted graphs, weaker clusters). ADiGraphis clustered by its symmetrization (A + A^T).spectral_embedding(count)returns the eigenpairs themselves:{"eigenvalues", "vectors" (n × count), "residuals", "iterations", "converged"}. On planted partitions it finds clusters Leiden misses; on graphs without a few clear clusters (de Bruijn graphs), Leiden's modularity is higher.betweenness()is networkx'sbetweenness_centrality(normalized=Trueby default,endpoints=False): exact by Brandes' algorithm (engine="exact"), or estimated by KADABRA's adaptive sampling (engine="kadabra"): every node withinepsilon(a fraction of ordered pairs:epsilon * n / (n - 2)normalized) with probability1 - delta, or, withk, until thekmost central nodes ("top") are told apart, which takes far fewer samples."auto"(the default) is exact when|V| |E| <= 1e10. Results are the same whatever the threads (seedseeds KADABRA); weights must be positive.louvain()andleiden()communities are connected: any community Louvain leaves internally disconnected is split into its parts, which only raises modularity.leiden()refines each pass's communities into connected sub-communities before aggregating them (GVE-Leiden), finding better communities on some graphs for some more time.pagerank'stolbounds the ranks' total change, while networkx's is multiplied by the number of nodes (which, ranks summing to 1, graphs of millions of nodes meet at once): networkx's results are those oftol=nx_tol * len(g). Edges are unweighted (weight=None) unless the graph is weighted.pagerank(engine=...)picks how ranks are computed, with the same results, bit for bit:"pull"(fastest while the ranks fit in the CPU's cache; undirected graphs only),"partition"(partition-centric, fastest once they don't) or"auto"(the default:"pull"for undirected graphs of up to a million nodes or so,"partition"otherwise).- The cache lives in
./.cache/(relative to the working directory) and is dropped when the graph is closed or garbage collected. Withkeep_cache=Trueit stays, andsparx.Graph.open(g.cache_file)reopens the graph without parsing it again. - With GPU support (built with the
gpufeature:maturin build --features gpu),triangles,hyperballanddegree_assortativitytakegpu=True, and run on any Vulkan, Metal or DX12 GPU.
Building from source
pip install maturin
cd python && maturin develop --release
pytest tests
Metadata
Release files for sparx-graph 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sparx_graph-0.2.1.tar.gz | 740.5 kB | With provenance | Details for sparx_graph-0.2.1.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ x86-64 | Details for sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_x86_64.whl |
| sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ ARM64 | Details for sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_aarch64.whl |
| sparx_graph-0.2.1-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details for sparx_graph-0.2.1-cp39-abi3-macosx_11_0_arm64.whl |
| sparx_graph-0.2.1-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details for sparx_graph-0.2.1-cp39-abi3-macosx_10_12_x86_64.whl |
Total release size: 16.3 MB
Release files / sparx_graph-0.2.1.tar.gz
| Download URL | sparx_graph-0.2.1.tar.gz |
|---|---|
| Size | 740.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28ea2b0f37512182223c75a5f1125ea4b2a7d2c684f584762aae91c4a7bed2f4
|
|
BLAKE2b-256 checksum How to use checksums |
f7e636255753ff8c172e4af21b78e4cdc814dc2a1933f74538cb677b80812b40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_x86_64.whl
| Download URL | sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1f40f5a021ae7126da9c9a9fd5bf9996bf4ec6d3b5d7e46acfab9d82fd99418d
|
|
BLAKE2b-256 checksum How to use checksums |
dc67434e8a703177be38e3e936d251cb05ee5a15daaad8b0182b627de761cce6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_aarch64.whl
| Download URL | sparx_graph-0.2.1-cp39-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
ec75780c8037c56b4237396e76aca5edb6ae58fa74171e055782136c66b4b085
|
|
BLAKE2b-256 checksum How to use checksums |
ad205f97a3fe2bf5114dd1c69439f5fcb9e7fac3ccb6171ee61c2339aa25ef5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / sparx_graph-0.2.1-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | sparx_graph-0.2.1-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
c71c50d96244b0855ff8d377ef009d99fcab57a38991833726ef34b5b61fdc57
|
|
BLAKE2b-256 checksum How to use checksums |
382df039716ee42f908f5cb1ba29d5dec67f557cb36a5086272d91576cf7d1ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / sparx_graph-0.2.1-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | sparx_graph-0.2.1-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 4.0 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
e2d9b390d7c2a84c37ce975e9bf2a87ca62de4574334c9fcd0dd800e2bf1df5f
|
|
BLAKE2b-256 checksum How to use checksums |
ab2b54b35a70e173eaf4a7afada8b3f65ad275cd4011cb547b4bea9162fd8b57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logSecurity for sparx-graph 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for sparx_graph-0.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.