Skip to main content

Fast, numerically stable BETULA clustering with a Rust core

Project description

betula-cluster

PyPI Python CI Python coverage 100% License: MIT Rust core · PyO3

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 · spectral · Leiden community detection · HDBSCAN-CF · Mapper — so cost scales with the microcluster count, not N. Streaming partial_fit, a scikit-learn API, from-scratch Rust core + PyO3, no LAPACK or SciPy at runtime.

pip install betula-cluster

Verified: a 190-case Python suite at 100% wrapper coverage + 158 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 — or better. betula's k-means is at exact parity with scikit-learn (blobs 0.861 = 0.861), full-covariance GMM recovers anisotropic clusters just as well (0.90 vs 0.90), and Ward beats raw Ward while running the full N (which O(N²) sklearn-ward cannot). On non-convex moons & circles the density and spectral heads hit ARI 1.00 — spectral matching scikit-learn's own SpectralClustering at 3–4× the speed (it eigensolves the microclusters, not all N).
  • 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=True beats scikit-learn (k-means 0.44 vs 0.32) — full table, and the honest trade-offs, in bench/RESULTS.md.
Fit time vs N Peak memory vs N
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 coordinate 1e7; 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 in N and clusters data larger than RAM.
  • Complete — one stable engine spanning k-means / GMM (diag & full) / Ward / spectral / Leiden community detection / 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)

Memory-aware hyperparameter tuning (tune, optional Optuna), Mapper topology (mapper), 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, spectral (non-convex / manifold), and Leiden graph community detection (auto community count, resolution / CPM), all over the numerically stable BETULA CF-tree.
  • Streamingpartial_fit at bounded memory (max_leaves / memory_budget_mb), EWMA decay.
  • scikit-learn APIfit / predict / fit_predict, get_params / set_params (works with Pipeline / clone / GridSearchCV); typed abi3 wheel, save / load + pickle, reusable Rust core.
  • Inspection & robustnesspredict_proba, coresets, microcluster/cluster geometry, outliers, near-duplicates, representatives, diagnostics, and consensus (per-point stability across insertion-order permutations).
  • Tuningtune: memory-aware hyperparameter search with a quality / memory / speed Pareto mode; NumPy-only, optional Optuna backend (pip install 'betula-cluster[tune]').

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 & dataDenStream / DbStream evolving-stream density, mergeable KllSketch / DdSketch quantiles, 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):

And five end-to-end use cases (each scored against ground truth):

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: 158 Rust unit/integration tests + a 190-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:

  1. 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).
  2. threshold / max_leaves are real hyperparameters — they trade compression against resolution; n_rebuilds_ / threshold_ expose thrashing / over-coarsening.
  3. CF-level heads approximate raw-data clustering — Phase-3 runs on the M ≪ N microclusters; quality degrades when clusters overlap at the compression scale. Mitigation: more leaves.
  4. HDBSCAN-on-CF ≠ raw-point HDBSCAN — mass-aware HDBSCAN over microclusters: fast and close, but an approximation (weaker on overlapping blobs; see the benchmarks).
  5. The expected-log GMM optimizes a CF-level objective, not pointwise EM (a deliberate, measured choice for coarse CFs).
  6. 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

betula_cluster-0.1.5.tar.gz (5.2 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

betula_cluster-0.1.5-cp311-abi3-win_amd64.whl (1.8 MB view details)

Uploaded CPython 3.11+Windows x86-64

betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ x86-64

betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.5 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

betula_cluster-0.1.5-cp311-abi3-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

betula_cluster-0.1.5-cp311-abi3-macosx_10_12_x86_64.whl (1.8 MB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

Details for the file betula_cluster-0.1.5.tar.gz.

File metadata

  • Download URL: betula_cluster-0.1.5.tar.gz
  • Upload date:
  • Size: 5.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for betula_cluster-0.1.5.tar.gz
Algorithm Hash digest
SHA256 c212235d2fd4fd7155672399f52ebfaf3e9ac5e904a4f314d57771d008537814
MD5 0809fcba127fa2c65714ad673bb3aad3
BLAKE2b-256 462eb8e46687bc3e0858ccc4bd518681b14e0e27dbd191e1ffbe974d5c2bcfd3

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5.tar.gz:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betula_cluster-0.1.5-cp311-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for betula_cluster-0.1.5-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 9f668d033052fbb05066fdc4fad6abb1e8bfcd9083a05535f3943d2b8ef39a87
MD5 c240a31c48bf879505942e1c59c154b5
BLAKE2b-256 9c74842bb66c62dd90348019dc3b96fc9355b0d4f27d7e9f9422dbf783ecb2e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5-cp311-abi3-win_amd64.whl:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 bd50f17f7d66da259dd8e6174f9ba71039ca19ce62eca955e5f3bbb0f12abf77
MD5 3a0586f237197f8ca0935fd4f609d469
BLAKE2b-256 a8d7491664cb771b4e113824aae5e98486e47bef742e59cc56b0e36f29aeb537

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 43c732ce6bd169f299977c8550dbec93dbdbfa295854ba75a65cf04d217815c0
MD5 c143dc75eea411f25762b4b8e460870c
BLAKE2b-256 e9bedcb934f1827a8330c7f6aa3ec03a33715689defdb25995767326e6e0bf79

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betula_cluster-0.1.5-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for betula_cluster-0.1.5-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c2209e89bfec30a748c1c519fd23a7b707e0f608d1671fa5273c0b4c16d04264
MD5 755b30c7e004a14de2d82d3d8b0d7f1c
BLAKE2b-256 b87b3785900ffb79af20fcc56cf5f18d920edbfc100d439c31f85503120cbc0b

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file betula_cluster-0.1.5-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for betula_cluster-0.1.5-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 5ce775d635f62a58f57e6d7440fc463a4da819112fa41495ec8a6e9cd8287fad
MD5 2dce8bc7eb8337ad582f2c5fb391c364
BLAKE2b-256 708cac6d02ae9f05ac5a938ca55f5b85cf8651d166b68e2dd5a28009f69a078a

See more details on using hashes here.

Provenance

The following attestation bundles were made for betula_cluster-0.1.5-cp311-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on ilgrad/betula-cluster

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page