From 0b619fe7eb07b5c2842814b6060e8821e8912493 Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Tue, 27 Sep 2022 14:23:18 +0100
Subject: [PATCH 01/12] Add contribution guidelines (#178)
---
.github/CODEOWNERS | 7 ++++++
.github/CODE_OF_CONDUCT.md | 22 ++++++++++++++++
.github/CONTRIBUTING.md | 43 ++++++++++++++++++++++++++++++++
.github/ISSUE_TEMPLATE.md | 24 ++++++++++++++++++
.github/PULL_REQUEST_TEMPLATE.md | 29 +++++++++++++++++++++
README.md | 5 +++-
6 files changed, 129 insertions(+), 1 deletion(-)
create mode 100644 .github/CODEOWNERS
create mode 100644 .github/CODE_OF_CONDUCT.md
create mode 100644 .github/CONTRIBUTING.md
create mode 100644 .github/ISSUE_TEMPLATE.md
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..faeb28c
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,7 @@
+# These owners will be the default owners for everything in
+# the repo. Unless a later match takes precedence,
+# Developers in this list will be requested for
+# review when someone opens a pull request.
+* @VolodymyrOrlov
+* @morenol
+* @Mec-iS
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..1ac5dfa
--- /dev/null
+++ b/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+# Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic addresses, without explicit permission
+* Other unethical or unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
+
+This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
\ No newline at end of file
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..c45af53
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,43 @@
+# **Contributing**
+
+When contributing to this repository, please first discuss the change you wish to make via issue,
+email, or any other method with the owners of this repository before making a change.
+
+Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
+
+## Pull Request Process
+
+1. Open a PR following the template.
+2. Update the CHANGELOG.md with details of changes to the interface if they are breaking changes, this includes new environment variables, exposed ports useful file locations and container parameters.
+3. Pull Request can be merged in once you have the sign-off of one other developer, or if you do not have permission to do that you may request the reviewer to merge it for you.
+
+## Issue Report Process
+
+1. Go to the project's issues.
+2. Select the template that better fits your issue.
+3. Read carefully the instructions and write within the template guidelines.
+4. Submit it and wait for support.
+
+## Reviewing process
+
+1. After a PR is opened maintainers are notified
+2. Probably changes will be required to comply with the workflow, these commands are run automatically and all tests shall pass:
+ * **Coverage**: `tarpaulin` is used with command `cargo tarpaulin --out Lcov --all-features -- --test-threads 1`
+ * **Linting**: `clippy` is used with command `cargo clippy --all-features -- -Drust-2018-idioms -Dwarnings`
+ * **Testing**: multiple test pipelines are run for different targets
+3. When everything is OK, code is merged.
+
+
+## Contribution Best Practices
+
+* Read this [how-to about Github workflow here](https://guides.github.com/introduction/flow/) if you are not familiar with.
+
+* Read all the texts related to [contributing for an OS community](https://github.com/HTTP-APIs/hydrus/tree/master/.github).
+
+* Read this [how-to about writing a PR](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) and this [other how-to about writing a issue](https://wiredcraft.com/blog/how-we-write-our-github-issues/)
+
+* **read history**: search past open or closed issues for your problem before opening a new issue.
+
+* **PRs on develop**: any change should be PRed first in `development`
+
+* **testing**: everything should work and be tested as defined in the workflow. If any is failing for non-related reasons, annotate the test failure in the PR comment.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..4fee515
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,24 @@
+### I'm submitting a
+- [ ] bug report.
+- [ ] feature request.
+
+### Current Behaviour:
+
+
+### Expected Behaviour:
+
+
+### Steps to reproduce:
+
+
+### Snapshot:
+
+
+### Environment:
+
+* rustc version
+* cargo version
+* OS details
+
+### Do you want to work on this issue?
+
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..600af94
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,29 @@
+
+
+
+
+Fixes #
+
+### Checklist
+- [ ] My branch is up-to-date with development branch.
+- [ ] Everything works and tested on latest stable Rust.
+- [ ] Coverage and Linting have been applied
+
+### Current behaviour
+
+
+### New expected behaviour
+
+
+### Change logs
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index e096131..8c95f8c 100644
--- a/README.md
+++ b/README.md
@@ -15,4 +15,7 @@
The Most Advanced Machine Learning Library In Rust.
------
\ No newline at end of file
+-----
+
+
+Contributions welcome, please start from [CONTRIBUTING and other relevant files](.github/CONTRIBUTING.md).
\ No newline at end of file
From 9c59e37a0ff754779b3b787f5b24b2bd1aba9a1a Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Tue, 27 Sep 2022 14:27:27 +0100
Subject: [PATCH 02/12] Update CONTRIBUTING.md
---
.github/CONTRIBUTING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index c45af53..ba02b8a 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -22,7 +22,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
1. After a PR is opened maintainers are notified
2. Probably changes will be required to comply with the workflow, these commands are run automatically and all tests shall pass:
- * **Coverage**: `tarpaulin` is used with command `cargo tarpaulin --out Lcov --all-features -- --test-threads 1`
+ * **Coverage** (optional): `tarpaulin` is used with command `cargo tarpaulin --out Lcov --all-features -- --test-threads 1`
* **Linting**: `clippy` is used with command `cargo clippy --all-features -- -Drust-2018-idioms -Dwarnings`
* **Testing**: multiple test pipelines are run for different targets
3. When everything is OK, code is merged.
@@ -40,4 +40,4 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
* **PRs on develop**: any change should be PRed first in `development`
-* **testing**: everything should work and be tested as defined in the workflow. If any is failing for non-related reasons, annotate the test failure in the PR comment.
\ No newline at end of file
+* **testing**: everything should work and be tested as defined in the workflow. If any is failing for non-related reasons, annotate the test failure in the PR comment.
From 3c62686d6ee36854c584534d02cdfc5cd0b65819 Mon Sep 17 00:00:00 2001
From: morenol <22335041+morenol@users.noreply.github.com>
Date: Sat, 1 Oct 2022 12:47:56 -0500
Subject: [PATCH 03/12] feat: expose hyper tuning module in model_selection
(#179)
* feat: expose hyper tuning module in model_selection
* Move to a folder
Co-authored-by: Luis Moreno
---
.../grid_search.rs} | 110 ++++++++++--------
src/model_selection/hyper_tuning/mod.rs | 2 +
src/model_selection/mod.rs | 2 +
3 files changed, 65 insertions(+), 49 deletions(-)
rename src/model_selection/{hyper_tuning.rs => hyper_tuning/grid_search.rs} (53%)
create mode 100644 src/model_selection/hyper_tuning/mod.rs
diff --git a/src/model_selection/hyper_tuning.rs b/src/model_selection/hyper_tuning/grid_search.rs
similarity index 53%
rename from src/model_selection/hyper_tuning.rs
rename to src/model_selection/hyper_tuning/grid_search.rs
index cb69da1..053611a 100644
--- a/src/model_selection/hyper_tuning.rs
+++ b/src/model_selection/hyper_tuning/grid_search.rs
@@ -1,3 +1,12 @@
+use crate::{
+ api::Predictor,
+ error::{Failed, FailedError},
+ linalg::Matrix,
+ math::num::RealNumber,
+};
+
+use crate::model_selection::{cross_validate, BaseKFold, CrossValidationResult};
+
/// grid search results.
#[derive(Clone, Debug)]
pub struct GridSearchResult {
@@ -60,58 +69,61 @@ where
#[cfg(test)]
mod tests {
- use crate::linear::logistic_regression::{
- LogisticRegression, LogisticRegressionSearchParameters,
-};
+ use crate::{
+ linalg::naive::dense_matrix::DenseMatrix,
+ linear::logistic_regression::{LogisticRegression, LogisticRegressionSearchParameters},
+ metrics::accuracy,
+ model_selection::{hyper_tuning::grid_search, KFold},
+ };
- #[test]
- fn test_grid_search() {
- let x = DenseMatrix::from_2d_array(&[
- &[5.1, 3.5, 1.4, 0.2],
- &[4.9, 3.0, 1.4, 0.2],
- &[4.7, 3.2, 1.3, 0.2],
- &[4.6, 3.1, 1.5, 0.2],
- &[5.0, 3.6, 1.4, 0.2],
- &[5.4, 3.9, 1.7, 0.4],
- &[4.6, 3.4, 1.4, 0.3],
- &[5.0, 3.4, 1.5, 0.2],
- &[4.4, 2.9, 1.4, 0.2],
- &[4.9, 3.1, 1.5, 0.1],
- &[7.0, 3.2, 4.7, 1.4],
- &[6.4, 3.2, 4.5, 1.5],
- &[6.9, 3.1, 4.9, 1.5],
- &[5.5, 2.3, 4.0, 1.3],
- &[6.5, 2.8, 4.6, 1.5],
- &[5.7, 2.8, 4.5, 1.3],
- &[6.3, 3.3, 4.7, 1.6],
- &[4.9, 2.4, 3.3, 1.0],
- &[6.6, 2.9, 4.6, 1.3],
- &[5.2, 2.7, 3.9, 1.4],
- ]);
- let y = vec![
- 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
- ];
+ #[test]
+ fn test_grid_search() {
+ let x = DenseMatrix::from_2d_array(&[
+ &[5.1, 3.5, 1.4, 0.2],
+ &[4.9, 3.0, 1.4, 0.2],
+ &[4.7, 3.2, 1.3, 0.2],
+ &[4.6, 3.1, 1.5, 0.2],
+ &[5.0, 3.6, 1.4, 0.2],
+ &[5.4, 3.9, 1.7, 0.4],
+ &[4.6, 3.4, 1.4, 0.3],
+ &[5.0, 3.4, 1.5, 0.2],
+ &[4.4, 2.9, 1.4, 0.2],
+ &[4.9, 3.1, 1.5, 0.1],
+ &[7.0, 3.2, 4.7, 1.4],
+ &[6.4, 3.2, 4.5, 1.5],
+ &[6.9, 3.1, 4.9, 1.5],
+ &[5.5, 2.3, 4.0, 1.3],
+ &[6.5, 2.8, 4.6, 1.5],
+ &[5.7, 2.8, 4.5, 1.3],
+ &[6.3, 3.3, 4.7, 1.6],
+ &[4.9, 2.4, 3.3, 1.0],
+ &[6.6, 2.9, 4.6, 1.3],
+ &[5.2, 2.7, 3.9, 1.4],
+ ]);
+ let y = vec![
+ 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
+ ];
- let cv = KFold {
- n_splits: 5,
- ..KFold::default()
- };
+ let cv = KFold {
+ n_splits: 5,
+ ..KFold::default()
+ };
- let parameters = LogisticRegressionSearchParameters {
- alpha: vec![0., 1.],
- ..Default::default()
- };
+ let parameters = LogisticRegressionSearchParameters {
+ alpha: vec![0., 1.],
+ ..Default::default()
+ };
- let results = grid_search(
- LogisticRegression::fit,
- &x,
- &y,
- parameters.into_iter(),
- cv,
- &accuracy,
- )
- .unwrap();
+ let results = grid_search(
+ LogisticRegression::fit,
+ &x,
+ &y,
+ parameters.into_iter(),
+ cv,
+ &accuracy,
+ )
+ .unwrap();
- assert!([0., 1.].contains(&results.parameters.alpha));
- }
+ assert!([0., 1.].contains(&results.parameters.alpha));
+ }
}
diff --git a/src/model_selection/hyper_tuning/mod.rs b/src/model_selection/hyper_tuning/mod.rs
new file mode 100644
index 0000000..6810d1a
--- /dev/null
+++ b/src/model_selection/hyper_tuning/mod.rs
@@ -0,0 +1,2 @@
+mod grid_search;
+pub use grid_search::{grid_search, GridSearchResult};
diff --git a/src/model_selection/mod.rs b/src/model_selection/mod.rs
index 21cf7ed..943c143 100644
--- a/src/model_selection/mod.rs
+++ b/src/model_selection/mod.rs
@@ -110,8 +110,10 @@ use crate::math::num::RealNumber;
use crate::rand::get_rng_impl;
use rand::seq::SliceRandom;
+pub(crate) mod hyper_tuning;
pub(crate) mod kfold;
+pub use hyper_tuning::{grid_search, GridSearchResult};
pub use kfold::{KFold, KFoldIter};
/// An interface for the K-Folds cross-validator
From f8210d0af91e527e78fef2139ee5b9f89d476457 Mon Sep 17 00:00:00 2001
From: morenol <22335041+morenol@users.noreply.github.com>
Date: Sat, 1 Oct 2022 16:44:08 -0500
Subject: [PATCH 04/12] refactor: Try to follow similar pattern to other APIs
(#180)
Co-authored-by: Luis Moreno
---
.../hyper_tuning/grid_search.rs | 217 +++++++++++++-----
src/model_selection/hyper_tuning/mod.rs | 2 +-
src/model_selection/mod.rs | 2 +-
3 files changed, 164 insertions(+), 57 deletions(-)
diff --git a/src/model_selection/hyper_tuning/grid_search.rs b/src/model_selection/hyper_tuning/grid_search.rs
index 053611a..1544faf 100644
--- a/src/model_selection/hyper_tuning/grid_search.rs
+++ b/src/model_selection/hyper_tuning/grid_search.rs
@@ -1,5 +1,5 @@
use crate::{
- api::Predictor,
+ api::{Predictor, SupervisedEstimator},
error::{Failed, FailedError},
linalg::Matrix,
math::num::RealNumber,
@@ -7,74 +7,169 @@ use crate::{
use crate::model_selection::{cross_validate, BaseKFold, CrossValidationResult};
-/// grid search results.
-#[derive(Clone, Debug)]
-pub struct GridSearchResult {
- /// Vector with test scores on each cv split
- pub cross_validation_result: CrossValidationResult,
- /// Vector with training scores on each cv split
- pub parameters: I,
-}
-
-/// Search for the best estimator by testing all possible combinations with cross-validation using given metric.
-/// * `fit_estimator` - a `fit` function of an estimator
-/// * `x` - features, matrix of size _NxM_ where _N_ is number of samples and _M_ is number of attributes.
-/// * `y` - target values, should be of size _N_
-/// * `parameter_search` - an iterator for parameters that will be tested.
-/// * `cv` - the cross-validation splitting strategy, should be an instance of [`BaseKFold`](./trait.BaseKFold.html)
-/// * `score` - a metric to use for evaluation, see [metrics](../metrics/index.html)
-pub fn grid_search(
- fit_estimator: F,
- x: &M,
- y: &M::RowVector,
- parameter_search: I,
- cv: K,
- score: S,
-) -> Result, Failed>
-where
+/// Parameters for GridSearchCV
+#[derive(Debug)]
+pub struct GridSearchCVParameters<
T: RealNumber,
M: Matrix,
- I: Iterator,
- I::Item: Clone,
+ C: Clone,
+ I: Iterator- ,
E: Predictor,
+ F: Fn(&M, &M::RowVector, C) -> Result,
K: BaseKFold,
- F: Fn(&M, &M::RowVector, I::Item) -> Result,
S: Fn(&M::RowVector, &M::RowVector) -> T,
-{
- let mut best_result: Option> = None;
- let mut best_parameters = None;
+> {
+ _phantom: std::marker::PhantomData<(T, M)>,
- for parameters in parameter_search {
- let result = cross_validate(&fit_estimator, x, y, ¶meters, &cv, &score)?;
- if best_result.is_none()
- || result.mean_test_score() > best_result.as_ref().unwrap().mean_test_score()
+ parameters_search: I,
+ estimator: F,
+ score: S,
+ cv: K,
+}
+
+impl<
+ T: RealNumber,
+ M: Matrix,
+ C: Clone,
+ I: Iterator
- ,
+ E: Predictor,
+ F: Fn(&M, &M::RowVector, C) -> Result,
+ K: BaseKFold,
+ S: Fn(&M::RowVector, &M::RowVector) -> T,
+ > GridSearchCVParameters
+{
+ /// Create new GridSearchCVParameters
+ pub fn new(parameters_search: I, estimator: F, score: S, cv: K) -> Self {
+ GridSearchCVParameters {
+ _phantom: std::marker::PhantomData,
+ parameters_search,
+ estimator,
+ score,
+ cv,
+ }
+ }
+}
+/// Exhaustive search over specified parameter values for an estimator.
+#[derive(Debug)]
+pub struct GridSearchCV, C: Clone, E: Predictor> {
+ _phantom: std::marker::PhantomData<(T, M)>,
+ predictor: E,
+ /// Cross validation results.
+ pub cross_validation_result: CrossValidationResult,
+ /// best parameter
+ pub best_parameter: C,
+}
+
+impl, E: Predictor, C: Clone>
+ GridSearchCV
+{
+ /// Search for the best estimator by testing all possible combinations with cross-validation using given metric.
+ /// * `x` - features, matrix of size _NxM_ where _N_ is number of samples and _M_ is number of attributes.
+ /// * `y` - target values, should be of size _N_
+ /// * `gs_parameters` - GridSearchCVParameters struct
+ pub fn fit<
+ I: Iterator
- ,
+ K: BaseKFold,
+ F: Fn(&M, &M::RowVector, C) -> Result,
+ S: Fn(&M::RowVector, &M::RowVector) -> T,
+ >(
+ x: &M,
+ y: &M::RowVector,
+ gs_parameters: GridSearchCVParameters,
+ ) -> Result {
+ let mut best_result: Option> = None;
+ let mut best_parameters = None;
+ let parameters_search = gs_parameters.parameters_search;
+ let estimator = gs_parameters.estimator;
+ let cv = gs_parameters.cv;
+ let score = gs_parameters.score;
+
+ for parameters in parameters_search {
+ let result = cross_validate(&estimator, x, y, ¶meters, &cv, &score)?;
+ if best_result.is_none()
+ || result.mean_test_score() > best_result.as_ref().unwrap().mean_test_score()
+ {
+ best_parameters = Some(parameters);
+ best_result = Some(result);
+ }
+ }
+
+ if let (Some(best_parameter), Some(cross_validation_result)) =
+ (best_parameters, best_result)
{
- best_parameters = Some(parameters);
- best_result = Some(result);
+ let predictor = estimator(x, y, best_parameter.clone())?;
+ Ok(Self {
+ _phantom: gs_parameters._phantom,
+ predictor,
+ cross_validation_result,
+ best_parameter,
+ })
+ } else {
+ Err(Failed::because(
+ FailedError::FindFailed,
+ "there were no parameter sets found",
+ ))
}
}
- if let (Some(parameters), Some(cross_validation_result)) = (best_parameters, best_result) {
- Ok(GridSearchResult {
- cross_validation_result,
- parameters,
- })
- } else {
- Err(Failed::because(
- FailedError::FindFailed,
- "there were no parameter sets found",
- ))
+ /// Return grid search cross validation results
+ pub fn cv_results(&self) -> &CrossValidationResult {
+ &self.cross_validation_result
+ }
+
+ /// Return best parameters found
+ pub fn best_parameters(&self) -> &C {
+ &self.best_parameter
+ }
+
+ /// Call predict on the estimator with the best found parameters
+ pub fn predict(&self, x: &M) -> Result {
+ self.predictor.predict(x)
+ }
+}
+
+impl<
+ T: RealNumber,
+ M: Matrix,
+ C: Clone,
+ I: Iterator
- ,
+ E: Predictor,
+ F: Fn(&M, &M::RowVector, C) -> Result,
+ K: BaseKFold,
+ S: Fn(&M::RowVector, &M::RowVector) -> T,
+ > SupervisedEstimator>
+ for GridSearchCV
+{
+ fn fit(
+ x: &M,
+ y: &M::RowVector,
+ parameters: GridSearchCVParameters,
+ ) -> Result {
+ GridSearchCV::fit(x, y, parameters)
+ }
+}
+
+impl, C: Clone, E: Predictor>
+ Predictor for GridSearchCV
+{
+ fn predict(&self, x: &M) -> Result {
+ self.predict(x)
}
}
#[cfg(test)]
mod tests {
+
use crate::{
linalg::naive::dense_matrix::DenseMatrix,
linear::logistic_regression::{LogisticRegression, LogisticRegressionSearchParameters},
metrics::accuracy,
- model_selection::{hyper_tuning::grid_search, KFold},
+ model_selection::{
+ hyper_tuning::grid_search::{self, GridSearchCVParameters},
+ KFold,
+ },
};
+ use grid_search::GridSearchCV;
#[test]
fn test_grid_search() {
@@ -114,16 +209,28 @@ mod tests {
..Default::default()
};
- let results = grid_search(
- LogisticRegression::fit,
+ let grid_search = GridSearchCV::fit(
&x,
&y,
- parameters.into_iter(),
- cv,
- &accuracy,
+ GridSearchCVParameters {
+ estimator: LogisticRegression::fit,
+ score: accuracy,
+ cv,
+ parameters_search: parameters.into_iter(),
+ _phantom: Default::default(),
+ },
)
.unwrap();
+ let best_parameters = grid_search.best_parameters();
- assert!([0., 1.].contains(&results.parameters.alpha));
+ assert!([1.].contains(&best_parameters.alpha));
+
+ let cv_results = grid_search.cv_results();
+
+ assert_eq!(cv_results.mean_test_score(), 0.9);
+
+ let x = DenseMatrix::from_2d_array(&[&[5., 3., 1., 0.]]);
+ let result = grid_search.predict(&x).unwrap();
+ assert_eq!(result, vec![0.]);
}
}
diff --git a/src/model_selection/hyper_tuning/mod.rs b/src/model_selection/hyper_tuning/mod.rs
index 6810d1a..dfe0d06 100644
--- a/src/model_selection/hyper_tuning/mod.rs
+++ b/src/model_selection/hyper_tuning/mod.rs
@@ -1,2 +1,2 @@
mod grid_search;
-pub use grid_search::{grid_search, GridSearchResult};
+pub use grid_search::{GridSearchCV, GridSearchCVParameters};
diff --git a/src/model_selection/mod.rs b/src/model_selection/mod.rs
index 943c143..f16b955 100644
--- a/src/model_selection/mod.rs
+++ b/src/model_selection/mod.rs
@@ -113,7 +113,7 @@ use rand::seq::SliceRandom;
pub(crate) mod hyper_tuning;
pub(crate) mod kfold;
-pub use hyper_tuning::{grid_search, GridSearchResult};
+pub use hyper_tuning::{GridSearchCV, GridSearchCVParameters};
pub use kfold::{KFold, KFoldIter};
/// An interface for the K-Folds cross-validator
From 12df301f32bcf0de60dae11f54c3ddf42e0119f2 Mon Sep 17 00:00:00 2001
From: morenol <22335041+morenol@users.noreply.github.com>
Date: Sun, 2 Oct 2022 06:15:28 -0500
Subject: [PATCH 05/12] fix: fix issue with iterator for svc search (#182)
---
src/svm/svc.rs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/svm/svc.rs b/src/svm/svc.rs
index 97b91de..3354d0d 100644
--- a/src/svm/svc.rs
+++ b/src/svm/svc.rs
@@ -173,7 +173,7 @@ impl, K: Kernel> Iterator
&& self.current_c == self.svc_search_parameters.c.len()
&& self.current_tol == self.svc_search_parameters.tol.len()
&& self.current_kernel == self.svc_search_parameters.kernel.len()
- && self.current_seed == self.svc_search_parameters.kernel.len()
+ && self.current_seed == self.svc_search_parameters.seed.len()
{
return None;
}
@@ -201,7 +201,7 @@ impl, K: Kernel> Iterator
self.current_c = 0;
self.current_tol = 0;
self.current_kernel += 1;
- } else if self.current_kernel + 1 < self.svc_search_parameters.kernel.len() {
+ } else if self.current_seed + 1 < self.svc_search_parameters.seed.len() {
self.current_epoch = 0;
self.current_c = 0;
self.current_tol = 0;
@@ -972,7 +972,6 @@ mod tests {
// x can be classified by a straight line through [6.0, 0.0] and [0.0, 6.0],
// so the score should increase as points get further away from that line
- println!("{:?}", y_hat);
assert!(y_hat[1] < y_hat[2]);
assert!(y_hat[2] < y_hat[3]);
From b823b55460a50036992c357e94f1aaa3a0621482 Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Wed, 12 Oct 2022 12:21:09 +0100
Subject: [PATCH 06/12] Update CONTRIBUTING.md
---
.github/CONTRIBUTING.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index ba02b8a..af2d654 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -11,6 +11,12 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in
2. Update the CHANGELOG.md with details of changes to the interface if they are breaking changes, this includes new environment variables, exposed ports useful file locations and container parameters.
3. Pull Request can be merged in once you have the sign-off of one other developer, or if you do not have permission to do that you may request the reviewer to merge it for you.
+### generic guidelines
+Take a look to the conventions established by existing code:
+* Every module should come with some reference to scientific literature that allows relating the code to research. Use the `//!` comments at the top of the module to tell readers about the basics of the procedure you are implementing.
+* Every module should provide a Rust doctest, a brief test embedded with the documentation that explains how to use the procedure implemented.
+* Every module should provide comprehensive tests at the end, in its `mod tests {}` sub-module. These tests can be flagged or not with configuration flags to allow WebAssembly target.
+
## Issue Report Process
1. Go to the project's issues.
From 8a2bdd5a7593d78d0a5052386475896aad4e92d7 Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Thu, 13 Oct 2022 19:47:52 +0100
Subject: [PATCH 07/12] Update README.md
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 8c95f8c..9ee31f1 100644
--- a/README.md
+++ b/README.md
@@ -17,5 +17,9 @@
-----
+## Developers
+Contributions welcome, please start from [CONTRIBUTING and other relevant files](.github/CONTRIBUTING.md).
-Contributions welcome, please start from [CONTRIBUTING and other relevant files](.github/CONTRIBUTING.md).
\ No newline at end of file
+## Current status
+* Current working branch is `development` (if you want something that you can test right away).
+* Breaking changes are undergoing development at [`v0.5-wip`](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) (if you are a newcomer better to start from [this README](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) as this will be the next major release).
From edbac7e4c7b323a6b6a867ab871f50b986373d89 Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Tue, 18 Oct 2022 15:44:38 +0100
Subject: [PATCH 08/12] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 9ee31f1..743ad36 100644
--- a/README.md
+++ b/README.md
@@ -23,3 +23,5 @@ Contributions welcome, please start from [CONTRIBUTING and other relevant files]
## Current status
* Current working branch is `development` (if you want something that you can test right away).
* Breaking changes are undergoing development at [`v0.5-wip`](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) (if you are a newcomer better to start from [this README](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) as this will be the next major release).
+
+To start getting familiar with the new Smartcore v0.5 API, there is now available a [**Jupyter Notebook environment repository**](https://github.com/smartcorelib/smartcore-jupyter).
From bb7165613792d309bab605c90c623e3a80240eab Mon Sep 17 00:00:00 2001
From: RJ Nowling
Date: Sun, 30 Oct 2022 04:32:41 -0500
Subject: [PATCH 09/12] Dataset doc cleanup (#205)
* Update iris.rs
* Update mod.rs
* Update digits.rs
---
src/dataset/digits.rs | 2 +-
src/dataset/iris.rs | 2 +-
src/dataset/mod.rs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/dataset/digits.rs b/src/dataset/digits.rs
index 9120e59..b7dd2d4 100644
--- a/src/dataset/digits.rs
+++ b/src/dataset/digits.rs
@@ -1,4 +1,4 @@
-//! # Optical Recognition of Handwritten Digits Data Set
+//! # Optical Recognition of Handwritten Digits Dataset
//!
//! | Number of Instances | Number of Attributes | Missing Values? | Associated Tasks: |
//! |-|-|-|-|
diff --git a/src/dataset/iris.rs b/src/dataset/iris.rs
index 888d3e8..2771558 100644
--- a/src/dataset/iris.rs
+++ b/src/dataset/iris.rs
@@ -1,4 +1,4 @@
-//! # The Iris Dataset flower
+//! # The Iris flower dataset
//!
//! | Number of Instances | Number of Attributes | Missing Values? | Associated Tasks: |
//! |-|-|-|-|
diff --git a/src/dataset/mod.rs b/src/dataset/mod.rs
index acd7641..48fdced 100644
--- a/src/dataset/mod.rs
+++ b/src/dataset/mod.rs
@@ -1,6 +1,6 @@
//! Datasets
//!
-//! In this module you will find small datasets that are used in SmartCore for demonstration purpose mostly.
+//! In this module you will find small datasets that are used in SmartCore mostly for demonstration purposes.
pub mod boston;
pub mod breast_cancer;
pub mod diabetes;
From 52eb6ce02319c34fd4faf3e3131bfc294278f726 Mon Sep 17 00:00:00 2001
From: Lorenzo
Date: Mon, 31 Oct 2022 10:44:57 +0000
Subject: [PATCH 10/12] Merge potential next release v0.4 (#187) Breaking
Changes
* First draft of the new n-dimensional arrays + NB use case
* Improves default implementation of multiple Array methods
* Refactors tree methods
* Adds matrix decomposition routines
* Adds matrix decomposition methods to ndarray and nalgebra bindings
* Refactoring + linear regression now uses array2
* Ridge & Linear regression
* LBFGS optimizer & logistic regression
* LBFGS optimizer & logistic regression
* Changes linear methods, metrics and model selection methods to new n-dimensional arrays
* Switches KNN and clustering algorithms to new n-d array layer
* Refactors distance metrics
* Optimizes knn and clustering methods
* Refactors metrics module
* Switches decomposition methods to n-dimensional arrays
* Linalg refactoring - cleanup rng merge (#172)
* Remove legacy DenseMatrix and BaseMatrix implementation. Port the new Number, FloatNumber and Array implementation into module structure.
* Exclude AUC metrics. Needs reimplementation
* Improve developers walkthrough
New traits system in place at `src/numbers` and `src/linalg`
Co-authored-by: Lorenzo
* Provide SupervisedEstimator with a constructor to avoid explicit dynamical box allocation in 'cross_validate' and 'cross_validate_predict' as required by the use of 'dyn' as per Rust 2021
* Implement getters to use as_ref() in src/neighbors
* Implement getters to use as_ref() in src/naive_bayes
* Implement getters to use as_ref() in src/linear
* Add Clone to src/naive_bayes
* Change signature for cross_validate and other model_selection functions to abide to use of dyn in Rust 2021
* Implement ndarray-bindings. Remove FloatNumber from implementations
* Drop nalgebra-bindings support (as decided in conf-call to go for ndarray)
* Remove benches. Benches will have their own repo at smartcore-benches
* Implement SVC
* Implement SVC serialization. Move search parameters in dedicated module
* Implement SVR. Definitely too slow
* Fix compilation issues for wasm (#202)
Co-authored-by: Luis Moreno
* Fix tests (#203)
* Port linalg/traits/stats.rs
* Improve methods naming
* Improve Display for DenseMatrix
Co-authored-by: Montana Low
Co-authored-by: VolodymyrOrlov
---
.github/CONTRIBUTING.md | 12 +-
.gitignore | 10 +
CHANGELOG.md | 6 +
Cargo.toml | 41 +-
README.md | 45 +-
benches/distance.rs | 18 -
benches/fastpair.rs | 56 -
benches/naive_bayes.rs | 73 -
src/algorithm/neighbour/bbd_tree.rs | 112 +-
src/algorithm/neighbour/cover_tree.rs | 149 +-
src/algorithm/neighbour/distances.rs | 2 +-
src/algorithm/neighbour/fastpair.rs | 23 +-
src/algorithm/neighbour/linear_search.rs | 47 +-
src/algorithm/neighbour/mod.rs | 23 +-
src/algorithm/sort/quick_sort.rs | 4 +-
src/api.rs | 36 +-
src/cluster/dbscan.rs | 158 +-
src/cluster/kmeans.rs | 146 +-
src/dataset/breast_cancer.rs | 28 +-
src/dataset/diabetes.rs | 28 +-
src/dataset/generator.rs | 8 +-
src/dataset/iris.rs | 37 +-
src/dataset/mod.rs | 4 +-
src/decomposition/pca.rs | 188 +-
src/decomposition/svd.rs | 119 +-
src/ensemble/random_forest_classifier.rs | 262 +-
src/ensemble/random_forest_regressor.rs | 220 +-
src/error/mod.rs | 3 +
src/lib.rs | 27 +-
src/linalg/basic/arrays.rs | 2174 +++++++++++++++++
src/linalg/basic/matrix.rs | 714 ++++++
src/linalg/basic/mod.rs | 8 +
src/linalg/basic/vector.rs | 327 +++
src/linalg/mod.rs | 926 +------
src/linalg/naive/dense_matrix.rs | 1356 ----------
src/linalg/naive/mod.rs | 26 -
src/linalg/nalgebra_bindings.rs | 1027 --------
src/linalg/ndarray/matrix.rs | 286 +++
src/linalg/ndarray/mod.rs | 4 +
src/linalg/ndarray/vector.rs | 184 ++
src/linalg/ndarray_bindings.rs | 1020 --------
src/linalg/stats.rs | 207 --
src/linalg/{ => traits}/cholesky.rs | 66 +-
src/linalg/{ => traits}/evd.rs | 368 ++-
src/linalg/{ => traits}/high_order.rs | 15 +-
src/linalg/{ => traits}/lu.rs | 74 +-
src/linalg/traits/mod.rs | 15 +
src/linalg/{ => traits}/qr.rs | 65 +-
src/linalg/traits/stats.rs | 294 +++
src/linalg/{ => traits}/svd.rs | 171 +-
src/linear/bg_solver.rs | 126 +-
src/linear/elastic_net.rs | 285 ++-
src/linear/lasso.rs | 244 +-
src/linear/lasso_optimizer.rs | 148 +-
src/linear/linear_regression.rs | 218 +-
src/linear/logistic_regression.rs | 476 ++--
src/linear/mod.rs | 4 +-
src/linear/ridge_regression.rs | 242 +-
src/math/distance/euclidian.rs | 70 -
src/math/mod.rs | 4 -
src/math/vector.rs | 42 -
src/metrics/accuracy.rs | 71 +-
src/metrics/auc.rs | 71 +-
src/metrics/cluster_hcv.rs | 105 +-
src/metrics/cluster_helpers.rs | 67 +-
src/metrics/distance/euclidian.rs | 89 +
src/{math => metrics}/distance/hamming.rs | 52 +-
src/{math => metrics}/distance/mahalanobis.rs | 70 +-
src/{math => metrics}/distance/manhattan.rs | 44 +-
src/{math => metrics}/distance/minkowski.rs | 50 +-
src/{math => metrics}/distance/mod.rs | 29 +-
src/metrics/f1.rs | 57 +-
src/metrics/mean_absolute_error.rs | 50 +-
src/metrics/mean_squared_error.rs | 48 +-
src/metrics/mod.rs | 211 +-
src/metrics/precision.rs | 57 +-
src/metrics/r2.rs | 61 +-
src/metrics/recall.rs | 58 +-
.../hyper_tuning/grid_search.rs | 21 +-
src/model_selection/kfold.rs | 29 +-
src/model_selection/mod.rs | 262 +-
src/naive_bayes/bernoulli.rs | 344 +--
src/naive_bayes/categorical.rs | 329 ++-
src/naive_bayes/gaussian.rs | 247 +-
src/naive_bayes/mod.rs | 46 +-
src/naive_bayes/multinomial.rs | 320 +--
src/neighbors/knn_classifier.rs | 193 +-
src/neighbors/knn_regressor.rs | 165 +-
src/neighbors/mod.rs | 11 +-
src/numbers/basenum.rs | 51 +
src/numbers/floatnum.rs | 117 +
src/numbers/mod.rs | 10 +
src/{math/num.rs => numbers/realnum.rs} | 32 +-
.../first_order/gradient_descent.rs | 65 +-
src/optimization/first_order/lbfgs.rs | 135 +-
src/optimization/first_order/mod.rs | 18 +-
src/optimization/line_search.rs | 17 +
src/optimization/mod.rs | 9 +
src/preprocessing/categorical.rs | 20 +-
src/preprocessing/mod.rs | 2 +-
src/preprocessing/numerical.rs | 128 +-
src/preprocessing/series_encoder.rs | 14 +-
.../{data_traits.rs => traits.rs} | 2 +-
src/{rand.rs => rand_custom.rs} | 6 +-
src/svm/mod.rs | 293 ++-
src/svm/svc.rs | 807 +++---
src/svm/svc_gridsearch.rs | 184 ++
src/svm/svr.rs | 649 ++---
src/tree/decision_tree_classifier.rs | 341 +--
src/tree/decision_tree_regressor.rs | 306 ++-
110 files changed, 10327 insertions(+), 9107 deletions(-)
delete mode 100644 benches/distance.rs
delete mode 100644 benches/fastpair.rs
delete mode 100644 benches/naive_bayes.rs
create mode 100644 src/linalg/basic/arrays.rs
create mode 100644 src/linalg/basic/matrix.rs
create mode 100644 src/linalg/basic/mod.rs
create mode 100644 src/linalg/basic/vector.rs
delete mode 100644 src/linalg/naive/dense_matrix.rs
delete mode 100644 src/linalg/naive/mod.rs
delete mode 100644 src/linalg/nalgebra_bindings.rs
create mode 100644 src/linalg/ndarray/matrix.rs
create mode 100644 src/linalg/ndarray/mod.rs
create mode 100644 src/linalg/ndarray/vector.rs
delete mode 100644 src/linalg/ndarray_bindings.rs
delete mode 100644 src/linalg/stats.rs
rename src/linalg/{ => traits}/cholesky.rs (74%)
rename src/linalg/{ => traits}/evd.rs (68%)
rename src/linalg/{ => traits}/high_order.rs (70%)
rename src/linalg/{ => traits}/lu.rs (76%)
create mode 100644 src/linalg/traits/mod.rs
rename src/linalg/{ => traits}/qr.rs (75%)
create mode 100644 src/linalg/traits/stats.rs
rename src/linalg/{ => traits}/svd.rs (81%)
delete mode 100644 src/math/distance/euclidian.rs
delete mode 100644 src/math/mod.rs
delete mode 100644 src/math/vector.rs
create mode 100644 src/metrics/distance/euclidian.rs
rename src/{math => metrics}/distance/hamming.rs (56%)
rename src/{math => metrics}/distance/mahalanobis.rs (71%)
rename src/{math => metrics}/distance/manhattan.rs (53%)
rename src/{math => metrics}/distance/minkowski.rs (59%)
rename src/{math => metrics}/distance/mod.rs (75%)
create mode 100644 src/numbers/basenum.rs
create mode 100644 src/numbers/floatnum.rs
create mode 100644 src/numbers/mod.rs
rename src/{math/num.rs => numbers/realnum.rs} (83%)
rename src/preprocessing/{data_traits.rs => traits.rs} (95%)
rename src/{rand.rs => rand_custom.rs} (81%)
create mode 100644 src/svm/svc_gridsearch.rs
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index af2d654..c09dfa7 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -5,9 +5,17 @@ email, or any other method with the owners of this repository before making a ch
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
+## Background
+
+We try to follow these principles:
+* follow as much as possible the sklearn API to give a frictionless user experience for practitioners already familiar with it
+* use only pure-Rust implementations for safety and future-proofing (with some low-level limited exceptions)
+* do not use macros in the library code to allow readability and transparent behavior
+* priority is not on "big data" dataset, try to be fast for small/average dataset with limited memory footprint.
+
## Pull Request Process
-1. Open a PR following the template.
+1. Open a PR following the template (erase the part of the template you don't need).
2. Update the CHANGELOG.md with details of changes to the interface if they are breaking changes, this includes new environment variables, exposed ports useful file locations and container parameters.
3. Pull Request can be merged in once you have the sign-off of one other developer, or if you do not have permission to do that you may request the reviewer to merge it for you.
@@ -16,6 +24,7 @@ Take a look to the conventions established by existing code:
* Every module should come with some reference to scientific literature that allows relating the code to research. Use the `//!` comments at the top of the module to tell readers about the basics of the procedure you are implementing.
* Every module should provide a Rust doctest, a brief test embedded with the documentation that explains how to use the procedure implemented.
* Every module should provide comprehensive tests at the end, in its `mod tests {}` sub-module. These tests can be flagged or not with configuration flags to allow WebAssembly target.
+* Run `cargo doc --no-deps --open` and read the generated documentation in the browser to be sure that your changes reflects in the documentation and new code is documented.
## Issue Report Process
@@ -29,6 +38,7 @@ Take a look to the conventions established by existing code:
1. After a PR is opened maintainers are notified
2. Probably changes will be required to comply with the workflow, these commands are run automatically and all tests shall pass:
* **Coverage** (optional): `tarpaulin` is used with command `cargo tarpaulin --out Lcov --all-features -- --test-threads 1`
+ * **Formatting**: run `rustfmt src/*.rs` to apply automatic formatting
* **Linting**: `clippy` is used with command `cargo clippy --all-features -- -Drust-2018-idioms -Dwarnings`
* **Testing**: multiple test pipelines are run for different targets
3. When everything is OK, code is merged.
diff --git a/.gitignore b/.gitignore
index e4ee4c2..9c0651c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,13 @@ smartcore.code-workspace
# OS
.DS_Store
+
+
+flamegraph.svg
+perf.data
+perf.data.old
+src.dot
+out.svg
+
+FlameGraph/
+out.stacks
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79e77e4..a9dda10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
- Seeds to multiple algorithims that depend on random number generation.
- Added feature `js` to use WASM in browser
+- Drop `nalgebra-bindings` feature
+- 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 towards Rust 2021, in particular the use of `dyn` and `as_ref`
+ * reorganization of the code base, trying to eliminate duplicates
## BREAKING CHANGE
- Added a new parameter to `train_test_split` to define the seed.
diff --git a/Cargo.toml b/Cargo.toml
index 51b9887..d048eea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,9 +2,9 @@
name = "smartcore"
description = "The most advanced machine learning library in rust."
homepage = "https://smartcorelib.org"
-version = "0.2.1"
+version = "0.4.0"
authors = ["SmartCore Developers"]
-edition = "2018"
+edition = "2021"
license = "Apache-2.0"
documentation = "https://docs.rs/smartcore"
repository = "https://github.com/smartcorelib/smartcore"
@@ -13,31 +13,27 @@ keywords = ["machine-learning", "statistical", "ai", "optimization", "linear-alg
categories = ["science"]
[features]
-default = ["datasets"]
+default = ["datasets", "serde"]
ndarray-bindings = ["ndarray"]
-nalgebra-bindings = ["nalgebra"]
datasets = ["rand_distr", "std"]
-fp_bench = ["itertools"]
std = ["rand/std", "rand/std_rng"]
# wasm32 only
js = ["getrandom/js"]
[dependencies]
+approx = "0.5.1"
+cfg-if = "1.0.0"
ndarray = { version = "0.15", optional = true }
-nalgebra = { version = "0.31", optional = true }
-num-traits = "0.2"
+num-traits = "0.2.12"
num = "0.4"
rand = { version = "0.8", default-features = false, features = ["small_rng"] }
rand_distr = { version = "0.4", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
-itertools = { version = "0.10.3", optional = true }
-cfg-if = "1.0.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", optional = true }
[dev-dependencies]
-smartcore = { path = ".", features = ["fp_bench"] }
criterion = { version = "0.4", default-features = false }
serde_json = "1.0"
bincode = "1.3.1"
@@ -45,16 +41,19 @@ bincode = "1.3.1"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3"
-[[bench]]
-name = "distance"
-harness = false
+[profile.bench]
+debug = true
-[[bench]]
-name = "naive_bayes"
-harness = false
-required-features = ["ndarray-bindings", "nalgebra-bindings"]
+resolver = "2"
-[[bench]]
-name = "fastpair"
-harness = false
-required-features = ["fp_bench"]
+[profile.test]
+debug = 1
+opt-level = 3
+split-debuginfo = "unpacked"
+
+[profile.release]
+strip = true
+debug = 1
+lto = true
+codegen-units = 1
+overflow-checks = true
\ No newline at end of file
diff --git a/README.md b/README.md
index 743ad36..516a43a 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,48 @@
-----
-## Developers
-Contributions welcome, please start from [CONTRIBUTING and other relevant files](.github/CONTRIBUTING.md).
-
## Current status
* Current working branch is `development` (if you want something that you can test right away).
* Breaking changes are undergoing development at [`v0.5-wip`](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) (if you are a newcomer better to start from [this README](https://github.com/smartcorelib/smartcore/tree/v0.5-wip#readme) as this will be the next major release).
-To start getting familiar with the new Smartcore v0.5 API, there is now available a [**Jupyter Notebook environment repository**](https://github.com/smartcorelib/smartcore-jupyter).
+To start getting familiar with the new Smartcore v0.5 API, there is now available a [**Jupyter Notebook environment repository**](https://github.com/smartcorelib/smartcore-jupyter). Please see instructions there, your feedback is valuable for the future of the library.
+
+## Developers
+Contributions welcome, please start from [CONTRIBUTING and other relevant files](.github/CONTRIBUTING.md).
+
+### Walkthrough: traits system and basic structures
+
+#### numbers
+The library is founded on basic traits provided by `num-traits`. Basic traits are in `src/numbers`. These traits are used to define all the procedures in the library to make everything safer and provide constraints to what implementations can handle.
+
+#### linalg
+`numbers` are made at use in linear algebra structures in the **`src/linalg/basic`** module. These sub-modules define the traits used all over the code base.
+
+* *arrays*: In particular data structures like `Array`, `Array1` (1-dimensional), `Array2` (matrix, 2-D); plus their "views" traits. Views are used to provide no-footprint access to data, they have composed traits to allow writing (mutable traits: `MutArray`, `ArrayViewMut`, ...).
+* *matrix*: This provides the main entrypoint to matrices operations and currently the only structure provided in the shape of `struct DenseMatrix`. A matrix can be instantiated and automatically make available all the traits in "arrays" (sparse matrices implementation will be provided).
+* *vector*: Convenience traits are implemented for `std::Vec` to allow extensive reuse.
+
+These are all traits and by definition they do not allow instantiation. For instantiable structures see implementation like `DenseMatrix` with relative constructor.
+
+#### linalg/traits
+The traits in `src/linalg/traits` are closely linked to Linear Algebra's theoretical framework. These traits are used to specify characteristics and constraints for types accepted by various algorithms. For example these allow to define if a matrix is `QRDecomposable` and/or `SVDDecomposable`. See docstring for referencese to theoretical framework.
+
+As above these are all traits and by definition they do not allow instantiation. They are mostly used to provide constraints for implementations. For example, the implementation for Linear Regression requires the input data `X` to be in `smartcore`'s trait system `Array2 + QRDecomposable + SVDDecomposable`, a 2-D matrix that is both QR and SVD decomposable; that is what the provided strucure `linalg::arrays::matrix::DenseMatrix` happens to be: `impl QRDecomposable for DenseMatrix {};impl SVDDecomposable for DenseMatrix {}`.
+
+#### metrics
+Implementations for metrics (classification, regression, cluster, ...) and distance measure (Euclidean, Hamming, Manhattan, ...). For example: `Accuracy`, `F1`, `AUC`, `Precision`, `R2`. As everything else in the code base, these implementations reuse `numbers` and `linalg` traits and structures.
+
+These are collected in structures like `pub struct ClassificationMetrics {}` that implements `metrics::Metrics`, these are groups of functions (classification, regression, cluster, ...) that provide instantiation for the structures. Each of those instantiation can be passed around using the relative function, like `pub fn accuracy>(y_true: &V, y_pred: &V) -> T`. This provides a mechanism for metrics to be passed to higher interfaces like the `cross_validate`:
+```rust
+let results =
+ cross_validate(
+ BiasedEstimator::fit, // custom estimator
+ &x, &y, // input data
+ NoParameters {}, // extra parameters
+ cv, // type of cross validator
+ &accuracy // **metrics function** <--------
+ ).unwrap();
+```
+
+
+TODO: complete for all modules
diff --git a/benches/distance.rs b/benches/distance.rs
deleted file mode 100644
index b44e948..0000000
--- a/benches/distance.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-#[macro_use]
-extern crate criterion;
-extern crate smartcore;
-
-use criterion::black_box;
-use criterion::Criterion;
-use smartcore::math::distance::*;
-
-fn criterion_benchmark(c: &mut Criterion) {
- let a = vec![1., 2., 3.];
-
- c.bench_function("Euclidean Distance", move |b| {
- b.iter(|| Distances::euclidian().distance(black_box(&a), black_box(&a)))
- });
-}
-
-criterion_group!(benches, criterion_benchmark);
-criterion_main!(benches);
diff --git a/benches/fastpair.rs b/benches/fastpair.rs
deleted file mode 100644
index baa0e90..0000000
--- a/benches/fastpair.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
-
-// to run this bench you have to change the declaraion in mod.rs ---> pub mod fastpair;
-use smartcore::algorithm::neighbour::fastpair::FastPair;
-use smartcore::linalg::naive::dense_matrix::*;
-use std::time::Duration;
-
-fn closest_pair_bench(n: usize, m: usize) -> () {
- let x = DenseMatrix::::rand(n, m);
- let fastpair = FastPair::new(&x);
- let result = fastpair.unwrap();
-
- result.closest_pair();
-}
-
-fn closest_pair_brute_bench(n: usize, m: usize) -> () {
- let x = DenseMatrix::::rand(n, m);
- let fastpair = FastPair::new(&x);
- let result = fastpair.unwrap();
-
- result.closest_pair_brute();
-}
-
-fn bench_fastpair(c: &mut Criterion) {
- let mut group = c.benchmark_group("FastPair");
-
- // with full samples size (100) the test will take too long
- group.significance_level(0.1).sample_size(30);
- // increase from default 5.0 secs
- group.measurement_time(Duration::from_secs(60));
-
- for n_samples in [100_usize, 1000_usize].iter() {
- for n_features in [10_usize, 100_usize, 1000_usize].iter() {
- group.bench_with_input(
- BenchmarkId::from_parameter(format!(
- "fastpair --- n_samples: {}, n_features: {}",
- n_samples, n_features
- )),
- n_samples,
- |b, _| b.iter(|| closest_pair_bench(*n_samples, *n_features)),
- );
- group.bench_with_input(
- BenchmarkId::from_parameter(format!(
- "brute --- n_samples: {}, n_features: {}",
- n_samples, n_features
- )),
- n_samples,
- |b, _| b.iter(|| closest_pair_brute_bench(*n_samples, *n_features)),
- );
- }
- }
- group.finish();
-}
-
-criterion_group!(benches, bench_fastpair);
-criterion_main!(benches);
diff --git a/benches/naive_bayes.rs b/benches/naive_bayes.rs
deleted file mode 100644
index ba8cb6f..0000000
--- a/benches/naive_bayes.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use criterion::BenchmarkId;
-use criterion::{black_box, criterion_group, criterion_main, Criterion};
-
-use nalgebra::DMatrix;
-use ndarray::Array2;
-use smartcore::linalg::naive::dense_matrix::DenseMatrix;
-use smartcore::linalg::BaseMatrix;
-use smartcore::linalg::BaseVector;
-use smartcore::naive_bayes::gaussian::GaussianNB;
-
-pub fn gaussian_naive_bayes_fit_benchmark(c: &mut Criterion) {
- let mut group = c.benchmark_group("GaussianNB::fit");
-
- for n_samples in [100_usize, 1000_usize, 10000_usize].iter() {
- for n_features in [10_usize, 100_usize, 1000_usize].iter() {
- let x = DenseMatrix::::rand(*n_samples, *n_features);
- let y: Vec = (0..*n_samples)
- .map(|i| (i % *n_samples / 5_usize) as f64)
- .collect::>();
- group.bench_with_input(
- BenchmarkId::from_parameter(format!(
- "n_samples: {}, n_features: {}",
- n_samples, n_features
- )),
- n_samples,
- |b, _| {
- b.iter(|| {
- GaussianNB::fit(black_box(&x), black_box(&y), Default::default()).unwrap();
- })
- },
- );
- }
- }
- group.finish();
-}
-
-pub fn gaussian_naive_matrix_datastructure(c: &mut Criterion) {
- let mut group = c.benchmark_group("GaussianNB");
- let classes = (0..10000).map(|i| (i % 25) as f64).collect::>();
-
- group.bench_function("DenseMatrix", |b| {
- let x = DenseMatrix::::rand(10000, 500);
- let y = as BaseMatrix>::RowVector::from_array(&classes);
-
- b.iter(|| {
- GaussianNB::fit(black_box(&x), black_box(&y), Default::default()).unwrap();
- })
- });
-
- group.bench_function("ndarray", |b| {
- let x = Array2::::rand(10000, 500);
- let y = as BaseMatrix>::RowVector::from_array(&classes);
-
- b.iter(|| {
- GaussianNB::fit(black_box(&x), black_box(&y), Default::default()).unwrap();
- })
- });
-
- group.bench_function("ndalgebra", |b| {
- let x = DMatrix::::rand(10000, 500);
- let y = as BaseMatrix>::RowVector::from_array(&classes);
-
- b.iter(|| {
- GaussianNB::fit(black_box(&x), black_box(&y), Default::default()).unwrap();
- })
- });
-}
-criterion_group!(
- benches,
- gaussian_naive_bayes_fit_benchmark,
- gaussian_naive_matrix_datastructure
-);
-criterion_main!(benches);
diff --git a/src/algorithm/neighbour/bbd_tree.rs b/src/algorithm/neighbour/bbd_tree.rs
index 93ea050..e84f6de 100644
--- a/src/algorithm/neighbour/bbd_tree.rs
+++ b/src/algorithm/neighbour/bbd_tree.rs
@@ -1,45 +1,45 @@
use std::fmt::Debug;
-use crate::linalg::Matrix;
-use crate::math::distance::euclidian::*;
-use crate::math::num::RealNumber;
+use crate::linalg::basic::arrays::Array2;
+use crate::metrics::distance::euclidian::*;
+use crate::numbers::basenum::Number;
#[derive(Debug)]
-pub struct BBDTree {
- nodes: Vec>,
+pub struct BBDTree {
+ nodes: Vec,
index: Vec,
root: usize,
}
#[derive(Debug)]
-struct BBDTreeNode {
+struct BBDTreeNode {
count: usize,
index: usize,
- center: Vec,
- radius: Vec,
- sum: Vec,
- cost: T,
+ center: Vec,
+ radius: Vec,
+ sum: Vec,
+ cost: f64,
lower: Option,
upper: Option,
}
-impl BBDTreeNode {
- fn new(d: usize) -> BBDTreeNode {
+impl BBDTreeNode {
+ fn new(d: usize) -> BBDTreeNode {
BBDTreeNode {
count: 0,
index: 0,
- center: vec![T::zero(); d],
- radius: vec![T::zero(); d],
- sum: vec![T::zero(); d],
- cost: T::zero(),
+ center: vec![0f64; d],
+ radius: vec![0f64; d],
+ sum: vec![0f64; d],
+ cost: 0f64,
lower: Option::None,
upper: Option::None,
}
}
}
-impl BBDTree {
- pub fn new>(data: &M) -> BBDTree {
+impl BBDTree {
+ pub fn new>(data: &M) -> BBDTree {
let nodes = Vec::new();
let (n, _) = data.shape();
@@ -61,18 +61,18 @@ impl BBDTree {
pub(crate) fn clustering(
&self,
- centroids: &[Vec],
- sums: &mut Vec>,
+ centroids: &[Vec],
+ sums: &mut Vec>,
counts: &mut Vec,
membership: &mut Vec,
- ) -> T {
+ ) -> f64 {
let k = centroids.len();
counts.iter_mut().for_each(|v| *v = 0);
let mut candidates = vec![0; k];
for i in 0..k {
candidates[i] = i;
- sums[i].iter_mut().for_each(|v| *v = T::zero());
+ sums[i].iter_mut().for_each(|v| *v = 0f64);
}
self.filter(
@@ -89,13 +89,13 @@ impl BBDTree {
fn filter(
&self,
node: usize,
- centroids: &[Vec],
+ centroids: &[Vec],
candidates: &[usize],
k: usize,
- sums: &mut Vec>,
+ sums: &mut Vec>,
counts: &mut Vec,
membership: &mut Vec,
- ) -> T {
+ ) -> f64 {
let d = centroids[0].len();
let mut min_dist =
@@ -163,9 +163,9 @@ impl BBDTree {
}
fn prune(
- center: &[T],
- radius: &[T],
- centroids: &[Vec],
+ center: &[f64],
+ radius: &[f64],
+ centroids: &[Vec],
best_index: usize,
test_index: usize,
) -> bool {
@@ -177,22 +177,22 @@ impl BBDTree {
let best = ¢roids[best_index];
let test = ¢roids[test_index];
- let mut lhs = T::zero();
- let mut rhs = T::zero();
+ let mut lhs = 0f64;
+ let mut rhs = 0f64;
for i in 0..d {
let diff = test[i] - best[i];
lhs += diff * diff;
- if diff > T::zero() {
+ if diff > 0f64 {
rhs += (center[i] + radius[i] - best[i]) * diff;
} else {
rhs += (center[i] - radius[i] - best[i]) * diff;
}
}
- lhs >= T::two() * rhs
+ lhs >= 2f64 * rhs
}
- fn build_node>(&mut self, data: &M, begin: usize, end: usize) -> usize {
+ fn build_node>(&mut self, data: &M, begin: usize, end: usize) -> usize {
let (_, d) = data.shape();
let mut node = BBDTreeNode::new(d);
@@ -200,17 +200,17 @@ impl BBDTree {
node.count = end - begin;
node.index = begin;
- let mut lower_bound = vec![T::zero(); d];
- let mut upper_bound = vec![T::zero(); d];
+ let mut lower_bound = vec![0f64; d];
+ let mut upper_bound = vec![0f64; d];
for i in 0..d {
- lower_bound[i] = data.get(self.index[begin], i);
- upper_bound[i] = data.get(self.index[begin], i);
+ lower_bound[i] = data.get((self.index[begin], i)).to_f64().unwrap();
+ upper_bound[i] = data.get((self.index[begin], i)).to_f64().unwrap();
}
for i in begin..end {
for j in 0..d {
- let c = data.get(self.index[i], j);
+ let c = data.get((self.index[i], j)).to_f64().unwrap();
if lower_bound[j] > c {
lower_bound[j] = c;
}
@@ -220,32 +220,32 @@ impl BBDTree {
}
}
- let mut max_radius = T::from(-1.).unwrap();
+ let mut max_radius = -1f64;
let mut split_index = 0;
for i in 0..d {
- node.center[i] = (lower_bound[i] + upper_bound[i]) / T::two();
- node.radius[i] = (upper_bound[i] - lower_bound[i]) / T::two();
+ node.center[i] = (lower_bound[i] + upper_bound[i]) / 2f64;
+ node.radius[i] = (upper_bound[i] - lower_bound[i]) / 2f64;
if node.radius[i] > max_radius {
max_radius = node.radius[i];
split_index = i;
}
}
- if max_radius < T::from(1E-10).unwrap() {
+ if max_radius < 1E-10 {
node.lower = Option::None;
node.upper = Option::None;
for i in 0..d {
- node.sum[i] = data.get(self.index[begin], i);
+ node.sum[i] = data.get((self.index[begin], i)).to_f64().unwrap();
}
if end > begin + 1 {
let len = end - begin;
for i in 0..d {
- node.sum[i] *= T::from(len).unwrap();
+ node.sum[i] *= len as f64;
}
}
- node.cost = T::zero();
+ node.cost = 0f64;
return self.add_node(node);
}
@@ -254,8 +254,10 @@ impl BBDTree {
let mut i2 = end - 1;
let mut size = 0;
while i1 <= i2 {
- let mut i1_good = data.get(self.index[i1], split_index) < split_cutoff;
- let mut i2_good = data.get(self.index[i2], split_index) >= split_cutoff;
+ let mut i1_good =
+ data.get((self.index[i1], split_index)).to_f64().unwrap() < split_cutoff;
+ let mut i2_good =
+ data.get((self.index[i2], split_index)).to_f64().unwrap() >= split_cutoff;
if !i1_good && !i2_good {
self.index.swap(i1, i2);
@@ -281,9 +283,9 @@ impl BBDTree {
self.nodes[node.lower.unwrap()].sum[i] + self.nodes[node.upper.unwrap()].sum[i];
}
- let mut mean = vec![T::zero(); d];
+ let mut mean = vec![0f64; d];
for (i, mean_i) in mean.iter_mut().enumerate().take(d) {
- *mean_i = node.sum[i] / T::from(node.count).unwrap();
+ *mean_i = node.sum[i] / node.count as f64;
}
node.cost = BBDTree::node_cost(&self.nodes[node.lower.unwrap()], &mean)
@@ -292,17 +294,17 @@ impl BBDTree {
self.add_node(node)
}
- fn node_cost(node: &BBDTreeNode, center: &[T]) -> T {
+ fn node_cost(node: &BBDTreeNode, center: &[f64]) -> f64 {
let d = center.len();
- let mut scatter = T::zero();
+ let mut scatter = 0f64;
for (i, center_i) in center.iter().enumerate().take(d) {
- let x = (node.sum[i] / T::from(node.count).unwrap()) - *center_i;
+ let x = (node.sum[i] / node.count as f64) - *center_i;
scatter += x * x;
}
- node.cost + T::from(node.count).unwrap() * scatter
+ node.cost + node.count as f64 * scatter
}
- fn add_node(&mut self, new_node: BBDTreeNode) -> usize {
+ fn add_node(&mut self, new_node: BBDTreeNode) -> usize {
let idx = self.nodes.len();
self.nodes.push(new_node);
idx
@@ -312,7 +314,7 @@ impl BBDTree {
#[cfg(test)]
mod tests {
use super::*;
- use crate::linalg::naive::dense_matrix::DenseMatrix;
+ use crate::linalg::basic::matrix::DenseMatrix;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
diff --git a/src/algorithm/neighbour/cover_tree.rs b/src/algorithm/neighbour/cover_tree.rs
index 5664acc..85e0d22 100644
--- a/src/algorithm/neighbour/cover_tree.rs
+++ b/src/algorithm/neighbour/cover_tree.rs
@@ -4,12 +4,12 @@
//!
//! ```
//! use smartcore::algorithm::neighbour::cover_tree::*;
-//! use smartcore::math::distance::Distance;
+//! use smartcore::metrics::distance::Distance;
//!
//! #[derive(Clone)]
//! struct SimpleDistance {} // Our distance function
//!
-//! impl Distance for SimpleDistance {
+//! impl Distance for SimpleDistance {
//! fn distance(&self, a: &i32, b: &i32) -> f64 { // simple simmetrical scalar distance
//! (a - b).abs() as f64
//! }
@@ -29,28 +29,27 @@ use serde::{Deserialize, Serialize};
use crate::algorithm::sort::heap_select::HeapSelection;
use crate::error::{Failed, FailedError};
-use crate::math::distance::Distance;
-use crate::math::num::RealNumber;
+use crate::metrics::distance::Distance;
/// Implements Cover Tree algorithm
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
-pub struct CoverTree> {
- base: F,
- inv_log_base: F,
+pub struct CoverTree> {
+ base: f64,
+ inv_log_base: f64,
distance: D,
- root: Node,
+ root: Node,
data: Vec,
identical_excluded: bool,
}
-impl> PartialEq for CoverTree {
+impl> PartialEq for CoverTree {
fn eq(&self, other: &Self) -> bool {
if self.data.len() != other.data.len() {
return false;
}
for i in 0..self.data.len() {
- if self.distance.distance(&self.data[i], &other.data[i]) != F::zero() {
+ if self.distance.distance(&self.data[i], &other.data[i]) != 0f64 {
return false;
}
}
@@ -60,36 +59,36 @@ impl> PartialEq for CoverTree {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
-struct Node {
+struct Node {
idx: usize,
- max_dist: F,
- parent_dist: F,
- children: Vec>,
- _scale: i64,
+ max_dist: f64,
+ parent_dist: f64,
+ children: Vec,
+ scale: i64,
}
#[derive(Debug)]
-struct DistanceSet {
+struct DistanceSet {
idx: usize,
- dist: Vec,
+ dist: Vec,
}
-impl> CoverTree {
+impl> CoverTree {
/// Construct a cover tree.
/// * `data` - vector of data points to search for.
/// * `distance` - distance metric to use for searching. This function should extend [`Distance`](../../../math/distance/index.html) interface.
- pub fn new(data: Vec, distance: D) -> Result, Failed> {
- let base = F::from_f64(1.3).unwrap();
+ pub fn new(data: Vec, distance: D) -> Result, Failed> {
+ let base = 1.3f64;
let root = Node {
idx: 0,
- max_dist: F::zero(),
- parent_dist: F::zero(),
+ max_dist: 0f64,
+ parent_dist: 0f64,
children: Vec::new(),
- _scale: 0,
+ scale: 0,
};
let mut tree = CoverTree {
base,
- inv_log_base: F::one() / base.ln(),
+ inv_log_base: 1f64 / base.ln(),
distance,
root,
data,
@@ -104,7 +103,7 @@ impl> CoverTree
/// Find k nearest neighbors of `p`
/// * `p` - look for k nearest points to `p`
/// * `k` - the number of nearest neighbors to return
- pub fn find(&self, p: &T, k: usize) -> Result, Failed> {
+ pub fn find(&self, p: &T, k: usize) -> Result, Failed> {
if k == 0 {
return Err(Failed::because(FailedError::FindFailed, "k should be > 0"));
}
@@ -119,13 +118,13 @@ impl> CoverTree
let e = self.get_data_value(self.root.idx);
let mut d = self.distance.distance(e, p);
- let mut current_cover_set: Vec<(F, &Node)> = Vec::new();
- let mut zero_set: Vec<(F, &Node)> = Vec::new();
+ let mut current_cover_set: Vec<(f64, &Node)> = Vec::new();
+ let mut zero_set: Vec<(f64, &Node)> = Vec::new();
current_cover_set.push((d, &self.root));
let mut heap = HeapSelection::with_capacity(k);
- heap.add(F::max_value());
+ heap.add(std::f64::MAX);
let mut empty_heap = true;
if !self.identical_excluded || self.get_data_value(self.root.idx) != p {
@@ -134,7 +133,7 @@ impl> CoverTree
}
while !current_cover_set.is_empty() {
- let mut next_cover_set: Vec<(F, &Node)> = Vec::new();
+ let mut next_cover_set: Vec<(f64, &Node)> = Vec::new();
for par in current_cover_set {
let parent = par.1;
for c in 0..parent.children.len() {
@@ -146,7 +145,7 @@ impl> CoverTree
}
let upper_bound = if empty_heap {
- F::infinity()
+ std::f64::INFINITY
} else {
*heap.peek()
};
@@ -169,7 +168,7 @@ impl> CoverTree
current_cover_set = next_cover_set;
}
- let mut neighbors: Vec<(usize, F, &T)> = Vec::new();
+ let mut neighbors: Vec<(usize, f64, &T)> = Vec::new();
let upper_bound = *heap.peek();
for ds in zero_set {
if ds.0 <= upper_bound {
@@ -189,25 +188,25 @@ impl> CoverTree
/// Find all nearest neighbors within radius `radius` from `p`
/// * `p` - look for k nearest points to `p`
/// * `radius` - radius of the search
- pub fn find_radius(&self, p: &T, radius: F) -> Result, Failed> {
- if radius <= F::zero() {
+ pub fn find_radius(&self, p: &T, radius: f64) -> Result, Failed> {
+ if radius <= 0f64 {
return Err(Failed::because(
FailedError::FindFailed,
"radius should be > 0",
));
}
- let mut neighbors: Vec<(usize, F, &T)> = Vec::new();
+ let mut neighbors: Vec<(usize, f64, &T)> = Vec::new();
- let mut current_cover_set: Vec<(F, &Node)> = Vec::new();
- let mut zero_set: Vec<(F, &Node)> = Vec::new();
+ let mut current_cover_set: Vec<(f64, &Node)> = Vec::new();
+ let mut zero_set: Vec<(f64, &Node)> = Vec::new();
let e = self.get_data_value(self.root.idx);
let mut d = self.distance.distance(e, p);
current_cover_set.push((d, &self.root));
while !current_cover_set.is_empty() {
- let mut next_cover_set: Vec<(F, &Node)> = Vec::new();
+ let mut next_cover_set: Vec<(f64, &Node)> = Vec::new();
for par in current_cover_set {
let parent = par.1;
for c in 0..parent.children.len() {
@@ -240,23 +239,23 @@ impl> CoverTree
Ok(neighbors)
}
- fn new_leaf(&self, idx: usize) -> Node {
+ fn new_leaf(&self, idx: usize) -> Node {
Node {
idx,
- max_dist: F::zero(),
- parent_dist: F::zero(),
+ max_dist: 0f64,
+ parent_dist: 0f64,
children: Vec::new(),
- _scale: 100,
+ scale: 100,
}
}
fn build_cover_tree(&mut self) {
- let mut point_set: Vec> = Vec::new();
- let mut consumed_set: Vec> = Vec::new();
+ let mut point_set: Vec = Vec::new();
+ let mut consumed_set: Vec = Vec::new();
let point = &self.data[0];
let idx = 0;
- let mut max_dist = -F::one();
+ let mut max_dist = -1f64;
for i in 1..self.data.len() {
let dist = self.distance.distance(point, &self.data[i]);
@@ -284,16 +283,16 @@ impl> CoverTree
p: usize,
max_scale: i64,
top_scale: i64,
- point_set: &mut Vec>,
- consumed_set: &mut Vec>,
- ) -> Node {
+ point_set: &mut Vec,
+ consumed_set: &mut Vec,
+ ) -> Node {
if point_set.is_empty() {
self.new_leaf(p)
} else {
let max_dist = self.max(point_set);
let next_scale = (max_scale - 1).min(self.get_scale(max_dist));
if next_scale == std::i64::MIN {
- let mut children: Vec> = Vec::new();
+ let mut children: Vec = Vec::new();
let mut leaf = self.new_leaf(p);
children.push(leaf);
while !point_set.is_empty() {
@@ -304,13 +303,13 @@ impl> CoverTree
}
Node {
idx: p,
- max_dist: F::zero(),
- parent_dist: F::zero(),
+ max_dist: 0f64,
+ parent_dist: 0f64,
children,
- _scale: 100,
+ scale: 100,
}
} else {
- let mut far: Vec> = Vec::new();
+ let mut far: Vec = Vec::new();
self.split(point_set, &mut far, max_scale);
let child = self.batch_insert(p, next_scale, top_scale, point_set, consumed_set);
@@ -319,14 +318,14 @@ impl> CoverTree
point_set.append(&mut far);
child
} else {
- let mut children: Vec> = vec![child];
- let mut new_point_set: Vec> = Vec::new();
- let mut new_consumed_set: Vec> = Vec::new();
+ let mut children: Vec = vec![child];
+ let mut new_point_set: Vec = Vec::new();
+ let mut new_consumed_set: Vec = Vec::new();
while !point_set.is_empty() {
- let set: DistanceSet = point_set.remove(point_set.len() - 1);
+ let set: DistanceSet = point_set.remove(point_set.len() - 1);
- let new_dist: F = set.dist[set.dist.len() - 1];
+ let new_dist = set.dist[set.dist.len() - 1];
self.dist_split(
point_set,
@@ -374,9 +373,9 @@ impl> CoverTree
Node {
idx: p,
max_dist: self.max(consumed_set),
- parent_dist: F::zero(),
+ parent_dist: 0f64,
children,
- _scale: (top_scale - max_scale),
+ scale: (top_scale - max_scale),
}
}
}
@@ -385,12 +384,12 @@ impl> CoverTree
fn split(
&self,
- point_set: &mut Vec>,
- far_set: &mut Vec>,
+ point_set: &mut Vec,
+ far_set: &mut Vec,
max_scale: i64,
) {
let fmax = self.get_cover_radius(max_scale);
- let mut new_set: Vec> = Vec::new();
+ let mut new_set: Vec = Vec::new();
for n in point_set.drain(0..) {
if n.dist[n.dist.len() - 1] <= fmax {
new_set.push(n);
@@ -404,13 +403,13 @@ impl> CoverTree
fn dist_split(
&self,
- point_set: &mut Vec>,
- new_point_set: &mut Vec>,
+ point_set: &mut Vec,
+ new_point_set: &mut Vec,
new_point: &T,
max_scale: i64,
) {
let fmax = self.get_cover_radius(max_scale);
- let mut new_set: Vec> = Vec::new();
+ let mut new_set: Vec = Vec::new();
for mut n in point_set.drain(0..) {
let new_dist = self
.distance
@@ -426,24 +425,24 @@ impl> CoverTree
point_set.append(&mut new_set);
}
- fn get_cover_radius(&self, s: i64) -> F {
- self.base.powf(F::from_i64(s).unwrap())
+ fn get_cover_radius(&self, s: i64) -> f64 {
+ self.base.powf(s as f64)
}
fn get_data_value(&self, idx: usize) -> &T {
&self.data[idx]
}
- fn get_scale(&self, d: F) -> i64 {
- if d == F::zero() {
+ fn get_scale(&self, d: f64) -> i64 {
+ if d == 0f64 {
std::i64::MIN
} else {
- (self.inv_log_base * d.ln()).ceil().to_i64().unwrap()
+ (self.inv_log_base * d.ln()).ceil() as i64
}
}
- fn max(&self, distance_set: &[DistanceSet]) -> F {
- let mut max = F::zero();
+ fn max(&self, distance_set: &[DistanceSet]) -> f64 {
+ let mut max = 0f64;
for n in distance_set {
if max < n.dist[n.dist.len() - 1] {
max = n.dist[n.dist.len() - 1];
@@ -457,13 +456,13 @@ impl> CoverTree
mod tests {
use super::*;
- use crate::math::distance::Distances;
+ use crate::metrics::distance::Distances;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
struct SimpleDistance {}
- impl Distance for SimpleDistance {
+ impl Distance for SimpleDistance {
fn distance(&self, a: &i32, b: &i32) -> f64 {
(a - b).abs() as f64
}
@@ -513,7 +512,7 @@ mod tests {
let tree = CoverTree::new(data, SimpleDistance {}).unwrap();
- let deserialized_tree: CoverTree =
+ let deserialized_tree: CoverTree =
serde_json::from_str(&serde_json::to_string(&tree).unwrap()).unwrap();
assert_eq!(tree, deserialized_tree);
diff --git a/src/algorithm/neighbour/distances.rs b/src/algorithm/neighbour/distances.rs
index 56a7ed6..eee99ca 100644
--- a/src/algorithm/neighbour/distances.rs
+++ b/src/algorithm/neighbour/distances.rs
@@ -9,7 +9,7 @@ use std::cmp::{Eq, Ordering, PartialOrd};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
-use crate::math::num::RealNumber;
+use crate::numbers::realnum::RealNumber;
///
/// The edge of the subgraph is defined by `PairwiseDistance`.
diff --git a/src/algorithm/neighbour/fastpair.rs b/src/algorithm/neighbour/fastpair.rs
index bf3bca3..d676460 100644
--- a/src/algorithm/neighbour/fastpair.rs
+++ b/src/algorithm/neighbour/fastpair.rs
@@ -27,9 +27,10 @@ use std::collections::HashMap;
use crate::algorithm::neighbour::distances::PairwiseDistance;
use crate::error::{Failed, FailedError};
-use crate::linalg::Matrix;
-use crate::math::distance::euclidian::Euclidian;
-use crate::math::num::RealNumber;
+use crate::linalg::basic::arrays::Array2;
+use crate::metrics::distance::euclidian::Euclidian;
+use crate::numbers::realnum::RealNumber;
+use crate::numbers::floatnum::FloatNumber;
///
/// Inspired by Python implementation:
@@ -39,7 +40,7 @@ use crate::math::num::RealNumber;
/// affinity used is Euclidean so to allow linkage with single, ward, complete and average
///
#[derive(Debug, Clone)]
-pub struct FastPair<'a, T: RealNumber, M: Matrix> {
+pub struct FastPair<'a, T: RealNumber + FloatNumber, M: Array2> {
/// initial matrix
samples: &'a M,
/// closest pair hashmap (connectivity matrix for closest pairs)
@@ -48,7 +49,7 @@ pub struct FastPair<'a, T: RealNumber, M: Matrix> {
pub neighbours: Vec,
}
-impl<'a, T: RealNumber, M: Matrix> FastPair<'a, T, M> {
+impl<'a, T: RealNumber + FloatNumber, M: Array2> FastPair<'a, T, M> {
///
/// Constructor
/// Instantiate and inizialise the algorithm
@@ -72,7 +73,7 @@ impl<'a, T: RealNumber, M: Matrix