diff --git a/Cargo.toml b/Cargo.toml index e14df48..42bba39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,6 @@ exclude = [ "docs/*", ] -[lib] -name = "ffsvm" -path = "src/lib.rs" -crate-type = ["rlib"] - [dependencies] simd_aligned = "0.6.1" #simd_aligned = { path = "../simd_aligned" } @@ -26,6 +21,3 @@ simd_aligned = "0.6.1" [dev-dependencies] rand = "0.8.5" -[profile.release] -opt-level = 3 -lto = true diff --git a/benches/svm_dense.rs b/benches/svm_dense.rs index e9522a4..7091f5f 100644 --- a/benches/svm_dense.rs +++ b/benches/svm_dense.rs @@ -24,7 +24,7 @@ mod svm_dense { problem_mut[i as usize] = i as f32; } - move || (&svm).predict_value(&mut problem).expect("This should work") + move || svm.predict_value(&mut problem).expect("This should work") } // RBF diff --git a/benches/svm_sparse.rs b/benches/svm_sparse.rs index f53be60..0b9be8e 100644 --- a/benches/svm_sparse.rs +++ b/benches/svm_sparse.rs @@ -24,7 +24,7 @@ mod svm_sparse { problem_mut[i as usize] = i as f32; } - move || (&svm).predict_value(&mut problem).expect("This should work") + move || svm.predict_value(&mut problem).expect("This should work") } // RBF diff --git a/src/errors.rs b/src/errors.rs index 483eef3..fb75daf 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -51,9 +51,9 @@ pub enum Error { // } impl From for Error { - fn from(_e: ParseFloatError) -> Self { Error::Parsing("ParseFloatError".to_owned()) } + fn from(_e: ParseFloatError) -> Self { Self::Parsing("ParseFloatError".to_owned()) } } impl From for Error { - fn from(_: ParseIntError) -> Self { Error::Parsing("ParseIntError".to_owned()) } + fn from(_: ParseIntError) -> Self { Self::Parsing("ParseIntError".to_owned()) } } diff --git a/src/parser.rs b/src/parser.rs index 0dce1a7..5f18fac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -127,7 +127,7 @@ impl<'a> TryFrom<&'a str> for ModelFile<'a> { for line in input.lines() { let tokens = line.split_whitespace().collect::>(); - match tokens.get(0) { + match tokens.first() { // Single value headers // // svm_type c_svc @@ -190,7 +190,7 @@ impl<'a> TryFrom<&'a str> for ModelFile<'a> { let split = x.split(':').collect::>(); Some(Attribute { - index: split.get(0)?.parse::().ok()?, + index: split.first()?.parse::().ok()?, value: split.get(1)?.parse::().ok()?, }) }) diff --git a/src/sparse.rs b/src/sparse.rs index 5f6d68c..6c534b2 100644 --- a/src/sparse.rs +++ b/src/sparse.rs @@ -21,11 +21,11 @@ impl SparseVector where T: Clone + Copy + Default, { - pub fn new() -> Self { Self { entries: Vec::new() } } + pub const fn new() -> Self { Self { entries: Vec::new() } } pub fn clear(&mut self) { self.entries.clear(); } - pub fn iter(&self) -> SparseVectorIter<'_, T> { SparseVectorIter { vector: self, index: 0 } } + pub const fn iter(&self) -> SparseVectorIter<'_, T> { SparseVectorIter { vector: self, index: 0 } } } /// Basic iterator struct to go over matrix @@ -41,7 +41,7 @@ where pub(crate) index: usize, } -impl<'a, T> Iterator for SparseVectorIter<'a, T> +impl Iterator for SparseVectorIter<'_, T> where T: Clone + Copy + Default, { @@ -86,10 +86,7 @@ where fn index_mut(&mut self, index: usize) -> &mut T { // TODO: Beautify me - let highest_so_far: i32 = match self.entries.last() { - None => -1, - Some(x) => x.index as i32, - }; + let highest_so_far: i32 = self.entries.last().map_or(-1, |x| x.index as i32); if index as i32 <= highest_so_far { unimplemented!("We still need to implement unsorted insertion. As of today, you need to insert element in strictly ascending order."); @@ -126,7 +123,7 @@ where pub fn row(&self, row: usize) -> &SparseVector { &self.vectors[row] } #[inline] - pub fn row_iter(&self) -> SparseMatrixIter<'_, T> { SparseMatrixIter { matrix: self, index: 0 } } + pub const fn row_iter(&self) -> SparseMatrixIter<'_, T> { SparseMatrixIter { matrix: self, index: 0 } } } impl Index<(usize, usize)> for SparseMatrix diff --git a/src/svm/class.rs b/src/svm/class.rs index cdc2080..60ca7a2 100644 --- a/src/svm/class.rs +++ b/src/svm/class.rs @@ -7,7 +7,7 @@ use simd_aligned::{ /// Represents one class of the SVM model. #[derive(Clone, Debug)] #[doc(hidden)] -pub(crate) struct Class { +pub struct Class { /// The label of this class pub(crate) label: i32, diff --git a/src/svm/core/dense.rs b/src/svm/core/dense.rs index 4efa208..88d873f 100644 --- a/src/svm/core/dense.rs +++ b/src/svm/core/dense.rs @@ -60,7 +60,7 @@ impl DenseSVM { /// /// If the label was found its index returned in the [`Option`], otherwise `None` /// is returned. - pub fn class_index_for_label(&self, label: i32) -> Option { + #[must_use] pub fn class_index_for_label(&self, label: i32) -> Option { for (i, class) in self.classes.iter().enumerate() { if class.label != label { continue; @@ -84,7 +84,7 @@ impl DenseSVM { /// /// If the index was found it is returned in the [`Option`], otherwise `None` /// is returned. - pub fn class_label_for_index(&self, index: usize) -> Option { + #[must_use] pub fn class_label_for_index(&self, index: usize) -> Option { if index >= self.classes.len() { None } else { @@ -132,10 +132,10 @@ impl DenseSVM { } /// Returns number of attributes, reflecting the libSVM model. - pub const fn attributes(&self) -> usize { self.num_attributes } + #[must_use] pub const fn attributes(&self) -> usize { self.num_attributes } /// Returns number of classes, reflecting the libSVM model. - pub fn classes(&self) -> usize { self.classes.len() } + #[must_use] pub fn classes(&self) -> usize { self.classes.len() } } impl Predict> for DenseSVM { @@ -173,7 +173,7 @@ impl<'a> TryFrom<&'a str> for DenseSVM { } } -impl<'a, 'b> TryFrom<&'a ModelFile<'b>> for DenseSVM { +impl<'a> TryFrom<&'a ModelFile<'_>> for DenseSVM { type Error = Error; fn try_from(raw_model: &'a ModelFile<'_>) -> Result { diff --git a/src/svm/core/mod.rs b/src/svm/core/mod.rs index 7ab9ac0..b779692 100644 --- a/src/svm/core/mod.rs +++ b/src/svm/core/mod.rs @@ -154,10 +154,10 @@ macro_rules! compute_multiclass_probabilities_impl { let diff = (-qp[t] + pqp) / q[(t, t)]; probabilities[t] += diff; - pqp = (pqp + diff * (diff * q[(t, t)] + 2.0 * qp[t])) / (1.0 + diff) / (1.0 + diff); + pqp = diff.mul_add(diff.mul_add(q[(t, t)], 2.0 * qp[t]), pqp) / (1.0 + diff) / (1.0 + diff); for j in 0 .. num_classes { - qp[j] = (qp[j] + diff * q[(t, j)]) / (1.0 + diff); + qp[j] = diff.mul_add(q[(t, j)], qp[j]) / (1.0 + diff); probabilities[j] /= 1.0 + diff; } } diff --git a/src/svm/core/sparse.rs b/src/svm/core/sparse.rs index 68feffc..8bc480f 100644 --- a/src/svm/core/sparse.rs +++ b/src/svm/core/sparse.rs @@ -171,7 +171,7 @@ impl Predict> for SparseSVM { fn predict_probability(&self, problem: &mut FeatureVector>) -> Result<(), Error> { predict_probability_impl!(self, problem) } } -impl<'a, 'b> TryFrom<&'a str> for SparseSVM { +impl<'a> TryFrom<&'a str> for SparseSVM { type Error = Error; fn try_from(input: &'a str) -> Result { diff --git a/src/svm/kernel/poly.rs b/src/svm/kernel/poly.rs index 8a97e79..1d7ff53 100644 --- a/src/svm/kernel/poly.rs +++ b/src/svm/kernel/poly.rs @@ -27,7 +27,7 @@ impl KernelDense for Poly { sum += *a * *b; } - output[i] = crate::util::powi(f64::from(self.gamma * sum.sum() + self.coef0), self.degree); + output[i] = crate::util::powi(f64::from(self.gamma.mul_add(sum.sum(), self.coef0)), self.degree); } } } @@ -51,7 +51,7 @@ impl KernelSparse for Poly { } (Some((i_a, _)), Some((i_b, _))) if i_a < i_b => a = a_iter.next(), (Some((i_a, _)), Some((i_b, _))) if i_a > i_b => b = b_iter.next(), - _ => break crate::util::powi(f64::from(self.gamma * sum + self.coef0), self.degree), + _ => break crate::util::powi(f64::from(self.gamma.mul_add(sum, self.coef0)), self.degree), } } } @@ -66,6 +66,6 @@ impl<'a, 'b> TryFrom<&'a ModelFile<'b>> for Poly { let coef0 = raw_model.header().coef0.ok_or(Error::NoCoef0)?; let degree = raw_model.header().degree.ok_or(Error::NoDegree)?; - Ok(Self { gamma, coef0, degree }) + Ok(Self { degree, gamma, coef0 }) } } diff --git a/src/svm/kernel/rbf.rs b/src/svm/kernel/rbf.rs index 1b018ad..3bceae6 100644 --- a/src/svm/kernel/rbf.rs +++ b/src/svm/kernel/rbf.rs @@ -57,7 +57,7 @@ fn compute(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, o } else if is_x86_feature_detected!("avx") { unsafe { compute_avx(rbf, vectors, feature, output) } } else { - compute_core(rbf, vectors, feature, output) + compute_core(rbf, vectors, feature, output); } } diff --git a/src/svm/kernel/sigmoid.rs b/src/svm/kernel/sigmoid.rs index 579ad09..6508395 100644 --- a/src/svm/kernel/sigmoid.rs +++ b/src/svm/kernel/sigmoid.rs @@ -26,7 +26,7 @@ impl KernelDense for Sigmoid { sum += *a * *b; } - output[i] = (f64::from(self.gamma * sum.sum() + self.coef0)).tanh(); + output[i] = (f64::from(self.gamma.mul_add(sum.sum(), self.coef0))).tanh(); } } } @@ -50,7 +50,7 @@ impl KernelSparse for Sigmoid { } (Some((i_a, _)), Some((i_b, _))) if i_a < i_b => a = a_iter.next(), (Some((i_a, _)), Some((i_b, _))) if i_a > i_b => b = b_iter.next(), - _ => break (f64::from(self.gamma * sum + self.coef0)).tanh(), + _ => break (f64::from(self.gamma.mul_add(sum, self.coef0))).tanh(), } } } diff --git a/src/svm/mod.rs b/src/svm/mod.rs index 4fbb412..88b9427 100644 --- a/src/svm/mod.rs +++ b/src/svm/mod.rs @@ -1,13 +1,13 @@ -pub(crate) mod class; -pub(crate) mod core; -pub(crate) mod features; -pub(crate) mod kernel; -pub(crate) mod predict; +pub mod class; +pub mod core; +pub mod features; +pub mod kernel; +pub mod predict; use crate::vectors::Triangular; #[derive(Clone, Debug, Default)] -pub(crate) struct Probabilities { +pub struct Probabilities { pub(crate) a: Triangular, pub(crate) b: Triangular, diff --git a/src/svm/predict.rs b/src/svm/predict.rs index a76e32f..f9a4502 100644 --- a/src/svm/predict.rs +++ b/src/svm/predict.rs @@ -32,7 +32,7 @@ use crate::{errors::Error, svm::features::FeatureVector}; /// } /// ``` /// -/// Predicting probabilities automatically predicts the best label. In addition [`FeatureVector::probabilities`] +/// Predicting probabilities automatically predicts the best label. In addition, [`FeatureVector::probabilities`] /// will be updated accordingly. The class labels for each probablity entry can be obtained /// by the SVM's `class_label_for_index` and `class_index_for_label` methods. pub trait Predict @@ -43,6 +43,10 @@ where /// /// The problem needs to have all features set. Once this method returns, /// the [`FeatureVector::label`] will be set. + /// + /// # Errors + /// + /// Can fail if the feature vector didn't match the original SVM configuration. fn predict_value(&self, problem: &mut FeatureVector) -> Result<(), Error>; /// Predict a probability value for a problem. @@ -50,5 +54,9 @@ where /// The problem needs to have all features set. Once this method returns, /// both [`FeatureVector::label`] will be set, and all [`FeatureVector::probabilities`] will /// be available accordingly. + /// + /// # Errors + /// + /// Can fail if the model was not trained with probability estimates support. fn predict_probability(&self, problem: &mut FeatureVector) -> Result<(), Error>; } diff --git a/src/util.rs b/src/util.rs index 6a9474d..2a1f806 100644 --- a/src/util.rs +++ b/src/util.rs @@ -28,7 +28,7 @@ where /// As implemented in `libsvm`. pub fn sigmoid_predict(decision_value: f64, a: f64, b: f64) -> f64 { - let fapb = decision_value * a + b; + let fapb = decision_value.mul_add(a, b); // Citing from the original libSVM implementation: // "1-p used later; avoid catastrophic cancellation" @@ -47,7 +47,7 @@ pub fn powi(base: f64, times: u32) -> f64 { while t > 0 { if t % 2 == 1 { - ret *= tmp + ret *= tmp; }; tmp = tmp * tmp; diff --git a/src/vectors.rs b/src/vectors.rs index 9ea0304..3244db3 100644 --- a/src/vectors.rs +++ b/src/vectors.rs @@ -89,7 +89,7 @@ where } } -impl<'a, T> From<&'a Vec> for Triangular +impl From<&Vec> for Triangular where T: Copy + Sized, {