Ursa — Polars-shaped dataframes for graph data
Status: v0.1 in progress. An in-memory, single-machine graph analytics library with a dataframe-first API — what Polars is to tabular data, for graphs. The engine foundation is in place: real algorithm kernels, and
collect()executing as one DataFusion plan with graph ops as first-class logical nodes. The full design lives indocs/SPEC.md; this README describes what is actually built right now and how the pieces fit.
Ursa is a Rust core (Apache Arrow throughout), a DataFusion query engine with
graph operators as first-class plan nodes, and a fluent, Polars-shaped Python
expression API. There is no Graph object — an EdgeFrame is the graph, and
every operation returns a frame.
import ursa as ur
edges = ur.scan_edges("web-google.csv", src="FromNodeId", dst="ToNodeId")
top = (
edges.nodes()
.with_columns(
pagerank = ur.pagerank(edges, damping=0.85),
in_degree = ur.degree(edges, direction="in"),
)
.sort("pagerank", descending=True)
.head(20)
.collect() # runs as one DataFusion plan; results are Arrow
)
What works today
The architecture, crate boundaries, and load-bearing seams are all in place, and the parts that prove the design is sound are real and tested end-to-end.
| Layer | State |
|---|---|
ursa-core — CSR topology index + kernels |
✅ Real & unit-tested. Dense u32 indexing, lazy-transpose CSR with the edge_ids permutation, and working degree / pagerank (pull-based) / connected_components (union-find) / triangle_count / clustering_coefficient (sorted-adjacency intersection) / bfs (frontier) / closeness / betweenness (Brandes, with source sampling) / label_propagation / louvain (modularity) kernels. Weighted variants are the remaining kernel work. |
ursa-plan — DataFusion engine |
✅ Unified plan. Each collect() is one DataFusion LogicalPlan — Limit → Sort → Filter → GraphAlgorithmNode — where GraphAlgorithmNode is a real UserDefinedLogicalNode lowered to GraphAlgorithmExec by our own ExtensionPlanner. Graph ops are first-class citizens of the plan (not orchestrated from outside), which is where future optimizer rules register. A DataFusion scan reads Parquet/CSV edge/node files, local or from object storage (s3:// / gs:// / az://), with the column projection pushed into the file. |
ursa-py — PyO3 bindings |
✅ Wired. Arrow in/out zero-copy (PyCapsule), GIL released during compute. |
Python dialect + collect() |
✅ Live & executing. The Polars-shaped expression/plan builder, plus collect() for a standalone algorithm, a composed with_columns(...).filter(...).sort(...).head(n) pipeline, node-attribute enrichment (in-memory or scan_nodes file-backed tables joined by id, ur.col("attr") usable in filter/sort), neighbors().agg() over numeric and string attributes, the traversals hop() and shortest_path() (first-class HopNode/ShortestPathNode returning EdgeFrames) plus random_walk() (a RandomWalkNode returning a (walk_id, step, node) frame), and the whole-graph stats describe() / density() / avg_path_length() / diameter(). Over in-memory or scan_edges/scan_nodes sources — local files or object storage (s3:// / gs:// / az://, with storage_options={...}). |
import ursa as ur, pyarrow as pa
edges = ur.from_arrow(pa.table({"s": [1, 2, 3, 0], "d": [0, 0, 0, 1]}), src="s", dst="d")
# Composed pipeline — runs through DataFusion end to end:
(
edges.nodes()
.with_columns(pr=ur.pagerank(edges), indeg=ur.degree(edges, direction="in"))
.filter(ur.col("indeg") > 0)
.sort("pr", descending=True)
.head(10)
.collect()
.to_polars()
)
# ...or straight from a file (Parquet/CSV, projection pushed into the scan):
ur.pagerank(ur.scan_edges("edges.parquet", src="s", dst="d")).collect().to_polars()
# Node ids may be int64 (the fast path) or strings (e.g. UUIDs) — auto-detected
# from the column type; results come back keyed by the original ids:
str_edges = ur.from_arrow(pa.table({"s": ["u1", "u1", "u2"], "d": ["u2", "u3", "u3"]}), src="s", dst="d")
ur.pagerank(str_edges).collect().to_polars() # id column is Utf8
Architecture
ursa/
├── ursa-core/ # Topology (CSR) + algorithm kernels. Pure Rust: arrow + rayon.
│ # NO DataFusion dependency. Independently testable.
├── ursa-plan/ # DataFusion extensions: custom logical node + ExecutionPlan
│ # (-> ursa-core), the query builder, scan/session plumbing.
│ # The ONE seam where our dialect lowers to DataFusion.
│ # (optimizer rules + object_store: future work)
├── ursa-py/ # PyO3 bindings. Thin: plan builders, collect(), Arrow FFI.
└── python/ursa/ # Python package: dialect, frames, IO, graph verbs, stats.
Governing rule: Arrow at the boundaries, index in the middle. Every kernel takes Arrow columns plus a shared topology index in, and hands Arrow arrays out.
Why DataFusion (not the Polars crates): extensibility is the designed use case —
graph ops must be first-class citizens of one query plan, not coordinated by a
"traffic cop" around a closed planner. The accepted cost is that we own a
Polars-shaped expression frontend; it is deliberately quarantined at one seam —
python/ursa/_expr.py builds the dialect, and ursa-plan/src/query.rs lowers it
(today a small JSON column IR + comparison filters) to a DataFusion plan.
Develop
The Python side is managed with uv; linting and formatting use ruff and type-checking uses ty.
# Rust core: real kernels, fast to build/test (arrow + rayon only)
cargo test -p ursa-core
# Whole workspace (compiles DataFusion; slower)
cargo check
# Python: uv creates the venv, builds the maturin extension, and installs the
# dev dependency group in one step.
uv sync
uv run pytest # pure-Python tests + native-kernel tests
uv run ruff check . # lint
uv run ruff format . # format
uv run ty check # type-check
uv run rebuilds the native extension as needed, so editing Rust and re-running
uv run pytest picks up the change. Requirements: Python ≥ 3.10 and
uv; the Rust toolchain is pinned in
rust-toolchain.toml (rustup installs it automatically),
so local cargo clippy uses the exact same lint set as CI.
Roadmap
The engine foundation is in place — every collect() is one DataFusion plan with
custom graph logical nodes — and many features have fanned out on top of it:
node-valued algorithms (pagerank, degree, connected_components, triangle_count,
clustering_coefficient, closeness, betweenness, label_propagation, louvain),
composed pipelines, scan_edges/scan_nodes sources,
node-attribute enrichment (in-memory or file-backed tables joined by id),
neighbors().agg() over numeric and string attributes, the traversals
hop() and shortest_path() (each its own first-class logical node returning an
EdgeFrame, on a shared single-source BFS kernel family) plus random_walk(), the eager whole-graph
stats density / avg_path_length / diameter and the one-row
describe, object-storage scans (s3:// / gs:// / az:// via
object_store, with storage_options), and sink_parquet/sink_csv egress.
Weighted algorithms are live across the board: weight= is a per-operation
expression over edge columns (weight=ur.col("amount") * ur.col("fx")),
evaluated to an f64 per edge and gathered per CSR slot via the edge_ids
permutation. Weighted PageRank, shortest_path (Dijkstra), closeness,
betweenness (Dijkstra-Brandes), and louvain all ship.
Next, in rough priority order:
- The direction-optimizing (top-down/bottom-up) BFS switch and weighted SSSP via delta-stepping, for scale.
- Optimizer rules — push node-set filters before traversal, fuse
neighbors().agginto a segmented CSR reduction. (The topology index is now built once and shared across ops over a frame — the index-preservation contract — which is the seam these rules register on.) - Breadth — benchmarks vs NetworkX/rustworkx/igraph, a published docs site. (String/UUID node ids alongside int64 are already supported, auto-detected from the column type.)
License
MIT OR Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ursa_graph-0.1.1.tar.gz.
File metadata
- Download URL: ursa_graph-0.1.1.tar.gz
- Upload date:
- Size: 117.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1a5df634f59ff70f0008764d7d7626e7661beb2b026d8f5720dbd5f11cbb654
|
|
| MD5 |
8e087b9692940dc7824ba7611bfa5e7f
|
|
| BLAKE2b-256 |
acec10db50bf8e5d1130e247703a547f1f4f4241c4a6b4c550e771699861de43
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1.tar.gz:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1.tar.gz -
Subject digest:
a1a5df634f59ff70f0008764d7d7626e7661beb2b026d8f5720dbd5f11cbb654 - Sigstore transparency entry: 2227816643
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ursa_graph-0.1.1-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: ursa_graph-0.1.1-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 20.4 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c17e67bf15a511234a1d32baf5e56ab8b6406d547f4f03ff552509eaa2b7c51
|
|
| MD5 |
b2a9c62d25f672242f6ecb12217f66c2
|
|
| BLAKE2b-256 |
596add9065881682a7f8adc67a89e991e00b09fde65ceab5c82acbd501d30790
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1-cp310-abi3-win_amd64.whl:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1-cp310-abi3-win_amd64.whl -
Subject digest:
8c17e67bf15a511234a1d32baf5e56ab8b6406d547f4f03ff552509eaa2b7c51 - Sigstore transparency entry: 2227817179
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 20.8 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2efc3afd1e19bbdf6c14b4e1f16a6fc897f812cc56edb0ee270b67a046e1e382
|
|
| MD5 |
471ca24ef33d290231a2b10c2b7cff40
|
|
| BLAKE2b-256 |
6aa949a9b1d03a8fea2eb0302f3ebe1086a7e14ca709b481471458f31dbc40d4
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
2efc3afd1e19bbdf6c14b4e1f16a6fc897f812cc56edb0ee270b67a046e1e382 - Sigstore transparency entry: 2227817710
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 18.0 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
419206cabcea8b3981fe21a25bfd526ae8afe4fcb4fcb307f1b6c59cb223a494
|
|
| MD5 |
d97fef2fcba5939c3bb09cf4e44f3efe
|
|
| BLAKE2b-256 |
2745bdebe8fba90417c6894ad22eb16a9530e72b6f4bc99bce9341e407cb564e
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
419206cabcea8b3981fe21a25bfd526ae8afe4fcb4fcb307f1b6c59cb223a494 - Sigstore transparency entry: 2227817436
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ursa_graph-0.1.1-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: ursa_graph-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 18.7 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
730fdad46a99d5292dcaf8f3586235ee3dfb4c7aecac7fcd5a5c5c1357f6a539
|
|
| MD5 |
7b255834e4e23bfd3ec15e3c5a9fa9c3
|
|
| BLAKE2b-256 |
9e880690424a178057ef214fab84ffb6b74b4958ce911fb38cf4d59e47664b54
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
730fdad46a99d5292dcaf8f3586235ee3dfb4c7aecac7fcd5a5c5c1357f6a539 - Sigstore transparency entry: 2227817594
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ursa_graph-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: ursa_graph-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 20.0 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33656ac3828aeb832f856d77b9f645853b33d6bb3343f922658cdd0cfd1a49d7
|
|
| MD5 |
0efa712c4f0906410a483b3ed69abdb7
|
|
| BLAKE2b-256 |
0497088cd4315fbf76fe728c4d8b05a3cda200ec0968a0df8dadde77ac2d5607
|
Provenance
The following attestation bundles were made for ursa_graph-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on cldixon/ursa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursa_graph-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
33656ac3828aeb832f856d77b9f645853b33d6bb3343f922658cdd0cfd1a49d7 - Sigstore transparency entry: 2227816919
- Sigstore integration time:
-
Permalink:
cldixon/ursa@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cldixon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5466efb31771be22c612cc06beac7ec0cbccd9b8 -
Trigger Event:
release
-
Statement type: