0131 lodestar cluster writes what netstandard2 0 lacks - CyrilB1531/lodestar GitHub Wiki
0131 โ Lodestar.Cluster writes what netstandard2.0 lacks: DBSCAN and agglomerative clustering
Status: accepted ยท Date: 2026-09-14 ยท Applies: 0074, 0075, 0093, 0129
Context
Lodestar.Cluster is one class. docs/equivalence.md listed the rest of scikit-learn's clustering
as MiniBatchKMeans, DBSCAN, AgglomerativeClustering, SpectralClustering, GaussianMixture
โ no counterpart yet, pointing at #442, which
is closed. #681 asked what the package is for
now that a maintained library ships four algorithms to its one, and asked for an answer that is
either fill it or scope it down, not a list of equals with nobody tracking it.
The reading
Decision 0129 read the four
numerics libraries on 2026-09-14; the clustering members, read again with tools/survey.cs
(decision 0110), are:
| NumFlat 1.3.4 | Meta.Numerics 4.2.0 | Numerics.NET 10.7.0 | MathNet.Numerics 5.0.0 | |
|---|---|---|---|---|
| licence, from the package | MIT | MS-PL | commercial | MIT |
lib/ |
net8.0 |
netstandard2.0 |
net462, netstandard2.0, net8.0+ |
net461, net48, netstandard2.0, net5.0, net6.0 |
| k-means | KMeans: k-means++ with TryCount restarts, and Update for one Lloyd step from given centroids |
Multivariate.MeansClustering(columns, m): no seed, no starting centres |
KMeansClusterAnalysis |
none |
| DBSCAN | Clustering.DbScan(xs, eps, minPoints) |
none | none | none |
| agglomerative | none | none | HierarchicalClusterAnalysis |
none |
| Gaussian mixture | GaussianMixtureModel, DiagonalGaussianMixtureModel |
none | GaussianMixtureDistribution |
none |
| k-medoids | KMedoids<T>, any distance |
none | none | none |
| spectral, mini-batch | none | none | none | none |
Among the numerics libraries, on netstandard2.0 and free, clustering is two k-means: this
package's and Meta.Numerics'. Accord.MachineLearning 3.8.0 adds k-means and a Gaussian mixture under
LGPL-2.1, last released in 2017 and archived in 2020.
The dedicated packages
nuget.org, 2026-09-14, searched for dbscan, hierarchical clustering, agglomerative,
clustering and gaussian mixture. Three single-purpose packages answer, and a reading that stopped
at the numerics libraries would have claimed a void that is not there:
| package | published | licence | lib/ |
surface | what it is |
|---|---|---|---|---|---|
Dbscan 3.0.0 |
2022-07-12 | MIT, <license type="expression"> |
netstandard1.2, netcoreapp3.1, net6.0 |
9 types, 31 members | DBSCAN over IPointData, whose Point carries X and Y and nothing else: two dimensions only. The repository was last pushed 2023-03-01. |
Aglomera 1.1.1 |
2020-07-05 | MIT, by licenseUrl to the repository's LICENSE.md โ the package carries no licence field |
netstandard1.3 |
39 types, 136 members | Agglomerative clustering over any dissimilarity: SingleLinkage, CompleteLinkage, AverageLinkage, CentroidLinkage, WardsMinimumVarianceLinkage, MinimumEnergyLinkage. No release in six years; the repository was last pushed 2022-12-08. |
HdbscanSharp 3.0.1 |
2025-05-06 | MIT, by licenseUrl |
netstandard2.0, net8.0 |
23 types, 85 members | HDBSCAN โ HdbscanRunner.Run returning Labels and OutliersScore. scikit-learn has carried HDBSCAN since 1.3. |
So the voids are narrower than the numerics libraries suggest. An n-dimensional DBSCAN is absent
below net8.0: the one free package is planar. Agglomerative clustering is present on
netstandard2.0, with every linkage scikit-learn offers, in a package not released since 2020 and
never compared with scikit-learn. HDBSCAN is present and maintained.
The measurement
bench/README.md section 34 has the method and docs/guides/performance.md the numbers, on an AMD
Ryzen 7 8700G with the default job. Every pair agreed on the centres before it was timed, and the
largest difference was zero.
- Lloyd's iterations from the same centres, against NumFlat's
Update: ahead on every shape, 1.52ร to 3.66ร. This is the like-for-like pair. - A whole default fit, k-means++ included: 5.5ร to 13ร cheaper than NumFlat's, 4.1ร to 20ร cheaper than Meta.Numerics'. Part of that is scikit-learn's scaled tolerance stopping after one or two iterations on separated blobs, and neither incumbent reports its own count.
The one algorithm the package has is not the weak point; its scope is.
Decision
Lodestar.Cluster is scikit-learn's clustering where netstandard2.0 has none, and it fills that
scope in two lots. The two grounds #681 offered decide it: netstandard2.0 reach is where the
package's reason to exist lies, and sparse input decides nothing yet, since none of the incumbents
takes a CsrMatrix either.
| scikit-learn | verdict | why |
|---|---|---|
DBSCAN |
written, #759 | Below net8.0 the only free one is Dbscan, and it is planar. Deterministic given the neighbourhood, so parity is exact labels. Dbscan is benchmarked on two-dimensional input and NumFlat on the rest. |
AgglomerativeClustering |
written, #760, with Aglomera as its measured incumbent |
Aglomera covers the linkages on netstandard2.0 and has not been released since 2020, which is the "no maintained equivalent" this project writes against โ the position Accord.Statistics put Lodestar.Stats in. The lot compares the two before claiming anything: if Aglomera agrees with scikit-learn on labels and merge tree, the lot says so and is re-weighed. Merges are deterministic, so parity is exact labels and an exact children_. |
HDBSCAN |
delegated to HdbscanSharp |
MIT, netstandard2.0, released 2025. Not compared with scikit-learn here; the migration row says so. |
GaussianMixture |
delegated to NumFlat on net8.0; a scoped gap below it |
NumFlat covers it for the targets most callers are on. Parity is possible โ means_init, weights_init and precisions_init make EM an input-driven loop, as init made Lloyd's โ so this is a lot not yet taken, not a refusal. |
| k-medoids | delegated to NumFlat | Not scikit-learn: it lives in scikit-learn-extra, outside the reference this package holds itself to. |
MiniBatchKMeans |
not written | Its batches are drawn inside the fit from random_state through numpy's generator, and no parameter hands them in. There is no input that makes a run reproducible from .NET, so there is nothing to hold a corpus to. |
SpectralClustering |
not written until the eigensolver exists | It needs the leading eigenvectors of an affinity Laplacian โ ARPACK by default. PrincipalComponentVariance computes eigenvalues of a small dense Gram matrix; that is not this. |
Ties are recorded, not chased. Decision 0093
found scikit-learn breaking an exact distance tie both ways inside a BLAS-driven kernel. DBSCAN's
border points reachable from two clusters and agglomerative merges at equal distance are the same
kind of choice; each lot measures the reference's and states it in docs/equivalence.md, rather
than letting a tie decide what parity means.
Options that lost
- Delegate agglomerative clustering to
Aglomeraand DBSCAN toDbscan. Both install onnetstandard2.0and both are MIT. It lost on dimensions for DBSCAN, and forAglomerait is not lost yet so much as unproven: #760 measures it first. - Scope the package down to k-means and delegate the rest to NumFlat. Honest, and it would
close #681 in a day. It lost on the first table: a
.NET Framework, Mono or Unity caller would be delegated to a library that does not install, which is the failure decision 0116 refused for PCA's migration row. - Write all five. The list reads like one scope and is not: two are exact and not maintained
anywhere free below
net8.0, one is present where most callers are, one has no reproducible reference, and one needs a solver this repository does not have. Writing them as equals is how the row sat untracked after #442 closed. - Write the Gaussian mixture first, since NumFlat proves the demand. Demand is not the ground;
reach is. On
net8.0a caller has it today; below it, a caller has a planar DBSCAN and an agglomerative package six years without a release.
Consequences
docs/equivalence.md's single row becomes one row per verdict, spectral clustering among them with its own reason.docs/migration/sklearn.mdgains the clustering rows:KMeanshere,GaussianMixtureand k-medoids to NumFlat onnet8.0,HDBSCANtoHdbscanSharp, DBSCAN and agglomerative as being written, withDbscanandAglomeranamed beside them.README.md's incumbent row forLodestar.Clusterreads from the measurement instead of not measured.bench/Lodestar.Text.Benchmarksreferences Meta.Numerics beside NumFlat, for this comparison alone.