Fast, numerically stable BETULA clustering with a Rust core
Project description
betula-cluster
Rust-powered, memory-bounded clustering for large embeddings & tabular streams. It compresses raw data into numerically stable BETULA microclusters, then runs the clustering head on the compressed representation — k-means · GMM (diagonal & full) · Ward · HDBSCAN-CF · Mapper — so cost scales with the microcluster count, not
N. Streamingpartial_fit, a scikit-learn API, from-scratch Rust core + PyO3, no LAPACK or SciPy at runtime.
pip install betula-cluster
Verified: a 153-case Python suite at 100% wrapper coverage + 129 Rust tests,
clippy -D warnings + fmt clean across all feature sets, CI on CPython 3.11–3.14 (one abi3 wheel).
At a glance — honest benchmarks
Measured against scikit-learn on StandardScaler-normalized data, each method in its own subprocess
with peak RSS sampled from /proc/self/statm. Full methodology, every metric, and all tables (wins
and losses) live in bench/RESULTS.md.
- 🎯 Quality at parity. betula's k-means / GMM / Ward land within ≈0.01 ARI of their scikit-learn counterparts; full-covariance GMM recovers anisotropic clusters just as well (0.90 vs 0.90); HDBSCAN-on-CF nails non-convex moons & circles (ARI 1.00).
- ⚡ 15–40× faster at N = 1 000 000. betula-kmeans labels a million points in 0.20 s vs scikit-learn KMeans 3.3 s (17×), Birch 8.0 s (40×), GaussianMixture 5.5 s (27×).
- 🪶 Bounded memory. Streaming 10 M points peaks at ~57 MB — flat in N — while an in-core KMeans must hold the array and peaks at ~5 GB (≈88× less, and the gap grows without limit).
- 🌍 Real data, not just blobs. Matches scikit-learn on
digits(k-means 0.53 vs 0.47) and clusters full covtype (581 k rows) ~7× faster at better ARI; in very high dimensions (MNIST, 784-D)normalize=Truebeats scikit-learn (k-means 0.44 vs 0.32) — full table, and the honest trade-offs, inbench/RESULTS.md.
| Phase-3 clusters only the ~2 000 leaf microclusters, not the raw points, so every head finishes 1 M points in under ⅓ s. | The CF-tree is capped by max_leaves, so streaming memory stays flat — it clusters data larger than RAM. |
Why
Clustering libraries tend to either not scale (full GMM/HDBSCAN on raw points), lose precision
(classic BIRCH computes variance as SS − ‖LS‖²/n, which catastrophically cancels far from the
origin), or blow up in memory (BIRCH-family subcluster explosion in high dimensions). betula-cluster
addresses all three:
- Numerically stable — clustering features
(n, μ, S)via Welford / Chan updates; the covariance is PSD by construction. Classic BIRCH loses all digits near coordinate1e7; betula does not. - Memory-bounded by design — the CF-tree caps its leaves (
max_leaves) and rebuilds, so it never explodes; streaming memory is flat inNand clusters data larger than RAM. - Complete — one stable engine spanning k-means / GMM (diag & full) / Ward / HDBSCAN-style /
Mapper, with streaming
partial_fit, a scikit-learn API, and dataset-structure inspection.
The math (stable CF, the expected-log GMM E-step, distance derivations, relation to BIRCH/BETULA) is
written up — verified symbolically and numerically — in docs/MATH.md.
When to use it
Reach for betula-cluster when the data is large or streaming, memory must stay bounded, you want
fast predict on new points, or you want one numerically stable engine spanning k-means / GMM / Ward /
density / topology plus dedup / outliers / representatives — especially on embeddings and tabular
streams.
Use raw scikit-learn instead when N fits comfortably in RAM and you want the exact point-level
algorithm with no compression: at small N the two-phase overhead removes the speed edge, and raw
HDBSCAN is stronger on overlapping density. betula-cluster trades a CF-compression approximation for
scale and bounded memory — if you need neither, a plain in-core clusterer is simpler.
Quick start
import numpy as np
import betula_cluster
X = np.random.default_rng(0).normal(size=(100_000, 10))
labels = betula_cluster.fit_predict(X, n_clusters=10, method="kmeans")
labels = betula_cluster.fit_predict(X, n_clusters=0, feature="full", method="gmm-full") # auto-k via BIC
labels = betula_cluster.fit_predict(X, method="hdbscan", min_cluster_size=25) # HDBSCAN-CF; -1 = noise
Streaming / out-of-core — feed chunks, finalize, predict; memory stays bounded by max_leaves:
est = betula_cluster.Betula(method="gmm", memory_budget_mb=512)
for chunk in stream_of_arrays: # each chunk is a 2-D float32/float64 array
est.partial_fit(chunk)
est.partial_fit() # finalize the global clustering over everything seen
labels = est.predict(X_query)
Constraints (COP-KMeans), mixed numeric+categorical (KPrototypes), streaming density (DenStream /
DbStream), quantile sketches, scipy.sparse input, soft assignment / coresets / diagnostics, the
Rust API, and the CLI — all in the usage guide.
Capabilities
Stable core — production-ready:
- Clustering heads — weighted k-means (Hamerly), GMM (diagonal & full covariance, BIC auto-
k), exact Ward HAC, all over the numerically stable BETULA CF-tree. - Streaming —
partial_fitat bounded memory (max_leaves/memory_budget_mb), EWMAdecay. - scikit-learn API —
fit/predict/fit_predict,get_params/set_params(works withPipeline/clone/GridSearchCV); typed abi3 wheel,save/load+ pickle, reusable Rust core. - Inspection —
predict_proba, coresets, microcluster/cluster geometry, outliers, near-duplicates, representatives, diagnostics.
Experimental / evolving — useful today, API may still move:
- Density & topology — HDBSCAN-CF (density over microclusters) and a Mapper topological skeleton
(
mapper/mapper_stability). - More heads & data —
DenStream/DbStreamevolving-stream density, mergeableKllSketch/DdSketchquantiles,scipy.sparse(O(nnz), never densified), mixed numeric+categorical (KPrototypes), COP-KMeans constraints, robust (Huber) insertion, drift snapshots, dependency-free CLI.
Full reference: docs/FEATURES.md.
Examples
Twelve executed, plotted notebooks — one per capability — live in
examples/ (render on GitHub):
- Core — quickstart, embeddings & inspection, streaming & persistence, method comparison, Mapper topology.
- Streaming density —
DenStream&DbStream. - Mixed data —
KPrototypes. - Sketches —
KllSketch&DdSketch. - Semi-supervised — must-link / cannot-link.
- Sparse / high-dim —
scipy.sparse+fit_predict_sparse. - Soft assignment & coresets —
predict_proba, coresets, diagnostics. - Production ops — drift, active learning, robust, memory budgets.
And three end-to-end use cases (each scored against ground truth):
- 🧹 Embedding dedup — collapse a repost-heavy corpus to representatives.
- 🚨 Log anomaly detection — batch outlier scoring + streaming
DbStreamflags. - 👥 Customer segmentation — mixed RFM + categorical personas with
KPrototypes. - 🧠 RAG corpus curation — junk removal, topic coherence, and topic-leakage detection via Mapper.
- 🔢 Real-data clustering — handwritten digits, ARI parity + centroid/exemplar inspection.
Documentation
- Usage guide — runnable snippets for every interface.
- Features — full capability reference + crate architecture.
- Math — stable CF, GMM E-step, distance derivations, relation to BIRCH/BETULA.
- Benchmarks — methodology, every metric, all tables, honest wins & losses.
- Design — internal design, invariants, and testing strategy.
Verified: 129 Rust unit/integration tests + a 153-case Python suite at 100% wrapper
coverage (Rust ≥95%, CI-enforced), clippy -D warnings + fmt clean across all feature sets, on
Python 3.11–3.14 (single abi3 wheel).
Known limitations
Honest scope — inherent to a CF-compression + streaming design, not bugs:
- Insertion-order sensitive — like every BIRCH-family streaming method, the labels depend on the order points arrive (the parallel build differs from the serial one, as a different order would).
threshold/max_leavesare real hyperparameters — they trade compression against resolution;n_rebuilds_/threshold_expose thrashing / over-coarsening.- CF-level heads approximate raw-data clustering — Phase-3 runs on the
M ≪ Nmicroclusters; quality degrades when clusters overlap at the compression scale. Mitigation: more leaves. - HDBSCAN-on-CF ≠ raw-point HDBSCAN — mass-aware HDBSCAN over microclusters: fast and close, but an approximation (weaker on overlapping blobs; see the benchmarks).
- The expected-log GMM optimizes a CF-level objective, not pointwise EM (a deliberate, measured choice for coarse CFs).
- Frequent-Directions is an approximate low-rank covariance (exact only up to its rank
ℓ).
License
MIT.
Project details
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 betula_cluster-0.1.2.tar.gz.
File metadata
- Download URL: betula_cluster-0.1.2.tar.gz
- Upload date:
- Size: 4.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6afb46ad01e7c6ed2d54d4eec01410e68ba97fc336c190075135e5ee864571fa
|
|
| MD5 |
e69c16d90578c8cb966c3a0f7eb3cab8
|
|
| BLAKE2b-256 |
e187b7fd987dd00538740e89c9427018bb02dbdc8825bd9a626fa141e721c4bf
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2.tar.gz:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2.tar.gz -
Subject digest:
6afb46ad01e7c6ed2d54d4eec01410e68ba97fc336c190075135e5ee864571fa - Sigstore transparency entry: 1994050128
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.2-cp311-abi3-win_amd64.whl.
File metadata
- Download URL: betula_cluster-0.1.2-cp311-abi3-win_amd64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.11+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be65f103a2e08f9957cbcca5b26fd5e4b01e4291030b358d63464bd1943058e7
|
|
| MD5 |
24bfb2ded906145ff1e2f3bacb4f8d8c
|
|
| BLAKE2b-256 |
cad8c68412ceb982458ddcd2148e82cdab85d43c10ee8985feb20e0e3c84049f
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2-cp311-abi3-win_amd64.whl:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2-cp311-abi3-win_amd64.whl -
Subject digest:
be65f103a2e08f9957cbcca5b26fd5e4b01e4291030b358d63464bd1943058e7 - Sigstore transparency entry: 1994050480
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.11+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6757cd7634a54f287e9c96e35d8b9861346571bd7bb4054b278ee2b7cafc114
|
|
| MD5 |
ddb5aabc9fdd6a53b3cbee834f065a06
|
|
| BLAKE2b-256 |
722533b6bea0fcf1c8f0cd6a33ec76e36b30cc56392b532d9f9e8a9b9ca491fb
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
d6757cd7634a54f287e9c96e35d8b9861346571bd7bb4054b278ee2b7cafc114 - Sigstore transparency entry: 1994050617
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.11+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4c60b7ea7aeac6fe49f68e9d7ca40aa17f5bdfa6b6f90980401a708654d5985
|
|
| MD5 |
b23451b1f84645d3baadd671aa67c1c6
|
|
| BLAKE2b-256 |
d09e61a5b41077ed3422eff0f9344e696c70af001a8dc0f313a76ec43c25986b
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
f4c60b7ea7aeac6fe49f68e9d7ca40aa17f5bdfa6b6f90980401a708654d5985 - Sigstore transparency entry: 1994050301
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.2-cp311-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: betula_cluster-0.1.2-cp311-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.11+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4ffeabb08334fcd8e296540ae2222b2a5de057071b90e05bef228218c30c4ed
|
|
| MD5 |
1d30cba5c50e7ec7bf7dd9ef5734f07d
|
|
| BLAKE2b-256 |
ba30a0ee25772dc81227d0a113b3b94fadd2ca5da1900410d8f959e8b76f0d2e
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2-cp311-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2-cp311-abi3-macosx_11_0_arm64.whl -
Subject digest:
d4ffeabb08334fcd8e296540ae2222b2a5de057071b90e05bef228218c30c4ed - Sigstore transparency entry: 1994050879
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.2-cp311-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: betula_cluster-0.1.2-cp311-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.11+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d76ba9ada8c06f5ff07838f3f66d38bc6f7139faf88f7f78bce71c1248673546
|
|
| MD5 |
aa7bcb40546663af3eba20b1fc174569
|
|
| BLAKE2b-256 |
a6c7be92478e6cc2209fb498d64162d1eb2ca7bb9c2190ee02351dd49d34accc
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.2-cp311-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on ilgrad/betula-cluster
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betula_cluster-0.1.2-cp311-abi3-macosx_10_12_x86_64.whl -
Subject digest:
d76ba9ada8c06f5ff07838f3f66d38bc6f7139faf88f7f78bce71c1248673546 - Sigstore transparency entry: 1994050755
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb5fe0f8b7ae6a3c0994e7349b7aae321111315a -
Trigger Event:
push
-
Statement type: