Skip to content

Releases: ilgrad/betula-cluster

betula-cluster v0.1.5

Choose a tag to compare

@github-actions github-actions released this 04 Jul 19:22

Added

  • method="leiden" / method="leiden-cpm"graph clustering / community detection over the
    microcluster affinity graph via the full Leiden algorithm (Traag, Waltman & van Eck 2019):
    local moving → refinement (sub-communities grown from singletons along edges, so each is
    connected by construction — Leiden's guarantee over Louvain) → aggregation seeded from the
    pre-refinement partition. It discovers the community count — no k (like the density head).
    A resolution (γ) knob trades community count against size; the modularity objective
    ("leiden", γ = 1 default) has a resolution limit, the CPM objective ("leiden-cpm") is
    resolution-limit-free (γ on a smaller, density scale). Pure Rust — no eigensolver, NumPy-only.
    Best for community/blob structure at a moderate threshold; use method="spectral" for elongated
    manifolds. The self-tuning k-NN affinity graph is shared between the spectral and Leiden heads.
  • betula_cluster.consensus(X, n_clusters, n_runs=…) — clusters X under several random
    insertion-order permutations and votes, turning the CF-tree's insertion-order sensitivity
    (Known Limitation #1) into a measurable quantity: a consensus labelling plus a per-point
    stability score
    in [0, 1] (ConsensusResult.confidence — low on unstable boundaries, high
    where every order agrees). NumPy-only; for the partitional heads at a fixed n_clusters. The
    independent runs parallelize across threads with n_jobs (the Rust core releases the GIL).
  • method="spectral" — spectral clustering over the CF-tree leaf microclusters for non-convex /
    manifold
    clusters (moons, rings, spirals) that the centroid heads cannot separate. Self-tuning
    symmetric k-NN affinity (Zelnik-Manor & Perona local scaling), the normalized Laplacian embedding
    (Ng-Jordan-Weiss) via the in-house Jacobi eigensolver — no LAPACK/ARPACK, the crate stays
    NumPy-only — with a k-means landmark reduction above 256 microclusters so the O(m³) solve stays
    bounded. Dense input only; pair it with a small threshold (many leaves) so the microclusters
    resolve the manifold. No built-in cluster-count selection: n_clusters=0 defaults to 2.
  • threshold="auto" for the Betula estimator — removes the one hyperparameter users most often
    have to guess. A bounded-subsample pilot fits a threshold=0 tree at the same max_leaves and
    reads the threshold it converges to, warm-starting the full fit near-converged instead of growing
    it from zero (fewer rebuild passes, lower peak leaf count on large n). Cached across refits /
    streaming batches; below the pilot cap it is a no-op (growing from zero is already cheap), and it
    is dense-only (raises on sparse input).

Changed

  • Benchmarks now cover every head (spectral, Leiden added to bench/comprehensive.py) and the
    compression heads run at max_leaves = 4000: betula-kmeans is at exact parity with scikit-learn
    (blobs 0.861 = 0.861) and Ward beats raw Ward while running the full N. Docs / README / docs site
    surface the spectral, Leiden and consensus additions; test counts reconciled (190 Python, 158
    Rust). The docs site now renders the CHANGELOG and redeploys on every published release.

betula-cluster v0.1.4

Choose a tag to compare

@github-actions github-actions released this 04 Jul 10:55

Added

  • MapperGraph.persistence_diagram / MapperGraph.persistence(filtration=…) — 0-D persistent homology
    of the Mapper nerve by single-linkage union-find (elder rule, O(E log E), pure Rust). Two
    filtrations: "overlap" (the 1 − edge_overlap Bhattacharyya gap — a finite bar's death is the depth
    of a bottleneck, ranking the boolean bridges) and "lens" (the lens sublevel diagram). Essential
    connected-component classes carry inf death.
  • Greedy weighted k-means++ init (scikit-learn's default): lower-inertia, lower-variance seeds at
    ~ln k× the negligible init cost over the leaves.
  • objective="dbcv" for tune — Density-Based Clustering Validation (Moulavi et al. 2014, in
    [-1, 1]). Unlike the convex Calinski-Harabasz / Davies-Bouldin metrics (which penalise correct
    non-convex partitions), DBCV validates variable-density / non-convex clusters, so it is the right
    selection metric for the HDBSCAN-CF and DbStream density heads. NumPy-only, computed over a
    subsample.

Changed

  • fit_predict_sparse / the _core CSR entry points now cap n_features (MAX_SPARSE_FEATURES) and
    validate CSR arrays through the pure-Rust sparse::validate_csr, closing an unbounded-allocation DoS
    where a hostile caller could force an ~8 EB allocation with a single-nonzero row.
  • Docs reconciled to the current suite: 172-case Python suite, 147 Rust tests (143 unit + 4
    integration under default features; the python / persistence / cli surfaces add more, 155 total).

Tests

  • Mutation-testing infrastructure (cargo-mutants scoped to the CF math core, mutmut for the Python
    wrapper, a weekly non-blocking workflow) plus a CSR-fuzzing proptest and the two coverage gaps it
    surfaced (the CF-tree absorption boundary, exact tune-metric values).

betula-cluster v0.1.3

Choose a tag to compare

@github-actions github-actions released this 04 Jul 08:43

Added

  • betula_cluster.tune — memory-aware hyperparameter search over the CF knobs, scored by an internal
    metric (Calinski-Harabasz / Davies-Bouldin) or ARI, with a multi-objective quality / memory /
    speed
    Pareto mode. NumPy-only by default; an optional Optuna backend (TPE / NSGA-II) via
    pip install 'betula-cluster[tune]'.
  • Property-based tests (proptest, dev-only) for the CF-tree invariants: the clustering feature is a
    commutative monoid (merge is associative/commutative and equals a sequential build), folding a
    tree's leaf features reconstructs the whole-dataset feature, the full-covariance upper-triangular
    index is a bijection (incl. dim ≥ 4), and the Frequent-Directions sketch is lossless on low-rank
    data and never overshoots the exact scatter.
  • Sparse-text benchmark (20 newsgroups, TF-IDF): the O(nnz) fit_predict_sparse path and the
    standard reduce-then-cluster pipeline (TruncatedSVD / NMF → k-means) vs scikit-learn, written up
    honestly in bench/RESULTS.md (raw high-d TF-IDF concentrates for every fast clusterer; on NMF
    topics betula matches sklearn).
  • MapperGraph.edge_overlap — a Bhattacharyya coefficient in (0, 1] per Mapper edge, from the pooled
    diagonal-Gaussian summaries of the two nodes' member microclusters. Surfaced on to_networkx() edges
    as overlap=…, so a bridge between well-separated regions reads as a lower-weight edge than one
    inside a dense blob.
  • Documentation site (MkDocs Material + mkdocstrings API autodoc, MathJax-rendered math) built from
    docs/, with a GitHub Pages deploy workflow; pip install 'betula-cluster[docs]' for the toolchain.

Changed

  • Coverage floor (cargo llvm-cov, ≥95 % lines) now also measures the persistence and cli feature
    sets, not just the default core.
  • Declared rust-version = "1.82" (MSRV) and lowered the real floor to it — the streaming heads had an
    implicit 1.87 dependency (u64::is_multiple_of), now rewritten. Added Documentation / Changelog
    project URLs.
  • Docs reconciled to the current suite: 167-case Python suite, 141 Rust tests (137 unit + 4
    integration), and five end-to-end use cases (README, DESIGN.md).
  • Repository hardening: macOS / Windows CI test legs, an sdist install smoke test, a nightly
    cargo audit cron, Dependabot, and SECURITY.md / CONTRIBUTING.md / issue templates.

betula-cluster 0.1.2

Choose a tag to compare

@ilgrad ilgrad released this 28 Jun 11:18

Maintenance & docs release — no API changes; the engine and benchmarks are unchanged from 0.1.1.

Install / upgrade: pip install -U betula-cluster

Added

  • betula_cluster.__version__, resolved from the installed package metadata.

Changed

  • README repositioned around the compress-then-cluster story: the test/coverage story is surfaced at the top, a "When to use it" section added, and capabilities split into stable core vs experimental. The density head is labelled HDBSCAN-CF consistently in prose (the method="hdbscan" API string is unchanged).

Fixed

  • Stale docs corrected: the Python suite is 153 cases (was 123); betula-index references now point to lexindex (the renamed indexing companion).

betula-cluster 0.1.1 — first public release

Choose a tag to compare

@ilgrad ilgrad released this 28 Jun 09:02

Fast, memory-bounded, numerically stable BETULA CF-tree clustering with a from-scratch Rust core and a scikit-learn API. No LAPACK, no SciPy at runtime.

Install: pip install betula-cluster

Highlights

  • 15–40× faster than scikit-learn at N = 1,000,000 — k-means labels 1M points in 0.20 s (vs KMeans 3.3 s, Birch 8.0 s, GaussianMixture 5.5 s).
  • 🪶 Bounded memory — streaming 10M points peaks at ~57 MB, flat in N (clusters data larger than RAM); an in-core KMeans needs ~5 GB.
  • 🎯 Quality at parity — k-means / GMM / Ward within ≈0.01 ARI of scikit-learn; full-covariance GMM recovers anisotropic clusters; HDBSCAN-on-CF nails non-convex shapes (ARI 1.00).
  • 🔢 High-dimensional normalize=True — MNIST-784 ARI 0.04 → 0.44, beating scikit-learn (0.32).
  • 🧱 Numerically stable(n, μ, S) Welford/Chan updates, PSD by construction; no catastrophic cancellation far from the origin.

What's inside

  • Heads: weighted k-means (Hamerly), GMM (diagonal & full), exact Ward-HAC, HDBSCAN-on-CF, Mapper topology; automatic k (BIC / dendrogram cut).
  • Streaming: partial_fit at bounded memory, DenStream / DbStream, mergeable KLL / DDSketch quantiles.
  • Data: dense f32/f64, scipy.sparse (O(nnz)), mixed numeric+categorical (k-prototypes).
  • Beyond labels: predict_proba, coresets, outliers, near-duplicate pairs, representatives, drift snapshots, COP-KMeans constraints, robust (Huber) insertion.
  • Engineering: scikit-learn API (Pipeline / clone / GridSearchCV), typed abi3 wheel, save/load + pickle, dependency-free CLI, reusable Rust crate.

Quality bar

153-case Python suite at 100% wrapper coverage + 129 Rust tests; clippy -D warnings + fmt clean across all feature sets; Python 3.11–3.14 (single abi3 wheel).

Wheels

abi3 (CPython 3.11+): manylinux x86_64 + aarch64, macOS x86_64 + arm64, Windows x64, + sdist.

📊 Benchmarks · 📖 Docs · 📓 Examples · 📝 Changelog