All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
decomposition/lda.rs:LDA, linear discriminant analysis for supervised dimensionality reduction (#136). It projects the data onto the directions that best separate the classes, keepingmin(n_classes - 1, n_features)components by default, and implements theTransformerinterface next toPCA. Directions match scikit-learn'sLinearDiscriminantAnalysis(solver="eigen")up to sign.
xgboost/xgb_regressor.rs:XGRegressor::fitno longer panics whensubsampleis less than 1.0 on a small dataset (#444). The sample for each tree now keeps a minimum of one row, as scikit-learn does for its ownsubsampleparameter. Sample sizes of one row or more are unchanged.
algorithm/neighbour/cosinepair.rs:CosinePair::query_row_top_know returns exact nearest neighbours wheneverapproximateisfalse(the default). Previously the query always sampled onlytop_kevenly strided candidate rows without documentation, and the bounded candidate heap evicted its closest entry, so the method could return the farthest of the sampled rows (#442). Strided sampling is now gated behindCosinePairParameters { approximate: true, .. }and is documented as approximate.algorithm/neighbour/cosinepair.rs:CosinePairconstruction now evaluates each unordered row pair once (symmetric half-scan), precomputes row norms once in O(n·d), and scores pairs through zero-copy row views instead of materialising twoVecs per pair (#442). Distances are unchanged (bit-identical formula and operation order asCosine::new().distance(...)); measured build time drops ~3x on a 1500x64 input. Construction remains Theta(n^2) dot products —top_kdoes not make it sub-quadratic; module and method docs now state both facts.
- Breaking:
CosinePairgained a privaterow_normsfield holding the precomputed row norms. Construct the structure throughnew/with_top_k/with_parametersinstead of struct literals.
model_selection: pinned theKFoldseed in thetest_cross_val_predict_knnandtest_cross_validate_knnunit tests. Under--all-features(std_rand) an unseededKFolddraws OS entropy, so each CI run shuffled the folds differently; a sweep of 20 000 seeds showed 0.17% of shuffles violate theMAE < 10.0assertion (worst 12.81) and 0.01% violatetrain_score < test_score, making CI flaky. Library behaviour is unchanged; the entropy-seeded path stays covered by therand_customtests.
metrics:precision,recall, andf1(the free functions, thePrecision/Recall/F1metric structs, and the matchingClassificationMetricsentry points) now accept any label type that implementsNumber, including ordered integers such asu16ori32; labels no longer need to implementRealNumberorFloatNumber(#322). The same integer labels can now feedRandomForestClassifier::fitand classification metrics insidemodel_selection::cross_validate. Class keys are derived through a sharedf64conversion instead of raw float bit transmutation; scores for float inputs are unchanged.
linear/linear_regression.rs:LinearRegression::fit/fit_matrixnow returnErr(Failed::fit(...))instead of panicking when the intercept-augmented system is underdetermined, i.e.n_features + 1 > n_samples(#435). Both the default SVD solver and the QR solver are covered.linalg/traits/svd.rs:svd_solve/svd_solve_mutreject systems where A has more columns than rows withFailedError::SolutionFailedinstead of writing the solution out ofb's bounds;SVD::solvegained a matching defensive guard.linalg/traits/qr.rs:qr_solve_mutreturnsErr(Failed)("Matrix is rank deficient.") for rank-deficient systems (duplicate/collinear columns, underdetermined shapes) instead of panicking.
linear/linear_regression.rs: native multi-output matrix support (#432, #433).LinearRegression::fit_matrixfits N x K targets directly (both QR and SVD solvers);predict_matrixreturns the K-column prediction matrix;intercept_matrixexposes the 1 x K intercept row.
- Breaking:
LinearRegressionfieldinterceptchanged fromOption<TX>toOption<X>(a 1 x K matrix) and the struct gainedPhantomData<TX>. Previously serialized models will not deserialize; re-fit or migrate saved models.intercept()still returns a scalar for single-output callers.
- Stage 2 test-coverage push (#393):
proptestdev-dependency + property-based invariant tests and linalg edge cases.linalg/basic/arrays.rs: proptest invariants — transpose involution(A^T)^T == A, matmul with identityA*I == A, matmul associativity(AB)C ≈ A(BC)(approximate comparison for FP),(AB)^T == B^T A^T, reshape preserves element count. Edge cases — 1x1 matmul, row×col matmul, shape-mismatch panic, reshape-incompatible panic, 1xN transpose.algorithm/sort/quick_sort.rs: proptest —quick_argsortproduces a valid permutation (all indices present exactly once, values non-decreasing in permutation order).metrics/distance/euclidian.rs: proptest —d(a,a) == 0, symmetryd(a,b) == d(b,a), triangle inequalityd(a,c) ≤ d(a,b) + d(b,c).
- Added
proptest = "1.5"to[dev-dependencies].
- Replaced the remaining 4
unsafe {}raw-pointer blocks inlinalg/basic/matrix.rs::iterator_mut/DenseMatrixMutView::iter_mutwith a safesplit_first_mut-based helperordered_iter_mut(#368). The traversal order and offset formula are identical to the previous raw-pointer implementation; only the borrow-proving mechanism changed — eliminatingunsafefrom library code entirely. - Performance note: the cross-axis path (axis ≠ natural storage order) now extracts the needed refs via
split_first_mutin sorted-offset order and reorders, introducing a small allocation. The fast path (axis matches storage order) shortcuts tovalues.iter_mut().take(n)with zero overhead. Benchmarks to quantify the cross-axis delta are tracked in #407.
- Ported the crate from Rust edition 2021 to edition 2024 (#401, #402).
cargo fix --editionmade no auto-edits; the only behavioral-adjacent change islinalg/basic/arrays.rs::approximate_eq, rewritten to the 2024-safe tail-expr drop-order form (bind the owned intermediate before the borrowing iterator) — numerical logic unchanged. - Declared
rust-version = "1.85"(MSRV) inCargo.tomland added anmsrvCI job that builds withdtolnay/[email protected]to verify the claim (#404). - Migrated lint suppressions:
#[allow(...)]→#[expect(...)]at sites where the lint still fires under--all-features;#[allow]retained where the lint genuinely does not fire (avoidsunfulfilled_lint_expectations) (#403). - Added
[lints.rust] unexpected_cfgscheck-cfgtable inCargo.tomlforcfg(coverage, coverage_nightly)andcfg(tarpaulin)(edition-2024unexpected_cfgslint). AGENTS.md: documented the edition-2024 invariants (no RPIT, explicitdyn Trait + 'a, tail-expr drop-order, lint-suppression policy,unsafestance) and the "preserve bespoke numerical-system logic and performance" constraint for non-behavioral refactors.
svm/svc.rs: removed two redundantlet svc = ...; svctail expressions surfaced by the edition-2024clippy::let_and_returnlint.preprocessing/categorical.rs: kept the nested-ifform (annotated#[allow(clippy::collapsible_if)]) because collapsing to a let-chain requires let-chains, unstable until Rust 1.88 — incompatible with the declared MSRV 1.85.
- Stage 1 test-coverage push (#392): tests for previously-untested modules.
linalg/traits/high_order.rs: implemented the/* TODO */test module — all 4abtranspose-flag branches, non-square inputs, and a matmul/transpose equivalence check.linear/lasso_optimizer.rs: direct tests forInteriorPointOptimizer(newshape ofata, known-answer l1-regularized least squares withlambda → 0).error/mod.rs: tests for all 6Failedconstructors, all 8FailedErrorvariants, bothDisplayimpls, bothPartialEqimpls, and theErrortrait impl.rand_custom.rs: seeded-RNG determinism andNone-seed usability tests.
- Revived 6 previously-commented-out serde round-trip tests (migrated to
postcard, the post-#390 serialization backend) forLinearRegression,RidgeRegression,Lasso,ElasticNet,PCA,SVD. - Fixed a latent type mismatch in the revived
SVDserde test: the original commented-out code deserialized intoSVD<f32, DenseMatrix<f32>>butSVD::fiton thef64iris literals producesSVD<f64, ...>— corrected toSVD<f64, DenseMatrix<f64>>. - Renamed two copy-paste-misnamed tests:
dataset::diabetes::boston_dataset→diabetes_dataset;algorithm::sort::quick_sort::with_capacity→quick_argsort.
- CI coverage workflow now includes doctests and enforces a strict 44% line-coverage gate via cargo-tarpaulin (#399).
- Classification metrics refactored:
Precision,Recall, andF1now derive per-class scores from a single sharedConfusionCountshelper (src/metrics/confusion.rs) instead of each re-implementing the per-class tp/predicted/support bookkeeping.PrecisionandRecallexpose a crate-privateper_class_scores_from_countsused byF1's multiclass path. PrecisionandRecallnow early-return0.0on empty input and drop the unreachableclasses == 0/support.is_empty()branches.- Multiclass macro
F1(landed in #382, cleaned up in #383) is unchanged behaviourally; it now consumesPrecision/Recall::per_class_scores_from_countsinstead of its ownHashMapbookkeeping.
- WARNING: Breaking changes!
LassoParametersandLassoSearchParametershave a new fieldfit_intercept. When it is set to false, thebeta_0term in the formula will be forced to zero, andinterceptfield inLassowill be set toNone.
- WARNING: Breaking changes!
DenseMatrixconstructor now returnsResultto avoid user instantiating inconsistent rows/cols count. Their return values need to be unwrapped withunwrap(), see tests
- WARNING: Breaking changes!
- Complete refactoring with extensive API changes that includes:
- moving to a new traits system, less structs more traits
- adapting all the modules to the new traits system
- moving to Rust 2021, use of object-safe traits and
as_ref - reorganization of the code base, eliminate duplicates
- implements
readers(needs "serde" feature) for read/write CSV file, extendible to other formats - default feature is now Wasm-/Wasi-first
- WARNING: Breaking changes!
- Seeds to multiple algorithims that depend on random number generation
- Added a new parameter to
train_test_splitto define the seed - changed use of "serde" feature
- WARNING: Breaking changes!
- Drop
nalgebra-bindingsfeature, onlyndarrayas supported library
- L2 regularization penalty to the Logistic Regression
- Getters for the naive bayes structs
- One hot encoder
- Make moons data generator
- Support for WASM.
- Make serde optional
- DBSCAN
- Epsilon-SVR, SVC
- Ridge, Lasso, ElasticNet
- Bernoulli, Gaussian, Categorical and Multinomial Naive Bayes
- K-fold Cross Validation
- Singular value decomposition
- New api module
- Integration with Clippy
- Cholesky decomposition
- ndarray upgraded to 0.14
- smartcore::error:FailedError is now non-exhaustive
- K-Means
- PCA
- Random Forest
- Linear and Logistic Regression
- KNN
- Decision Tree
- First release of smartcore.
- KNN + distance metrics (Euclidian, Minkowski, Manhattan, Hamming, Mahalanobis)
- Linear Regression (OLS)
- Logistic Regression
- Random Forest Classifier
- Decision Tree Classifier
- PCA
- K-Means
- Integrated with ndarray
- Abstract linear algebra methods
- RandomForest Regressor
- Decision Tree Regressor
- Serde integration
- Integrated with nalgebra
- LU, QR, SVD, EVD
- Evaluation Metrics