Fast, numerically stable BETULA clustering with a Rust core
Project description
betula-cluster
Fast, memory-bounded clustering for large tabular & embedding data. A numerically stable BETULA CF-tree with a full set of clustering heads — k-means · GMM (diagonal & full) · Ward · HDBSCAN · Mapper — plus streaming
partial_fitand a scikit-learn API. From-scratch Rust core, PyO3 bindings, no LAPACK, no SciPy at runtime.
pip install betula-cluster
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.
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) # -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
- Clustering heads — weighted k-means, GMM (diagonal & full covariance, BIC auto-
k), exact Ward HAC, HDBSCAN-style density over CF microclusters, and a Mapper topological skeleton. - Streaming —
partial_fitat bounded memory;DenStream&DbStreamfor evolving streams; mergeableKllSketch/DdSketchquantiles. - Data types — dense
f32/f64,scipy.sparse(never densified),O(nnz)sparse-native, and mixed numeric+categorical (k-prototypes). - Beyond labels —
predict_proba, coresets, diagnostics, outliers / near-duplicates / representatives, drift snapshots, COP-KMeans constraints, and robust (Huber) insertion. - Engineering — scikit-learn API (
Pipeline/clone/GridSearchCV), typed abi3 wheel,save/load+ pickle, a dependency-free CLI, and a reusable Rust core.
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 123-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.0.tar.gz.
File metadata
- Download URL: betula_cluster-0.1.0.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 |
51856be0c2ecdab2cf403d929c885ecc7b40185d4514b7fa0543d059fbb32acb
|
|
| MD5 |
df0000738c8db8a8dda829082130836d
|
|
| BLAKE2b-256 |
4c0f584f635d8a7e262c8b7024d89356072a22bd26189feb5b8e966305a9a4a2
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0.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.0.tar.gz -
Subject digest:
51856be0c2ecdab2cf403d929c885ecc7b40185d4514b7fa0543d059fbb32acb - Sigstore transparency entry: 1992449177
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.0-cp311-abi3-win_amd64.whl.
File metadata
- Download URL: betula_cluster-0.1.0-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 |
095c5e97f8a6f757a4d7c8ed3281e1bf53ff596fe0242e07ca7dfca801993826
|
|
| MD5 |
cc1891299b125e1d3e904351ff4e9cdf
|
|
| BLAKE2b-256 |
f6b6128c0e9fe501b58116a7034e07ceac54785cb2fa3db0cbdebb207bf7cd6b
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0-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.0-cp311-abi3-win_amd64.whl -
Subject digest:
095c5e97f8a6f757a4d7c8ed3281e1bf53ff596fe0242e07ca7dfca801993826 - Sigstore transparency entry: 1992449543
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: betula_cluster-0.1.0-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 |
0dd649a268ff2a698d24dab97609af8e12d1df1476aee4e188db7bdbf359b456
|
|
| MD5 |
71edbfe54f3bac0ef14e31e88741cc67
|
|
| BLAKE2b-256 |
95ea4e8d3235172be776cdf705bb466f1f16847f5f896b67ff034428e20964ea
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0-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.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
0dd649a268ff2a698d24dab97609af8e12d1df1476aee4e188db7bdbf359b456 - Sigstore transparency entry: 1992449448
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: betula_cluster-0.1.0-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 |
99692db071b68fd4292625cdfc96afb6af863e0bebcafcab031116db0204c56a
|
|
| MD5 |
251f2f77ac17465c35a1f16c764a57c8
|
|
| BLAKE2b-256 |
b9695ca8f7d7a621109e7e2007429874ea5541b6b13164482ffaf0debaa25163
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0-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.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
99692db071b68fd4292625cdfc96afb6af863e0bebcafcab031116db0204c56a - Sigstore transparency entry: 1992449594
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.0-cp311-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: betula_cluster-0.1.0-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 |
07992e68aa34f8dd68086a201e827fbee11ea0b4e2ef6085b652f4035adbf4e5
|
|
| MD5 |
e7d879a8e06d63264fddc90aa55c3567
|
|
| BLAKE2b-256 |
f26dc9d974ea72153e985519f717f9c453d824a8980276202984d6cd7a223762
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0-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.0-cp311-abi3-macosx_11_0_arm64.whl -
Subject digest:
07992e68aa34f8dd68086a201e827fbee11ea0b4e2ef6085b652f4035adbf4e5 - Sigstore transparency entry: 1992449371
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file betula_cluster-0.1.0-cp311-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: betula_cluster-0.1.0-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 |
e442a989171260306293c162dd75226c9273b22d77dfce9968ca56b80d7e6bf3
|
|
| MD5 |
b685ebaf06a6fdebf584ff7f90be7e17
|
|
| BLAKE2b-256 |
2b74825b02f35121fa23bad15fc333a28272f627f1d3a1a06e6449c5ab3fcae3
|
Provenance
The following attestation bundles were made for betula_cluster-0.1.0-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.0-cp311-abi3-macosx_10_12_x86_64.whl -
Subject digest:
e442a989171260306293c162dd75226c9273b22d77dfce9968ca56b80d7e6bf3 - Sigstore transparency entry: 1992449268
- Sigstore integration time:
-
Permalink:
ilgrad/betula-cluster@753bff87deeb53b26d1038dd733cb697432bbed5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ilgrad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@753bff87deeb53b26d1038dd733cb697432bbed5 -
Trigger Event:
push
-
Statement type: