From d511e372de5a5555ea44f3f681f98f17dd498e09 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Mon, 24 Jan 2022 23:17:30 +0100 Subject: [PATCH 001/112] add support for matrix market export --- nalgebra-sparse/Cargo.toml | 2 +- nalgebra-sparse/src/io/matrix_market.rs | 133 +++++++++++++++++- nalgebra-sparse/src/io/mod.rs | 6 +- .../tests/unit_tests/matrix_market.rs | 59 +++++++- 4 files changed, 190 insertions(+), 10 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 6f7a7b4a..0dfe743f 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -37,4 +37,4 @@ nalgebra = { version="0.30", path = "../", features = ["compare"] } [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs -features = [ "proptest-support", "compare" ] \ No newline at end of file +features = [ "proptest-support", "compare" , "io"] \ No newline at end of file diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index dea284ee..924efcf8 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1,9 +1,10 @@ //! Implementation of matrix market io code. //! //! See the [website](https://math.nist.gov/MatrixMarket/formats.html) or the [paper](https://www.researchgate.net/publication/2630533_The_Matrix_Market_Exchange_Formats_Initial_Design) for more details about matrix market. -use crate::coo::CooMatrix; +use crate::CooMatrix; use crate::SparseFormatError; use crate::SparseFormatErrorKind; +use matrixcompare_core::SparseAccess; use nalgebra::Complex; use pest::iterators::Pairs; use pest::Parser; @@ -12,7 +13,8 @@ use std::convert::Infallible; use std::convert::TryFrom; use std::fmt; use std::fmt::Formatter; -use std::fs; +use std::fs::{self, File}; +use std::io::Write; use std::num::ParseIntError; use std::num::TryFromIntError; use std::path::Path; @@ -267,7 +269,7 @@ impl fmt::Display for MatrixMarketError { write!(f, "InvalidHeader,")?; } MatrixMarketErrorKind::EntryMismatch => { - write!(f, "EntryNumUnmatched,")?; + write!(f, "EntryMismatch,")?; } MatrixMarketErrorKind::TypeMismatch => { write!(f, "TypeMismatch,")?; @@ -288,7 +290,7 @@ impl fmt::Display for MatrixMarketError { write!(f, "NotLowerTriangle,")?; } MatrixMarketErrorKind::NonSquare => { - write!(f, "NotSquareMatrix,")?; + write!(f, "NonSquare,")?; } } write!(f, " message: {}", self.message) @@ -506,6 +508,10 @@ mod internal { fn negative(self) -> Result; /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. fn conjugate(self) -> Result; + /// Returns the name of SupportedMatrixMarketScalar, used when write the matrix + fn typename() -> &'static str; + /// Convert the data to string + fn to_matrixmarket_string(&self) -> String; } } @@ -557,6 +563,14 @@ macro_rules! mm_int_impl { fn negative(self) -> Result { Ok(-self) } + #[inline] + fn typename() -> &'static str { + "integer" + } + #[inline] + fn to_matrixmarket_string(&self) -> String { + self.to_string() + } } }; } @@ -602,6 +616,14 @@ macro_rules! mm_real_impl { fn negative(self) -> Result { Ok(-self) } + #[inline] + fn typename() -> &'static str { + "real" + } + #[inline] + fn to_matrixmarket_string(&self) -> String { + self.to_string() + } } }; } @@ -648,6 +670,14 @@ macro_rules! mm_complex_impl { fn negative(self) -> Result { Ok(-self) } + #[inline] + fn typename() -> &'static str { + "complex" + } + #[inline] + fn to_matrixmarket_string(&self) -> String { + self.re.to_string() + " " + &self.im.to_string() + } } }; } @@ -697,6 +727,15 @@ macro_rules! mm_pattern_impl { format!("Pattern type has no negative"), )) } + #[inline] + fn typename() -> &'static str { + "pattern" + } + #[inline] + fn to_matrixmarket_string(&self) -> String { + // pattern type will return an empty string + String::new() + } } }; } @@ -1329,3 +1368,89 @@ fn next_dense_coordinate( } } } + +/// Write a sparse matrix into Matrix Market format string. +/// +/// Our exporter only writes matrix into `coordiante` and `general` format. +/// +/// +/// Examples +/// -------- +/// ``` +/// # use matrixcompare::assert_matrix_eq; +/// use nalgebra_sparse::io::{write_to_matrix_market_str,load_coo_from_matrix_market_str}; +/// let str = r#" +/// %%matrixmarket matrix coordinate integer general +/// 5 4 2 +/// 1 1 10 +/// 2 3 5 +/// "#; +/// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); +/// let generated_matrixmarket_string = write_to_matrix_market_str(&matrix); +/// // 'generated_matrixmarket' should equal to the 'matrix' +/// let generated_matrixmarket = load_coo_from_matrix_market_str::(&generated_matrixmarket_string).unwrap(); +/// assert_matrix_eq!(matrix,generated_matrixmarket); +/// ``` +pub fn write_to_matrix_market_str>( + sparse_matrix: &S, +) -> String { + let mut matrixmarket_string = String::new(); + // write header + matrixmarket_string.push_str("%%matrixmarket matrix coordinate "); + matrixmarket_string.push_str(T::typename()); + matrixmarket_string.push_str(" general\n% matrixmarket file generated by nalgebra-sparse.\n"); + // write shape information + matrixmarket_string.push_str(&sparse_matrix.rows().to_string()); + matrixmarket_string.push(' '); + matrixmarket_string.push_str(&sparse_matrix.cols().to_string()); + matrixmarket_string.push(' '); + matrixmarket_string.push_str(&sparse_matrix.nnz().to_string()); + matrixmarket_string.push('\n'); + + for (r, c, d) in sparse_matrix.fetch_triplets() { + matrixmarket_string.push_str(&(r + 1).to_string()); + matrixmarket_string.push_str(" "); + matrixmarket_string.push_str(&(c + 1).to_string()); + matrixmarket_string.push_str(" "); + matrixmarket_string.push_str(&d.to_matrixmarket_string()); + matrixmarket_string.push_str("\n"); + } + + matrixmarket_string +} + +/// Write a sparse matrix into Matrix Market format file. +/// +/// Our exporter only writes matrix into `coordiante` and `general` format. +/// +/// +/// Errors +/// -------- +/// +/// See [MatrixMarketErrorKind] for a list of possible error conditions. +/// +/// Examples +/// -------- +/// ``` +/// use nalgebra_sparse::io::{write_to_matrix_market_file,load_coo_from_matrix_market_str}; +/// let str = r#" +/// %%matrixmarket matrix coordinate integer general +/// 5 4 2 +/// 1 1 10 +/// 2 3 5 +/// "#; +/// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); +/// let res = write_to_matrix_market_file(&matrix,"path/to/matrix.mtx"); +/// if res.is_err(){ +/// // do something +/// } +/// ``` +pub fn write_to_matrix_market_file, P: AsRef>( + matrix: &S, + path: P, +) -> Result<(), MatrixMarketError> { + let matrixmarket_string = write_to_matrix_market_str(matrix); + let mut file = File::create(path)?; + write!(file, "{}", matrixmarket_string)?; + Ok(()) +} diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index 89b21ffb..ff61fb6c 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -6,7 +6,7 @@ //! //! | Format | Import | Export | //! | ------------------------------------------------|------------|------------| -//! | [Matrix market](#matrix-market-format) | Yes | No | +//! | [Matrix market](#matrix-market-format) | Yes | Yes | //! //! [Matrix market]: https://math.nist.gov/MatrixMarket/formats.html //! @@ -32,7 +32,7 @@ //! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996). pub use self::matrix_market::{ - load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, MatrixMarketError, - MatrixMarketErrorKind, MatrixMarketScalar, + load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file, + write_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketScalar, }; mod matrix_market; diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 48ff1a78..5d109a9c 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -1,7 +1,7 @@ use matrixcompare::assert_matrix_eq; use nalgebra::dmatrix; use nalgebra::Complex; -use nalgebra_sparse::io::load_coo_from_matrix_market_str; +use nalgebra_sparse::io::{load_coo_from_matrix_market_str, write_to_matrix_market_str}; use nalgebra_sparse::CooMatrix; #[test] @@ -19,6 +19,10 @@ fn test_matrixmarket_sparse_real_general_empty() { assert_eq!(sparse_mat.nrows(), shape.0); assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.nnz(), 0); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(sparse_mat, generated_matrix); } } @@ -37,6 +41,10 @@ fn test_matrixmarket_dense_real_general_empty() { assert_eq!(sparse_mat.nrows(), shape.0); assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.nnz(), 0); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(sparse_mat, generated_matrix); } } @@ -97,6 +105,10 @@ fn test_matrixmarket_sparse_real_general() { 0.0, 0.0, 0.0, 0.0, 12.0; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -125,6 +137,10 @@ fn test_matrixmarket_sparse_int_symmetric() { -15, 0, 35, 0, 55; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -152,6 +168,10 @@ fn test_matrixmarket_sparse_complex_hermitian() { Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:33.32},Complex::{re:12.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -175,6 +195,10 @@ fn test_matrixmarket_sparse_real_skew() { -15.0, 0.0, -35.0, 0.0, 0.0; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -198,7 +222,7 @@ fn test_matrixmarket_sparse_pattern_general() { let pattern_matrix = load_coo_from_matrix_market_str::<()>(file_str).unwrap(); let nrows = pattern_matrix.nrows(); let ncols = pattern_matrix.ncols(); - let (row_idx, col_idx, val) = pattern_matrix.disassemble(); + let (row_idx, col_idx, val) = pattern_matrix.clone().disassemble(); let values = vec![1; val.len()]; let sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap(); let expected = dmatrix![ @@ -209,6 +233,17 @@ fn test_matrixmarket_sparse_pattern_general() { 0, 1, 0, 1, 1; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&pattern_matrix); + let generated_matrix = load_coo_from_matrix_market_str::<()>(&generated_matrixmarket_str).unwrap(); + + let nrows = generated_matrix.nrows(); + let ncols = generated_matrix.ncols(); + let (row_idx, col_idx, val) = generated_matrix.clone().disassemble(); + let values = vec![1; val.len()]; + let generated_sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap(); + + assert_matrix_eq!(expected, generated_sparse_mat); } #[test] @@ -240,6 +275,10 @@ fn test_matrixmarket_dense_real_general() { 4.0, 8.0, 12.0; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -269,6 +308,10 @@ fn test_matrixmarket_dense_real_symmetric() { 4.0, 7.0, 9.0, 10.0; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -298,6 +341,10 @@ fn test_matrixmarket_dense_complex_hermitian() { Complex::{re:4.0,im:4.0}, Complex::{re:7.0,im:7.0} ,Complex::{re:9.0,im:9.0} ,Complex::{re:10.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -322,6 +369,10 @@ fn test_matrixmarket_dense_int_skew() { 3, 5, 6, 0; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } #[test] @@ -342,4 +393,8 @@ fn test_matrixmarket_dense_complex_general() { Complex::{re:1.0,im:0.0},Complex::{re:1.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); + + let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); + let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); + assert_matrix_eq!(expected, generated_matrix); } From 8904c01c7b9c5d49fcfb08f27ec6ee3dbc60509a Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Thu, 3 Feb 2022 11:49:15 +0100 Subject: [PATCH 002/112] export to file directly; add a new trait for export --- nalgebra-sparse/src/io/matrix_market.rs | 115 +++++++++++++++++++++--- nalgebra-sparse/src/io/mod.rs | 3 +- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 924efcf8..d00a873d 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1,10 +1,9 @@ //! Implementation of matrix market io code. //! //! See the [website](https://math.nist.gov/MatrixMarket/formats.html) or the [paper](https://www.researchgate.net/publication/2630533_The_Matrix_Market_Exchange_Formats_Initial_Design) for more details about matrix market. -use crate::CooMatrix; use crate::SparseFormatError; use crate::SparseFormatErrorKind; -use matrixcompare_core::SparseAccess; +use crate::{CooMatrix, CscMatrix, CsrMatrix}; use nalgebra::Complex; use pest::iterators::Pairs; use pest::Parser; @@ -513,6 +512,17 @@ mod internal { /// Convert the data to string fn to_matrixmarket_string(&self) -> String; } + + pub trait SupportedMatrixMarketExport { + /// iterate over triplets + fn triplet_iter(&self) -> Box + '_>; + /// number of rows + fn nrows(&self) -> usize; + /// number of columns + fn ncols(&self) -> usize; + /// number of non-zeros + fn nnz(&self) -> usize; + } } /// A marker trait for supported matrix market scalars. @@ -754,6 +764,46 @@ mm_complex_impl!(f64); mm_pattern_impl!(()); +/// A marker trait for supported sparse matrix types. +/// +/// This is a sealed trait; it cannot be implemented by external crates. This is done in order to prevent leaking +/// some of the implementation details we currently rely on. We may relax this restriction in the future. +pub trait MatrixMarketExport: + internal::SupportedMatrixMarketExport +{ +} + +macro_rules! mm_matrix_impl { + ($T_MATRIX:ty) => { + impl MatrixMarketExport for $T_MATRIX {} + + impl internal::SupportedMatrixMarketExport + for $T_MATRIX + { + #[inline] + fn triplet_iter(&self) -> Box + '_> { + Box::new(self.triplet_iter()) + } + #[inline] + fn nrows(&self) -> usize { + self.nrows() + } + #[inline] + fn ncols(&self) -> usize { + self.ncols() + } + #[inline] + fn nnz(&self) -> usize { + self.nnz() + } + } + }; +} + +mm_matrix_impl!(CooMatrix); +mm_matrix_impl!(CsrMatrix); +mm_matrix_impl!(CscMatrix); + #[derive(Parser)] #[grammar = "io/matrix_market.pest"] struct MatrixMarketParser; @@ -1391,23 +1441,29 @@ fn next_dense_coordinate( /// let generated_matrixmarket = load_coo_from_matrix_market_str::(&generated_matrixmarket_string).unwrap(); /// assert_matrix_eq!(matrix,generated_matrixmarket); /// ``` -pub fn write_to_matrix_market_str>( +pub fn write_to_matrix_market_str>( sparse_matrix: &S, ) -> String { let mut matrixmarket_string = String::new(); + // write header matrixmarket_string.push_str("%%matrixmarket matrix coordinate "); matrixmarket_string.push_str(T::typename()); - matrixmarket_string.push_str(" general\n% matrixmarket file generated by nalgebra-sparse.\n"); + matrixmarket_string.push_str(" general\n"); + + //write comment + matrixmarket_string.push_str("% matrixmarket file generated by nalgebra-sparse.\n"); + // write shape information - matrixmarket_string.push_str(&sparse_matrix.rows().to_string()); + matrixmarket_string.push_str(&sparse_matrix.nrows().to_string()); matrixmarket_string.push(' '); - matrixmarket_string.push_str(&sparse_matrix.cols().to_string()); + matrixmarket_string.push_str(&sparse_matrix.ncols().to_string()); matrixmarket_string.push(' '); matrixmarket_string.push_str(&sparse_matrix.nnz().to_string()); matrixmarket_string.push('\n'); - for (r, c, d) in sparse_matrix.fetch_triplets() { + //write triplets + for (r, c, d) in sparse_matrix.triplet_iter() { matrixmarket_string.push_str(&(r + 1).to_string()); matrixmarket_string.push_str(" "); matrixmarket_string.push_str(&(c + 1).to_string()); @@ -1415,7 +1471,6 @@ pub fn write_to_matrix_market_str>( matrixmarket_string.push_str(&d.to_matrixmarket_string()); matrixmarket_string.push_str("\n"); } - matrixmarket_string } @@ -1442,15 +1497,49 @@ pub fn write_to_matrix_market_str>( /// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); /// let res = write_to_matrix_market_file(&matrix,"path/to/matrix.mtx"); /// if res.is_err(){ -/// // do something +/// // do something /// } /// ``` -pub fn write_to_matrix_market_file, P: AsRef>( - matrix: &S, +pub fn write_to_matrix_market_file< + T: MatrixMarketScalar, + S: MatrixMarketExport, + P: AsRef, +>( + sparse_matrix: &S, path: P, ) -> Result<(), MatrixMarketError> { - let matrixmarket_string = write_to_matrix_market_str(matrix); + // The code is basically the same as write_to_matrix_market_str, but write the matrix into file instead. let mut file = File::create(path)?; - write!(file, "{}", matrixmarket_string)?; + + // write header + write!( + file, + "%%matrixmarket matrix coordinate {} general\n", + T::typename() + )?; + + // write comments + write!(file, "% matrixmarket file generated by nalgebra-sparse.\n",)?; + + // write shape information + write!( + file, + "{} {} {}\n", + sparse_matrix.nrows(), + sparse_matrix.ncols(), + sparse_matrix.nnz() + )?; + + // write triplets + for (r, c, d) in sparse_matrix.triplet_iter() { + write!( + file, + "{} {} {}\n", + r + 1, + c + 1, + d.to_matrixmarket_string() + )?; + } + Ok(()) } diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index ff61fb6c..52fe4446 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -33,6 +33,7 @@ pub use self::matrix_market::{ load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file, - write_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketScalar, + write_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketExport, + MatrixMarketScalar, }; mod matrix_market; From 9e0dfd14de228241aea6633f3670d552c056c617 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Sun, 13 Feb 2022 21:16:16 +0100 Subject: [PATCH 003/112] use std::io::Write trait when export to matrix market --- nalgebra-sparse/src/io/matrix_market.rs | 141 ++++++++++-------------- 1 file changed, 61 insertions(+), 80 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index d00a873d..8acedd77 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -508,9 +508,9 @@ mod internal { /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. fn conjugate(self) -> Result; /// Returns the name of SupportedMatrixMarketScalar, used when write the matrix - fn typename() -> &'static str; - /// Convert the data to string - fn to_matrixmarket_string(&self) -> String; + fn typename() -> &'static [u8]; + /// Convert the data to bytes + fn to_matrixmarket_bytes(&self) -> Vec; } pub trait SupportedMatrixMarketExport { @@ -574,12 +574,12 @@ macro_rules! mm_int_impl { Ok(-self) } #[inline] - fn typename() -> &'static str { - "integer" + fn typename() -> &'static [u8] { + b"integer" } #[inline] - fn to_matrixmarket_string(&self) -> String { - self.to_string() + fn to_matrixmarket_bytes(&self) -> Vec { + self.to_string().into_bytes() } } }; @@ -627,12 +627,12 @@ macro_rules! mm_real_impl { Ok(-self) } #[inline] - fn typename() -> &'static str { - "real" + fn typename() -> &'static [u8] { + b"real" } #[inline] - fn to_matrixmarket_string(&self) -> String { - self.to_string() + fn to_matrixmarket_bytes(&self) -> Vec { + self.to_string().into_bytes() } } }; @@ -681,12 +681,12 @@ macro_rules! mm_complex_impl { Ok(-self) } #[inline] - fn typename() -> &'static str { - "complex" + fn typename() -> &'static [u8] { + b"complex" } #[inline] - fn to_matrixmarket_string(&self) -> String { - self.re.to_string() + " " + &self.im.to_string() + fn to_matrixmarket_bytes(&self) -> Vec { + (self.re.to_string() + " " + &self.im.to_string()).into_bytes() } } }; @@ -738,13 +738,12 @@ macro_rules! mm_pattern_impl { )) } #[inline] - fn typename() -> &'static str { - "pattern" + fn typename() -> &'static [u8] { + b"pattern" } #[inline] - fn to_matrixmarket_string(&self) -> String { - // pattern type will return an empty string - String::new() + fn to_matrixmarket_bytes(&self) -> Vec { + Vec::::new() } } }; @@ -1444,34 +1443,14 @@ fn next_dense_coordinate( pub fn write_to_matrix_market_str>( sparse_matrix: &S, ) -> String { - let mut matrixmarket_string = String::new(); - - // write header - matrixmarket_string.push_str("%%matrixmarket matrix coordinate "); - matrixmarket_string.push_str(T::typename()); - matrixmarket_string.push_str(" general\n"); - - //write comment - matrixmarket_string.push_str("% matrixmarket file generated by nalgebra-sparse.\n"); - - // write shape information - matrixmarket_string.push_str(&sparse_matrix.nrows().to_string()); - matrixmarket_string.push(' '); - matrixmarket_string.push_str(&sparse_matrix.ncols().to_string()); - matrixmarket_string.push(' '); - matrixmarket_string.push_str(&sparse_matrix.nnz().to_string()); - matrixmarket_string.push('\n'); - - //write triplets - for (r, c, d) in sparse_matrix.triplet_iter() { - matrixmarket_string.push_str(&(r + 1).to_string()); - matrixmarket_string.push_str(" "); - matrixmarket_string.push_str(&(c + 1).to_string()); - matrixmarket_string.push_str(" "); - matrixmarket_string.push_str(&d.to_matrixmarket_string()); - matrixmarket_string.push_str("\n"); - } - matrixmarket_string + let mut bytes = Vec::::new(); + // This will call impl Write for Vec + // The vector will grow as needed. + // So, unwrap here won't cause any issue. + write_to_matrix_market(&mut bytes, sparse_matrix).unwrap(); + // safety issue is because 'from_utf8_unchecked' does not check that the bytes passed to it are valid UTF-8. + // Since 'bytes' created here is valid UTF-8 data, so it will not cause any problem. + unsafe { String::from_utf8_unchecked(bytes) } } /// Write a sparse matrix into Matrix Market format file. @@ -1508,38 +1487,40 @@ pub fn write_to_matrix_market_file< sparse_matrix: &S, path: P, ) -> Result<(), MatrixMarketError> { - // The code is basically the same as write_to_matrix_market_str, but write the matrix into file instead. let mut file = File::create(path)?; - - // write header - write!( - file, - "%%matrixmarket matrix coordinate {} general\n", - T::typename() - )?; - - // write comments - write!(file, "% matrixmarket file generated by nalgebra-sparse.\n",)?; - - // write shape information - write!( - file, - "{} {} {}\n", - sparse_matrix.nrows(), - sparse_matrix.ncols(), - sparse_matrix.nnz() - )?; - - // write triplets - for (r, c, d) in sparse_matrix.triplet_iter() { - write!( - file, - "{} {} {}\n", - r + 1, - c + 1, - d.to_matrixmarket_string() - )?; - } - + write_to_matrix_market(&mut file, sparse_matrix)?; + Ok(()) +} + +/// low level implementation of writing sparse matrix into any [std::io::Write] object +fn write_to_matrix_market, W: Write>( + w: &mut W, + sparse_matrix: &S, +) -> Result<(), MatrixMarketError> { + // write header + w.write_all(b"%%matrixmarket matrix coordinate ")?; + w.write_all(T::typename())?; + w.write_all(b" general\n")?; + + //write comment + w.write_all(b"% matrixmarket file generated by nalgebra-sparse.\n")?; + + // write shape information + w.write_all(sparse_matrix.nrows().to_string().as_bytes())?; + w.write_all(b" ")?; + w.write_all(sparse_matrix.ncols().to_string().as_bytes())?; + w.write_all(b" ")?; + w.write_all(sparse_matrix.nnz().to_string().as_bytes())?; + w.write_all(b"\n")?; + + //write triplets + for (r, c, d) in sparse_matrix.triplet_iter() { + w.write_all((r + 1).to_string().as_bytes())?; + w.write_all(b" ")?; + w.write_all((c + 1).to_string().as_bytes())?; + w.write_all(b" ")?; + w.write_all(&d.to_matrixmarket_bytes())?; + w.write_all(b"\n")?; + } Ok(()) } From 776fef26c31217f1c5a85c138c62c0fd3afe6fe4 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 15 Feb 2022 17:38:20 -0700 Subject: [PATCH 004/112] add spmm example and change the kernel --- nalgebra-sparse/Cargo.toml | 7 +++ nalgebra-sparse/src/ops/serial/cs.rs | 65 ++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index eec7326d..70cac4c8 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -24,6 +24,7 @@ io = [ "pest", "pest_derive" ] slow-tests = [] [dependencies] +criterion = { version = "0.3", features = ["html_reports"] } nalgebra = { version="0.30", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } @@ -31,6 +32,7 @@ matrixcompare-core = { version = "0.1.0", optional = true } pest = { version = "2", optional = true } pest_derive = { version = "2", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } +itertools = "0.10" [dev-dependencies] itertools = "0.10" @@ -38,6 +40,11 @@ matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.30", path = "../", features = ["compare"] } serde_json = "1.0" +[[example]] +name = "spmm" +required-features = ["io"] +path = "example/spmm.rs" + [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs features = [ "proptest-support", "compare" ] diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index 86484053..1642571c 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -1,16 +1,19 @@ +use std::collections::HashSet; + use crate::cs::CsMatrix; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; use crate::SparseEntryMut; +use itertools::Itertools; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; use num_traits::{One, Zero}; -fn spmm_cs_unexpected_entry() -> OperationError { - OperationError::from_kind_and_message( - OperationErrorKind::InvalidPattern, - String::from("Found unexpected entry that is not present in `c`."), - ) -} +//fn spmm_cs_unexpected_entry() -> OperationError { +// OperationError::from_kind_and_message( +// OperationErrorKind::InvalidPattern, +// String::from("Found unexpected entry that is not present in `c`."), +// ) +//} /// Helper functionality for implementing CSR/CSC SPMM. /// @@ -32,28 +35,54 @@ where { for i in 0..c.pattern().major_dim() { let a_lane_i = a.get_lane(i).unwrap(); + + let some_val = Zero::zero(); + let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; + let mut scratchpad_indices: HashSet = HashSet::new(); + let mut c_lane_i = c.get_lane_mut(i).unwrap(); - for c_ij in c_lane_i.values_mut() { - *c_ij = beta.clone() * c_ij.clone(); - } + //let (indices, values) = c_lane_i.indices_and_values_mut(); + //indices + // .iter() + // .zip(values.iter()) + // .for_each(|(id, val)| scratchpad_values[*id] = beta.clone() * val.clone()); + + //for (index, c_ij) in c_lane_i.indices_and_values_mut() { + // *c_ij = beta.clone() * c_ij.clone(); + //} for (&k, a_ik) in a_lane_i.minor_indices().iter().zip(a_lane_i.values()) { let b_lane_k = b.get_lane(k).unwrap(); - let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); + //let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); let alpha_aik = alpha.clone() * a_ik.clone(); for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { // Determine the location in C to append the value - let (c_local_idx, _) = c_lane_i_cols - .iter() - .enumerate() - .find(|(_, c_col)| *c_col == j) - .ok_or_else(spmm_cs_unexpected_entry)?; + // TODO make a scratchpad and defer the accumulation into C after processing one + // full row of A. + scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); + scratchpad_indices.insert(*j); + //let (c_local_idx, _) = c_lane_i_cols + // .iter() + // .enumerate() + // .find(|(_, c_col)| *c_col == j) + // .ok_or_else(spmm_cs_unexpected_entry)?; - c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); - c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; - c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; + //c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); + //c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; + //c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; } } + // sort the indices, and then access the relevant indices (in sorted order) from values + // into C. + let sorted_indices: Vec = + Itertools::sorted(scratchpad_indices.into_iter()).collect(); + c_lane_i + .values_mut() + .iter_mut() + .zip(sorted_indices.into_iter()) + .for_each(|(output_ref, index)| { + *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[index].clone() + }); } Ok(()) From 1323b376070c272616580c770853e21498f5816e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 18 Feb 2022 11:22:43 -0700 Subject: [PATCH 005/112] prealloc everything, remove hashset, make it 4x faster --- nalgebra-sparse/Cargo.toml | 6 ++++ nalgebra-sparse/src/ops/serial/cs.rs | 49 ++++++++++------------------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 70cac4c8..781d1696 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -48,3 +48,9 @@ path = "example/spmm.rs" [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs features = [ "proptest-support", "compare" ] + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "abort" diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index 1642571c..ec394c27 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -1,10 +1,9 @@ -use std::collections::HashSet; +//use std::collections::HashSet; use crate::cs::CsMatrix; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; use crate::SparseEntryMut; -use itertools::Itertools; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; use num_traits::{One, Zero}; @@ -33,56 +32,42 @@ pub fn spmm_cs_prealloc( where T: Scalar + ClosedAdd + ClosedMul + Zero + One, { + let some_val = Zero::zero(); + let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; + let mut scratchpad_indices: Vec = vec![0; b.pattern().minor_dim()]; + let mut scratchpad_used: Vec = vec![false; b.pattern().minor_dim()]; + let mut right_end = 0usize; for i in 0..c.pattern().major_dim() { let a_lane_i = a.get_lane(i).unwrap(); - let some_val = Zero::zero(); - let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; - let mut scratchpad_indices: HashSet = HashSet::new(); - let mut c_lane_i = c.get_lane_mut(i).unwrap(); - //let (indices, values) = c_lane_i.indices_and_values_mut(); - //indices - // .iter() - // .zip(values.iter()) - // .for_each(|(id, val)| scratchpad_values[*id] = beta.clone() * val.clone()); - - //for (index, c_ij) in c_lane_i.indices_and_values_mut() { - // *c_ij = beta.clone() * c_ij.clone(); - //} for (&k, a_ik) in a_lane_i.minor_indices().iter().zip(a_lane_i.values()) { let b_lane_k = b.get_lane(k).unwrap(); - //let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); let alpha_aik = alpha.clone() * a_ik.clone(); for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { // Determine the location in C to append the value - // TODO make a scratchpad and defer the accumulation into C after processing one - // full row of A. scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); - scratchpad_indices.insert(*j); - //let (c_local_idx, _) = c_lane_i_cols - // .iter() - // .enumerate() - // .find(|(_, c_col)| *c_col == j) - // .ok_or_else(spmm_cs_unexpected_entry)?; - - //c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); - //c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; - //c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; + if !scratchpad_used[*j] { + scratchpad_indices[right_end] = *j; + right_end += 1; + scratchpad_used[*j] = true; + } } } // sort the indices, and then access the relevant indices (in sorted order) from values // into C. - let sorted_indices: Vec = - Itertools::sorted(scratchpad_indices.into_iter()).collect(); + scratchpad_indices[0..right_end].sort_unstable(); c_lane_i .values_mut() .iter_mut() - .zip(sorted_indices.into_iter()) + .zip(scratchpad_indices[0..right_end].iter()) .for_each(|(output_ref, index)| { - *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[index].clone() + *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[*index].clone(); + scratchpad_used[*index] = false; + scratchpad_values[*index] = Zero::zero(); }); + right_end = 0usize; } Ok(()) From 0cae5842620052f72efb60d179629f3759f20411 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Fri, 25 Feb 2022 17:59:50 +0100 Subject: [PATCH 006/112] use writeln! macro; replace unsafe with expect --- nalgebra-sparse/src/io/matrix_market.rs | 96 ++++++++++++++----------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 8acedd77..c644c34f 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -508,9 +508,9 @@ mod internal { /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. fn conjugate(self) -> Result; /// Returns the name of SupportedMatrixMarketScalar, used when write the matrix - fn typename() -> &'static [u8]; - /// Convert the data to bytes - fn to_matrixmarket_bytes(&self) -> Vec; + fn typename() -> &'static str; + /// Write the data self to w + fn write_matrix_market(&self, w: W) -> Result<(), std::fmt::Error>; } pub trait SupportedMatrixMarketExport { @@ -574,12 +574,15 @@ macro_rules! mm_int_impl { Ok(-self) } #[inline] - fn typename() -> &'static [u8] { - b"integer" + fn typename() -> &'static str { + "integer" } #[inline] - fn to_matrixmarket_bytes(&self) -> Vec { - self.to_string().into_bytes() + fn write_matrix_market( + &self, + mut w: W, + ) -> Result<(), std::fmt::Error> { + write!(w, "{}", self) } } }; @@ -627,12 +630,15 @@ macro_rules! mm_real_impl { Ok(-self) } #[inline] - fn typename() -> &'static [u8] { - b"real" + fn typename() -> &'static str { + "real" } #[inline] - fn to_matrixmarket_bytes(&self) -> Vec { - self.to_string().into_bytes() + fn write_matrix_market( + &self, + mut w: W, + ) -> Result<(), std::fmt::Error> { + write!(w, "{}", self) } } }; @@ -681,12 +687,15 @@ macro_rules! mm_complex_impl { Ok(-self) } #[inline] - fn typename() -> &'static [u8] { - b"complex" + fn typename() -> &'static str { + "complex" } #[inline] - fn to_matrixmarket_bytes(&self) -> Vec { - (self.re.to_string() + " " + &self.im.to_string()).into_bytes() + fn write_matrix_market( + &self, + mut w: W, + ) -> Result<(), std::fmt::Error> { + write!(w, "{} {}", self.re, self.im) } } }; @@ -738,12 +747,15 @@ macro_rules! mm_pattern_impl { )) } #[inline] - fn typename() -> &'static [u8] { - b"pattern" + fn typename() -> &'static str { + "pattern" } #[inline] - fn to_matrixmarket_bytes(&self) -> Vec { - Vec::::new() + fn write_matrix_market( + &self, + mut _w: W, + ) -> Result<(), std::fmt::Error> { + Ok(()) } } }; @@ -1448,9 +1460,9 @@ pub fn write_to_matrix_market_str( sparse_matrix: &S, path: P, -) -> Result<(), MatrixMarketError> { +) -> Result<(), std::io::Error> { let mut file = File::create(path)?; write_to_matrix_market(&mut file, sparse_matrix)?; Ok(()) @@ -1494,33 +1506,35 @@ pub fn write_to_matrix_market_file< /// low level implementation of writing sparse matrix into any [std::io::Write] object fn write_to_matrix_market, W: Write>( - w: &mut W, + mut w: W, sparse_matrix: &S, -) -> Result<(), MatrixMarketError> { +) -> Result<(), std::io::Error> { // write header - w.write_all(b"%%matrixmarket matrix coordinate ")?; - w.write_all(T::typename())?; - w.write_all(b" general\n")?; + writeln!( + w, + "%%matrixmarket matrix coordinate {} general", + T::typename() + )?; //write comment - w.write_all(b"% matrixmarket file generated by nalgebra-sparse.\n")?; + writeln!(w, "% matrixmarket file generated by nalgebra-sparse.")?; // write shape information - w.write_all(sparse_matrix.nrows().to_string().as_bytes())?; - w.write_all(b" ")?; - w.write_all(sparse_matrix.ncols().to_string().as_bytes())?; - w.write_all(b" ")?; - w.write_all(sparse_matrix.nnz().to_string().as_bytes())?; - w.write_all(b"\n")?; + writeln!( + w, + "{} {} {}", + sparse_matrix.nrows(), + sparse_matrix.ncols(), + sparse_matrix.nnz() + )?; //write triplets + let mut buffer = String::new(); for (r, c, d) in sparse_matrix.triplet_iter() { - w.write_all((r + 1).to_string().as_bytes())?; - w.write_all(b" ")?; - w.write_all((c + 1).to_string().as_bytes())?; - w.write_all(b" ")?; - w.write_all(&d.to_matrixmarket_bytes())?; - w.write_all(b"\n")?; + buffer.clear(); + d.write_matrix_market(&mut buffer) + .expect("Unexpected format error was generated when write to String"); + writeln!(w, "{} {} {} ", r + 1, c + 1, buffer)?; } Ok(()) } From 46a757fc420a977c66fc0a914e1a23a918117eec Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 26 Feb 2022 18:24:24 -0700 Subject: [PATCH 007/112] Added missing example file --- nalgebra-sparse/example/spmm.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 nalgebra-sparse/example/spmm.rs diff --git a/nalgebra-sparse/example/spmm.rs b/nalgebra-sparse/example/spmm.rs new file mode 100644 index 00000000..17ae21d0 --- /dev/null +++ b/nalgebra-sparse/example/spmm.rs @@ -0,0 +1,25 @@ +extern crate nalgebra_sparse; +use nalgebra_sparse::CsrMatrix; +use std::time::{Duration, Instant}; + +#[cfg(feature = "io")] +use nalgebra_sparse::io::load_coo_from_matrix_market_file; +fn main() { + #[cfg(feature = "io")] + { + let sparse_input_matrix = + load_coo_from_matrix_market_file::("./data/crankseg_1.mtx").unwrap(); + let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let now = Instant::now(); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let spmm_time = now.elapsed().as_millis(); + println!("SGEMM time was {}", spmm_time); + let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); + println!("sum of product is {}", sum); + } + #[cfg(not(feature = "io"))] + { + panic!("Run with IO feature only"); + } +} From e7d8a008363f6c4c98aab06816b479ae54de17a1 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 15 Feb 2022 17:38:20 -0700 Subject: [PATCH 008/112] add spmm example and change the kernel --- nalgebra-sparse/Cargo.toml | 7 +++ nalgebra-sparse/src/ops/serial/cs.rs | 65 ++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index eec7326d..70cac4c8 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -24,6 +24,7 @@ io = [ "pest", "pest_derive" ] slow-tests = [] [dependencies] +criterion = { version = "0.3", features = ["html_reports"] } nalgebra = { version="0.30", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } @@ -31,6 +32,7 @@ matrixcompare-core = { version = "0.1.0", optional = true } pest = { version = "2", optional = true } pest_derive = { version = "2", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } +itertools = "0.10" [dev-dependencies] itertools = "0.10" @@ -38,6 +40,11 @@ matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.30", path = "../", features = ["compare"] } serde_json = "1.0" +[[example]] +name = "spmm" +required-features = ["io"] +path = "example/spmm.rs" + [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs features = [ "proptest-support", "compare" ] diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index 86484053..1642571c 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -1,16 +1,19 @@ +use std::collections::HashSet; + use crate::cs::CsMatrix; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; use crate::SparseEntryMut; +use itertools::Itertools; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; use num_traits::{One, Zero}; -fn spmm_cs_unexpected_entry() -> OperationError { - OperationError::from_kind_and_message( - OperationErrorKind::InvalidPattern, - String::from("Found unexpected entry that is not present in `c`."), - ) -} +//fn spmm_cs_unexpected_entry() -> OperationError { +// OperationError::from_kind_and_message( +// OperationErrorKind::InvalidPattern, +// String::from("Found unexpected entry that is not present in `c`."), +// ) +//} /// Helper functionality for implementing CSR/CSC SPMM. /// @@ -32,28 +35,54 @@ where { for i in 0..c.pattern().major_dim() { let a_lane_i = a.get_lane(i).unwrap(); + + let some_val = Zero::zero(); + let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; + let mut scratchpad_indices: HashSet = HashSet::new(); + let mut c_lane_i = c.get_lane_mut(i).unwrap(); - for c_ij in c_lane_i.values_mut() { - *c_ij = beta.clone() * c_ij.clone(); - } + //let (indices, values) = c_lane_i.indices_and_values_mut(); + //indices + // .iter() + // .zip(values.iter()) + // .for_each(|(id, val)| scratchpad_values[*id] = beta.clone() * val.clone()); + + //for (index, c_ij) in c_lane_i.indices_and_values_mut() { + // *c_ij = beta.clone() * c_ij.clone(); + //} for (&k, a_ik) in a_lane_i.minor_indices().iter().zip(a_lane_i.values()) { let b_lane_k = b.get_lane(k).unwrap(); - let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); + //let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); let alpha_aik = alpha.clone() * a_ik.clone(); for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { // Determine the location in C to append the value - let (c_local_idx, _) = c_lane_i_cols - .iter() - .enumerate() - .find(|(_, c_col)| *c_col == j) - .ok_or_else(spmm_cs_unexpected_entry)?; + // TODO make a scratchpad and defer the accumulation into C after processing one + // full row of A. + scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); + scratchpad_indices.insert(*j); + //let (c_local_idx, _) = c_lane_i_cols + // .iter() + // .enumerate() + // .find(|(_, c_col)| *c_col == j) + // .ok_or_else(spmm_cs_unexpected_entry)?; - c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); - c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; - c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; + //c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); + //c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; + //c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; } } + // sort the indices, and then access the relevant indices (in sorted order) from values + // into C. + let sorted_indices: Vec = + Itertools::sorted(scratchpad_indices.into_iter()).collect(); + c_lane_i + .values_mut() + .iter_mut() + .zip(sorted_indices.into_iter()) + .for_each(|(output_ref, index)| { + *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[index].clone() + }); } Ok(()) From ff3d1e4e35cfe1d55e3393648767030e3b4a30c1 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 18 Feb 2022 11:22:43 -0700 Subject: [PATCH 009/112] prealloc everything, remove hashset, make it 4x faster --- nalgebra-sparse/Cargo.toml | 6 ++++ nalgebra-sparse/src/ops/serial/cs.rs | 49 ++++++++++------------------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 70cac4c8..781d1696 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -48,3 +48,9 @@ path = "example/spmm.rs" [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs features = [ "proptest-support", "compare" ] + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "abort" diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index 1642571c..ec394c27 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -1,10 +1,9 @@ -use std::collections::HashSet; +//use std::collections::HashSet; use crate::cs::CsMatrix; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; use crate::SparseEntryMut; -use itertools::Itertools; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; use num_traits::{One, Zero}; @@ -33,56 +32,42 @@ pub fn spmm_cs_prealloc( where T: Scalar + ClosedAdd + ClosedMul + Zero + One, { + let some_val = Zero::zero(); + let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; + let mut scratchpad_indices: Vec = vec![0; b.pattern().minor_dim()]; + let mut scratchpad_used: Vec = vec![false; b.pattern().minor_dim()]; + let mut right_end = 0usize; for i in 0..c.pattern().major_dim() { let a_lane_i = a.get_lane(i).unwrap(); - let some_val = Zero::zero(); - let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; - let mut scratchpad_indices: HashSet = HashSet::new(); - let mut c_lane_i = c.get_lane_mut(i).unwrap(); - //let (indices, values) = c_lane_i.indices_and_values_mut(); - //indices - // .iter() - // .zip(values.iter()) - // .for_each(|(id, val)| scratchpad_values[*id] = beta.clone() * val.clone()); - - //for (index, c_ij) in c_lane_i.indices_and_values_mut() { - // *c_ij = beta.clone() * c_ij.clone(); - //} for (&k, a_ik) in a_lane_i.minor_indices().iter().zip(a_lane_i.values()) { let b_lane_k = b.get_lane(k).unwrap(); - //let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); let alpha_aik = alpha.clone() * a_ik.clone(); for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { // Determine the location in C to append the value - // TODO make a scratchpad and defer the accumulation into C after processing one - // full row of A. scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); - scratchpad_indices.insert(*j); - //let (c_local_idx, _) = c_lane_i_cols - // .iter() - // .enumerate() - // .find(|(_, c_col)| *c_col == j) - // .ok_or_else(spmm_cs_unexpected_entry)?; - - //c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); - //c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; - //c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; + if !scratchpad_used[*j] { + scratchpad_indices[right_end] = *j; + right_end += 1; + scratchpad_used[*j] = true; + } } } // sort the indices, and then access the relevant indices (in sorted order) from values // into C. - let sorted_indices: Vec = - Itertools::sorted(scratchpad_indices.into_iter()).collect(); + scratchpad_indices[0..right_end].sort_unstable(); c_lane_i .values_mut() .iter_mut() - .zip(sorted_indices.into_iter()) + .zip(scratchpad_indices[0..right_end].iter()) .for_each(|(output_ref, index)| { - *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[index].clone() + *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[*index].clone(); + scratchpad_used[*index] = false; + scratchpad_values[*index] = Zero::zero(); }); + right_end = 0usize; } Ok(()) From 8c1186db3c259a806a4334b1ea990955426b2a6e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 26 Feb 2022 18:24:24 -0700 Subject: [PATCH 010/112] Added missing example file --- nalgebra-sparse/example/spmm.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 nalgebra-sparse/example/spmm.rs diff --git a/nalgebra-sparse/example/spmm.rs b/nalgebra-sparse/example/spmm.rs new file mode 100644 index 00000000..17ae21d0 --- /dev/null +++ b/nalgebra-sparse/example/spmm.rs @@ -0,0 +1,25 @@ +extern crate nalgebra_sparse; +use nalgebra_sparse::CsrMatrix; +use std::time::{Duration, Instant}; + +#[cfg(feature = "io")] +use nalgebra_sparse::io::load_coo_from_matrix_market_file; +fn main() { + #[cfg(feature = "io")] + { + let sparse_input_matrix = + load_coo_from_matrix_market_file::("./data/crankseg_1.mtx").unwrap(); + let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let now = Instant::now(); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let spmm_time = now.elapsed().as_millis(); + println!("SGEMM time was {}", spmm_time); + let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); + println!("sum of product is {}", sum); + } + #[cfg(not(feature = "io"))] + { + panic!("Run with IO feature only"); + } +} From 424207914bb50b6e30ab2410f2b7cd1cc437efe7 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 28 Feb 2022 21:37:13 -0700 Subject: [PATCH 011/112] run over a directory --- nalgebra-sparse/example/spmm.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/nalgebra-sparse/example/spmm.rs b/nalgebra-sparse/example/spmm.rs index 17ae21d0..aa4eb473 100644 --- a/nalgebra-sparse/example/spmm.rs +++ b/nalgebra-sparse/example/spmm.rs @@ -1,5 +1,8 @@ extern crate nalgebra_sparse; use nalgebra_sparse::CsrMatrix; +use std::fs::{self, DirEntry}; +use std::io; +use std::path::Path; use std::time::{Duration, Instant}; #[cfg(feature = "io")] @@ -7,16 +10,20 @@ use nalgebra_sparse::io::load_coo_from_matrix_market_file; fn main() { #[cfg(feature = "io")] { - let sparse_input_matrix = - load_coo_from_matrix_market_file::("./data/crankseg_1.mtx").unwrap(); - let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let now = Instant::now(); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let spmm_time = now.elapsed().as_millis(); - println!("SGEMM time was {}", spmm_time); - let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); - println!("sum of product is {}", sum); + let mut file_iter = fs::read_dir("./data").unwrap(); + for f in file_iter { + println!("Benchmark file {:?}", f); + let f = f.unwrap().path(); + let sparse_input_matrix = load_coo_from_matrix_market_file::(&f).unwrap(); + let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let now = Instant::now(); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let spmm_time = now.elapsed().as_millis(); + println!("SGEMM time was {}", spmm_time); + let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); + println!("sum of product is {}", sum); + } } #[cfg(not(feature = "io"))] { From a2422ee02bd963334b27f116655429e17a49c7d4 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 12 Mar 2022 15:03:13 -0700 Subject: [PATCH 012/112] filter out only matrix files --- nalgebra-sparse/example/spmm.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/nalgebra-sparse/example/spmm.rs b/nalgebra-sparse/example/spmm.rs index aa4eb473..2be64735 100644 --- a/nalgebra-sparse/example/spmm.rs +++ b/nalgebra-sparse/example/spmm.rs @@ -10,19 +10,23 @@ use nalgebra_sparse::io::load_coo_from_matrix_market_file; fn main() { #[cfg(feature = "io")] { - let mut file_iter = fs::read_dir("./data").unwrap(); + let mut file_iter = fs::read_dir("data").unwrap(); for f in file_iter { println!("Benchmark file {:?}", f); let f = f.unwrap().path(); - let sparse_input_matrix = load_coo_from_matrix_market_file::(&f).unwrap(); - let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let now = Instant::now(); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let spmm_time = now.elapsed().as_millis(); - println!("SGEMM time was {}", spmm_time); - let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); - println!("sum of product is {}", sum); + + if f.extension().map_or(false, |ext| ext == "mtx") { + println!("Benchmark file {:?}", f); + let sparse_input_matrix = load_coo_from_matrix_market_file::(&f).unwrap(); + let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let now = Instant::now(); + let spmm_result = &sparse_input_matrix * &sparse_input_matrix; + let spmm_time = now.elapsed().as_millis(); + println!("SGEMM time was {}", spmm_time); + let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); + println!("sum of product is {}", sum); + } } } #[cfg(not(feature = "io"))] From b19f0d7473cc9400dbcee75455225d604034bedc Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 12 Mar 2022 15:03:36 -0700 Subject: [PATCH 013/112] unnecessary index information --- nalgebra-sparse/src/ops/serial/cs.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index ec394c27..aa9d93d9 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -34,9 +34,6 @@ where { let some_val = Zero::zero(); let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; - let mut scratchpad_indices: Vec = vec![0; b.pattern().minor_dim()]; - let mut scratchpad_used: Vec = vec![false; b.pattern().minor_dim()]; - let mut right_end = 0usize; for i in 0..c.pattern().major_dim() { let a_lane_i = a.get_lane(i).unwrap(); @@ -48,26 +45,19 @@ where for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { // Determine the location in C to append the value scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); - if !scratchpad_used[*j] { - scratchpad_indices[right_end] = *j; - right_end += 1; - scratchpad_used[*j] = true; - } } } // sort the indices, and then access the relevant indices (in sorted order) from values // into C. - scratchpad_indices[0..right_end].sort_unstable(); - c_lane_i - .values_mut() + let (indices, values) = c_lane_i.indices_and_values_mut(); + + values .iter_mut() - .zip(scratchpad_indices[0..right_end].iter()) + .zip(indices) .for_each(|(output_ref, index)| { *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[*index].clone(); - scratchpad_used[*index] = false; scratchpad_values[*index] = Zero::zero(); }); - right_end = 0usize; } Ok(()) From 8e48d26767f86c745f06a27f4d955108a5d2d5f5 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Mon, 21 Mar 2022 12:11:49 +0100 Subject: [PATCH 014/112] add more tests; use bufwritter; fix typo; use temp_dir to test with files --- nalgebra-sparse/src/io/matrix_market.rs | 46 ++-- nalgebra-sparse/src/io/mod.rs | 6 +- .../tests/unit_tests/matrix_market.rs | 250 +++++++++++------- 3 files changed, 187 insertions(+), 115 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index c644c34f..5143447c 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -13,7 +13,7 @@ use std::convert::TryFrom; use std::fmt; use std::fmt::Formatter; use std::fs::{self, File}; -use std::io::Write; +use std::io::{BufWriter, Write}; use std::num::ParseIntError; use std::num::TryFromIntError; use std::path::Path; @@ -1432,25 +1432,26 @@ fn next_dense_coordinate( /// Write a sparse matrix into Matrix Market format string. /// -/// Our exporter only writes matrix into `coordiante` and `general` format. +/// The exporter only writes matrix into `coordinate` and `general` format. /// /// /// Examples /// -------- /// ``` -/// # use matrixcompare::assert_matrix_eq; -/// use nalgebra_sparse::io::{write_to_matrix_market_str,load_coo_from_matrix_market_str}; -/// let str = r#" -/// %%matrixmarket matrix coordinate integer general +/// # use nalgebra_sparse::CooMatrix; +/// use nalgebra_sparse::io::{write_to_matrix_market_str}; +/// let expected_str = r#"%%matrixmarket matrix coordinate integer general +/// % matrixmarket file generated by nalgebra-sparse. /// 5 4 2 /// 1 1 10 /// 2 3 5 /// "#; -/// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); -/// let generated_matrixmarket_string = write_to_matrix_market_str(&matrix); -/// // 'generated_matrixmarket' should equal to the 'matrix' -/// let generated_matrixmarket = load_coo_from_matrix_market_str::(&generated_matrixmarket_string).unwrap(); -/// assert_matrix_eq!(matrix,generated_matrixmarket); +/// let row_indices = vec![0,1]; +/// let col_indices = vec![0,2]; +/// let values = vec![10,5]; +/// let matrix = CooMatrix::try_from_triplets(5,4,row_indices,col_indices,values).unwrap(); +/// let generated_matrixmarket_str = write_to_matrix_market_str(&matrix); +/// assert_eq!(expected_str,generated_matrixmarket_str); /// ``` pub fn write_to_matrix_market_str>( sparse_matrix: &S, @@ -1467,7 +1468,7 @@ pub fn write_to_matrix_market_str(&str).unwrap(); -/// let res = write_to_matrix_market_file(&matrix,"path/to/matrix.mtx"); -/// if res.is_err(){ -/// // do something -/// } +/// // create a temporary file 'temp.mtx' +/// let mut tempdir = std::env::temp_dir(); +/// tempdir.push("temp.mtx"); +/// write_to_matrix_market_file(&matrix,tempdir).unwrap(); /// ``` pub fn write_to_matrix_market_file< T: MatrixMarketScalar, @@ -1499,13 +1500,18 @@ pub fn write_to_matrix_market_file< sparse_matrix: &S, path: P, ) -> Result<(), std::io::Error> { - let mut file = File::create(path)?; + let file = File::create(path)?; + let mut file = BufWriter::new(file); write_to_matrix_market(&mut file, sparse_matrix)?; + // Quote from BufWriter doc. + // > It is critical to call flush before BufWriter is dropped. Though dropping will attempt to flush the contents of the buffer, any errors that happen in the process of dropping will be ignored. Calling flush ensures that the buffer is empty and thus dropping will not even attempt file operations. + file.flush() + .expect("Unexpected error when flushing the buffer data to File"); Ok(()) } /// low level implementation of writing sparse matrix into any [std::io::Write] object -fn write_to_matrix_market, W: Write>( +pub fn write_to_matrix_market, W: Write>( mut w: W, sparse_matrix: &S, ) -> Result<(), std::io::Error> { @@ -1534,7 +1540,7 @@ fn write_to_matrix_market, W: Wr buffer.clear(); d.write_matrix_market(&mut buffer) .expect("Unexpected format error was generated when write to String"); - writeln!(w, "{} {} {} ", r + 1, c + 1, buffer)?; + writeln!(w, "{} {} {}", r + 1, c + 1, buffer)?; } Ok(()) } diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index 52fe4446..f257d7e2 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -32,8 +32,8 @@ //! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996). pub use self::matrix_market::{ - load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file, - write_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketExport, - MatrixMarketScalar, + load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market, + write_to_matrix_market_file, write_to_matrix_market_str, MatrixMarketError, + MatrixMarketErrorKind, MatrixMarketExport, MatrixMarketScalar, }; mod matrix_market; diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 5d109a9c..7bf6891c 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -1,12 +1,20 @@ use matrixcompare::assert_matrix_eq; -use nalgebra::dmatrix; +use nalgebra::matrix; use nalgebra::Complex; -use nalgebra_sparse::io::{load_coo_from_matrix_market_str, write_to_matrix_market_str}; +use nalgebra_sparse::io::{ + load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file, + write_to_matrix_market_str, +}; +use nalgebra_sparse::proptest::coo_no_duplicates; use nalgebra_sparse::CooMatrix; +use proptest::prelude::*; + +type C64 = Complex; +type C32 = Complex; #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_real_general_empty() { +fn test_matrixmarket_load_sparse_real_general_empty() { // Test several valid zero-shapes of a sparse matrix let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let strings: Vec = shapes @@ -19,16 +27,12 @@ fn test_matrixmarket_sparse_real_general_empty() { assert_eq!(sparse_mat.nrows(), shape.0); assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.nnz(), 0); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(sparse_mat, generated_matrix); } } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_real_general_empty() { +fn test_matrixmarket_load_dense_real_general_empty() { // Test several valid zero-shapes of a dense matrix let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let strings: Vec = shapes @@ -41,16 +45,12 @@ fn test_matrixmarket_dense_real_general_empty() { assert_eq!(sparse_mat.nrows(), shape.0); assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.nnz(), 0); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(sparse_mat, generated_matrix); } } #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_real_general() { +fn test_matrixmarket_load_sparse_real_general() { let file_str = r#" %%MatrixMarket matrix CoOrdinate real general % This is also an example of free-format features. @@ -97,7 +97,7 @@ fn test_matrixmarket_sparse_real_general() { 5 5 1.200e+01 "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 1.0, 0.0, 0.0, 6.0, 0.0; 0.0, 10.5, 0.0, 0.0, 0.0; 0.0, 0.0, 0.015, 0.0, 0.0; @@ -105,15 +105,11 @@ fn test_matrixmarket_sparse_real_general() { 0.0, 0.0, 0.0, 0.0, 12.0; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_int_symmetric() { +fn test_matrixmarket_load_sparse_int_symmetric() { let file_str = r#" %%MatrixMarket matrix coordinate integer symmetric % @@ -129,7 +125,7 @@ fn test_matrixmarket_sparse_int_symmetric() { 5 5 55 "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 11, 0, 0, 0, -15; 0, 22, 23, 24, 0; 0, 23, 33, 0, 35; @@ -137,15 +133,11 @@ fn test_matrixmarket_sparse_int_symmetric() { -15, 0, 35, 0, 55; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_complex_hermitian() { +fn test_matrixmarket_load_sparse_complex_hermitian() { let file_str = r#" %%MatrixMarket matrix coordinate complex hermitian % @@ -160,23 +152,19 @@ fn test_matrixmarket_sparse_complex_hermitian() { "#; let sparse_mat = load_coo_from_matrix_market_str::>(file_str).unwrap(); - let expected = dmatrix![ - Complex::{re:1.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0},Complex::{re:0.0,im:0.0}; - Complex::{re:0.0,im:0.0}, Complex::{re:10.5,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:250.5,im:-22.22},Complex::{re:0.0,im:0.0}; - Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.015,im:0.0}, Complex::{re:0.0,im:0.0},Complex::{re:0.0,im:0.0}; - Complex::{re:0.0,im:0.0}, Complex::{re:250.5,im:22.22}, Complex::{re:0.0,im:0.0}, Complex::{re:-280.0,im:0.0},Complex::{re:0.0,im:-33.32}; - Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:0.0}, Complex::{re:0.0,im:33.32},Complex::{re:12.0,im:0.0}; + let expected = matrix![ + C64{re:1.0,im:0.0}, C64{re:0.0,im:0.0}, C64{re:0.0,im:0.0}, C64{re:0.0,im:0.0},C64{re:0.0,im:0.0}; + C64{re:0.0,im:0.0}, C64{re:10.5,im:0.0}, C64{re:0.0,im:0.0}, C64{re:250.5,im:-22.22},C64{re:0.0,im:0.0}; + C64{re:0.0,im:0.0}, C64{re:0.0,im:0.0}, C64{re:0.015,im:0.0}, C64{re:0.0,im:0.0},C64{re:0.0,im:0.0}; + C64{re:0.0,im:0.0}, C64{re:250.5,im:22.22}, C64{re:0.0,im:0.0}, C64{re:-280.0,im:0.0},C64{re:0.0,im:-33.32}; + C64{re:0.0,im:0.0}, C64{re:0.0,im:0.0}, C64{re:0.0,im:0.0}, C64{re:0.0,im:33.32},C64{re:12.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_real_skew() { +fn test_matrixmarket_load_sparse_real_skew() { let file_str = r#" %%MatrixMarket matrix coordinate real skew-symmetric % @@ -187,7 +175,7 @@ fn test_matrixmarket_sparse_real_skew() { 5 3 -35.0 "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 0.0, 0.0, 0.0, 0.0, 15.0; 0.0, 0.0, 23.0, 24.0, 0.0; 0.0, -23.0, 0.0, 0.0, 35.0; @@ -195,15 +183,11 @@ fn test_matrixmarket_sparse_real_skew() { -15.0, 0.0, -35.0, 0.0, 0.0; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_sparse_pattern_general() { +fn test_matrixmarket_load_sparse_pattern_general() { let file_str = r#" %%MatrixMarket matrix coordinate pattern general % @@ -225,7 +209,7 @@ fn test_matrixmarket_sparse_pattern_general() { let (row_idx, col_idx, val) = pattern_matrix.clone().disassemble(); let values = vec![1; val.len()]; let sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 1, 0, 0, 0, 1; 0, 0, 1, 1, 0; 0, 1, 0, 0, 1; @@ -233,22 +217,11 @@ fn test_matrixmarket_sparse_pattern_general() { 0, 1, 0, 1, 1; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&pattern_matrix); - let generated_matrix = load_coo_from_matrix_market_str::<()>(&generated_matrixmarket_str).unwrap(); - - let nrows = generated_matrix.nrows(); - let ncols = generated_matrix.ncols(); - let (row_idx, col_idx, val) = generated_matrix.clone().disassemble(); - let values = vec![1; val.len()]; - let generated_sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap(); - - assert_matrix_eq!(expected, generated_sparse_mat); } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_real_general() { +fn test_matrixmarket_load_dense_real_general() { let file_str = r#" %%MatrixMarket matrix array real general % @@ -268,22 +241,18 @@ fn test_matrixmarket_dense_real_general() { "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 1.0, 5.0, 9.0; 2.0, 6.0, 10.0; 3.0, 7.0, 11.0; 4.0, 8.0, 12.0; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_real_symmetric() { +fn test_matrixmarket_load_dense_real_symmetric() { let file_str = r#" %%MatrixMarket matrix array real symmetric % @@ -301,22 +270,18 @@ fn test_matrixmarket_dense_real_symmetric() { "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 1.0, 2.0, 3.0, 4.0; 2.0, 5.0, 6.0, 7.0; 3.0, 6.0, 8.0, 9.0; 4.0, 7.0, 9.0, 10.0; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_complex_hermitian() { +fn test_matrixmarket_load_dense_complex_hermitian() { let file_str = r#" %%MatrixMarket matrix array complex hermitian % @@ -333,23 +298,19 @@ fn test_matrixmarket_dense_complex_hermitian() { 10.0 0.0 "#; - let sparse_mat = load_coo_from_matrix_market_str::>(file_str).unwrap(); - let expected = dmatrix![ - Complex::{re:1.0,im:0.0}, Complex::{re:2.0,im:-2.0} ,Complex::{re:3.0,im:-3.0} ,Complex::{re:4.0,im:-4.0}; - Complex::{re:2.0,im:2.0}, Complex::{re:5.0,im:0.0} ,Complex::{re:6.0,im:-6.0} ,Complex::{re:7.0,im:-7.0}; - Complex::{re:3.0,im:3.0}, Complex::{re:6.0,im:6.0} ,Complex::{re:8.0,im:0.0} ,Complex::{re:9.0,im:-9.0}; - Complex::{re:4.0,im:4.0}, Complex::{re:7.0,im:7.0} ,Complex::{re:9.0,im:9.0} ,Complex::{re:10.0,im:0.0}; + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); + let expected = matrix![ + C64{re:1.0,im:0.0}, C64{re:2.0,im:-2.0} ,C64{re:3.0,im:-3.0} ,C64{re:4.0,im:-4.0}; + C64{re:2.0,im:2.0}, C64{re:5.0,im:0.0} ,C64{re:6.0,im:-6.0} ,C64{re:7.0,im:-7.0}; + C64{re:3.0,im:3.0}, C64{re:6.0,im:6.0} ,C64{re:8.0,im:0.0} ,C64{re:9.0,im:-9.0}; + C64{re:4.0,im:4.0}, C64{re:7.0,im:7.0} ,C64{re:9.0,im:9.0} ,C64{re:10.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_int_skew() { +fn test_matrixmarket_load_dense_int_skew() { let file_str = r#" %%MatrixMarket matrix array integer skew-symmetric % @@ -362,22 +323,18 @@ fn test_matrixmarket_dense_int_skew() { 6 "#; let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); - let expected = dmatrix![ + let expected = matrix![ 0,-1,-2,-3; 1, 0,-4,-5; 2, 4, 0,-6; 3, 5, 6, 0; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); } #[test] #[rustfmt::skip] -fn test_matrixmarket_dense_complex_general() { +fn test_matrixmarket_load_dense_complex_general() { let file_str = r#" %%MatrixMarket matrix array complex general % @@ -387,14 +344,123 @@ fn test_matrixmarket_dense_complex_general() { 1 0 1 0 "#; - let sparse_mat = load_coo_from_matrix_market_str::>(file_str).unwrap(); - let expected = dmatrix![ - Complex::{re:1.0,im:0.0},Complex::{re:1.0,im:0.0}; - Complex::{re:1.0,im:0.0},Complex::{re:1.0,im:0.0}; + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); + let expected = matrix![ + C32{re:1.0,im:0.0},C32{re:1.0,im:0.0}; + C32{re:1.0,im:0.0},C32{re:1.0,im:0.0}; ]; assert_matrix_eq!(sparse_mat, expected); - - let generated_matrixmarket_str = write_to_matrix_market_str(&sparse_mat); - let generated_matrix = load_coo_from_matrix_market_str::>(&generated_matrixmarket_str).unwrap(); - assert_matrix_eq!(expected, generated_matrix); +} + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_write_real(){ + let dense_matrix = matrix![ + 1.0, 2.0, 3.0; + 2.0, 0.0, 3.0; + ]; + let row_indices = vec![0,1,0,0,1]; + let col_indices = vec![0,0,1,2,2]; + let values = vec![1.0,2.0,2.0,3.0,3.0]; + let coo_matrix = CooMatrix::try_from_triplets(2, 3, row_indices, col_indices, values).unwrap(); + assert_matrix_eq!(dense_matrix,coo_matrix); + let expected = r#"%%matrixmarket matrix coordinate real general +% matrixmarket file generated by nalgebra-sparse. +2 3 5 +1 1 1 +2 1 2 +1 2 2 +1 3 3 +2 3 3 +"#; + let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + assert_eq!(matrixmarket_str,expected); +} + +#[test] +fn test_matrixmarket_write_int() { + let dense_matrix = matrix![ + 1,2,3; + 2,0,3; + ]; + let row_indices = vec![0, 1, 0, 0, 1]; + let col_indices = vec![0, 0, 1, 2, 2]; + let values = vec![1, 2, 2, 3, 3]; + let coo_matrix = CooMatrix::try_from_triplets(2, 3, row_indices, col_indices, values).unwrap(); + assert_matrix_eq!(dense_matrix, coo_matrix); + let expected = r#"%%matrixmarket matrix coordinate integer general +% matrixmarket file generated by nalgebra-sparse. +2 3 5 +1 1 1 +2 1 2 +1 2 2 +1 3 3 +2 3 3 +"#; + let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + assert_eq!(matrixmarket_str, expected); +} + +#[test] +fn test_matrixmarket_write_pattern() { + let row_indices = vec![0, 1, 0, 0, 1]; + let col_indices = vec![0, 0, 1, 2, 2]; + let values = vec![(), (), (), (), ()]; + let coo_matrix = CooMatrix::try_from_triplets(2, 3, row_indices, col_indices, values).unwrap(); + let expected = r#"%%matrixmarket matrix coordinate pattern general +% matrixmarket file generated by nalgebra-sparse. +2 3 5 +1 1 +2 1 +1 2 +1 3 +2 3 +"#; + let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + assert_eq!(matrixmarket_str, expected); +} + +#[test] +fn test_matrixmarket_write_complex() { + let row_indices = vec![0, 1, 0, 0, 1]; + let col_indices = vec![0, 0, 1, 2, 2]; + let values = vec![ + C64 { re: 1.0, im: 2.0 }, + C64 { re: 2.0, im: 3.0 }, + C64 { re: 3.0, im: 4.0 }, + C64 { re: 4.0, im: 5.0 }, + C64 { re: 5.0, im: 6.0 }, + ]; + let coo_matrix = CooMatrix::try_from_triplets(2, 3, row_indices, col_indices, values).unwrap(); + let expected = r#"%%matrixmarket matrix coordinate complex general +% matrixmarket file generated by nalgebra-sparse. +2 3 5 +1 1 1 2 +2 1 2 3 +1 2 3 4 +1 3 4 5 +2 3 5 6 +"#; + let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + assert_eq!(matrixmarket_str, expected); +} + +proptest! { + #[test] + fn coo_matrix_market_roundtrip_str(coo in coo_no_duplicates(-10 ..= 10, 0 ..= 10, 0..= 10, 100)) { + let generated_matrixmarket_string = write_to_matrix_market_str(&coo); + let generated_matrix = load_coo_from_matrix_market_str(&generated_matrixmarket_string).unwrap(); + assert_matrix_eq!(generated_matrix, coo); + } +} + +proptest! { + #[test] + fn coo_matrix_market_roundtrip_file(coo in coo_no_duplicates(-10 ..= 10, 0 ..= 10, 0..= 10, 100)) { + let mut tempdir = std::env::temp_dir(); + tempdir.push("temp.mtx"); + write_to_matrix_market_file(&coo,&tempdir).unwrap(); + let generated_matrix = load_coo_from_matrix_market_file(tempdir).unwrap(); + assert_matrix_eq!(generated_matrix, coo); + } } From c6f832d1d801d69bf8d978740259524730d59db6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Mar 2022 16:55:46 -0600 Subject: [PATCH 015/112] put back checked kernels and refactor upper layer --- nalgebra-sparse/src/ops/serial/cs.rs | 68 +++++++++++++++---- nalgebra-sparse/src/ops/serial/csc.rs | 94 +++++++++++++++++++------- nalgebra-sparse/src/ops/serial/csr.rs | 95 ++++++++++++++++++++------- 3 files changed, 196 insertions(+), 61 deletions(-) diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index a05e4fdf..f3a14924 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -1,5 +1,3 @@ -//use std::collections::HashSet; - use crate::cs::CsMatrix; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; @@ -7,12 +5,12 @@ use crate::SparseEntryMut; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; use num_traits::{One, Zero}; -//fn spmm_cs_unexpected_entry() -> OperationError { -// OperationError::from_kind_and_message( -// OperationErrorKind::InvalidPattern, -// String::from("Found unexpected entry that is not present in `c`."), -// ) -//} +fn spmm_cs_unexpected_entry() -> OperationError { + OperationError::from_kind_and_message( + OperationErrorKind::InvalidPattern, + String::from("Found unexpected entry that is not present in `c`."), + ) +} /// Helper functionality for implementing CSR/CSC SPMM. /// @@ -22,7 +20,7 @@ use num_traits::{One, Zero}; /// reversed (since transpose(AB) = transpose(B) * transpose(A) and CSC(A) = transpose(CSR(A)). /// /// We assume here that the matrices have already been verified to be dimensionally compatible. -pub fn spmm_cs_prealloc( +pub fn spmm_cs_prealloc_unchecked( beta: T, c: &mut CsMatrix, alpha: T, @@ -43,8 +41,10 @@ where let b_lane_k = b.get_lane(k).unwrap(); let alpha_aik = alpha.clone() * a_ik.clone(); for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { - // Determine the location in C to append the value - scratchpad_values[*j] += alpha_aik.clone() * b_kj.clone(); + // use a dense scatter vector to accumulate non-zeros quickly + unsafe { + *scratchpad_values.get_unchecked_mut(*j) += alpha_aik.clone() * b_kj.clone(); + } } } @@ -53,15 +53,55 @@ where values .iter_mut() .zip(indices) - .for_each(|(output_ref, index)| { - *output_ref = beta.clone() * output_ref.clone() + scratchpad_values[*index].clone(); - scratchpad_values[*index] = Zero::zero(); + .for_each(|(output_ref, index)| unsafe { + *output_ref = beta.clone() * output_ref.clone() + + scratchpad_values.get_unchecked(*index).clone(); + *scratchpad_values.get_unchecked_mut(*index) = Zero::zero(); }); } Ok(()) } +pub fn spmm_cs_prealloc_checked( + beta: T, + c: &mut CsMatrix, + alpha: T, + a: &CsMatrix, + b: &CsMatrix, +) -> Result<(), OperationError> +where + T: Scalar + ClosedAdd + ClosedMul + Zero + One, +{ + for i in 0..c.pattern().major_dim() { + let a_lane_i = a.get_lane(i).unwrap(); + let mut c_lane_i = c.get_lane_mut(i).unwrap(); + for c_ij in c_lane_i.values_mut() { + *c_ij = beta.clone() * c_ij.clone(); + } + + for (&k, a_ik) in a_lane_i.minor_indices().iter().zip(a_lane_i.values()) { + let b_lane_k = b.get_lane(k).unwrap(); + let (mut c_lane_i_cols, mut c_lane_i_values) = c_lane_i.indices_and_values_mut(); + let alpha_aik = alpha.clone() * a_ik.clone(); + for (j, b_kj) in b_lane_k.minor_indices().iter().zip(b_lane_k.values()) { + // Determine the location in C to append the value + let (c_local_idx, _) = c_lane_i_cols + .iter() + .enumerate() + .find(|(_, c_col)| *c_col == j) + .ok_or_else(spmm_cs_unexpected_entry)?; + + c_lane_i_values[c_local_idx] += alpha_aik.clone() * b_kj.clone(); + c_lane_i_cols = &c_lane_i_cols[c_local_idx..]; + c_lane_i_values = &mut c_lane_i_values[c_local_idx..]; + } + } + } + + Ok(()) +} + fn spadd_cs_unexpected_entry() -> OperationError { OperationError::from_kind_and_message( OperationErrorKind::InvalidPattern, diff --git a/nalgebra-sparse/src/ops/serial/csc.rs b/nalgebra-sparse/src/ops/serial/csc.rs index e5c9ae4e..42d27079 100644 --- a/nalgebra-sparse/src/ops/serial/csc.rs +++ b/nalgebra-sparse/src/ops/serial/csc.rs @@ -1,5 +1,7 @@ use crate::csc::CscMatrix; -use crate::ops::serial::cs::{spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc}; +use crate::ops::serial::cs::{ + spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc_checked, spmm_cs_prealloc_unchecked, +}; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, RealField, Scalar}; @@ -71,7 +73,7 @@ where /// # Panics /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub fn spmm_csc_prealloc( +pub fn spmm_csc_prealloc_checked( beta: T, c: &mut CscMatrix, alpha: T, @@ -83,35 +85,81 @@ where { assert_compatible_spmm_dims!(c, a, b); - use Op::{NoOp, Transpose}; + use Op::NoOp; match (&a, &b) { (NoOp(ref a), NoOp(ref b)) => { // Note: We have to reverse the order for CSC matrices - spmm_cs_prealloc(beta, &mut c.cs, alpha, &b.cs, &a.cs) - } - _ => { - // Currently we handle transposition by explicitly precomputing transposed matrices - // and calling the operation again without transposition - let a_ref: &CscMatrix = a.inner_ref(); - let b_ref: &CscMatrix = b.inner_ref(); - let (a, b) = { - use Cow::*; - match (&a, &b) { - (NoOp(_), NoOp(_)) => unreachable!(), - (Transpose(ref a), NoOp(_)) => (Owned(a.transpose()), Borrowed(b_ref)), - (NoOp(_), Transpose(ref b)) => (Borrowed(a_ref), Owned(b.transpose())), - (Transpose(ref a), Transpose(ref b)) => { - (Owned(a.transpose()), Owned(b.transpose())) - } - } - }; - - spmm_csc_prealloc(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) + spmm_cs_prealloc_checked(beta, &mut c.cs, alpha, &b.cs, &a.cs) } + _ => do_transposes(beta, c, alpha, a, b, spmm_csc_prealloc_checked), } } +/// Faster sparse-sparse matrix multiplication, `C <- beta * C + alpha * op(A) * op(B)`. +/// This will not return an error even if the patterns don't match. +/// Should be used for situations where pattern creation immediately preceeds multiplication. +/// +/// Panics if the dimensions of the matrices involved are not compatible with the expression. +pub(crate) fn spmm_csc_prealloc_unchecked( + beta: T, + c: &mut CscMatrix, + alpha: T, + a: Op<&CscMatrix>, + b: Op<&CscMatrix>, +) -> Result<(), OperationError> +where + T: Scalar + ClosedAdd + ClosedMul + Zero + One, +{ + assert_compatible_spmm_dims!(c, a, b); + + use Op::NoOp; + + match (&a, &b) { + (NoOp(ref a), NoOp(ref b)) => { + // Note: We have to reverse the order for CSC matrices + spmm_cs_prealloc_unchecked(beta, &mut c.cs, alpha, &b.cs, &a.cs) + } + _ => do_transposes(beta, c, alpha, a, b, spmm_csc_prealloc_unchecked), + } +} + +fn do_transposes( + beta: T, + c: &mut CscMatrix, + alpha: T, + a: Op<&CscMatrix>, + b: Op<&CscMatrix>, + caller: F, +) -> Result<(), OperationError> +where + T: Scalar + ClosedAdd + ClosedMul + Zero + One, + F: Fn( + T, + &mut CscMatrix, + T, + Op<&CscMatrix>, + Op<&CscMatrix>, + ) -> Result<(), OperationError>, +{ + use Op::{NoOp, Transpose}; + + // Currently we handle transposition by explicitly precomputing transposed matrices + // and calling the operation again without transposition + let a_ref: &CscMatrix = a.inner_ref(); + let b_ref: &CscMatrix = b.inner_ref(); + let (a, b) = { + use Cow::*; + match (&a, &b) { + (NoOp(_), NoOp(_)) => unreachable!(), + (Transpose(ref a), NoOp(_)) => (Owned(a.transpose()), Borrowed(b_ref)), + (NoOp(_), Transpose(ref b)) => (Borrowed(a_ref), Owned(b.transpose())), + (Transpose(ref a), Transpose(ref b)) => (Owned(a.transpose()), Owned(b.transpose())), + } + }; + caller(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) +} + /// Solve the lower triangular system `op(L) X = B`. /// /// Only the lower triangular part of L is read, and the result is stored in B. diff --git a/nalgebra-sparse/src/ops/serial/csr.rs b/nalgebra-sparse/src/ops/serial/csr.rs index fa317bbf..967c02f2 100644 --- a/nalgebra-sparse/src/ops/serial/csr.rs +++ b/nalgebra-sparse/src/ops/serial/csr.rs @@ -1,5 +1,7 @@ use crate::csr::CsrMatrix; -use crate::ops::serial::cs::{spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc}; +use crate::ops::serial::cs::{ + spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc_checked, spmm_cs_prealloc_unchecked, +}; use crate::ops::serial::OperationError; use crate::ops::Op; use nalgebra::{ClosedAdd, ClosedMul, DMatrixSlice, DMatrixSliceMut, Scalar}; @@ -65,7 +67,7 @@ where /// # Panics /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub fn spmm_csr_prealloc( +pub fn spmm_csr_prealloc_checked( beta: T, c: &mut CsrMatrix, alpha: T, @@ -77,30 +79,75 @@ where { assert_compatible_spmm_dims!(c, a, b); - use Op::{NoOp, Transpose}; + use Op::NoOp; match (&a, &b) { - (NoOp(ref a), NoOp(ref b)) => spmm_cs_prealloc(beta, &mut c.cs, alpha, &a.cs, &b.cs), - _ => { - // Currently we handle transposition by explicitly precomputing transposed matrices - // and calling the operation again without transposition - // TODO: At least use workspaces to allow control of allocations. Maybe - // consider implementing certain patterns (like A^T * B) explicitly - let a_ref: &CsrMatrix = a.inner_ref(); - let b_ref: &CsrMatrix = b.inner_ref(); - let (a, b) = { - use Cow::*; - match (&a, &b) { - (NoOp(_), NoOp(_)) => unreachable!(), - (Transpose(ref a), NoOp(_)) => (Owned(a.transpose()), Borrowed(b_ref)), - (NoOp(_), Transpose(ref b)) => (Borrowed(a_ref), Owned(b.transpose())), - (Transpose(ref a), Transpose(ref b)) => { - (Owned(a.transpose()), Owned(b.transpose())) - } - } - }; - - spmm_csr_prealloc(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) + (NoOp(ref a), NoOp(ref b)) => { + spmm_cs_prealloc_checked(beta, &mut c.cs, alpha, &a.cs, &b.cs) } + _ => do_transposes(beta, c, alpha, a, b, spmm_csr_prealloc_checked), } } + +/// Faster sparse-sparse matrix multiplication, `C <- beta * C + alpha * op(A) * op(B)`. +/// This will not return an error even if the patterns don't match. +/// Should be used for situations where pattern creation immediately preceeds multiplication. +/// +/// Panics if the dimensions of the matrices involved are not compatible with the expression. +pub(crate) fn spmm_csr_prealloc_unchecked( + beta: T, + c: &mut CsrMatrix, + alpha: T, + a: Op<&CsrMatrix>, + b: Op<&CsrMatrix>, +) -> Result<(), OperationError> +where + T: Scalar + ClosedAdd + ClosedMul + Zero + One, +{ + assert_compatible_spmm_dims!(c, a, b); + + use Op::NoOp; + + match (&a, &b) { + (NoOp(ref a), NoOp(ref b)) => { + spmm_cs_prealloc_unchecked(beta, &mut c.cs, alpha, &a.cs, &b.cs) + } + _ => do_transposes(beta, c, alpha, a, b, spmm_csr_prealloc_unchecked), + } +} + +fn do_transposes( + beta: T, + c: &mut CsrMatrix, + alpha: T, + a: Op<&CsrMatrix>, + b: Op<&CsrMatrix>, + caller: F, +) -> Result<(), OperationError> +where + T: Scalar + ClosedAdd + ClosedMul + Zero + One, + F: Fn( + T, + &mut CsrMatrix, + T, + Op<&CsrMatrix>, + Op<&CsrMatrix>, + ) -> Result<(), OperationError>, +{ + use Op::{NoOp, Transpose}; + + // Currently we handle transposition by explicitly precomputing transposed matrices + // and calling the operation again without transposition + let a_ref: &CsrMatrix = a.inner_ref(); + let b_ref: &CsrMatrix = b.inner_ref(); + let (a, b) = { + use Cow::*; + match (&a, &b) { + (NoOp(_), NoOp(_)) => unreachable!(), + (Transpose(ref a), NoOp(_)) => (Owned(a.transpose()), Borrowed(b_ref)), + (NoOp(_), Transpose(ref b)) => (Borrowed(a_ref), Owned(b.transpose())), + (Transpose(ref a), Transpose(ref b)) => (Owned(a.transpose()), Owned(b.transpose())), + } + }; + caller(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) +} From f795f0f8721ec28e692c78f9850597e7f44fe504 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Mar 2022 16:56:22 -0600 Subject: [PATCH 016/112] tests don't need to test the unchecked kernel separately --- nalgebra-sparse/src/ops/mod.rs | 4 ++-- nalgebra-sparse/tests/unit_tests/ops.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nalgebra-sparse/src/ops/mod.rs b/nalgebra-sparse/src/ops/mod.rs index 9a73148c..6fedfb6c 100644 --- a/nalgebra-sparse/src/ops/mod.rs +++ b/nalgebra-sparse/src/ops/mod.rs @@ -113,10 +113,10 @@ //! # use nalgebra_sparse::csr::CsrMatrix; //! # let a = CsrMatrix::identity(10); let b = CsrMatrix::identity(10); //! # let mut c = CsrMatrix::identity(10); -//! use nalgebra_sparse::ops::{Op, serial::spmm_csr_prealloc}; +//! use nalgebra_sparse::ops::{Op, serial::spmm_csr_prealloc_checked}; //! //! // Evaluate the expression `c <- 3.0 * c + 2.0 * a^T * b -//! spmm_csr_prealloc(3.0, &mut c, 2.0, Op::Transpose(&a), Op::NoOp(&b)) +//! spmm_csr_prealloc_checked(3.0, &mut c, 2.0, Op::Transpose(&a), Op::NoOp(&b)) //! .expect("We assume that the pattern of C is able to accommodate the result."); //! ``` //! Compared to the simpler example, this snippet is harder to read, but it calls a single diff --git a/nalgebra-sparse/tests/unit_tests/ops.rs b/nalgebra-sparse/tests/unit_tests/ops.rs index f2a02fd8..20274d97 100644 --- a/nalgebra-sparse/tests/unit_tests/ops.rs +++ b/nalgebra-sparse/tests/unit_tests/ops.rs @@ -5,8 +5,9 @@ use crate::common::{ use nalgebra_sparse::csc::CscMatrix; use nalgebra_sparse::csr::CsrMatrix; use nalgebra_sparse::ops::serial::{ - spadd_csc_prealloc, spadd_csr_prealloc, spadd_pattern, spmm_csc_dense, spmm_csc_prealloc, - spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc, spsolve_csc_lower_triangular, + spadd_csc_prealloc, spadd_csr_prealloc, spadd_pattern, spmm_csc_dense, + spmm_csc_prealloc_checked, spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc_checked, + spsolve_csc_lower_triangular, }; use nalgebra_sparse::ops::Op; use nalgebra_sparse::pattern::SparsityPattern; @@ -550,7 +551,7 @@ proptest! { // Test that we get the expected result by comparing to an equivalent dense operation // (here we give in the C matrix, so the sparsity pattern is essentially fixed) let mut c_sparse = c.clone(); - spmm_csr_prealloc(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csr_prealloc_checked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); let mut c_dense = DMatrix::from(&c); let op_a_dense = match a { @@ -605,7 +606,7 @@ proptest! { let result = catch_unwind(|| { let mut spmm_result = c.clone(); - spmm_csr_prealloc(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csr_prealloc_checked(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); }); prop_assert!(result.is_err(), @@ -689,7 +690,7 @@ proptest! { // Test that we get the expected result by comparing to an equivalent dense operation // (here we give in the C matrix, so the sparsity pattern is essentially fixed) let mut c_sparse = c.clone(); - spmm_csc_prealloc(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csc_prealloc_checked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); let mut c_dense = DMatrix::from(&c); let op_a_dense = match a { @@ -744,7 +745,7 @@ proptest! { let result = catch_unwind(|| { let mut spmm_result = c.clone(); - spmm_csc_prealloc(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csc_prealloc_checked(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); }); prop_assert!(result.is_err(), From d1674d757737a28c47d3635c8a27acd32b44151f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 21 Mar 2022 16:56:51 -0600 Subject: [PATCH 017/112] Mul trait goes to faster kernel, pattern is correct --- nalgebra-sparse/src/ops/impl_std_ops.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nalgebra-sparse/src/ops/impl_std_ops.rs b/nalgebra-sparse/src/ops/impl_std_ops.rs index 107c38ba..91b6574f 100644 --- a/nalgebra-sparse/src/ops/impl_std_ops.rs +++ b/nalgebra-sparse/src/ops/impl_std_ops.rs @@ -3,7 +3,7 @@ use crate::csr::CsrMatrix; use crate::ops::serial::{ spadd_csc_prealloc, spadd_csr_prealloc, spadd_pattern, spmm_csc_dense, spmm_csc_pattern, - spmm_csc_prealloc, spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc, + spmm_csc_prealloc_unchecked, spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc_unchecked, }; use crate::ops::Op; use nalgebra::allocator::Allocator; @@ -112,9 +112,9 @@ macro_rules! impl_spmm { } } -impl_spmm!(CsrMatrix, spmm_csr_pattern, spmm_csr_prealloc); +impl_spmm!(CsrMatrix, spmm_csr_pattern, spmm_csr_prealloc_unchecked); // Need to switch order of operations for CSC pattern -impl_spmm!(CscMatrix, spmm_csc_pattern, spmm_csc_prealloc); +impl_spmm!(CscMatrix, spmm_csc_pattern, spmm_csc_prealloc_unchecked); /// Implements Scalar * Matrix operations for *concrete* scalar types. The reason this is necessary /// is that we are not able to implement Mul> for all T generically due to orphan rules. From 2606409a02e631c3c2cc4100bdf1240ea2e1e2f2 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 1 Apr 2022 15:26:18 -0600 Subject: [PATCH 018/112] delete example and remove compiler optimisation flags --- nalgebra-sparse/Cargo.toml | 14 ------------- nalgebra-sparse/example/spmm.rs | 36 --------------------------------- 2 files changed, 50 deletions(-) delete mode 100644 nalgebra-sparse/example/spmm.rs diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 781d1696..bb4fdb8e 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -24,7 +24,6 @@ io = [ "pest", "pest_derive" ] slow-tests = [] [dependencies] -criterion = { version = "0.3", features = ["html_reports"] } nalgebra = { version="0.30", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } @@ -32,25 +31,12 @@ matrixcompare-core = { version = "0.1.0", optional = true } pest = { version = "2", optional = true } pest_derive = { version = "2", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } -itertools = "0.10" [dev-dependencies] -itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.30", path = "../", features = ["compare"] } serde_json = "1.0" -[[example]] -name = "spmm" -required-features = ["io"] -path = "example/spmm.rs" - [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs features = [ "proptest-support", "compare" ] - -[profile.release] -opt-level = 3 -lto = "fat" -codegen-units = 1 -panic = "abort" diff --git a/nalgebra-sparse/example/spmm.rs b/nalgebra-sparse/example/spmm.rs deleted file mode 100644 index 2be64735..00000000 --- a/nalgebra-sparse/example/spmm.rs +++ /dev/null @@ -1,36 +0,0 @@ -extern crate nalgebra_sparse; -use nalgebra_sparse::CsrMatrix; -use std::fs::{self, DirEntry}; -use std::io; -use std::path::Path; -use std::time::{Duration, Instant}; - -#[cfg(feature = "io")] -use nalgebra_sparse::io::load_coo_from_matrix_market_file; -fn main() { - #[cfg(feature = "io")] - { - let mut file_iter = fs::read_dir("data").unwrap(); - for f in file_iter { - println!("Benchmark file {:?}", f); - let f = f.unwrap().path(); - - if f.extension().map_or(false, |ext| ext == "mtx") { - println!("Benchmark file {:?}", f); - let sparse_input_matrix = load_coo_from_matrix_market_file::(&f).unwrap(); - let sparse_input_matrix = CsrMatrix::from(&sparse_input_matrix); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let now = Instant::now(); - let spmm_result = &sparse_input_matrix * &sparse_input_matrix; - let spmm_time = now.elapsed().as_millis(); - println!("SGEMM time was {}", spmm_time); - let sum: f64 = spmm_result.triplet_iter().map(|(_, _, v)| v).sum(); - println!("sum of product is {}", sum); - } - } - } - #[cfg(not(feature = "io"))] - { - panic!("Run with IO feature only"); - } -} From e3fd0e739328d796117140d84cbf6f73bd7e3f8d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 1 Apr 2022 15:26:36 -0600 Subject: [PATCH 019/112] remove the checked suffix to keep backward compatibility --- nalgebra-sparse/src/ops/serial/cs.rs | 2 +- nalgebra-sparse/src/ops/serial/csc.rs | 18 +++++++++--------- nalgebra-sparse/src/ops/serial/csr.rs | 20 +++++++++----------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index f3a14924..f65b5071 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -63,7 +63,7 @@ where Ok(()) } -pub fn spmm_cs_prealloc_checked( +pub fn spmm_cs_prealloc( beta: T, c: &mut CsMatrix, alpha: T, diff --git a/nalgebra-sparse/src/ops/serial/csc.rs b/nalgebra-sparse/src/ops/serial/csc.rs index 42d27079..6a691ef9 100644 --- a/nalgebra-sparse/src/ops/serial/csc.rs +++ b/nalgebra-sparse/src/ops/serial/csc.rs @@ -1,6 +1,6 @@ use crate::csc::CscMatrix; use crate::ops::serial::cs::{ - spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc_checked, spmm_cs_prealloc_unchecked, + spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc, spmm_cs_prealloc_unchecked, }; use crate::ops::serial::{OperationError, OperationErrorKind}; use crate::ops::Op; @@ -73,7 +73,7 @@ where /// # Panics /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub fn spmm_csc_prealloc_checked( +pub fn spmm_csc_prealloc( beta: T, c: &mut CscMatrix, alpha: T, @@ -90,9 +90,9 @@ where match (&a, &b) { (NoOp(ref a), NoOp(ref b)) => { // Note: We have to reverse the order for CSC matrices - spmm_cs_prealloc_checked(beta, &mut c.cs, alpha, &b.cs, &a.cs) + spmm_cs_prealloc(beta, &mut c.cs, alpha, &b.cs, &a.cs) } - _ => do_transposes(beta, c, alpha, a, b, spmm_csc_prealloc_checked), + _ => spmm_csc_transposed(beta, c, alpha, a, b, spmm_csc_prealloc), } } @@ -101,7 +101,7 @@ where /// Should be used for situations where pattern creation immediately preceeds multiplication. /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub(crate) fn spmm_csc_prealloc_unchecked( +pub fn spmm_csc_prealloc_unchecked( beta: T, c: &mut CscMatrix, alpha: T, @@ -120,17 +120,17 @@ where // Note: We have to reverse the order for CSC matrices spmm_cs_prealloc_unchecked(beta, &mut c.cs, alpha, &b.cs, &a.cs) } - _ => do_transposes(beta, c, alpha, a, b, spmm_csc_prealloc_unchecked), + _ => spmm_csc_transposed(beta, c, alpha, a, b, spmm_csc_prealloc_unchecked), } } -fn do_transposes( +fn spmm_csc_transposed( beta: T, c: &mut CscMatrix, alpha: T, a: Op<&CscMatrix>, b: Op<&CscMatrix>, - caller: F, + spmm_kernel: F, ) -> Result<(), OperationError> where T: Scalar + ClosedAdd + ClosedMul + Zero + One, @@ -157,7 +157,7 @@ where (Transpose(ref a), Transpose(ref b)) => (Owned(a.transpose()), Owned(b.transpose())), } }; - caller(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) + spmm_kernel(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) } /// Solve the lower triangular system `op(L) X = B`. diff --git a/nalgebra-sparse/src/ops/serial/csr.rs b/nalgebra-sparse/src/ops/serial/csr.rs index 967c02f2..708c81a3 100644 --- a/nalgebra-sparse/src/ops/serial/csr.rs +++ b/nalgebra-sparse/src/ops/serial/csr.rs @@ -1,6 +1,6 @@ use crate::csr::CsrMatrix; use crate::ops::serial::cs::{ - spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc_checked, spmm_cs_prealloc_unchecked, + spadd_cs_prealloc, spmm_cs_dense, spmm_cs_prealloc, spmm_cs_prealloc_unchecked, }; use crate::ops::serial::OperationError; use crate::ops::Op; @@ -67,7 +67,7 @@ where /// # Panics /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub fn spmm_csr_prealloc_checked( +pub fn spmm_csr_prealloc( beta: T, c: &mut CsrMatrix, alpha: T, @@ -82,10 +82,8 @@ where use Op::NoOp; match (&a, &b) { - (NoOp(ref a), NoOp(ref b)) => { - spmm_cs_prealloc_checked(beta, &mut c.cs, alpha, &a.cs, &b.cs) - } - _ => do_transposes(beta, c, alpha, a, b, spmm_csr_prealloc_checked), + (NoOp(ref a), NoOp(ref b)) => spmm_cs_prealloc(beta, &mut c.cs, alpha, &a.cs, &b.cs), + _ => spmm_csr_transposed(beta, c, alpha, a, b, spmm_csr_prealloc), } } @@ -94,7 +92,7 @@ where /// Should be used for situations where pattern creation immediately preceeds multiplication. /// /// Panics if the dimensions of the matrices involved are not compatible with the expression. -pub(crate) fn spmm_csr_prealloc_unchecked( +pub fn spmm_csr_prealloc_unchecked( beta: T, c: &mut CsrMatrix, alpha: T, @@ -112,17 +110,17 @@ where (NoOp(ref a), NoOp(ref b)) => { spmm_cs_prealloc_unchecked(beta, &mut c.cs, alpha, &a.cs, &b.cs) } - _ => do_transposes(beta, c, alpha, a, b, spmm_csr_prealloc_unchecked), + _ => spmm_csr_transposed(beta, c, alpha, a, b, spmm_csr_prealloc_unchecked), } } -fn do_transposes( +fn spmm_csr_transposed( beta: T, c: &mut CsrMatrix, alpha: T, a: Op<&CsrMatrix>, b: Op<&CsrMatrix>, - caller: F, + spmm_kernel: F, ) -> Result<(), OperationError> where T: Scalar + ClosedAdd + ClosedMul + Zero + One, @@ -149,5 +147,5 @@ where (Transpose(ref a), Transpose(ref b)) => (Owned(a.transpose()), Owned(b.transpose())), } }; - caller(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) + spmm_kernel(beta, c, alpha, NoOp(a.as_ref()), NoOp(b.as_ref())) } From 19c7f19afa27b99ef080800fc8724fd6e097d7a8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 1 Apr 2022 15:26:55 -0600 Subject: [PATCH 020/112] fix tests --- nalgebra-sparse/src/ops/mod.rs | 4 +- nalgebra-sparse/tests/unit_tests/ops.rs | 60 ++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/nalgebra-sparse/src/ops/mod.rs b/nalgebra-sparse/src/ops/mod.rs index 6fedfb6c..9a73148c 100644 --- a/nalgebra-sparse/src/ops/mod.rs +++ b/nalgebra-sparse/src/ops/mod.rs @@ -113,10 +113,10 @@ //! # use nalgebra_sparse::csr::CsrMatrix; //! # let a = CsrMatrix::identity(10); let b = CsrMatrix::identity(10); //! # let mut c = CsrMatrix::identity(10); -//! use nalgebra_sparse::ops::{Op, serial::spmm_csr_prealloc_checked}; +//! use nalgebra_sparse::ops::{Op, serial::spmm_csr_prealloc}; //! //! // Evaluate the expression `c <- 3.0 * c + 2.0 * a^T * b -//! spmm_csr_prealloc_checked(3.0, &mut c, 2.0, Op::Transpose(&a), Op::NoOp(&b)) +//! spmm_csr_prealloc(3.0, &mut c, 2.0, Op::Transpose(&a), Op::NoOp(&b)) //! .expect("We assume that the pattern of C is able to accommodate the result."); //! ``` //! Compared to the simpler example, this snippet is harder to read, but it calls a single diff --git a/nalgebra-sparse/tests/unit_tests/ops.rs b/nalgebra-sparse/tests/unit_tests/ops.rs index 20274d97..0a335567 100644 --- a/nalgebra-sparse/tests/unit_tests/ops.rs +++ b/nalgebra-sparse/tests/unit_tests/ops.rs @@ -5,9 +5,9 @@ use crate::common::{ use nalgebra_sparse::csc::CscMatrix; use nalgebra_sparse::csr::CsrMatrix; use nalgebra_sparse::ops::serial::{ - spadd_csc_prealloc, spadd_csr_prealloc, spadd_pattern, spmm_csc_dense, - spmm_csc_prealloc_checked, spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc_checked, - spsolve_csc_lower_triangular, + spadd_csc_prealloc, spadd_csr_prealloc, spadd_pattern, spmm_csc_dense, spmm_csc_prealloc, + spmm_csc_prealloc_unchecked, spmm_csr_dense, spmm_csr_pattern, spmm_csr_prealloc, + spmm_csr_prealloc_unchecked, spsolve_csc_lower_triangular, }; use nalgebra_sparse::ops::Op; use nalgebra_sparse::pattern::SparsityPattern; @@ -544,6 +544,29 @@ proptest! { prop_assert_eq!(&c_pattern, c_csr.pattern()); } + #[test] + fn spmm_csr_prealloc_unchecked_test(SpmmCsrArgs { c, beta, alpha, a, b } + in spmm_csr_prealloc_args_strategy() + ) { + // Test that we get the expected result by comparing to an equivalent dense operation + // (here we give in the C matrix, so the sparsity pattern is essentially fixed) + let mut c_sparse = c.clone(); + spmm_csr_prealloc_unchecked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + + let mut c_dense = DMatrix::from(&c); + let op_a_dense = match a { + Op::NoOp(ref a) => DMatrix::from(a), + Op::Transpose(ref a) => DMatrix::from(a).transpose(), + }; + let op_b_dense = match b { + Op::NoOp(ref b) => DMatrix::from(b), + Op::Transpose(ref b) => DMatrix::from(b).transpose(), + }; + c_dense = beta * c_dense + alpha * &op_a_dense * op_b_dense; + + prop_assert_eq!(&DMatrix::from(&c_sparse), &c_dense); + } + #[test] fn spmm_csr_prealloc_test(SpmmCsrArgs { c, beta, alpha, a, b } in spmm_csr_prealloc_args_strategy() @@ -551,7 +574,7 @@ proptest! { // Test that we get the expected result by comparing to an equivalent dense operation // (here we give in the C matrix, so the sparsity pattern is essentially fixed) let mut c_sparse = c.clone(); - spmm_csr_prealloc_checked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csr_prealloc(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); let mut c_dense = DMatrix::from(&c); let op_a_dense = match a { @@ -606,7 +629,7 @@ proptest! { let result = catch_unwind(|| { let mut spmm_result = c.clone(); - spmm_csr_prealloc_checked(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csr_prealloc(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); }); prop_assert!(result.is_err(), @@ -690,7 +713,30 @@ proptest! { // Test that we get the expected result by comparing to an equivalent dense operation // (here we give in the C matrix, so the sparsity pattern is essentially fixed) let mut c_sparse = c.clone(); - spmm_csc_prealloc_checked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csc_prealloc(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); + + let mut c_dense = DMatrix::from(&c); + let op_a_dense = match a { + Op::NoOp(ref a) => DMatrix::from(a), + Op::Transpose(ref a) => DMatrix::from(a).transpose(), + }; + let op_b_dense = match b { + Op::NoOp(ref b) => DMatrix::from(b), + Op::Transpose(ref b) => DMatrix::from(b).transpose(), + }; + c_dense = beta * c_dense + alpha * &op_a_dense * op_b_dense; + + prop_assert_eq!(&DMatrix::from(&c_sparse), &c_dense); + } + + #[test] + fn spmm_csc_prealloc_unchecked_test(SpmmCscArgs { c, beta, alpha, a, b } + in spmm_csc_prealloc_args_strategy() + ) { + // Test that we get the expected result by comparing to an equivalent dense operation + // (here we give in the C matrix, so the sparsity pattern is essentially fixed) + let mut c_sparse = c.clone(); + spmm_csc_prealloc_unchecked(beta, &mut c_sparse, alpha, a.as_ref(), b.as_ref()).unwrap(); let mut c_dense = DMatrix::from(&c); let op_a_dense = match a { @@ -745,7 +791,7 @@ proptest! { let result = catch_unwind(|| { let mut spmm_result = c.clone(); - spmm_csc_prealloc_checked(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); + spmm_csc_prealloc(beta, &mut spmm_result, alpha, a.as_ref(), b.as_ref()).unwrap(); }); prop_assert!(result.is_err(), From f77e12ff2f3d8a9b28484e9ee8a8220c754a098f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 22 Apr 2022 12:14:18 -0600 Subject: [PATCH 021/112] add checks to make sure access won't be out of bounds --- nalgebra-sparse/src/ops/serial/cs.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nalgebra-sparse/src/ops/serial/cs.rs b/nalgebra-sparse/src/ops/serial/cs.rs index f65b5071..cc13c168 100644 --- a/nalgebra-sparse/src/ops/serial/cs.rs +++ b/nalgebra-sparse/src/ops/serial/cs.rs @@ -30,6 +30,8 @@ pub fn spmm_cs_prealloc_unchecked( where T: Scalar + ClosedAdd + ClosedMul + Zero + One, { + assert_eq!(c.pattern().major_dim(), a.pattern().major_dim()); + assert_eq!(c.pattern().minor_dim(), b.pattern().minor_dim()); let some_val = Zero::zero(); let mut scratchpad_values: Vec = vec![some_val; b.pattern().minor_dim()]; for i in 0..c.pattern().major_dim() { From b38ceb4a1dda2243ba56ae6ec1513f11bce1bb53 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 3 May 2022 14:19:24 -0600 Subject: [PATCH 022/112] fix failing test --- nalgebra-sparse/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index c5ab9614..7220373e 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -33,6 +33,7 @@ pest_derive = { version = "2", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } [dev-dependencies] +itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.31", path = "../", features = ["compare"] } serde_json = "1.0" From 3195fba72763790f156c3179ee67a5dcf3345597 Mon Sep 17 00:00:00 2001 From: Saurabh Raje Date: Tue, 3 May 2022 14:21:34 -0600 Subject: [PATCH 023/112] Create rust.yml --- .github/workflows/rust.yml | 139 +++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..304ecf5f --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,139 @@ +name: nalgebra CI build + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + check-fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check formatting + run: cargo fmt -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install clippy + run: rustup component add clippy + - name: Run clippy + run: cargo clippy + build-nalgebra: + runs-on: ubuntu-latest +# env: +# RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - name: Build --no-default-feature + run: cargo build --no-default-features; + - name: Build (default features) + run: cargo build; + - name: Build --features serde-serialize + run: cargo build --features serde-serialize + - name: Build nalgebra-lapack + run: cd nalgebra-lapack; cargo build; + - name: Build nalgebra-sparse --no-default-features + run: cd nalgebra-sparse; cargo build --no-default-features; + - name: Build nalgebra-sparse (default features) + run: cd nalgebra-sparse; cargo build; + - name: Build nalgebra-sparse --all-features + run: cd nalgebra-sparse; cargo build --all-features; + # Run this on it’s own job because it alone takes a lot of time. + # So it’s best to let it run in parallel to the other jobs. + build-nalgebra-all-features: + runs-on: ubuntu-latest + steps: + # Needed because the --all-features build which enables cuda support. + - uses: Jimver/cuda-toolkit@v0.2.4 + - uses: actions/checkout@v2 + - run: cargo build --all-features; + - run: cargo build -p nalgebra-glm --all-features; + test-nalgebra: + runs-on: ubuntu-latest +# env: +# RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - name: test + run: cargo test --features arbitrary,rand,serde-serialize,sparse,debug,io,compare,libm,proptest-support,slow-tests; + test-nalgebra-glm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: test nalgebra-glm + run: cargo test -p nalgebra-glm --features arbitrary,serde-serialize; + test-nalgebra-sparse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: test nalgebra-sparse + # Manifest-path is necessary because cargo otherwise won't correctly forward features + # We increase number of proptest cases to hopefully catch more potential bugs + run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize + - name: test nalgebra-sparse (slow tests) + # Unfortunately, the "slow-tests" take so much time that we need to run them with --release + run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize,slow-tests slow + test-nalgebra-macros: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: test nalgebra-macros + run: cargo test -p nalgebra-macros + build-wasm: + runs-on: ubuntu-latest +# env: +# RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - run: rustup target add wasm32-unknown-unknown + - name: build nalgebra + run: cargo build --verbose --target wasm32-unknown-unknown; + - name: build nalgebra-glm + run: cargo build -p nalgebra-glm --verbose --target wasm32-unknown-unknown; + build-no-std: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt + - name: install xargo + run: cp .github/Xargo.toml .; rustup component add rust-src; cargo install -f xargo; + - name: build x86_64-unknown-linux-gnu + run: xargo build --verbose --no-default-features --target=x86_64-unknown-linux-gnu; + - name: build x86_64-unknown-linux-gnu --features rand-no-std + run: xargo build --verbose --no-default-features --target=x86_64-unknown-linux-gnu; + - name: build x86_64-unknown-linux-gnu --features alloc + run: xargo build --verbose --no-default-features --features alloc --target=x86_64-unknown-linux-gnu; + - name: build thumbv7em-none-eabihf + run: xargo build --verbose --no-default-features --target=thumbv7em-none-eabihf; + - name: build x86_64-unknown-linux-gnu nalgebra-glm + run: xargo build --verbose --no-default-features -p nalgebra-glm --target=x86_64-unknown-linux-gnu; + - name: build thumbv7em-none-eabihf nalgebra-glm + run: xargo build --verbose --no-default-features -p nalgebra-glm --target=thumbv7em-none-eabihf; + build-cuda: + runs-on: ubuntu-latest + steps: + - uses: Jimver/cuda-toolkit@v0.2.4 + with: + cuda: '11.2.2' + - name: Install nightly-2021-12-04 + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2021-12-04 + override: true + - uses: actions/checkout@v2 + - run: rustup target add nvptx64-nvidia-cuda + - run: cargo build --no-default-features --features cuda + - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda + env: + CUDA_ARCH: "350" From f6f49c510caabf67de2bb0b02cbba447ef219f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 11 Mar 2022 17:17:34 +0100 Subject: [PATCH 024/112] Update to cust 0.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 15a24d41..0adc5729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ glam019 = { package = "glam", version = "0.19", optional = true } glam020 = { package = "glam", version = "0.20", optional = true } [target.'cfg(not(target_os = "cuda"))'.dependencies] -cust = { version = "0.2", optional = true } +cust = { version = "0.3", optional = true } [dev-dependencies] From f3b10443754f94e539a8d1617617d586c4d276ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 11 Mar 2022 17:43:01 +0100 Subject: [PATCH 025/112] CI: pin the version of Cuda --- .github/workflows/nalgebra-ci-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nalgebra-ci-build.yml b/.github/workflows/nalgebra-ci-build.yml index bc2f9ca6..707c4143 100644 --- a/.github/workflows/nalgebra-ci-build.yml +++ b/.github/workflows/nalgebra-ci-build.yml @@ -124,6 +124,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: Jimver/cuda-toolkit@v0.2.4 + with: + cuda: '11.2.2' - name: Install nightly-2021-12-04 uses: actions-rs/toolchain@v1 with: From dd471d7a7e9d93996df54c576f1f31080f3daa0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 11 Mar 2022 18:06:40 +0100 Subject: [PATCH 026/112] CI: set the CUDA_ARCH env var when targetting nvptx --- .github/workflows/nalgebra-ci-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nalgebra-ci-build.yml b/.github/workflows/nalgebra-ci-build.yml index 707c4143..c00b6cbc 100644 --- a/.github/workflows/nalgebra-ci-build.yml +++ b/.github/workflows/nalgebra-ci-build.yml @@ -134,4 +134,6 @@ jobs: - uses: actions/checkout@v2 - run: rustup target add nvptx64-nvidia-cuda - run: cargo build --no-default-features --features cuda - - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda \ No newline at end of file + - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda + env: + CUDA_ARCH: "350" \ No newline at end of file From 213a9fbd21ca4631c251b2c4d06315a4a8f31bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 16 Mar 2022 18:07:29 +0100 Subject: [PATCH 027/112] Use cust_core instead of cust --- Cargo.toml | 6 ++---- src/base/array_storage.rs | 5 +---- src/base/dimension.rs | 10 ++-------- src/base/matrix.rs | 5 +---- src/base/unit.rs | 10 +++------- src/geometry/dual_quaternion.rs | 5 +---- src/geometry/isometry.rs | 5 +---- src/geometry/orthographic.rs | 5 +---- src/geometry/perspective.rs | 5 +---- src/geometry/point.rs | 7 +++---- src/geometry/quaternion.rs | 9 +++------ src/geometry/rotation.rs | 5 +---- src/geometry/scale.rs | 5 +---- src/geometry/similarity.rs | 5 +---- src/geometry/transform.rs | 23 +++++++---------------- src/geometry/translation.rs | 5 +---- src/geometry/unit_complex.rs | 4 ++-- 17 files changed, 32 insertions(+), 87 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0adc5729..8a3fea5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ compare = [ "matrixcompare-core" ] libm = [ "simba/libm" ] libm-force = [ "simba/libm_force" ] macros = [ "nalgebra-macros" ] -cuda = [ "cust", "simba/cuda" ] +cuda = [ "cust_core", "simba/cuda" ] # Conversion convert-mint = [ "mint" ] @@ -96,9 +96,7 @@ glam017 = { package = "glam", version = "0.17", optional = true } glam018 = { package = "glam", version = "0.18", optional = true } glam019 = { package = "glam", version = "0.19", optional = true } glam020 = { package = "glam", version = "0.20", optional = true } - -[target.'cfg(not(target_os = "cuda"))'.dependencies] -cust = { version = "0.3", optional = true } +cust_core = { version = "0.1", optional = true } [dev-dependencies] diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index 6851c381..b6bd236a 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -27,10 +27,7 @@ use std::mem; /// A array-based statically sized matrix data storage. #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct ArrayStorage(pub [[T; R]; C]); impl ArrayStorage { diff --git a/src/base/dimension.rs b/src/base/dimension.rs index 86006f3d..e43cb734 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -13,10 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Dim of dynamically-sized algebraic entities. #[derive(Clone, Copy, Eq, PartialEq, Debug)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Dynamic { value: usize, } @@ -201,10 +198,7 @@ dim_ops!( ); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Const; /// Trait implemented exclusively by type-level integers. diff --git a/src/base/matrix.rs b/src/base/matrix.rs index bdf3a8c7..cc69c9a1 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -150,10 +150,7 @@ pub type MatrixCross = /// some concrete types for `T` and a compatible data storage type `S`). #[repr(C)] #[derive(Clone, Copy)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Matrix { /// The data storage that contains all the matrix components. Disappointed? /// diff --git a/src/base/unit.rs b/src/base/unit.rs index 9336a5e5..bb8b56a1 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -21,10 +21,7 @@ use crate::{Dim, Matrix, OMatrix, RealField, Scalar, SimdComplexField, SimdRealF /// in their documentation, read their dedicated pages directly. #[repr(transparent)] #[derive(Clone, Hash, Copy)] -// #[cfg_attr( -// all(not(target_os = "cuda"), feature = "cuda"), -// derive(cust::DeviceCopy) -// )] +// #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Unit { pub(crate) value: T, } @@ -102,9 +99,8 @@ mod rkyv_impl { } } -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy - for Unit> +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for Unit> where T: Scalar, R: Dim, diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 4280668a..509b359a 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -39,10 +39,7 @@ use simba::scalar::{ClosedNeg, RealField}; /// See #[repr(C)] #[derive(Debug, Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct DualQuaternion { /// The real component of the quaternion pub real: Quaternion, diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 8cdd1bfc..0179f1ff 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -50,10 +50,7 @@ use crate::geometry::{AbstractRotation, Point, Translation}; /// #[repr(C)] #[derive(Debug, Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 18a7852d..085ba61b 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -19,10 +19,7 @@ use crate::geometry::{Point3, Projective3}; /// A 3D orthographic projection stored as a homogeneous 4x4 matrix. #[repr(C)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Orthographic3 { matrix: Matrix4, diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index 59b7f9f2..8ebab3e4 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -20,10 +20,7 @@ use crate::geometry::{Point3, Projective3}; /// A 3D perspective projection stored as a homogeneous 4x4 matrix. #[repr(C)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Perspective3 { matrix: Matrix4, diff --git a/src/geometry/point.rs b/src/geometry/point.rs index b62998c3..a8d7684b 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -69,12 +69,11 @@ where { } -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy - for OPoint +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for OPoint where DefaultAllocator: Allocator, - OVector: cust::memory::DeviceCopy, + OVector: cust_core::DeviceCopy, { } diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 0aa7f3d3..6d29f34f 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -23,10 +23,7 @@ use crate::geometry::{Point3, Rotation}; /// that may be used as a rotation. #[repr(C)] #[derive(Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Quaternion { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. pub coords: Vector4, @@ -1045,8 +1042,8 @@ impl fmt::Display for Quaternion { /// A unit quaternions. May be used to represent a rotation. pub type UnitQuaternion = Unit>; -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy for UnitQuaternion {} +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for UnitQuaternion {} impl PartialEq for UnitQuaternion { #[inline] diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index 69c4a355..4dbcfb43 100755 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -49,10 +49,7 @@ use crate::geometry::Point; /// * [Conversion to a matrix `matrix`, `to_homogeneous`…](#conversion-to-a-matrix) /// #[repr(C)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Rotation { matrix: SMatrix, diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index 064e0075..abaeeccc 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -17,10 +17,7 @@ use crate::geometry::Point; /// A scale which supports non-uniform scaling. #[repr(C)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Scale { /// The scale coordinates, i.e., how much is multiplied to a point's coordinates when it is diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index 46c86f5d..9658685e 100755 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -18,10 +18,7 @@ use crate::geometry::{AbstractRotation, Isometry, Point, Translation}; /// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. #[repr(C)] #[derive(Debug, Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index b0b5cced..2a7ca112 100755 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -60,26 +60,17 @@ where /// Tag representing the most general (not necessarily inversible) `Transform` type. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TGeneral {} /// Tag representing the most general inversible `Transform` type. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TProjective {} /// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TAffine {} impl TCategory for TGeneral { @@ -207,13 +198,13 @@ where { } -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl - cust::memory::DeviceCopy for Transform +#[cfg(feature = "cuda")] +unsafe impl + cust_core::DeviceCopy for Transform where Const: DimNameAdd, DefaultAllocator: Allocator, U1>, DimNameSum, U1>>, - Owned, U1>, DimNameSum, U1>>: cust::memory::DeviceCopy, + Owned, U1>, DimNameSum, U1>>: cust_core::DeviceCopy, { } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index b07cce20..5db46e82 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -17,10 +17,7 @@ use crate::geometry::Point; /// A translation. #[repr(C)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Translation { /// The translation coordinates, i.e., how much is added to a point's coordinates when it is diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 48405dd4..caf25493 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -31,8 +31,8 @@ use std::cmp::{Eq, PartialEq}; /// * [Conversion to a matrix `to_rotation_matrix`, `to_homogeneous`…](#conversion-to-a-matrix) pub type UnitComplex = Unit>; -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy for UnitComplex {} +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for UnitComplex {} impl PartialEq for UnitComplex { #[inline] From ba7efb52b09b28ab24188e89ff9634eedcce0702 Mon Sep 17 00:00:00 2001 From: sterlingjensen <5555776+sterlingjensen@users.noreply.github.com> Date: Tue, 22 Mar 2022 11:53:46 -0500 Subject: [PATCH 028/112] Cleanup examples and doc links Close example code fences and normalize containing head line in touched files. Remove stale reference to `slice_assume_init` (commit 8c6ebf27), fix long dead internal links in deprecation notices. --- nalgebra-macros/src/lib.rs | 9 ++- src/base/blas.rs | 69 ++++++++------------ src/base/matrix.rs | 23 ++++--- src/geometry/dual_quaternion.rs | 11 ++++ src/geometry/dual_quaternion_construction.rs | 4 +- src/geometry/orthographic.rs | 15 +++++ src/geometry/point.rs | 2 + src/geometry/quaternion.rs | 11 +--- src/geometry/similarity_construction.rs | 10 +-- src/geometry/translation.rs | 2 + src/lib.rs | 4 +- 11 files changed, 82 insertions(+), 78 deletions(-) diff --git a/nalgebra-macros/src/lib.rs b/nalgebra-macros/src/lib.rs index 4bd791ae..0d7889ae 100644 --- a/nalgebra-macros/src/lib.rs +++ b/nalgebra-macros/src/lib.rs @@ -125,7 +125,6 @@ impl Parse for Matrix { /// (`;`) designates that a new row begins. /// /// # Examples -/// /// ``` /// use nalgebra::matrix; /// @@ -170,6 +169,7 @@ pub fn matrix(stream: TokenStream) -> TokenStream { /// `SMatrix`, it produces instances of `DMatrix`. At the moment it is not usable /// in `const fn` contexts. /// +/// # Example /// ``` /// use nalgebra::dmatrix; /// @@ -243,8 +243,7 @@ impl Parse for Vector { /// `vector!` is intended to be the most readable and performant way of constructing small, /// fixed-size vectors, and it is usable in `const fn` contexts. /// -/// ## Examples -/// +/// # Example /// ``` /// use nalgebra::vector; /// @@ -271,6 +270,7 @@ pub fn vector(stream: TokenStream) -> TokenStream { /// `SVector`, it produces instances of `DVector`. At the moment it is not usable /// in `const fn` contexts. /// +/// # Example /// ``` /// use nalgebra::dvector; /// @@ -301,8 +301,7 @@ pub fn dvector(stream: TokenStream) -> TokenStream { /// `point!` is intended to be the most readable and performant way of constructing small, /// points, and it is usable in `const fn` contexts. /// -/// ## Examples -/// +/// # Example /// ``` /// use nalgebra::point; /// diff --git a/src/base/blas.rs b/src/base/blas.rs index 4f56a70e..e65304b5 100644 --- a/src/base/blas.rs +++ b/src/base/blas.rs @@ -175,8 +175,7 @@ where /// Note that this is **not** the matrix multiplication as in, e.g., numpy. For matrix /// multiplication, use one of: `.gemm`, `.mul_to`, `.mul`, the `*` operator. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector3, Matrix2x3}; /// let vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -207,8 +206,7 @@ where /// Note that this is **not** the matrix multiplication as in, e.g., numpy. For matrix /// multiplication, use one of: `.gemm`, `.mul_to`, `.mul`, the `*` operator. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector2, Complex}; /// let vec1 = Vector2::new(Complex::new(1.0, 2.0), Complex::new(3.0, 4.0)); @@ -232,8 +230,7 @@ where /// The dot product between the transpose of `self` and `rhs`. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector3, RowVector3, Matrix2x3, Matrix3x2}; /// let vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -285,8 +282,7 @@ where /// /// If `b` is zero, `self` is never read from. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Vector3; /// let mut vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -308,8 +304,7 @@ where /// /// If `b` is zero, `self` is never read from. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Vector3; /// let mut vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -333,8 +328,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut vec1 = Vector2::new(1.0, 2.0); @@ -425,8 +419,7 @@ where /// If `beta` is zero, `self` is never read. If `self` is read, only its lower-triangular part /// (including the diagonal) is actually read. /// - /// # Examples: - /// + /// # Examples /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mat = Matrix2::new(1.0, 2.0, @@ -468,8 +461,7 @@ where /// If `beta` is zero, `self` is never read. If `self` is read, only its lower-triangular part /// (including the diagonal) is actually read. /// - /// # Examples: - /// + /// # Examples /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mat = Matrix2::new(Complex::new(1.0, 0.0), Complex::new(2.0, -0.1), @@ -552,8 +544,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mat = Matrix2::new(1.0, 3.0, @@ -587,8 +578,7 @@ where /// For real matrices, this is the same as `.gemv_tr`. /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mat = Matrix2::new(Complex::new(1.0, 2.0), Complex::new(3.0, 4.0), @@ -656,8 +646,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2x3, Vector2, Vector3}; /// let mut mat = Matrix2x3::repeat(4.0); @@ -688,8 +677,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2x3, Vector2, Vector3, Complex}; @@ -722,8 +710,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2x3, Matrix3x4, Matrix2x4}; @@ -763,8 +750,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix3x2, Matrix3x4, Matrix2x4}; @@ -821,8 +807,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix3x2, Matrix3x4, Matrix2x4, Complex}; @@ -921,8 +906,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut mat = Matrix2::identity(); @@ -934,6 +918,7 @@ where /// mat.ger_symm(10.0, &vec1, &vec2, 5.0); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, 99999.99999); // This was untouched. + /// ``` #[inline] #[deprecated(note = "This is renamed `syger` to match the original BLAS terminology.")] pub fn ger_symm( @@ -958,8 +943,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut mat = Matrix2::identity(); @@ -971,6 +955,7 @@ where /// mat.syger(10.0, &vec1, &vec2, 5.0); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, 99999.99999); // This was untouched. + /// ``` #[inline] pub fn syger( &mut self, @@ -993,8 +978,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mut mat = Matrix2::identity(); @@ -1006,6 +990,7 @@ where /// mat.hegerc(Complex::new(10.0, 20.0), &vec1, &vec2, Complex::new(5.0, 15.0)); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, Complex::new(99999.99999, 88888.88888)); // This was untouched. + /// ``` #[inline] pub fn hegerc( &mut self, @@ -1031,8 +1016,7 @@ where /// /// This uses the provided workspace `work` to avoid allocations for intermediate results. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{DMatrix, DVector}; @@ -1053,6 +1037,7 @@ where /// /// mat.quadform_tr_with_workspace(&mut workspace, 10.0, &lhs, &mid, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_tr_with_workspace( &mut self, work: &mut Vector, @@ -1085,8 +1070,7 @@ where /// If `D1` is a type-level integer, then the allocation is performed on the stack. /// Use `.quadform_tr_with_workspace(...)` instead to avoid allocations. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2, Matrix3, Matrix2x3, Vector2}; @@ -1100,6 +1084,7 @@ where /// /// mat.quadform_tr(10.0, &lhs, &mid, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_tr( &mut self, alpha: T, @@ -1124,6 +1109,7 @@ where /// /// This uses the provided workspace `work` to avoid allocations for intermediate results. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{DMatrix, DVector}; @@ -1145,6 +1131,7 @@ where /// /// mat.quadform_with_workspace(&mut workspace, 10.0, &mid, &rhs, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_with_workspace( &mut self, work: &mut Vector, @@ -1180,6 +1167,7 @@ where /// If `D2` is a type-level integer, then the allocation is performed on the stack. /// Use `.quadform_with_workspace(...)` instead to avoid allocations. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2, Matrix3x2, Matrix3}; @@ -1194,6 +1182,7 @@ where /// /// mat.quadform(10.0, &mid, &rhs, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform( &mut self, alpha: T, diff --git a/src/base/matrix.rs b/src/base/matrix.rs index cc69c9a1..93b5ba8a 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -411,8 +411,6 @@ where { /// Assumes a matrix's entries to be initialized. This operation should be near zero-cost. /// - /// For the similar method that operates on matrix slices, see [`slice_assume_init`]. - /// /// # Safety /// The user must make sure that every single entry of the buffer has been initialized, /// or Undefined Behavior will immediately occur. @@ -433,12 +431,12 @@ impl> Matrix { /// The shape of this matrix returned as the tuple (number of rows, number of columns). /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.shape(), (3, 4)); + /// ``` #[inline] #[must_use] pub fn shape(&self) -> (usize, usize) { @@ -455,12 +453,12 @@ impl> Matrix { /// The number of rows of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.nrows(), 3); + /// ``` #[inline] #[must_use] pub fn nrows(&self) -> usize { @@ -469,12 +467,12 @@ impl> Matrix { /// The number of columns of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.ncols(), 4); + /// ``` #[inline] #[must_use] pub fn ncols(&self) -> usize { @@ -483,14 +481,14 @@ impl> Matrix { /// The strides (row stride, column stride) of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::DMatrix; /// let mat = DMatrix::::zeros(10, 10); /// let slice = mat.slice_with_steps((0, 0), (5, 3), (1, 2)); /// // The column strides is the number of steps (here 2) multiplied by the corresponding dimension. /// assert_eq!(mat.strides(), (1, 10)); + /// ``` #[inline] #[must_use] pub fn strides(&self) -> (usize, usize) { @@ -1085,8 +1083,7 @@ impl> Matrix { impl> Matrix { /// Iterates through this matrix coordinates in column-major order. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix2x3; /// let mat = Matrix2x3::new(11, 12, 13, @@ -1099,6 +1096,7 @@ impl> Matrix { /// assert_eq!(*it.next().unwrap(), 13); /// assert_eq!(*it.next().unwrap(), 23); /// assert!(it.next().is_none()); + /// ``` #[inline] pub fn iter(&self) -> MatrixIter<'_, T, R, C, S> { MatrixIter::new(&self.data) @@ -1121,6 +1119,7 @@ impl> Matrix { } /// Iterate through the columns of this matrix. + /// /// # Example /// ``` /// # use nalgebra::Matrix2x3; diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 509b359a..719ae13d 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -19,6 +19,7 @@ use simba::scalar::{ClosedNeg, RealField}; /// `DualQuaternions` are stored as \[..real, ..dual\]. /// Both of the quaternion components are laid out in `i, j, k, w` order. /// +/// # Example /// ``` /// # use nalgebra::{DualQuaternion, Quaternion}; /// @@ -620,6 +621,7 @@ where /// dq.rotation().euler_angles().0, std::f32::consts::FRAC_PI_2, epsilon = 1.0e-6 /// ); /// assert_relative_eq!(dq.translation().vector.y, 3.0, epsilon = 1.0e-6); + /// ``` #[inline] #[must_use] pub fn sclerp(&self, other: &Self, t: T) -> Self @@ -710,6 +712,7 @@ where /// Return the rotation part of this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -730,6 +733,7 @@ where /// Return the translation part of this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -755,6 +759,7 @@ where /// Builds an isometry from this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -780,6 +785,7 @@ where /// /// This is the same as the multiplication `self * pt`. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -804,6 +810,7 @@ where /// /// This is the same as the multiplication `self * v`. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -828,6 +835,7 @@ where /// This may be cheaper than inverting the unit dual quaternion and /// transforming the point. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -853,6 +861,7 @@ where /// This may be cheaper than inverting the unit dual quaternion and /// transforming the vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -877,6 +886,7 @@ where /// cheaper than inverting the unit dual quaternion and transforming the /// vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Unit, Vector3}; @@ -906,6 +916,7 @@ where /// Converts this unit dual quaternion interpreted as an isometry /// into its equivalent homogeneous transformation matrix. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix4, UnitDualQuaternion, UnitQuaternion, Vector3}; diff --git a/src/geometry/dual_quaternion_construction.rs b/src/geometry/dual_quaternion_construction.rs index 94bbc04f..ae7b5c97 100644 --- a/src/geometry/dual_quaternion_construction.rs +++ b/src/geometry/dual_quaternion_construction.rs @@ -27,7 +27,6 @@ impl DualQuaternion { /// The dual quaternion multiplicative identity. /// /// # Example - /// /// ``` /// # use nalgebra::{DualQuaternion, Quaternion}; /// @@ -134,6 +133,7 @@ impl UnitDualQuaternion { /// The unit dual quaternion multiplicative identity, which also represents /// the identity transformation as an isometry. /// + /// # Example /// ``` /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; /// let ident = UnitDualQuaternion::identity(); @@ -171,6 +171,7 @@ where /// Return a dual quaternion representing the translation and orientation /// given by the provided rotation quaternion and translation vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -196,6 +197,7 @@ where /// Return a unit dual quaternion representing the translation and orientation /// given by the provided isometry. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Isometry3, UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 085ba61b..1119d4e3 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -316,6 +316,7 @@ impl Orthographic3 { /// The left offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -333,6 +334,7 @@ impl Orthographic3 { /// The right offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -350,6 +352,7 @@ impl Orthographic3 { /// The bottom offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -367,6 +370,7 @@ impl Orthographic3 { /// The top offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -384,6 +388,7 @@ impl Orthographic3 { /// The near plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -401,6 +406,7 @@ impl Orthographic3 { /// The far plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -523,6 +529,7 @@ impl Orthographic3 { /// Sets the left offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -542,6 +549,7 @@ impl Orthographic3 { /// Sets the right offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -561,6 +569,7 @@ impl Orthographic3 { /// Sets the bottom offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -580,6 +589,7 @@ impl Orthographic3 { /// Sets the top offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -599,6 +609,7 @@ impl Orthographic3 { /// Sets the near plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -618,6 +629,7 @@ impl Orthographic3 { /// Sets the far plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -637,6 +649,7 @@ impl Orthographic3 { /// Sets the view cuboid offsets along the `x` axis. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -662,6 +675,7 @@ impl Orthographic3 { /// Sets the view cuboid offsets along the `y` axis. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -687,6 +701,7 @@ impl Orthographic3 { /// Sets the near and far plane offsets of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; diff --git a/src/geometry/point.rs b/src/geometry/point.rs index a8d7684b..cdc590fa 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -266,6 +266,7 @@ where /// assert_eq!(it.next(), Some(2.0)); /// assert_eq!(it.next(), Some(3.0)); /// assert_eq!(it.next(), None); + /// ``` #[inline] pub fn iter( &self, @@ -292,6 +293,7 @@ where /// } /// /// assert_eq!(p, Point3::new(10.0, 20.0, 30.0)); + /// ``` #[inline] pub fn iter_mut( &mut self, diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 6d29f34f..987c9757 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -402,6 +402,7 @@ where /// let expected = Quaternion::new(-20.0, 0.0, 0.0, 0.0); /// let result = a.inner(&b); /// assert_relative_eq!(expected, result, epsilon = 1.0e-5); + /// ``` #[inline] #[must_use] pub fn inner(&self, other: &Self) -> Self { @@ -1227,8 +1228,7 @@ where /// Panics if the angle between both quaternion is 180 degrees (in which case the interpolation /// is not well-defined). Use `.try_slerp` instead to avoid the panic. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::geometry::UnitQuaternion; /// @@ -1450,7 +1450,6 @@ where /// Builds a rotation matrix from this unit quaternion. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1533,7 +1532,6 @@ where /// Converts this unit quaternion into its equivalent homogeneous transformation matrix. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1557,7 +1555,6 @@ where /// This is the same as the multiplication `self * pt`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1578,7 +1575,6 @@ where /// This is the same as the multiplication `self * v`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1599,7 +1595,6 @@ where /// point. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1622,7 +1617,6 @@ where /// vector. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1643,7 +1637,6 @@ where /// vector. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; diff --git a/src/geometry/similarity_construction.rs b/src/geometry/similarity_construction.rs index 8d1d38b8..cabb1676 100644 --- a/src/geometry/similarity_construction.rs +++ b/src/geometry/similarity_construction.rs @@ -38,7 +38,6 @@ where /// Creates a new identity similarity. /// /// # Example - /// /// ``` /// # use nalgebra::{Similarity2, Point2, Similarity3, Point3}; /// @@ -95,7 +94,6 @@ where /// its axis passing through the point `p`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -146,7 +144,6 @@ where /// Creates a new similarity from a translation, a rotation, and an uniform scaling factor. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -188,7 +185,6 @@ where /// Creates a new similarity from a translation and a rotation angle. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -232,7 +228,6 @@ macro_rules! similarity_construction_impl( /// factor. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -288,7 +283,6 @@ macro_rules! similarity_construction_impl( /// to `eye - at`. Non-collinearity is not checked. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -316,7 +310,7 @@ macro_rules! similarity_construction_impl( Self::from_isometry(Isometry::<_, $Rot, 3>::face_towards(eye, target, up), scaling) } - /// Deprecated: Use [`SimilarityMatrix3::face_towards`] instead. + /// Deprecated: Use [`SimilarityMatrix3::face_towards`](Self::face_towards) instead. #[deprecated(note="renamed to `face_towards`")] pub fn new_observer_frames(eye: &Point3, target: &Point3, @@ -338,7 +332,6 @@ macro_rules! similarity_construction_impl( /// requirement of this parameter is to not be collinear to `target - eye`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -376,7 +369,6 @@ macro_rules! similarity_construction_impl( /// requirement of this parameter is to not be collinear to `target - eye`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 5db46e82..e1921d0a 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -228,6 +228,7 @@ impl Translation { /// let t = Translation3::new(1.0, 2.0, 3.0); /// let transformed_point = t.transform_point(&Point3::new(4.0, 5.0, 6.0)); /// assert_eq!(transformed_point, Point3::new(5.0, 7.0, 9.0)); + /// ``` #[inline] #[must_use] pub fn transform_point(&self, pt: &Point) -> Point { @@ -244,6 +245,7 @@ impl Translation { /// let t = Translation3::new(1.0, 2.0, 3.0); /// let transformed_point = t.inverse_transform_point(&Point3::new(4.0, 5.0, 6.0)); /// assert_eq!(transformed_point, Point3::new(3.0, 3.0, 3.0)); + /// ``` #[inline] #[must_use] pub fn inverse_transform_point(&self, pt: &Point) -> Point { diff --git a/src/lib.rs b/src/lib.rs index 28701cfa..92b28dcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,8 +246,8 @@ pub fn min(a: T, b: T) -> T { /// The absolute value of `a`. /// -/// Deprecated: Use [`Matrix::abs`] or [`RealField::abs`] instead. -#[deprecated(note = "use the inherent method `Matrix::abs` or `RealField::abs` instead")] +/// Deprecated: Use [`Matrix::abs`] or [`ComplexField::abs`] instead. +#[deprecated(note = "use the inherent method `Matrix::abs` or `ComplexField::abs` instead")] #[inline] pub fn abs(a: &T) -> T { a.abs() From 93e353234f5993dcdf70dd9984bd1b462c2f8406 Mon Sep 17 00:00:00 2001 From: sterlingjensen <5555776+sterlingjensen@users.noreply.github.com> Date: Wed, 23 Mar 2022 22:08:20 -0500 Subject: [PATCH 029/112] run `cargo fmt` --- src/base/matrix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 93b5ba8a..f12cb3fa 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -1119,7 +1119,7 @@ impl> Matrix { } /// Iterate through the columns of this matrix. - /// + /// /// # Example /// ``` /// # use nalgebra::Matrix2x3; From 0e076564c2c7cab584037b2658a78c925aad8a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 23 Mar 2022 22:55:16 +0100 Subject: [PATCH 030/112] Simplify the type definitions of Const aliases, to help rust-analyzer --- src/base/dimension.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/base/dimension.rs b/src/base/dimension.rs index e43cb734..de51339f 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -303,24 +303,24 @@ impl DimName for Const { pub type U1 = Const<1>; -impl ToTypenum for Const<{ typenum::U1::USIZE }> { +impl ToTypenum for Const<1> { type Typenum = typenum::U1; } impl ToConst for typenum::U1 { - type Const = Const<{ typenum::U1::USIZE }>; + type Const = Const<1>; } macro_rules! from_to_typenum ( - ($($D: ident),* $(,)*) => {$( - pub type $D = Const<{ typenum::$D::USIZE }>; + ($($D: ident, $VAL: expr);* $(;)*) => {$( + pub type $D = Const<$VAL>; - impl ToTypenum for Const<{ typenum::$D::USIZE }> { + impl ToTypenum for Const<$VAL> { type Typenum = typenum::$D; } impl ToConst for typenum::$D { - type Const = Const<{ typenum::$D::USIZE }>; + type Const = Const<$VAL>; } impl IsNotStaticOne for $D { } @@ -328,12 +328,12 @@ macro_rules! from_to_typenum ( ); from_to_typenum!( - U0, /*U1,*/ U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, - U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32, U33, U34, U35, U36, U37, - U38, U39, U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, U50, U51, U52, U53, U54, U55, U56, - U57, U58, U59, U60, U61, U62, U63, U64, U65, U66, U67, U68, U69, U70, U71, U72, U73, U74, U75, - U76, U77, U78, U79, U80, U81, U82, U83, U84, U85, U86, U87, U88, U89, U90, U91, U92, U93, U94, - U95, U96, U97, U98, U99, U100, U101, U102, U103, U104, U105, U106, U107, U108, U109, U110, - U111, U112, U113, U114, U115, U116, U117, U118, U119, U120, U121, U122, U123, U124, U125, U126, - U127 + U0, 0; /*U1,1;*/ U2, 2; U3, 3; U4, 4; U5, 5; U6, 6; U7, 7; U8, 8; U9, 9; U10, 10; U11, 11; U12, 12; U13, 13; U14, 14; U15, 15; U16, 16; U17, 17; U18, 18; + U19, 19; U20, 20; U21, 21; U22, 22; U23, 23; U24, 24; U25, 25; U26, 26; U27, 27; U28, 28; U29, 29; U30, 30; U31, 31; U32, 32; U33, 33; U34, 34; U35, 35; U36, 36; U37, 37; + U38, 38; U39, 39; U40, 40; U41, 41; U42, 42; U43, 43; U44, 44; U45, 45; U46, 46; U47, 47; U48, 48; U49, 49; U50, 50; U51, 51; U52, 52; U53, 53; U54, 54; U55, 55; U56, 56; + U57, 57; U58, 58; U59, 59; U60, 60; U61, 61; U62, 62; U63, 63; U64, 64; U65, 65; U66, 66; U67, 67; U68, 68; U69, 69; U70, 70; U71, 71; U72, 72; U73, 73; U74, 74; U75, 75; + U76, 76; U77, 77; U78, 78; U79, 79; U80, 80; U81, 81; U82, 82; U83, 83; U84, 84; U85, 85; U86, 86; U87, 87; U88, 88; U89, 89; U90, 90; U91, 91; U92, 92; U93, 93; U94, 94; + U95, 95; U96, 96; U97, 97; U98, 98; U99, 99; U100, 100; U101, 101; U102, 102; U103, 103; U104, 104; U105, 105; U106, 106; U107, 107; U108, 108; U109, 109; U110, 110; + U111, 111; U112, 112; U113, 113; U114, 114; U115, 115; U116, 116; U117, 117; U118, 118; U119, 119; U120, 120; U121, 121; U122, 122; U123, 123; U124, 124; U125, 125; U126, 126; + U127, 127 ); From 1c9ac6616c6a8111a6d03f71e156facc6b2a94ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Mon, 18 Apr 2022 10:45:38 +0200 Subject: [PATCH 031/112] Readme: update sponsors --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa1e0904..62ab4759 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ And our gold sponsors:

- + + + +

\ No newline at end of file From 1e1135ef64007d188a6807d1602968c26cd89c72 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Mon, 21 Mar 2022 16:08:53 -0500 Subject: [PATCH 032/112] fix for `UnitComplex::slerp()` #1093 --- src/geometry/unit_complex.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index caf25493..eb6f4b74 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -410,7 +410,8 @@ where #[inline] #[must_use] pub fn slerp(&self, other: &Self, t: T) -> Self { - Self::new(self.angle() * (T::one() - t.clone()) + other.angle() * t) + let delta = other / self; + self * Self::new(delta.angle()*t) } } From c2cfe4a7ce6e208ee2ec30deb58d2cc7cd8da504 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sat, 26 Mar 2022 17:32:12 -0500 Subject: [PATCH 033/112] fixed cargo fmt error --- src/geometry/unit_complex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index eb6f4b74..efe0dac2 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -411,7 +411,7 @@ where #[must_use] pub fn slerp(&self, other: &Self, t: T) -> Self { let delta = other / self; - self * Self::new(delta.angle()*t) + self * Self::new(delta.angle() * t) } } From f4f73c365945c75e31170f7ca1b1999157a9464d Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 29 Mar 2022 13:38:10 -0500 Subject: [PATCH 034/112] added tests for complex and quaternion slerp pathing --- tests/geometry/rotation.rs | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 9a29772e..bb7aacb6 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -33,7 +33,9 @@ fn quaternion_euler_angles_issue_494() { #[cfg(feature = "proptest-support")] mod proptest_tests { use na::{self, Rotation2, Rotation3, Unit}; + use na::{UnitComplex, UnitQuaternion}; use simba::scalar::RealField; + use approx::AbsDiffEq; use std::f64; use crate::proptest::*; @@ -229,5 +231,74 @@ mod proptest_tests { prop_assert_eq!(r, Rotation3::identity()) } } + + // + //In general, `slerp(a,b,t)` should equal `(b/a)^t * a` even though in practice, + //we may not use that formula directly for complex numbers or quaternions + // + + #[test] + fn slerp_powf_agree_2(a in unit_complex(), b in unit_complex(), t in PROPTEST_F64) { + let z1 = a.slerp(&b, t); + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + + #[test] + fn slerp_powf_agree_3(a in unit_quaternion(), b in unit_quaternion(), t in PROPTEST_F64) { + if let Some(z1) = a.try_slerp(&b, t, f64::default_epsilon()) { + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + } + + // + //when not antipodal, slerp should always take the shortest path between two orientations + // + + #[test] + fn slerp_takes_shortest_path_2( + z in unit_complex(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if dtheta.abs() != f64::pi() { + + //make two complex numbers separated by an angle between -pi and pi + let (z1, z2) = (z, z * UnitComplex::new(dtheta)); + let z3 = z1.slerp(&z2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let a1 = z3.angle(); + let a2 = na::wrap(z1.angle() + dtheta*t, -f64::pi(), f64::pi()); + + prop_assert!(relative_eq!(a1, a2, epsilon=1e-10)); + } + + } + + #[test] + fn slerp_takes_shortest_path_3( + q in unit_quaternion(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if let Some(axis) = q.axis() { + + //make two quaternions separated by an angle between -pi and pi + let (q1, q2) = (q, q * UnitQuaternion::from_axis_angle(&axis, dtheta)); + let q3 = q1.slerp(&q2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let q4 = q1 * UnitQuaternion::from_axis_angle(&axis, dtheta*t); + prop_assert!(relative_eq!(q3, q4, epsilon=1e-10)); + + } + + } + + } } From 7ae70f599a87849601dd8a23cb85388209657fe4 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 29 Mar 2022 13:42:38 -0500 Subject: [PATCH 035/112] fixed cargo fmt --- tests/geometry/rotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index bb7aacb6..84bba676 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -32,10 +32,10 @@ fn quaternion_euler_angles_issue_494() { #[cfg(feature = "proptest-support")] mod proptest_tests { + use approx::AbsDiffEq; use na::{self, Rotation2, Rotation3, Unit}; use na::{UnitComplex, UnitQuaternion}; use simba::scalar::RealField; - use approx::AbsDiffEq; use std::f64; use crate::proptest::*; From 28fc867b73cd594c1f17b8cdfaa1dd3cf6163f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 28 Apr 2022 14:50:59 +0200 Subject: [PATCH 036/112] glam: add conversion from Vec2/3/4 to UnitVector2/3/4 + remove ambigous conversions --- src/third_party/glam/common/glam_isometry.rs | 64 ++------------- src/third_party/glam/common/glam_matrix.rs | 78 ++++++++++++++++++- .../glam/common/glam_unit_complex.rs | 2 +- src/third_party/glam/mod.rs | 2 - src/third_party/glam/v013/mod.rs | 18 ----- 5 files changed, 86 insertions(+), 78 deletions(-) delete mode 100644 src/third_party/glam/v013/mod.rs diff --git a/src/third_party/glam/common/glam_isometry.rs b/src/third_party/glam/common/glam_isometry.rs index 3a8d4961..5a528302 100644 --- a/src/third_party/glam/common/glam_isometry.rs +++ b/src/third_party/glam/common/glam_isometry.rs @@ -36,18 +36,18 @@ impl From> for (DVec3, DQuat) { } } -impl From> for (Vec3, Quat) { - fn from(iso: Isometry2) -> (Vec3, Quat) { - let tra = Vec3::new(iso.translation.x, iso.translation.y, 0.0); - let rot = Quat::from_axis_angle(Vec3::Z, iso.rotation.angle()); +impl From> for (Vec2, f32) { + fn from(iso: Isometry2) -> (Vec2, f32) { + let tra = Vec2::new(iso.translation.x, iso.translation.y); + let rot = iso.rotation.angle(); (tra, rot) } } -impl From> for (DVec3, DQuat) { - fn from(iso: Isometry2) -> (DVec3, DQuat) { - let tra = DVec3::new(iso.translation.x, iso.translation.y, 0.0); - let rot = DQuat::from_axis_angle(DVec3::Z, iso.rotation.angle()); +impl From> for (DVec2, f64) { + fn from(iso: Isometry2) -> (DVec2, f64) { + let tra = DVec2::new(iso.translation.x, iso.translation.y); + let rot = iso.rotation.angle(); (tra, rot) } } @@ -64,30 +64,6 @@ impl From<(DVec3, DQuat)> for Isometry3 { } } -impl From<(Vec3, Quat)> for Isometry2 { - fn from((tra, rot): (Vec3, Quat)) -> Self { - Isometry2::new([tra.x, tra.y].into(), rot.to_axis_angle().1) - } -} - -impl From<(DVec3, DQuat)> for Isometry2 { - fn from((tra, rot): (DVec3, DQuat)) -> Self { - Isometry2::new([tra.x, tra.y].into(), rot.to_axis_angle().1) - } -} - -impl From<(Vec2, Quat)> for Isometry2 { - fn from((tra, rot): (Vec2, Quat)) -> Self { - Isometry2::new(tra.into(), rot.to_axis_angle().1) - } -} - -impl From<(DVec2, DQuat)> for Isometry2 { - fn from((tra, rot): (DVec2, DQuat)) -> Self { - Isometry2::new(tra.into(), rot.to_axis_angle().1) - } -} - impl From<(Vec2, f32)> for Isometry2 { fn from((tra, rot): (Vec2, f32)) -> Self { Isometry2::new(tra.into(), rot) @@ -112,18 +88,6 @@ impl From for Isometry3 { } } -impl From for Isometry2 { - fn from(rot: Quat) -> Self { - Isometry2::new(Vector2::zeros(), rot.to_axis_angle().1) - } -} - -impl From for Isometry2 { - fn from(rot: DQuat) -> Self { - Isometry2::new(Vector2::zeros(), rot.to_axis_angle().1) - } -} - impl From for Isometry3 { fn from(tra: Vec3) -> Self { Isometry3::from_parts(tra.into(), UnitQuaternion::identity()) @@ -148,18 +112,6 @@ impl From for Isometry2 { } } -impl From for Isometry2 { - fn from(tra: Vec3) -> Self { - Isometry2::new([tra.x, tra.y].into(), 0.0) - } -} - -impl From for Isometry2 { - fn from(tra: DVec3) -> Self { - Isometry2::new([tra.x, tra.y].into(), 0.0) - } -} - impl TryFrom for Isometry2 { type Error = (); diff --git a/src/third_party/glam/common/glam_matrix.rs b/src/third_party/glam/common/glam_matrix.rs index 80f88054..fa9f713f 100644 --- a/src/third_party/glam/common/glam_matrix.rs +++ b/src/third_party/glam/common/glam_matrix.rs @@ -3,7 +3,11 @@ use super::glam::{ Mat4, UVec2, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec4, }; use crate::storage::RawStorage; -use crate::{Matrix, Matrix2, Matrix3, Matrix4, Vector, Vector2, Vector3, Vector4, U2, U3, U4}; +use crate::{ + Matrix, Matrix2, Matrix3, Matrix4, Unit, UnitVector2, UnitVector3, UnitVector4, Vector, + Vector2, Vector3, Vector4, U2, U3, U4, +}; +use std::convert::TryFrom; macro_rules! impl_vec_conversion( ($N: ty, $Vec2: ty, $Vec3: ty, $Vec4: ty) => { @@ -66,6 +70,63 @@ impl_vec_conversion!(i32, IVec2, IVec3, IVec4); impl_vec_conversion!(u32, UVec2, UVec3, UVec4); impl_vec_conversion!(bool, BVec2, BVec3, BVec4); +const ERR: &'static str = "Normalization failed."; + +macro_rules! impl_unit_vec_conversion( + ($N: ty, $Vec2: ty, $Vec3: ty, $Vec4: ty) => { + impl TryFrom<$Vec2> for UnitVector2<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec2) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec2 + { + #[inline] + fn from(e: UnitVector2<$N>) -> $Vec2 { + e.into_inner().into() + } + } + + impl TryFrom<$Vec3> for UnitVector3<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec3) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec3 + { + #[inline] + fn from(e: UnitVector3<$N>) -> $Vec3 { + e.into_inner().into() + } + } + + impl TryFrom<$Vec4> for UnitVector4<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec4) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec4 + { + #[inline] + fn from(e: UnitVector4<$N>) -> $Vec4 { + e.into_inner().into() + } + } + } +); + +impl_unit_vec_conversion!(f32, Vec2, Vec3, Vec4); +impl_unit_vec_conversion!(f64, DVec2, DVec3, DVec4); + impl From for Vector3 { #[inline] fn from(e: Vec3A) -> Vector3 { @@ -83,6 +144,21 @@ where } } +impl TryFrom for UnitVector3 { + type Error = &'static str; + #[inline] + fn try_from(e: Vec3A) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } +} + +impl From> for Vec3A { + #[inline] + fn from(e: UnitVector3) -> Vec3A { + e.into_inner().into() + } +} + impl From for Matrix2 { #[inline] fn from(e: Mat2) -> Matrix2 { diff --git a/src/third_party/glam/common/glam_unit_complex.rs b/src/third_party/glam/common/glam_unit_complex.rs index 7ee6fc65..d44a2f09 100644 --- a/src/third_party/glam/common/glam_unit_complex.rs +++ b/src/third_party/glam/common/glam_unit_complex.rs @@ -1,4 +1,4 @@ -use super::glam::{DMat2, Mat2}; +use super::glam::{DMat2, DQuat, DVec3, Mat2, Quat, Vec3}; use crate::{Complex, UnitComplex}; impl From> for Mat2 { diff --git a/src/third_party/glam/mod.rs b/src/third_party/glam/mod.rs index d24ff7e5..ae2c4514 100644 --- a/src/third_party/glam/mod.rs +++ b/src/third_party/glam/mod.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "glam013")] -mod v013; #[cfg(feature = "glam014")] mod v014; #[cfg(feature = "glam015")] diff --git a/src/third_party/glam/v013/mod.rs b/src/third_party/glam/v013/mod.rs deleted file mode 100644 index 4787fb21..00000000 --- a/src/third_party/glam/v013/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[path = "../common/glam_isometry.rs"] -mod glam_isometry; -#[path = "../common/glam_matrix.rs"] -mod glam_matrix; -#[path = "../common/glam_point.rs"] -mod glam_point; -#[path = "../common/glam_quaternion.rs"] -mod glam_quaternion; -#[path = "../common/glam_rotation.rs"] -mod glam_rotation; -#[path = "../common/glam_similarity.rs"] -mod glam_similarity; -#[path = "../common/glam_translation.rs"] -mod glam_translation; -#[path = "../common/glam_unit_complex.rs"] -mod glam_unit_complex; - -pub(self) use glam013 as glam; From d1c72f0686edfbc138940a48321580efb82e2390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 28 Apr 2022 14:58:15 +0200 Subject: [PATCH 037/112] Fix warnings --- Cargo.toml | 2 -- nalgebra-glm/Cargo.toml | 1 - src/third_party/glam/common/glam_isometry.rs | 2 +- src/third_party/glam/common/glam_unit_complex.rs | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a3fea5c..a629b554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ cuda = [ "cust_core", "simba/cuda" ] # Conversion convert-mint = [ "mint" ] convert-bytemuck = [ "bytemuck" ] -convert-glam013 = [ "glam013" ] convert-glam014 = [ "glam014" ] convert-glam015 = [ "glam015" ] convert-glam016 = [ "glam016" ] @@ -88,7 +87,6 @@ pest_derive = { version = "2", optional = true } bytemuck = { version = "1.5", optional = true } matrixcompare-core = { version = "0.1", optional = true } proptest = { version = "1", optional = true, default-features = false, features = ["std"] } -glam013 = { package = "glam", version = "0.13", optional = true } glam014 = { package = "glam", version = "0.14", optional = true } glam015 = { package = "glam", version = "0.15", optional = true } glam016 = { package = "glam", version = "0.16", optional = true } diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index f8087581..adf05fa3 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -26,7 +26,6 @@ cuda = [ "nalgebra/cuda" ] # Conversion convert-mint = [ "nalgebra/mint" ] convert-bytemuck = [ "nalgebra/bytemuck" ] -convert-glam013 = [ "nalgebra/glam013" ] convert-glam014 = [ "nalgebra/glam014" ] convert-glam015 = [ "nalgebra/glam015" ] convert-glam016 = [ "nalgebra/glam016" ] diff --git a/src/third_party/glam/common/glam_isometry.rs b/src/third_party/glam/common/glam_isometry.rs index 5a528302..7b188c39 100644 --- a/src/third_party/glam/common/glam_isometry.rs +++ b/src/third_party/glam/common/glam_isometry.rs @@ -1,5 +1,5 @@ use super::glam::{DMat3, DMat4, DQuat, DVec2, DVec3, Mat3, Mat4, Quat, Vec2, Vec3}; -use crate::{Isometry2, Isometry3, Matrix3, Matrix4, Translation3, UnitQuaternion, Vector2}; +use crate::{Isometry2, Isometry3, Matrix3, Matrix4, Translation3, UnitQuaternion}; use std::convert::TryFrom; impl From> for Mat3 { diff --git a/src/third_party/glam/common/glam_unit_complex.rs b/src/third_party/glam/common/glam_unit_complex.rs index d44a2f09..7ee6fc65 100644 --- a/src/third_party/glam/common/glam_unit_complex.rs +++ b/src/third_party/glam/common/glam_unit_complex.rs @@ -1,4 +1,4 @@ -use super::glam::{DMat2, DQuat, DVec3, Mat2, Quat, Vec3}; +use super::glam::{DMat2, Mat2}; use crate::{Complex, UnitComplex}; impl From> for Mat2 { From 372152dc31850aa23b74efa7507e020c81bc32d0 Mon Sep 17 00:00:00 2001 From: metric-space Date: Tue, 18 Jan 2022 22:35:11 -0500 Subject: [PATCH 038/112] First attempt at xgges (qz decomposition), passing tests. Serialization failing across many modules --- nalgebra-lapack/src/lib.rs | 2 + nalgebra-lapack/src/qz.rs | 288 ++++++++++++++++++++++++++++ nalgebra-lapack/tests/linalg/mod.rs | 1 + nalgebra-lapack/tests/linalg/qz.rs | 27 +++ 4 files changed, 318 insertions(+) create mode 100644 nalgebra-lapack/src/qz.rs create mode 100644 nalgebra-lapack/tests/linalg/qz.rs diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs index 9cf0d73d..e89ab160 100644 --- a/nalgebra-lapack/src/lib.rs +++ b/nalgebra-lapack/src/lib.rs @@ -86,6 +86,7 @@ mod eigen; mod hessenberg; mod lu; mod qr; +mod qz; mod schur; mod svd; mod symmetric_eigen; @@ -97,6 +98,7 @@ pub use self::eigen::Eigen; pub use self::hessenberg::Hessenberg; pub use self::lu::{LUScalar, LU}; pub use self::qr::QR; +pub use self::qz::QZ; pub use self::schur::Schur; pub use self::svd::SVD; pub use self::symmetric_eigen::SymmetricEigen; diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs new file mode 100644 index 00000000..47efc08c --- /dev/null +++ b/nalgebra-lapack/src/qz.rs @@ -0,0 +1,288 @@ +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + +use num::Zero; +use num_complex::Complex; + +use simba::scalar::RealField; + +use crate::ComplexHelper; +use na::allocator::Allocator; +use na::dimension::{Const, Dim}; +use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; + +use lapack; + +/// Eigendecomposition of a real square matrix with complex eigenvalues. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(serialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Serialize") + ) +)] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(deserialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Deserialize<'de>") + ) +)] +#[derive(Clone, Debug)] +pub struct QZ +where + DefaultAllocator: Allocator + Allocator, +{ + alphar: OVector, + alphai: OVector, + beta: OVector, + vsl: OMatrix, + s: OMatrix, + vsr: OMatrix, + t: OMatrix +} + +impl Copy for QZ +where + DefaultAllocator: Allocator + Allocator, + OMatrix: Copy, + OVector: Copy, +{ +} + +impl QZ +where + DefaultAllocator: Allocator + Allocator, +{ + /// Computes the eigenvalues and real Schur form of the matrix `m`. + /// + /// Panics if the method did not converge. + pub fn new(a: OMatrix, b: OMatrix) -> Self { + Self::try_new(a,b).expect("Schur decomposition: convergence failed.") + } + + /// Computes the eigenvalues and real Schur form of the matrix `m`. + /// + /// Returns `None` if the method did not converge. + pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { + assert!( + a.is_square() && b.is_square(), + "Unable to compute the qz decomposition of non-square matrices." + ); + + // another assert to compare shape? + + let (nrows, ncols) = a.shape_generic(); + let n = nrows.value(); + + let lda = n as i32; + let ldb = lda.clone(); + + let mut info = 0; + + let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); + let mut alphai = Matrix::zeros_generic(nrows, Const::<1>); + let mut beta = Matrix::zeros_generic(nrows, Const::<1>); + let mut vsl = Matrix::zeros_generic(nrows, ncols); + let mut vsr = Matrix::zeros_generic(nrows, ncols); + // Placeholders: + let mut bwork = [0i32]; + let mut unused = 0; + + let lwork = T::xgges_work_size( + b'V', + b'V', + b'N', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + &mut unused, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut bwork, + &mut info, + ); + lapack_check!(info); + + let mut work = vec![T::zero(); lwork as usize]; + + T::xgges( + b'V', + b'V', + b'N', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + &mut unused, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut work, + lwork, + &mut bwork, + &mut info, + ); + lapack_check!(info); + + Some(QZ {alphar, alphai, beta, + vsl, s:a, + vsr, t:b}) + } + + /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the + /// decomposed matrix equals `Q * T * Q.transpose()`. + pub fn unpack(self) -> (OMatrix, OMatrix, OMatrix, OMatrix){ + (self.vsl, self.s, self.t, self.vsr) + } + + /// computes the generalized eigenvalues + #[must_use] + pub fn eigenvalues(&self) -> OVector, D> + where + DefaultAllocator: Allocator, D>, + { + let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); + + for i in 0..out.len() { + out[i] = Complex::new(self.alphar[i].clone()/self.beta[i].clone(), + self.alphai[i].clone()/self.beta[i].clone()) + } + + out + } +} + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the RealField QZ decomposition. +pub trait QZScalar: Scalar { + #[allow(missing_docs)] + fn xgges( + jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta : &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + work: &mut [Self], + lwork: i32, + bwork: &mut [i32], + info: &mut i32 + ); + + #[allow(missing_docs)] + fn xgges_work_size( + jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta : &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + bwork: &mut [i32], + info: &mut i32 + ) -> i32; +} + +macro_rules! real_eigensystem_scalar_impl ( + ($N: ty, $xgges: path) => ( + impl QZScalar for $N { + #[inline] + fn xgges(jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + sdim: &mut i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + work: &mut [$N], + lwork: i32, + bwork: &mut [i32], + info: &mut i32) { + unsafe { $xgges(jobvsl, jobvsr, sort, None, n, a, lda, b, ldb, sdim, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, work, lwork, bwork, info); } + } + + + #[inline] + fn xgges_work_size(jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + sdim: &mut i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + bwork: &mut [i32], + info: &mut i32) + -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + unsafe { $xgges(jobvsl, jobvsr, sort, None, n, a, lda, b, ldb, sdim, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, &mut work, lwork, bwork, info); } + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +real_eigensystem_scalar_impl!(f32, lapack::sgges); +real_eigensystem_scalar_impl!(f64, lapack::dgges); diff --git a/nalgebra-lapack/tests/linalg/mod.rs b/nalgebra-lapack/tests/linalg/mod.rs index a6742217..a4043469 100644 --- a/nalgebra-lapack/tests/linalg/mod.rs +++ b/nalgebra-lapack/tests/linalg/mod.rs @@ -1,6 +1,7 @@ mod cholesky; mod lu; mod qr; +mod qz; mod real_eigensystem; mod schur; mod svd; diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs new file mode 100644 index 00000000..6b4efbda --- /dev/null +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -0,0 +1,27 @@ +use na::DMatrix; +use nl::QZ; +use std::cmp; + +use crate::proptest::*; +use proptest::{prop_assert, proptest}; + +proptest! { + #[test] + fn qz(n in PROPTEST_MATRIX_DIM) { + let n = cmp::max(1, cmp::min(n, 10)); + let a = DMatrix::::new_random(n, n); + let b = DMatrix::::new_random(n, n); + + let (vsl,s,t,vsr) = QZ::new(a.clone(), b.clone()).unpack(); + + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)) + } + + #[test] + fn qz_static(a in matrix4(), b in matrix4()) { + let (vsl,s,t,vsr) = QZ::new(a.clone(), b.clone()).unpack(); + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)) + } +} From ccbd0f02e56ba257e4ed5f7486602c6bc6fefe1b Mon Sep 17 00:00:00 2001 From: metric-space Date: Tue, 18 Jan 2022 22:42:12 -0500 Subject: [PATCH 039/112] Format file --- nalgebra-lapack/src/qz.rs | 113 +++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index 47efc08c..d6abac22 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -38,11 +38,11 @@ where { alphar: OVector, alphai: OVector, - beta: OVector, - vsl: OMatrix, - s: OMatrix, - vsr: OMatrix, - t: OMatrix + beta: OVector, + vsl: OMatrix, + s: OMatrix, + vsr: OMatrix, + t: OMatrix, } impl Copy for QZ @@ -61,7 +61,7 @@ where /// /// Panics if the method did not converge. pub fn new(a: OMatrix, b: OMatrix) -> Self { - Self::try_new(a,b).expect("Schur decomposition: convergence failed.") + Self::try_new(a, b).expect("Schur decomposition: convergence failed.") } /// Computes the eigenvalues and real Schur form of the matrix `m`. @@ -85,9 +85,9 @@ where let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); let mut alphai = Matrix::zeros_generic(nrows, Const::<1>); - let mut beta = Matrix::zeros_generic(nrows, Const::<1>); - let mut vsl = Matrix::zeros_generic(nrows, ncols); - let mut vsr = Matrix::zeros_generic(nrows, ncols); + let mut beta = Matrix::zeros_generic(nrows, Const::<1>); + let mut vsl = Matrix::zeros_generic(nrows, ncols); + let mut vsr = Matrix::zeros_generic(nrows, ncols); // Placeholders: let mut bwork = [0i32]; let mut unused = 0; @@ -140,14 +140,27 @@ where ); lapack_check!(info); - Some(QZ {alphar, alphai, beta, - vsl, s:a, - vsr, t:b}) + Some(QZ { + alphar, + alphai, + beta, + vsl, + s: a, + vsr, + t: b, + }) } /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the /// decomposed matrix equals `Q * T * Q.transpose()`. - pub fn unpack(self) -> (OMatrix, OMatrix, OMatrix, OMatrix){ + pub fn unpack( + self, + ) -> ( + OMatrix, + OMatrix, + OMatrix, + OMatrix, + ) { (self.vsl, self.s, self.t, self.vsr) } @@ -160,8 +173,10 @@ where let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); for i in 0..out.len() { - out[i] = Complex::new(self.alphar[i].clone()/self.beta[i].clone(), - self.alphai[i].clone()/self.beta[i].clone()) + out[i] = Complex::new( + self.alphar[i].clone() / self.beta[i].clone(), + self.alphai[i].clone() / self.beta[i].clone(), + ) } out @@ -177,50 +192,50 @@ where pub trait QZScalar: Scalar { #[allow(missing_docs)] fn xgges( - jobvsl: u8, - jobvsr: u8, - sort: u8, + jobvsl: u8, + jobvsr: u8, + sort: u8, // select: ??? - n: i32, - a: &mut [Self], - lda: i32, - b: &mut [Self], - ldb: i32, - sdim: &mut i32, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, alphar: &mut [Self], alphai: &mut [Self], - beta : &mut [Self], - vsl: &mut [Self], - ldvsl: i32, - vsr: &mut [Self], - ldvsr: i32, - work: &mut [Self], - lwork: i32, - bwork: &mut [i32], - info: &mut i32 + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + work: &mut [Self], + lwork: i32, + bwork: &mut [i32], + info: &mut i32, ); #[allow(missing_docs)] fn xgges_work_size( - jobvsl: u8, - jobvsr: u8, - sort: u8, + jobvsl: u8, + jobvsr: u8, + sort: u8, // select: ??? - n: i32, - a: &mut [Self], - lda: i32, - b: &mut [Self], - ldb: i32, - sdim: &mut i32, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, alphar: &mut [Self], alphai: &mut [Self], - beta : &mut [Self], - vsl: &mut [Self], - ldvsl: i32, - vsr: &mut [Self], - ldvsr: i32, - bwork: &mut [i32], - info: &mut i32 + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + bwork: &mut [i32], + info: &mut i32, ) -> i32; } From ebe6d10a471141576af520fcad65e29fb94801e6 Mon Sep 17 00:00:00 2001 From: metric-space Date: Wed, 19 Jan 2022 02:42:22 -0500 Subject: [PATCH 040/112] Comments more tailored to QZ --- nalgebra-lapack/src/qz.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index d6abac22..06ce0ba3 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -13,7 +13,7 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; -/// Eigendecomposition of a real square matrix with complex eigenvalues. +/// Generalized eigendecomposition of a pair of N*N square matrices. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize", @@ -57,14 +57,15 @@ impl QZ where DefaultAllocator: Allocator + Allocator, { - /// Computes the eigenvalues and real Schur form of the matrix `m`. + /// Attempts to compute the QZ decomposition of input square matrices `a` and `b`. /// /// Panics if the method did not converge. pub fn new(a: OMatrix, b: OMatrix) -> Self { - Self::try_new(a, b).expect("Schur decomposition: convergence failed.") + Self::try_new(a, b).expect("QZ decomposition: convergence failed.") } - /// Computes the eigenvalues and real Schur form of the matrix `m`. + /// Computes the decomposition of input matrices `a` and `b` into a pair of matrices of Schur vectors + /// , a quasi-upper triangular matrix and an upper-triangular matrix . /// /// Returns `None` if the method did not converge. pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { @@ -73,14 +74,14 @@ where "Unable to compute the qz decomposition of non-square matrices." ); - // another assert to compare shape? + assert!( + a.shape_generic() == b.shape_generic(), + "Unable to compute the qz decomposition of two square matrices of different dimensions." + ); let (nrows, ncols) = a.shape_generic(); let n = nrows.value(); - let lda = n as i32; - let ldb = lda.clone(); - let mut info = 0; let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); @@ -151,8 +152,10 @@ where }) } - /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the - /// decomposed matrix equals `Q * T * Q.transpose()`. + /// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) + /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the + /// decomposed matrix `A` equals `VSL * S * VSL.transpose()` and + /// decomposed matrix `B` equals `VSL * T * VSL.transpose()`. pub fn unpack( self, ) -> ( From d7a0e415bd9a5550b3a2d724858f73a4f329c455 Mon Sep 17 00:00:00 2001 From: metric-space Date: Wed, 19 Jan 2022 21:47:44 -0500 Subject: [PATCH 041/112] Add non-naive way of calculate generalized eigenvalue, write spotty test for generalized eigenvalues --- nalgebra-lapack/src/qz.rs | 15 +++++++++++---- nalgebra-lapack/tests/linalg/qz.rs | 18 ++++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index 06ce0ba3..b02a095f 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -176,10 +176,17 @@ where let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); for i in 0..out.len() { - out[i] = Complex::new( - self.alphar[i].clone() / self.beta[i].clone(), - self.alphai[i].clone() / self.beta[i].clone(), - ) + let b = self.beta[i].clone(); + out[i] = { + if b < T::RealField::zero() { + Complex::::zero() + } else { + Complex::new( + self.alphar[i].clone() / b.clone(), + self.alphai[i].clone() / b.clone(), + ) + } + } } out diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index 6b4efbda..2b7730a7 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -1,5 +1,6 @@ -use na::DMatrix; +use na::{zero, DMatrix, Normed}; use nl::QZ; +use num_complex::Complex; use std::cmp; use crate::proptest::*; @@ -12,10 +13,19 @@ proptest! { let a = DMatrix::::new_random(n, n); let b = DMatrix::::new_random(n, n); - let (vsl,s,t,vsr) = QZ::new(a.clone(), b.clone()).unpack(); + let qz = QZ::new(a.clone(), b.clone()); + let (vsl,s,t,vsr) = qz.clone().unpack(); + let eigenvalues = qz.eigenvalues(); + let a_c = a.clone().map(|x| Complex::new(x, zero::())); - prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); - prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)) + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a.clone(), epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b.clone(), epsilon = 1.0e-7)); + // spotty test that skips over the first eiegenvalue which in some cases is extremely large relative to the other ones + // and fails the condition + for i in 1..n { + let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); + prop_assert!(relative_eq!((&a_c - &b_c).determinant().norm(), 0.0, epsilon = 1.0e-6)); + } } #[test] From abd463f42790ae06e017a8d368c945d8800655ff Mon Sep 17 00:00:00 2001 From: metric-space Date: Wed, 19 Jan 2022 23:51:46 -0500 Subject: [PATCH 042/112] Commented out failing tests, refactored checks for almost zeroes --- nalgebra-lapack/src/qz.rs | 28 ++++++++++++++++++--------- nalgebra-lapack/tests/linalg/qz.rs | 31 ++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index b02a095f..477ddfb7 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -176,16 +176,26 @@ where let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); for i in 0..out.len() { - let b = self.beta[i].clone(); - out[i] = { - if b < T::RealField::zero() { - Complex::::zero() + out[i] = if self.beta[i].clone() < T::RealField::default_epsilon() { + Complex::zero() + } else { + let mut cr = self.alphar[i].clone(); + let mut ci = self.alphai[i].clone(); + let b = self.beta[i].clone(); + + if cr < T::RealField::default_epsilon() { + cr = T::RealField::zero() } else { - Complex::new( - self.alphar[i].clone() / b.clone(), - self.alphai[i].clone() / b.clone(), - ) - } + cr = cr / b.clone() + }; + + if ci < T::RealField::default_epsilon() { + ci = T::RealField::zero() + } else { + ci = ci / b + }; + + Complex::new(cr, ci) } } diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index 2b7730a7..84a7b030 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -1,6 +1,7 @@ -use na::{zero, DMatrix, Normed}; +use na::{zero, DMatrix, SMatrix}; use nl::QZ; use num_complex::Complex; +use simba::scalar::ComplexField; use std::cmp; use crate::proptest::*; @@ -15,23 +16,33 @@ proptest! { let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.clone().unpack(); - let eigenvalues = qz.eigenvalues(); - let a_c = a.clone().map(|x| Complex::new(x, zero::())); + //let eigenvalues = qz.eigenvalues(); + //let a_c = a.clone().map(|x| Complex::new(x, zero::())); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a.clone(), epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b.clone(), epsilon = 1.0e-7)); - // spotty test that skips over the first eiegenvalue which in some cases is extremely large relative to the other ones + // spotty test that skips over the first eigenvalue which in some cases is extremely large relative to the other ones // and fails the condition - for i in 1..n { - let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); - prop_assert!(relative_eq!((&a_c - &b_c).determinant().norm(), 0.0, epsilon = 1.0e-6)); - } + //for i in 1..n { + // let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); + // prop_assert!(relative_eq!((&a_c - &b_c).determinant().modulus(), 0.0, epsilon = 1.0e-6)); + //} } #[test] fn qz_static(a in matrix4(), b in matrix4()) { - let (vsl,s,t,vsr) = QZ::new(a.clone(), b.clone()).unpack(); + let qz = QZ::new(a.clone(), b.clone()); + let (vsl,s,t,vsr) = qz.unpack(); + //let eigenvalues = qz.eigenvalues(); + //let a_c = a.clone().map(|x| Complex::new(x, zero::())); + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); - prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)) + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); + + //for i in 0..4 { + // let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); + // println!("{}",eigenvalues); + // prop_assert!(relative_eq!((&a_c - &b_c).determinant().modulus(), 0.0, epsilon = 1.0e-4)) + //} } } From 2947d63a0c00c4669fc64f160742d5388d865991 Mon Sep 17 00:00:00 2001 From: metric-space Date: Fri, 21 Jan 2022 06:41:06 -0500 Subject: [PATCH 043/112] Correction for not calculating absolurte value --- nalgebra-lapack/src/qz.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index 477ddfb7..e3319452 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -167,6 +167,8 @@ where (self.vsl, self.s, self.t, self.vsr) } + + /// computes the generalized eigenvalues #[must_use] pub fn eigenvalues(&self) -> OVector, D> @@ -176,20 +178,20 @@ where let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); for i in 0..out.len() { - out[i] = if self.beta[i].clone() < T::RealField::default_epsilon() { + out[i] = if self.beta[i].clone().abs() < T::RealField::default_epsilon() { Complex::zero() } else { let mut cr = self.alphar[i].clone(); let mut ci = self.alphai[i].clone(); let b = self.beta[i].clone(); - if cr < T::RealField::default_epsilon() { + if cr.clone().abs() < T::RealField::default_epsilon() { cr = T::RealField::zero() } else { cr = cr / b.clone() }; - if ci < T::RealField::default_epsilon() { + if ci.clone().abs() < T::RealField::default_epsilon() { ci = T::RealField::zero() } else { ci = ci / b From 23950400e42200e2a6e4320f3779d69010186eb7 Mon Sep 17 00:00:00 2001 From: metric-space Date: Mon, 24 Jan 2022 23:56:44 -0500 Subject: [PATCH 044/112] New wrapper for generalized eigenvalues and associated eigenvectors via LAPACK routines sggev/dggev --- .../src/generalized_eigenvalues.rs | 339 ++++++++++++++++++ nalgebra-lapack/src/lib.rs | 2 + .../tests/linalg/generalized_eigenvalues.rs | 59 +++ nalgebra-lapack/tests/linalg/mod.rs | 1 + 4 files changed, 401 insertions(+) create mode 100644 nalgebra-lapack/src/generalized_eigenvalues.rs create mode 100644 nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs new file mode 100644 index 00000000..6332f2db --- /dev/null +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -0,0 +1,339 @@ +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + +use num::Zero; +use num_complex::Complex; + +use simba::scalar:: RealField; + +use crate::ComplexHelper; +use na::allocator::Allocator; +use na::dimension::{Const, Dim}; +use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; + +use lapack; + +/// Generalized eigenvalues and generalized eigenvectors(left and right) of a pair of N*N square matrices. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(serialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Serialize") + ) +)] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(deserialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Deserialize<'de>") + ) +)] +#[derive(Clone, Debug)] +pub struct GE +where + DefaultAllocator: Allocator + Allocator, +{ + alphar: OVector, + alphai: OVector, + beta: OVector, + vsl: OMatrix, + vsr: OMatrix, +} + +impl Copy for GE +where + DefaultAllocator: Allocator + Allocator, + OMatrix: Copy, + OVector: Copy, +{ +} + +impl GE +where + DefaultAllocator: Allocator + Allocator, +{ + /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's + /// dggev and sggev routines + /// + /// For each e in generalized eigenvalues and the associated eigenvectors e_l and e_r (left andf right) + /// it satisfies e_l*a = e*e_l*b and a*e_r = e*b*e_r + /// + /// Panics if the method did not converge. + pub fn new(a: OMatrix, b: OMatrix) -> Self { + Self::try_new(a, b).expect("Calculation of generalized eigenvalues failed.") + } + + /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's + /// dggev and sggev routines + /// + /// For each e in generalized eigenvalues and the associated eigenvectors e_l and e_r (left andf right) + /// it satisfies e_l*a = e*e_l*b and a*e_r = e*b*e_r + /// + /// Returns `None` if the method did not converge. + pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { + assert!( + a.is_square() && b.is_square(), + "Unable to compute the generalized eigenvalues of non-square matrices." + ); + + assert!( + a.shape_generic() == b.shape_generic(), + "Unable to compute the generalized eigenvalues of two square matrices of different dimensions." + ); + + let (nrows, ncols) = a.shape_generic(); + let n = nrows.value(); + + let mut info = 0; + + let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); + let mut alphai = Matrix::zeros_generic(nrows, Const::<1>); + let mut beta = Matrix::zeros_generic(nrows, Const::<1>); + let mut vsl = Matrix::zeros_generic(nrows, ncols); + let mut vsr = Matrix::zeros_generic(nrows, ncols); + + let lwork = T::xggev_work_size( + b'V', + b'V', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut info, + ); + lapack_check!(info); + + let mut work = vec![T::zero(); lwork as usize]; + + T::xggev( + b'V', + b'V', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut work, + lwork, + &mut info, + ); + lapack_check!(info); + + Some(GE { + alphar, + alphai, + beta, + vsl, + vsr, + }) + } + + /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues + pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) + where + DefaultAllocator: Allocator, D, D> + Allocator, D>, + { + let n = self.vsl.shape().0; + let mut l = self + .vsl + .clone() + .map(|x| Complex::new(x, T::RealField::zero())); + let mut r = self + .vsr + .clone() + .map(|x| Complex::new(x, T::RealField::zero())); + + let eigenvalues = &self.eigenvalues(); + + let mut ll; + let mut c = 0; + while c < n { + if eigenvalues[c].im.abs() > T::RealField::default_epsilon() && c + 1 < n && { + let e_conj = eigenvalues[c].conj(); + let e = eigenvalues[c + 1]; + ((e_conj.re - e.re).abs() < T::RealField::default_epsilon()) + && ((e_conj.im - e.im).abs() < T::RealField::default_epsilon()) + } { + ll = l.column(c + 1).into_owned(); + l.column_mut(c).zip_apply(&ll, |r, i| { + *r = Complex::new(r.re.clone(), i.re); + }); + ll.copy_from(&l.column(c)); + l.column_mut(c + 1).zip_apply(&ll, |r, i| { + *r = i.conj(); + }); + + ll.copy_from(&r.column(c + 1)); + r.column_mut(c).zip_apply(&ll, |r, i| { + *r = Complex::new(r.re, i.re); + }); + ll.copy_from(&r.column(c)); + r.column_mut(c + 1).zip_apply(&ll, |r, i| { + *r = i.conj(); + }); + + c += 2; + } else { + c += 1; + } + } + + (l, r) + } + + /// computes the generalized eigenvalues + #[must_use] + pub fn eigenvalues(&self) -> OVector, D> + where + DefaultAllocator: Allocator, D>, + { + let mut out = Matrix::zeros_generic(self.vsl.shape_generic().0, Const::<1>); + + for i in 0..out.len() { + out[i] = if self.beta[i].clone().abs() < T::RealField::default_epsilon() { + Complex::zero() + } else { + let mut cr = self.alphar[i].clone(); + let mut ci = self.alphai[i].clone(); + let b = self.beta[i].clone(); + + if cr.clone().abs() < T::RealField::default_epsilon() { + cr = T::RealField::zero() + } else { + cr = cr / b.clone() + }; + + if ci.clone().abs() < T::RealField::default_epsilon() { + ci = T::RealField::zero() + } else { + ci = ci / b + }; + + Complex::new(cr, ci) + } + } + + out + } +} + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the RealField GE decomposition. +pub trait GEScalar: Scalar { + #[allow(missing_docs)] + fn xggev( + jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + work: &mut [Self], + lwork: i32, + info: &mut i32, + ); + + #[allow(missing_docs)] + fn xggev_work_size( + jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + info: &mut i32, + ) -> i32; +} + +macro_rules! real_eigensystem_scalar_impl ( + ($N: ty, $xggev: path) => ( + impl GEScalar for $N { + #[inline] + fn xggev(jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + work: &mut [$N], + lwork: i32, + info: &mut i32) { + unsafe { $xggev(jobvsl, jobvsr, n, a, lda, b, ldb, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, work, lwork, info); } + } + + + #[inline] + fn xggev_work_size(jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + info: &mut i32) + -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + unsafe { $xggev(jobvsl, jobvsr, n, a, lda, b, ldb, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, &mut work, lwork, info); } + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +real_eigensystem_scalar_impl!(f32, lapack::sggev); +real_eigensystem_scalar_impl!(f64, lapack::dggev); diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs index e89ab160..dec4daac 100644 --- a/nalgebra-lapack/src/lib.rs +++ b/nalgebra-lapack/src/lib.rs @@ -87,6 +87,7 @@ mod hessenberg; mod lu; mod qr; mod qz; +mod generalized_eigenvalues; mod schur; mod svd; mod symmetric_eigen; @@ -99,6 +100,7 @@ pub use self::hessenberg::Hessenberg; pub use self::lu::{LUScalar, LU}; pub use self::qr::QR; pub use self::qz::QZ; +pub use self::generalized_eigenvalues::GE; pub use self::schur::Schur; pub use self::svd::SVD; pub use self::symmetric_eigen::SymmetricEigen; diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs new file mode 100644 index 00000000..275691c8 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -0,0 +1,59 @@ +use na::dimension::{Const, Dynamic}; +use na::{DMatrix, EuclideanNorm, Norm, OMatrix}; +use nl::GE; +use num_complex::Complex; +use simba::scalar::ComplexField; +use std::cmp; + +use crate::proptest::*; +use proptest::{prop_assert, proptest}; + +proptest! { + #[test] + fn ge(n in PROPTEST_MATRIX_DIM) { + let n = cmp::max(1, cmp::min(n, 10)); + let a = DMatrix::::new_random(n, n); + let b = DMatrix::::new_random(n, n); + let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); + let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); + + if a_condition_no.unwrap_or(200000.0) < 10.0 && b_condition_no.unwrap_or(200000.0) < 10.0 { + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + + let ge = GE::new(a.clone(), b.clone()); + let (vsl,vsr) = ge.clone().eigenvectors(); + let eigenvalues = ge.clone().eigenvalues(); + + for i in 0..n { + let left_eigenvector = &vsl.column(i); + prop_assert!(relative_eq!((left_eigenvector.transpose()*&a_c - left_eigenvector.transpose()*&b_c*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<1>,Dynamic::new(n)) ,epsilon = 1.0e-7)); + + let right_eigenvector = &vsr.column(i); + prop_assert!(relative_eq!((&a_c*right_eigenvector - &b_c*right_eigenvector*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Dynamic::new(n), Const::<1>) ,epsilon = 1.0e-7)); + }; + }; + } + + #[test] + fn ge_static(a in matrix4(), b in matrix4()) { + let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); + let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); + + if a_condition_no.unwrap_or(200000.0) < 10.0 && b_condition_no.unwrap_or(200000.0) < 10.0{ + let ge = GE::new(a.clone(), b.clone()); + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let (vsl,vsr) = ge.eigenvectors(); + let eigenvalues = ge.eigenvalues(); + + for i in 0..4 { + let left_eigenvector = &vsl.column(i); + prop_assert!(relative_eq!((left_eigenvector.transpose()*&a_c - left_eigenvector.transpose()*&b_c*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<1>,Const::<4>) ,epsilon = 1.0e-7)); + + let right_eigenvector = &vsr.column(i); + prop_assert!(relative_eq!((&a_c*right_eigenvector - &b_c*right_eigenvector*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<4>, Const::<1>) ,epsilon = 1.0e-7)); + }; + }; + } +} diff --git a/nalgebra-lapack/tests/linalg/mod.rs b/nalgebra-lapack/tests/linalg/mod.rs index a4043469..9fd539c4 100644 --- a/nalgebra-lapack/tests/linalg/mod.rs +++ b/nalgebra-lapack/tests/linalg/mod.rs @@ -2,6 +2,7 @@ mod cholesky; mod lu; mod qr; mod qz; +mod generalized_eigenvalues; mod real_eigensystem; mod schur; mod svd; From a439121641d4eb25c91e3204fbf15cb310bcda4c Mon Sep 17 00:00:00 2001 From: metric-space Date: Mon, 24 Jan 2022 23:58:21 -0500 Subject: [PATCH 045/112] Cleanup of QZ module and added GE's calculation of eigenvalues as a test for QZ's calculation of eigenvalues --- nalgebra-lapack/src/qz.rs | 17 +++++++++++----- nalgebra-lapack/tests/linalg/qz.rs | 31 ++++++++++++------------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index e3319452..ea775ea6 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -13,7 +13,11 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; -/// Generalized eigendecomposition of a pair of N*N square matrices. +/// QZ decomposition of a pair of N*N square matrices. +/// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) +/// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the +/// decomposed input matrix `a` equals `VSL * S * VSL.transpose()` and +/// decomposed input matrix `b` equals `VSL * T * VSL.transpose()`. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize", @@ -59,6 +63,11 @@ where { /// Attempts to compute the QZ decomposition of input square matrices `a` and `b`. /// + /// i.e retrieves the left and right matrices of Schur Vectors (VSL and VSR) + /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the + /// decomposed matrix `a` equals `VSL * S * VSL.transpose()` and + /// decomposed matrix `b` equals `VSL * T * VSL.transpose()`. + /// /// Panics if the method did not converge. pub fn new(a: OMatrix, b: OMatrix) -> Self { Self::try_new(a, b).expect("QZ decomposition: convergence failed.") @@ -154,8 +163,8 @@ where /// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the - /// decomposed matrix `A` equals `VSL * S * VSL.transpose()` and - /// decomposed matrix `B` equals `VSL * T * VSL.transpose()`. + /// decomposed input matrix `a` equals `VSL * S * VSL.transpose()` and + /// decomposed input matrix `b` equals `VSL * T * VSL.transpose()`. pub fn unpack( self, ) -> ( @@ -167,8 +176,6 @@ where (self.vsl, self.s, self.t, self.vsr) } - - /// computes the generalized eigenvalues #[must_use] pub fn eigenvalues(&self) -> OVector, D> diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index 84a7b030..d7fe4132 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -1,7 +1,5 @@ -use na::{zero, DMatrix, SMatrix}; -use nl::QZ; -use num_complex::Complex; -use simba::scalar::ComplexField; +use na::DMatrix; +use nl::{GE, QZ}; use std::cmp; use crate::proptest::*; @@ -16,33 +14,28 @@ proptest! { let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.clone().unpack(); - //let eigenvalues = qz.eigenvalues(); - //let a_c = a.clone().map(|x| Complex::new(x, zero::())); + let eigenvalues = qz.eigenvalues(); + + let ge = GE::new(a.clone(), b.clone()); + let eigenvalues2 = ge.eigenvalues(); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a.clone(), epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b.clone(), epsilon = 1.0e-7)); - // spotty test that skips over the first eigenvalue which in some cases is extremely large relative to the other ones - // and fails the condition - //for i in 1..n { - // let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); - // prop_assert!(relative_eq!((&a_c - &b_c).determinant().modulus(), 0.0, epsilon = 1.0e-6)); - //} + prop_assert!(eigenvalues == eigenvalues2); } #[test] fn qz_static(a in matrix4(), b in matrix4()) { let qz = QZ::new(a.clone(), b.clone()); + let ge = GE::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.unpack(); - //let eigenvalues = qz.eigenvalues(); - //let a_c = a.clone().map(|x| Complex::new(x, zero::())); + let eigenvalues = qz.eigenvalues(); + let eigenvalues2 = ge.eigenvalues(); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); - //for i in 0..4 { - // let b_c = b.clone().map(|x| eigenvalues[i]*Complex::new(x,zero::())); - // println!("{}",eigenvalues); - // prop_assert!(relative_eq!((&a_c - &b_c).determinant().modulus(), 0.0, epsilon = 1.0e-4)) - //} + prop_assert!(eigenvalues == eigenvalues2); + } } From 714f2ac98795935acecfdf47da7339a171bd2ec7 Mon Sep 17 00:00:00 2001 From: metric-space Date: Thu, 3 Feb 2022 06:36:10 -0500 Subject: [PATCH 046/112] New code and modified tests for generalized_eigenvalues --- .../src/generalized_eigenvalues.rs | 89 ++++++++++++++++--- .../tests/linalg/generalized_eigenvalues.rs | 63 ++++++++----- 2 files changed, 118 insertions(+), 34 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 6332f2db..5c273e9b 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use num::Zero; use num_complex::Complex; -use simba::scalar:: RealField; +use simba::scalar::RealField; use crate::ComplexHelper; use na::allocator::Allocator; @@ -14,6 +14,19 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; /// Generalized eigenvalues and generalized eigenvectors(left and right) of a pair of N*N square matrices. +/// +/// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 +/// +/// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) +/// of (A,B) satisfies +/// +/// A * v(j) = lambda(j) * B * v(j). +/// +/// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) +/// of (A,B) satisfies +/// +/// u(j)**H * A = lambda(j) * u(j)**H * B . +/// where u(j)**H is the conjugate-transpose of u(j). #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize", @@ -55,11 +68,21 @@ impl GE where DefaultAllocator: Allocator + Allocator, { - /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's - /// dggev and sggev routines + /// Attempts to compute the generalized eigenvalues, and left and right associated eigenvectors + /// via the raw returns from LAPACK's dggev and sggev routines /// - /// For each e in generalized eigenvalues and the associated eigenvectors e_l and e_r (left andf right) - /// it satisfies e_l*a = e*e_l*b and a*e_r = e*b*e_r + /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 + /// + /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// A * v(j) = lambda(j) * B * v(j). + /// + /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// u(j)**H * A = lambda(j) * u(j)**H * B . + /// where u(j)**H is the conjugate-transpose of u(j). /// /// Panics if the method did not converge. pub fn new(a: OMatrix, b: OMatrix) -> Self { @@ -69,8 +92,18 @@ where /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's /// dggev and sggev routines /// - /// For each e in generalized eigenvalues and the associated eigenvectors e_l and e_r (left andf right) - /// it satisfies e_l*a = e*e_l*b and a*e_r = e*b*e_r + /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 + /// + /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// A * v(j) = lambda(j) * B * v(j). + /// + /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// u(j)**H * A = lambda(j) * u(j)**H * B . + /// where u(j)**H is the conjugate-transpose of u(j). /// /// Returns `None` if the method did not converge. pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { @@ -147,9 +180,24 @@ where } /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues + /// Outputs two matrices, the first one containing the left eigenvectors of the generalized eigenvalues + /// as columns and the second matrix contains the right eigenvectors of the generalized eigenvalues + /// as columns + /// + /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// A * v(j) = lambda(j) * B * v(j). + /// + /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies + /// + /// u(j)**H * A = lambda(j) * u(j)**H * B . + /// where u(j)**H is the conjugate-transpose of u(j). pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) where - DefaultAllocator: Allocator, D, D> + Allocator, D>, + DefaultAllocator: + Allocator, D, D> + Allocator, D> + Allocator<(Complex, T), D>, { let n = self.vsl.shape().0; let mut l = self @@ -199,9 +247,10 @@ where (l, r) } - /// computes the generalized eigenvalues + /// computes the generalized eigenvalues i.e values of lambda that satisfy the following equation + /// determinant(A - lambda* B) = 0 #[must_use] - pub fn eigenvalues(&self) -> OVector, D> + fn eigenvalues(&self) -> OVector, D> where DefaultAllocator: Allocator, D>, { @@ -233,6 +282,26 @@ where out } + + /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alpai), beta) + /// straight from LAPACK + #[must_use] + pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> + where + DefaultAllocator: Allocator<(Complex, T), D>, + { + let mut out = Matrix::from_element_generic( + self.vsl.shape_generic().0, + Const::<1>, + (Complex::zero(), T::RealField::zero()), + ); + + for i in 0..out.len() { + out[i] = (Complex::new(self.alphar[i], self.alphai[i]), self.beta[i]) + } + + out + } } /* diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs index 275691c8..8da21b30 100644 --- a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -17,21 +17,29 @@ proptest! { let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); - if a_condition_no.unwrap_or(200000.0) < 10.0 && b_condition_no.unwrap_or(200000.0) < 10.0 { - let a_c =a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { + let a_c = a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); - let ge = GE::new(a.clone(), b.clone()); - let (vsl,vsr) = ge.clone().eigenvectors(); - let eigenvalues = ge.clone().eigenvalues(); + let ge = GE::new(a.clone(), b.clone()); + let (vsl,vsr) = ge.clone().eigenvectors(); - for i in 0..n { - let left_eigenvector = &vsl.column(i); - prop_assert!(relative_eq!((left_eigenvector.transpose()*&a_c - left_eigenvector.transpose()*&b_c*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<1>,Dynamic::new(n)) ,epsilon = 1.0e-7)); + for (i,(alpha,beta)) in ge.raw_eigenvalues().iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; - let right_eigenvector = &vsr.column(i); - prop_assert!(relative_eq!((&a_c*right_eigenvector - &b_c*right_eigenvector*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Dynamic::new(n), Const::<1>) ,epsilon = 1.0e-7)); - }; + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(Dynamic::new(n), Const::<1>), + epsilon = 1.0e-7)); + + prop_assert!( + relative_eq!( + (vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, Dynamic::new(n)), + epsilon = 1.0e-7)) + }; }; } @@ -40,20 +48,27 @@ proptest! { let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); - if a_condition_no.unwrap_or(200000.0) < 10.0 && b_condition_no.unwrap_or(200000.0) < 10.0{ - let ge = GE::new(a.clone(), b.clone()); - let a_c =a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); - let (vsl,vsr) = ge.eigenvectors(); - let eigenvalues = ge.eigenvalues(); + if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { + let ge = GE::new(a.clone(), b.clone()); + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let (vsl,vsr) = ge.eigenvectors(); + let eigenvalues = ge.raw_eigenvalues(); - for i in 0..4 { - let left_eigenvector = &vsl.column(i); - prop_assert!(relative_eq!((left_eigenvector.transpose()*&a_c - left_eigenvector.transpose()*&b_c*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<1>,Const::<4>) ,epsilon = 1.0e-7)); + for (i,(alpha,beta)) in eigenvalues.iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; - let right_eigenvector = &vsr.column(i); - prop_assert!(relative_eq!((&a_c*right_eigenvector - &b_c*right_eigenvector*eigenvalues[i]).map(|x| x.modulus()), OMatrix::zeros_generic(Const::<4>, Const::<1>) ,epsilon = 1.0e-7)); - }; + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<4>, Const::<1>), + epsilon = 1.0e-7)); + prop_assert!( + relative_eq!((vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, Const::<4>), + epsilon = 1.0e-7)) + } }; } } From 5828a0a6ad4319fd7aa4c9ed7dcc33271dd64fc9 Mon Sep 17 00:00:00 2001 From: metric-space Date: Thu, 3 Feb 2022 06:36:41 -0500 Subject: [PATCH 047/112] New code and modified tests for qz --- nalgebra-lapack/src/qz.rs | 43 +++++++---------------- nalgebra-lapack/tests/linalg/qz.rs | 55 ++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index ea775ea6..ee0e6208 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -42,11 +42,11 @@ where { alphar: OVector, alphai: OVector, - beta: OVector, - vsl: OMatrix, - s: OMatrix, - vsr: OMatrix, - t: OMatrix, + beta: OVector, + vsl: OMatrix, + s: OMatrix, + vsr: OMatrix, + t: OMatrix, } impl Copy for QZ @@ -176,36 +176,19 @@ where (self.vsl, self.s, self.t, self.vsr) } - /// computes the generalized eigenvalues + /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alpai), beta) + /// straight from LAPACK #[must_use] - pub fn eigenvalues(&self) -> OVector, D> + pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> where - DefaultAllocator: Allocator, D>, + DefaultAllocator: Allocator<(Complex, T), D>, { - let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>); + let mut out = Matrix::from_element_generic(self.vsl.shape_generic().0, Const::<1>, (Complex::zero(), T::RealField::zero())); for i in 0..out.len() { - out[i] = if self.beta[i].clone().abs() < T::RealField::default_epsilon() { - Complex::zero() - } else { - let mut cr = self.alphar[i].clone(); - let mut ci = self.alphai[i].clone(); - let b = self.beta[i].clone(); - - if cr.clone().abs() < T::RealField::default_epsilon() { - cr = T::RealField::zero() - } else { - cr = cr / b.clone() - }; - - if ci.clone().abs() < T::RealField::default_epsilon() { - ci = T::RealField::zero() - } else { - ci = ci / b - }; - - Complex::new(cr, ci) - } + out[i] = (Complex::new(self.alphar[i].clone(), + self.alphai[i].clone()), + self.beta[i].clone()) } out diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index d7fe4132..6f9cf7f8 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -1,5 +1,7 @@ -use na::DMatrix; -use nl::{GE, QZ}; +use na::{DMatrix, EuclideanNorm, Norm}; +use nl::QZ; +use num_complex::Complex; +use simba::scalar::ComplexField; use std::cmp; use crate::proptest::*; @@ -14,28 +16,59 @@ proptest! { let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.clone().unpack(); - let eigenvalues = qz.eigenvalues(); - - let ge = GE::new(a.clone(), b.clone()); - let eigenvalues2 = ge.eigenvalues(); + let eigenvalues = qz.raw_eigenvalues(); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a.clone(), epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b.clone(), epsilon = 1.0e-7)); - prop_assert!(eigenvalues == eigenvalues2); + + let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); + let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); + + if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { + let a_c = a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + + + for (alpha,beta) in eigenvalues.iter() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; + + prop_assert!( + relative_eq!( + (&l_a - &l_b).determinant().modulus(), + 0.0, + epsilon = 1.0e-7)); + + }; + }; } #[test] fn qz_static(a in matrix4(), b in matrix4()) { let qz = QZ::new(a.clone(), b.clone()); - let ge = GE::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.unpack(); - let eigenvalues = qz.eigenvalues(); - let eigenvalues2 = ge.eigenvalues(); + let eigenvalues = qz.raw_eigenvalues(); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); - prop_assert!(eigenvalues == eigenvalues2); + let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); + let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); + if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + + for (alpha,beta) in eigenvalues.iter() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; + + prop_assert!( + relative_eq!( + (&l_a - &l_b).determinant().modulus(), + 0.0, + epsilon = 1.0e-7)); + } + }; } } From 4c50d882a3019d954b7ce4db89d7cf6792a11dac Mon Sep 17 00:00:00 2001 From: metric-space Date: Thu, 3 Feb 2022 06:43:41 -0500 Subject: [PATCH 048/112] Formatting --- nalgebra-lapack/src/qz.rs | 23 ++++++++++++++--------- nalgebra-lapack/tests/linalg/mod.rs | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index ee0e6208..a322f8fa 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -42,11 +42,11 @@ where { alphar: OVector, alphai: OVector, - beta: OVector, - vsl: OMatrix, - s: OMatrix, - vsr: OMatrix, - t: OMatrix, + beta: OVector, + vsl: OMatrix, + s: OMatrix, + vsr: OMatrix, + t: OMatrix, } impl Copy for QZ @@ -183,12 +183,17 @@ where where DefaultAllocator: Allocator<(Complex, T), D>, { - let mut out = Matrix::from_element_generic(self.vsl.shape_generic().0, Const::<1>, (Complex::zero(), T::RealField::zero())); + let mut out = Matrix::from_element_generic( + self.vsl.shape_generic().0, + Const::<1>, + (Complex::zero(), T::RealField::zero()), + ); for i in 0..out.len() { - out[i] = (Complex::new(self.alphar[i].clone(), - self.alphai[i].clone()), - self.beta[i].clone()) + out[i] = ( + Complex::new(self.alphar[i].clone(), self.alphai[i].clone()), + self.beta[i].clone(), + ) } out diff --git a/nalgebra-lapack/tests/linalg/mod.rs b/nalgebra-lapack/tests/linalg/mod.rs index 9fd539c4..251bbe7b 100644 --- a/nalgebra-lapack/tests/linalg/mod.rs +++ b/nalgebra-lapack/tests/linalg/mod.rs @@ -1,8 +1,8 @@ mod cholesky; +mod generalized_eigenvalues; mod lu; mod qr; mod qz; -mod generalized_eigenvalues; mod real_eigensystem; mod schur; mod svd; From 3c717927cc0c5ad80e6b1352c39250f17f03b9af Mon Sep 17 00:00:00 2001 From: metric-space Date: Thu, 3 Feb 2022 06:45:29 -0500 Subject: [PATCH 049/112] Formatting --- nalgebra-lapack/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs index dec4daac..1e6c396f 100644 --- a/nalgebra-lapack/src/lib.rs +++ b/nalgebra-lapack/src/lib.rs @@ -83,11 +83,11 @@ mod lapack_check; mod cholesky; mod eigen; +mod generalized_eigenvalues; mod hessenberg; mod lu; mod qr; mod qz; -mod generalized_eigenvalues; mod schur; mod svd; mod symmetric_eigen; @@ -96,11 +96,11 @@ use num_complex::Complex; pub use self::cholesky::{Cholesky, CholeskyScalar}; pub use self::eigen::Eigen; +pub use self::generalized_eigenvalues::GE; pub use self::hessenberg::Hessenberg; pub use self::lu::{LUScalar, LU}; pub use self::qr::QR; pub use self::qz::QZ; -pub use self::generalized_eigenvalues::GE; pub use self::schur::Schur; pub use self::svd::SVD; pub use self::symmetric_eigen::SymmetricEigen; From 0c89aceed3e5c7c44d4666154396fe6a1f7ae469 Mon Sep 17 00:00:00 2001 From: metric-space Date: Fri, 4 Feb 2022 00:09:29 -0500 Subject: [PATCH 050/112] Added comment on logic --- nalgebra-lapack/src/generalized_eigenvalues.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 5c273e9b..4a2a293f 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -194,6 +194,11 @@ where /// /// u(j)**H * A = lambda(j) * u(j)**H * B . /// where u(j)**H is the conjugate-transpose of u(j). + /// + /// What is going on below? + /// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, + /// then v_l(j) = VSL(:,j)+i*VSL(:,j+1) and v_l(j+1) = VSL(:,j)-i*VSL(:,j+1). + /// and then v_r(j) = VSR(:,j)+i*VSR(:,j+1) and v_r(j+1) = VSR(:,j)-i*VSR(:,j+1). pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) where DefaultAllocator: From ed09700280b2d17bf90264c5082ab310012f6b09 Mon Sep 17 00:00:00 2001 From: metric-space Date: Fri, 4 Feb 2022 00:13:01 -0500 Subject: [PATCH 051/112] Correction to keep naming of left and right eigenvector matrices consistent --- nalgebra-lapack/src/generalized_eigenvalues.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 4a2a293f..c9fb0e57 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -197,8 +197,8 @@ where /// /// What is going on below? /// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, - /// then v_l(j) = VSL(:,j)+i*VSL(:,j+1) and v_l(j+1) = VSL(:,j)-i*VSL(:,j+1). - /// and then v_r(j) = VSR(:,j)+i*VSR(:,j+1) and v_r(j+1) = VSR(:,j)-i*VSR(:,j+1). + /// then u(j) = VSL(:,j)+i*VSL(:,j+1) and u(j+1) = VSL(:,j)-i*VSL(:,j+1). + /// and then v(j) = VSR(:,j)+i*VSR(:,j+1) and v(j+1) = VSR(:,j)-i*VSR(:,j+1). pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) where DefaultAllocator: From 8ef7c4232824c1b74e43a391dc14302a1d931669 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 5 Feb 2022 23:44:05 -0500 Subject: [PATCH 052/112] Removed extra memory allocation for buffer (now redundant) --- .../src/generalized_eigenvalues.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index c9fb0e57..f60f9df2 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -205,10 +205,12 @@ where Allocator, D, D> + Allocator, D> + Allocator<(Complex, T), D>, { let n = self.vsl.shape().0; + let mut l = self .vsl .clone() .map(|x| Complex::new(x, T::RealField::zero())); + let mut r = self .vsr .clone() @@ -216,8 +218,8 @@ where let eigenvalues = &self.eigenvalues(); - let mut ll; let mut c = 0; + while c < n { if eigenvalues[c].im.abs() > T::RealField::default_epsilon() && c + 1 < n && { let e_conj = eigenvalues[c].conj(); @@ -225,22 +227,20 @@ where ((e_conj.re - e.re).abs() < T::RealField::default_epsilon()) && ((e_conj.im - e.im).abs() < T::RealField::default_epsilon()) } { - ll = l.column(c + 1).into_owned(); - l.column_mut(c).zip_apply(&ll, |r, i| { - *r = Complex::new(r.re.clone(), i.re); + // taking care of the left eigenvector matrix + l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { + *r = Complex::new(r.re.clone(), i.clone()); }); - ll.copy_from(&l.column(c)); - l.column_mut(c + 1).zip_apply(&ll, |r, i| { - *r = i.conj(); + l.column_mut(c + 1).zip_apply(&self.vsl.column(c), |i, r| { + *i = Complex::new(r.clone(), -i.re.clone()); }); - ll.copy_from(&r.column(c + 1)); - r.column_mut(c).zip_apply(&ll, |r, i| { - *r = Complex::new(r.re, i.re); + // taking care of the right eigenvector matrix + r.column_mut(c).zip_apply(&self.vsr.column(c + 1), |r, i| { + *r = Complex::new(r.re.clone(), i.clone()); }); - ll.copy_from(&r.column(c)); - r.column_mut(c + 1).zip_apply(&ll, |r, i| { - *r = i.conj(); + r.column_mut(c + 1).zip_apply(&self.vsr.column(c), |i, r| { + *i = Complex::new(r.clone(), -i.re.clone()); }); c += 2; From 2e35cd6aa72273368740b6a2815e4fc315758fc2 Mon Sep 17 00:00:00 2001 From: metric-space Date: Wed, 9 Feb 2022 08:48:38 -0500 Subject: [PATCH 053/112] Corrected deserialization term in serialization impls --- nalgebra-lapack/src/generalized_eigenvalues.rs | 2 +- nalgebra-lapack/src/qz.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index f60f9df2..a14420e6 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -40,7 +40,7 @@ use lapack; feature = "serde-serialize", serde( bound(deserialize = "DefaultAllocator: Allocator + Allocator, - OVector: Serialize, + OVector: Deserialize<'de>, OMatrix: Deserialize<'de>") ) )] diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index a322f8fa..6004d68a 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -31,7 +31,7 @@ use lapack; feature = "serde-serialize", serde( bound(deserialize = "DefaultAllocator: Allocator + Allocator, - OVector: Serialize, + OVector: Deserialize<'de>, OMatrix: Deserialize<'de>") ) )] From f6c35b34ecf1da3a27d4bdf04360a93b7bd6f1ca Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:26:46 -0500 Subject: [PATCH 054/112] Correction in eigenvector matrices build up algorithm --- .../src/generalized_eigenvalues.rs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index a14420e6..e9057792 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -220,12 +220,13 @@ where let mut c = 0; + let epsilon = T::RealField::default_epsilon(); + while c < n { - if eigenvalues[c].im.abs() > T::RealField::default_epsilon() && c + 1 < n && { + if eigenvalues[c].im.abs() > epsilon && c + 1 < n && { let e_conj = eigenvalues[c].conj(); let e = eigenvalues[c + 1]; - ((e_conj.re - e.re).abs() < T::RealField::default_epsilon()) - && ((e_conj.im - e.im).abs() < T::RealField::default_epsilon()) + (&e_conj.re).ulps_eq(&e.re, epsilon, 6) && (&e_conj.im).ulps_eq(&e.im, epsilon, 6) } { // taking care of the left eigenvector matrix l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { @@ -255,7 +256,7 @@ where /// computes the generalized eigenvalues i.e values of lambda that satisfy the following equation /// determinant(A - lambda* B) = 0 #[must_use] - fn eigenvalues(&self) -> OVector, D> + pub fn eigenvalues(&self) -> OVector, D> where DefaultAllocator: Allocator, D>, { @@ -265,23 +266,8 @@ where out[i] = if self.beta[i].clone().abs() < T::RealField::default_epsilon() { Complex::zero() } else { - let mut cr = self.alphar[i].clone(); - let mut ci = self.alphai[i].clone(); - let b = self.beta[i].clone(); - - if cr.clone().abs() < T::RealField::default_epsilon() { - cr = T::RealField::zero() - } else { - cr = cr / b.clone() - }; - - if ci.clone().abs() < T::RealField::default_epsilon() { - ci = T::RealField::zero() - } else { - ci = ci / b - }; - - Complex::new(cr, ci) + Complex::new(self.alphar[i].clone(), self.alphai[i].clone()) + * (Complex::new(self.beta[i].clone(), T::RealField::zero()).inv()) } } From fd0398f4935bb0238fb366b904afd28dc8ef173a Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:27:29 -0500 Subject: [PATCH 055/112] Remove condition number, tests pass without. Add proper test generator for dynamic f64 type square matrices --- .../tests/linalg/generalized_eigenvalues.rs | 98 +++++++++---------- nalgebra-lapack/tests/linalg/qz.rs | 64 +++--------- 2 files changed, 58 insertions(+), 104 deletions(-) diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs index 8da21b30..8b868fc9 100644 --- a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -1,74 +1,70 @@ -use na::dimension::{Const, Dynamic}; -use na::{DMatrix, EuclideanNorm, Norm, OMatrix}; +use na::dimension::Const; +use na::{DMatrix, OMatrix}; use nl::GE; use num_complex::Complex; use simba::scalar::ComplexField; -use std::cmp; use crate::proptest::*; -use proptest::{prop_assert, proptest}; +use proptest::{prop_assert, prop_compose, proptest}; + +prop_compose! { +fn f64_squares() (n in PROPTEST_MATRIX_DIM) (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + (a,b) +}} proptest! { #[test] - fn ge(n in PROPTEST_MATRIX_DIM) { - let n = cmp::max(1, cmp::min(n, 10)); - let a = DMatrix::::new_random(n, n); - let b = DMatrix::::new_random(n, n); - let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); - let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); + fn ge((a,b) in f64_squares()){ - if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { - let a_c = a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let a_c = a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let n = a.shape_generic().0; - let ge = GE::new(a.clone(), b.clone()); - let (vsl,vsr) = ge.clone().eigenvectors(); + let ge = GE::new(a.clone(), b.clone()); + let (vsl,vsr) = ge.clone().eigenvectors(); - for (i,(alpha,beta)) in ge.raw_eigenvalues().iter().enumerate() { - let l_a = a_c.clone() * Complex::new(*beta, 0.0); - let l_b = b_c.clone() * *alpha; - prop_assert!( - relative_eq!( - ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), - OMatrix::zeros_generic(Dynamic::new(n), Const::<1>), - epsilon = 1.0e-7)); + for (i,(alpha,beta)) in ge.raw_eigenvalues().iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; - prop_assert!( - relative_eq!( - (vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), - OMatrix::zeros_generic(Const::<1>, Dynamic::new(n)), - epsilon = 1.0e-7)) - }; + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(n, Const::<1>), + epsilon = 1.0e-5)); + + prop_assert!( + relative_eq!( + (vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, n), + epsilon = 1.0e-5)) }; } #[test] fn ge_static(a in matrix4(), b in matrix4()) { - let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); - let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); - if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { - let ge = GE::new(a.clone(), b.clone()); - let a_c =a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); - let (vsl,vsr) = ge.eigenvectors(); - let eigenvalues = ge.raw_eigenvalues(); + let ge = GE::new(a.clone(), b.clone()); + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let (vsl,vsr) = ge.eigenvectors(); + let eigenvalues = ge.raw_eigenvalues(); - for (i,(alpha,beta)) in eigenvalues.iter().enumerate() { - let l_a = a_c.clone() * Complex::new(*beta, 0.0); - let l_b = b_c.clone() * *alpha; + for (i,(alpha,beta)) in eigenvalues.iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; - prop_assert!( - relative_eq!( - ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), - OMatrix::zeros_generic(Const::<4>, Const::<1>), - epsilon = 1.0e-7)); - prop_assert!( - relative_eq!((vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), - OMatrix::zeros_generic(Const::<1>, Const::<4>), - epsilon = 1.0e-7)) - } - }; + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<4>, Const::<1>), + epsilon = 1.0e-5)); + prop_assert!( + relative_eq!((vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, Const::<4>), + epsilon = 1.0e-5)) + } } + } diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index 6f9cf7f8..f70f1c9e 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -1,74 +1,32 @@ -use na::{DMatrix, EuclideanNorm, Norm}; +use na::DMatrix; use nl::QZ; -use num_complex::Complex; -use simba::scalar::ComplexField; -use std::cmp; use crate::proptest::*; -use proptest::{prop_assert, proptest}; +use proptest::{prop_assert, prop_compose, proptest}; + +prop_compose! { +fn f64_squares() (n in PROPTEST_MATRIX_DIM) (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + (a,b) +}} proptest! { #[test] - fn qz(n in PROPTEST_MATRIX_DIM) { - let n = cmp::max(1, cmp::min(n, 10)); - let a = DMatrix::::new_random(n, n); - let b = DMatrix::::new_random(n, n); + fn qz((a,b) in f64_squares()) { - let qz = QZ::new(a.clone(), b.clone()); + let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.clone().unpack(); - let eigenvalues = qz.raw_eigenvalues(); - prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a.clone(), epsilon = 1.0e-7)); - prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b.clone(), epsilon = 1.0e-7)); + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); - let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); - let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); - - if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { - let a_c = a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); - - - for (alpha,beta) in eigenvalues.iter() { - let l_a = a_c.clone() * Complex::new(*beta, 0.0); - let l_b = b_c.clone() * *alpha; - - prop_assert!( - relative_eq!( - (&l_a - &l_b).determinant().modulus(), - 0.0, - epsilon = 1.0e-7)); - - }; - }; } #[test] fn qz_static(a in matrix4(), b in matrix4()) { let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.unpack(); - let eigenvalues = qz.raw_eigenvalues(); prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); - - let a_condition_no = a.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&a))); - let b_condition_no = b.clone().try_inverse().and_then(|x| Some(EuclideanNorm.norm(&x)* EuclideanNorm.norm(&b))); - - if a_condition_no.unwrap_or(200000.0) < 5.0 && b_condition_no.unwrap_or(200000.0) < 5.0 { - let a_c =a.clone().map(|x| Complex::new(x, 0.0)); - let b_c = b.clone().map(|x| Complex::new(x, 0.0)); - - for (alpha,beta) in eigenvalues.iter() { - let l_a = a_c.clone() * Complex::new(*beta, 0.0); - let l_b = b_c.clone() * *alpha; - - prop_assert!( - relative_eq!( - (&l_a - &l_b).determinant().modulus(), - 0.0, - epsilon = 1.0e-7)); - } - }; } } From 5f3e2f5b45ed40ff3afd72955fc3a72aae541395 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:37:26 -0500 Subject: [PATCH 056/112] Name change --- nalgebra-lapack/src/generalized_eigenvalues.rs | 14 +++++++------- nalgebra-lapack/src/lib.rs | 2 +- .../tests/linalg/generalized_eigenvalues.rs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index e9057792..467d24da 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -45,7 +45,7 @@ use lapack; ) )] #[derive(Clone, Debug)] -pub struct GE +pub struct GeneralizedEigen where DefaultAllocator: Allocator + Allocator, { @@ -56,7 +56,7 @@ where vsr: OMatrix, } -impl Copy for GE +impl Copy for GeneralizedEigen where DefaultAllocator: Allocator + Allocator, OMatrix: Copy, @@ -64,7 +64,7 @@ where { } -impl GE +impl GeneralizedEigen where DefaultAllocator: Allocator + Allocator, { @@ -170,7 +170,7 @@ where ); lapack_check!(info); - Some(GE { + Some(GeneralizedEigen { alphar, alphai, beta, @@ -300,8 +300,8 @@ where * Lapack functions dispatch. * */ -/// Trait implemented by scalars for which Lapack implements the RealField GE decomposition. -pub trait GEScalar: Scalar { +/// Trait implemented by scalars for which Lapack implements the RealField GeneralizedEigen decomposition. +pub trait GeneralizedEigenScalar: Scalar { #[allow(missing_docs)] fn xggev( jobvsl: u8, @@ -345,7 +345,7 @@ pub trait GEScalar: Scalar { macro_rules! real_eigensystem_scalar_impl ( ($N: ty, $xggev: path) => ( - impl GEScalar for $N { + impl GeneralizedEigenScalar for $N { #[inline] fn xggev(jobvsl: u8, jobvsr: u8, diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs index 1e6c396f..ea2e2b53 100644 --- a/nalgebra-lapack/src/lib.rs +++ b/nalgebra-lapack/src/lib.rs @@ -96,7 +96,7 @@ use num_complex::Complex; pub use self::cholesky::{Cholesky, CholeskyScalar}; pub use self::eigen::Eigen; -pub use self::generalized_eigenvalues::GE; +pub use self::generalized_eigenvalues::GeneralizedEigen; pub use self::hessenberg::Hessenberg; pub use self::lu::{LUScalar, LU}; pub use self::qr::QR; diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs index 8b868fc9..ab678bf3 100644 --- a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -1,6 +1,6 @@ use na::dimension::Const; use na::{DMatrix, OMatrix}; -use nl::GE; +use nl::GeneralizedEigen; use num_complex::Complex; use simba::scalar::ComplexField; @@ -20,7 +20,7 @@ proptest! { let b_c = b.clone().map(|x| Complex::new(x, 0.0)); let n = a.shape_generic().0; - let ge = GE::new(a.clone(), b.clone()); + let ge = GeneralizedEigen::new(a.clone(), b.clone()); let (vsl,vsr) = ge.clone().eigenvectors(); @@ -45,7 +45,7 @@ proptest! { #[test] fn ge_static(a in matrix4(), b in matrix4()) { - let ge = GE::new(a.clone(), b.clone()); + let ge = GeneralizedEigen::new(a.clone(), b.clone()); let a_c =a.clone().map(|x| Complex::new(x, 0.0)); let b_c = b.clone().map(|x| Complex::new(x, 0.0)); let (vsl,vsr) = ge.eigenvectors(); From 10f6c4867752fc9d1a59bee891e3efde70736ea9 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:42:13 -0500 Subject: [PATCH 057/112] Change name of test generator --- nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs | 6 ++++-- nalgebra-lapack/tests/linalg/qz.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs index ab678bf3..b0d9777c 100644 --- a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -8,13 +8,15 @@ use crate::proptest::*; use proptest::{prop_assert, prop_compose, proptest}; prop_compose! { -fn f64_squares() (n in PROPTEST_MATRIX_DIM) (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + fn f64_dynamic_dim_squares() + (n in PROPTEST_MATRIX_DIM) + (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ (a,b) }} proptest! { #[test] - fn ge((a,b) in f64_squares()){ + fn ge((a,b) in f64_dynamic_dim_squares()){ let a_c = a.clone().map(|x| Complex::new(x, 0.0)); let b_c = b.clone().map(|x| Complex::new(x, 0.0)); diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs index f70f1c9e..c7a702ca 100644 --- a/nalgebra-lapack/tests/linalg/qz.rs +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -5,13 +5,15 @@ use crate::proptest::*; use proptest::{prop_assert, prop_compose, proptest}; prop_compose! { -fn f64_squares() (n in PROPTEST_MATRIX_DIM) (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + fn f64_dynamic_dim_squares() + (n in PROPTEST_MATRIX_DIM) + (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ (a,b) }} proptest! { #[test] - fn qz((a,b) in f64_squares()) { + fn qz((a,b) in f64_dynamic_dim_squares()) { let qz = QZ::new(a.clone(), b.clone()); let (vsl,s,t,vsr) = qz.clone().unpack(); From c47e31f6ea5ea31b0aaddfd586c64be424290d8c Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:52:18 -0500 Subject: [PATCH 058/112] Doc string corrections --- nalgebra-lapack/src/generalized_eigenvalues.rs | 2 +- nalgebra-lapack/src/qz.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 467d24da..ff58abe4 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -13,7 +13,7 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; -/// Generalized eigenvalues and generalized eigenvectors(left and right) of a pair of N*N square matrices. +/// Generalized eigenvalues and generalized eigenvectors (left and right) of a pair of N*N square matrices. /// /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 /// diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index 6004d68a..c38af508 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -14,6 +14,7 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; /// QZ decomposition of a pair of N*N square matrices. +/// /// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the /// decomposed input matrix `a` equals `VSL * S * VSL.transpose()` and From c287a169afb36c962eeb3b779ca549fc59c8889c Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 12 Feb 2022 02:59:04 -0500 Subject: [PATCH 059/112] Change name of copied macro base --- nalgebra-lapack/src/generalized_eigenvalues.rs | 6 +++--- nalgebra-lapack/src/qz.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index ff58abe4..aede9e07 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -343,7 +343,7 @@ pub trait GeneralizedEigenScalar: Scalar { ) -> i32; } -macro_rules! real_eigensystem_scalar_impl ( +macro_rules! generalized_eigen_scalar_impl ( ($N: ty, $xggev: path) => ( impl GeneralizedEigenScalar for $N { #[inline] @@ -395,5 +395,5 @@ macro_rules! real_eigensystem_scalar_impl ( ) ); -real_eigensystem_scalar_impl!(f32, lapack::sggev); -real_eigensystem_scalar_impl!(f64, lapack::dggev); +generalized_eigen_scalar_impl!(f32, lapack::sggev); +generalized_eigen_scalar_impl!(f64, lapack::dggev); diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index c38af508..17342a2e 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -257,7 +257,7 @@ pub trait QZScalar: Scalar { ) -> i32; } -macro_rules! real_eigensystem_scalar_impl ( +macro_rules! qz_scalar_impl ( ($N: ty, $xgges: path) => ( impl QZScalar for $N { #[inline] @@ -317,5 +317,5 @@ macro_rules! real_eigensystem_scalar_impl ( ) ); -real_eigensystem_scalar_impl!(f32, lapack::sgges); -real_eigensystem_scalar_impl!(f64, lapack::dgges); +qz_scalar_impl!(f32, lapack::sgges); +qz_scalar_impl!(f64, lapack::dgges); From 73543b2121de99591f9f5e526c4603d9be720789 Mon Sep 17 00:00:00 2001 From: metric-space Date: Tue, 15 Feb 2022 01:45:33 -0500 Subject: [PATCH 060/112] Add another case for when eigenvalues should be mapped to zero. Make method private --- nalgebra-lapack/src/generalized_eigenvalues.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index aede9e07..dac8004c 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -253,17 +253,21 @@ where (l, r) } - /// computes the generalized eigenvalues i.e values of lambda that satisfy the following equation - /// determinant(A - lambda* B) = 0 - #[must_use] - pub fn eigenvalues(&self) -> OVector, D> + // only used for internal calculation for assembling eigenvectors based on realness of + // eigenvalues and complex-conjugate checks of subsequent non-real eigenvalues + fn eigenvalues(&self) -> OVector, D> where DefaultAllocator: Allocator, D>, { let mut out = Matrix::zeros_generic(self.vsl.shape_generic().0, Const::<1>); + let epsilon = T::RealField::default_epsilon(); + for i in 0..out.len() { - out[i] = if self.beta[i].clone().abs() < T::RealField::default_epsilon() { + out[i] = if self.beta[i].clone().abs() < epsilon + || (self.alphai[i].clone().abs() < epsilon + && self.alphar[i].clone().abs() < epsilon) + { Complex::zero() } else { Complex::new(self.alphar[i].clone(), self.alphai[i].clone()) From 6a22e74c0014e4f1a402c089587360f85a333fc1 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sun, 27 Feb 2022 17:17:31 -0500 Subject: [PATCH 061/112] Minimal post-processing and fix to documentation --- .../src/generalized_eigenvalues.rs | 82 ++++++++----------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index dac8004c..132be1b7 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -180,25 +180,44 @@ where } /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues - /// Outputs two matrices, the first one containing the left eigenvectors of the generalized eigenvalues - /// as columns and the second matrix contains the right eigenvectors of the generalized eigenvalues - /// as columns + /// Outputs two matrices. + /// The first output matix contains the left eigenvectors of the generalized eigenvalues + /// as columns. + /// The second matrix contains the right eigenvectors of the generalized eigenvalues + /// as columns. /// - /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies + /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies /// - /// A * v(j) = lambda(j) * B * v(j). + /// A * v(j) = lambda(j) * B * v(j) /// - /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies + /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) + /// of (A,B) satisfies /// - /// u(j)**H * A = lambda(j) * u(j)**H * B . - /// where u(j)**H is the conjugate-transpose of u(j). + /// u(j)**H * A = lambda(j) * u(j)**H * B + /// where u(j)**H is the conjugate-transpose of u(j). + /// + /// How the eigenvectors are build up: + /// + /// Since the input entries are all real, the generalized eigenvalues if complex come in pairs + /// as a consequence of + /// The Lapack routine output reflects this by expecting the user to unpack the complex eigenvalues associated + /// eigenvectors from the real matrix output via the following procedure + /// + /// (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, + /// VR stands for the lapack real matrix output containing the right eigenvectors as columns) + /// + /// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, + /// then + /// + /// u(j) = VL(:,j)+i*VL(:,j+1) + /// u(j+1) = VL(:,j)-i*VL(:,j+1) + /// + /// and + /// + /// u(j) = VR(:,j)+i*VR(:,j+1) + /// v(j+1) = VR(:,j)-i*VR(:,j+1). /// - /// What is going on below? - /// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, - /// then u(j) = VSL(:,j)+i*VSL(:,j+1) and u(j+1) = VSL(:,j)-i*VSL(:,j+1). - /// and then v(j) = VSR(:,j)+i*VSR(:,j+1) and v(j+1) = VSR(:,j)-i*VSR(:,j+1). pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) where DefaultAllocator: @@ -216,18 +235,14 @@ where .clone() .map(|x| Complex::new(x, T::RealField::zero())); - let eigenvalues = &self.eigenvalues(); + let eigenvalues = &self.raw_eigenvalues(); let mut c = 0; let epsilon = T::RealField::default_epsilon(); while c < n { - if eigenvalues[c].im.abs() > epsilon && c + 1 < n && { - let e_conj = eigenvalues[c].conj(); - let e = eigenvalues[c + 1]; - (&e_conj.re).ulps_eq(&e.re, epsilon, 6) && (&e_conj.im).ulps_eq(&e.im, epsilon, 6) - } { + if eigenvalues[c].0.im.abs() > epsilon && c + 1 < n { // taking care of the left eigenvector matrix l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { *r = Complex::new(r.re.clone(), i.clone()); @@ -253,32 +268,7 @@ where (l, r) } - // only used for internal calculation for assembling eigenvectors based on realness of - // eigenvalues and complex-conjugate checks of subsequent non-real eigenvalues - fn eigenvalues(&self) -> OVector, D> - where - DefaultAllocator: Allocator, D>, - { - let mut out = Matrix::zeros_generic(self.vsl.shape_generic().0, Const::<1>); - - let epsilon = T::RealField::default_epsilon(); - - for i in 0..out.len() { - out[i] = if self.beta[i].clone().abs() < epsilon - || (self.alphai[i].clone().abs() < epsilon - && self.alphar[i].clone().abs() < epsilon) - { - Complex::zero() - } else { - Complex::new(self.alphar[i].clone(), self.alphai[i].clone()) - * (Complex::new(self.beta[i].clone(), T::RealField::zero()).inv()) - } - } - - out - } - - /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alpai), beta) + /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alphai), beta) /// straight from LAPACK #[must_use] pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> From 8590d82ad47ac92161d483a5b562e4f453213338 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 5 Mar 2022 13:52:42 -0500 Subject: [PATCH 062/112] Correct typos, move doc portion to comment and fix borrow to clone --- .../src/generalized_eigenvalues.rs | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 132be1b7..e365f96a 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -181,7 +181,7 @@ where /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues /// Outputs two matrices. - /// The first output matix contains the left eigenvectors of the generalized eigenvalues + /// The first output matrix contains the left eigenvectors of the generalized eigenvalues /// as columns. /// The second matrix contains the right eigenvectors of the generalized eigenvalues /// as columns. @@ -196,46 +196,45 @@ where /// /// u(j)**H * A = lambda(j) * u(j)**H * B /// where u(j)**H is the conjugate-transpose of u(j). - /// - /// How the eigenvectors are build up: - /// - /// Since the input entries are all real, the generalized eigenvalues if complex come in pairs - /// as a consequence of - /// The Lapack routine output reflects this by expecting the user to unpack the complex eigenvalues associated - /// eigenvectors from the real matrix output via the following procedure - /// - /// (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, - /// VR stands for the lapack real matrix output containing the right eigenvectors as columns) - /// - /// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, - /// then - /// - /// u(j) = VL(:,j)+i*VL(:,j+1) - /// u(j+1) = VL(:,j)-i*VL(:,j+1) - /// - /// and - /// - /// u(j) = VR(:,j)+i*VR(:,j+1) - /// v(j+1) = VR(:,j)-i*VR(:,j+1). - /// - pub fn eigenvectors(self) -> (OMatrix, D, D>, OMatrix, D, D>) + pub fn eigenvectors(&self) -> (OMatrix, D, D>, OMatrix, D, D>) where DefaultAllocator: Allocator, D, D> + Allocator, D> + Allocator<(Complex, T), D>, { + /* + How the eigenvectors are built up: + + Since the input entries are all real, the generalized eigenvalues if complex come in pairs + as a consequence of the [complex conjugate root thorem](https://en.wikipedia.org/wiki/Complex_conjugate_root_theorem) + The Lapack routine output reflects this by expecting the user to unpack the complex eigenvalues associated + eigenvectors from the real matrix output via the following procedure + + (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, + VR stands for the lapack real matrix output containing the right eigenvectors as columns) + + If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, + then + + u(j) = VL(:,j)+i*VL(:,j+1) + u(j+1) = VL(:,j)-i*VL(:,j+1) + + and + + u(j) = VR(:,j)+i*VR(:,j+1) + v(j+1) = VR(:,j)-i*VR(:,j+1). + */ + let n = self.vsl.shape().0; let mut l = self .vsl - .clone() .map(|x| Complex::new(x, T::RealField::zero())); let mut r = self .vsr - .clone() .map(|x| Complex::new(x, T::RealField::zero())); - let eigenvalues = &self.raw_eigenvalues(); + let eigenvalues = self.raw_eigenvalues(); let mut c = 0; From 5d67b07ebe85c7e69c2150ccd162da4a6054d611 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 5 Mar 2022 14:39:22 -0500 Subject: [PATCH 063/112] Fix doc --- nalgebra-lapack/src/generalized_eigenvalues.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index e365f96a..91b4e597 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -206,7 +206,7 @@ where Since the input entries are all real, the generalized eigenvalues if complex come in pairs as a consequence of the [complex conjugate root thorem](https://en.wikipedia.org/wiki/Complex_conjugate_root_theorem) - The Lapack routine output reflects this by expecting the user to unpack the complex eigenvalues associated + The Lapack routine output reflects this by expecting the user to unpack the real and complex eigenvalues associated eigenvectors from the real matrix output via the following procedure (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, From 71699f35b75e65679830df3e597534b5c2b250a5 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 5 Mar 2022 14:43:50 -0500 Subject: [PATCH 064/112] Fix formatting --- nalgebra-lapack/src/generalized_eigenvalues.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 91b4e597..95db3e18 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -226,13 +226,9 @@ where let n = self.vsl.shape().0; - let mut l = self - .vsl - .map(|x| Complex::new(x, T::RealField::zero())); + let mut l = self.vsl.map(|x| Complex::new(x, T::RealField::zero())); - let mut r = self - .vsr - .map(|x| Complex::new(x, T::RealField::zero())); + let mut r = self.vsr.map(|x| Complex::new(x, T::RealField::zero())); let eigenvalues = self.raw_eigenvalues(); From ed3a17ded8917d28821c84dfed1ee70ff2f57d17 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 5 Mar 2022 15:01:22 -0500 Subject: [PATCH 065/112] Add in explicit type of matrix element to module overview docs --- nalgebra-lapack/src/generalized_eigenvalues.rs | 2 +- nalgebra-lapack/src/qz.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 95db3e18..db758332 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -13,7 +13,7 @@ use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; use lapack; -/// Generalized eigenvalues and generalized eigenvectors (left and right) of a pair of N*N square matrices. +/// Generalized eigenvalues and generalized eigenvectors (left and right) of a pair of N*N real square matrices. /// /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 /// diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs index 17342a2e..99f3c374 100644 --- a/nalgebra-lapack/src/qz.rs +++ b/nalgebra-lapack/src/qz.rs @@ -62,7 +62,7 @@ impl QZ where DefaultAllocator: Allocator + Allocator, { - /// Attempts to compute the QZ decomposition of input square matrices `a` and `b`. + /// Attempts to compute the QZ decomposition of input real square matrices `a` and `b`. /// /// i.e retrieves the left and right matrices of Schur Vectors (VSL and VSR) /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the From 3e4be691bfb86596cc6c5b35f276b6aae0f49716 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 16 Apr 2022 02:23:38 -0400 Subject: [PATCH 066/112] Update check for zero --- nalgebra-lapack/src/generalized_eigenvalues.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index db758332..69e5e465 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -234,10 +234,8 @@ where let mut c = 0; - let epsilon = T::RealField::default_epsilon(); - while c < n { - if eigenvalues[c].0.im.abs() > epsilon && c + 1 < n { + if eigenvalues[c].0.im.abs() != T::RealField::zero() && c + 1 < n { // taking care of the left eigenvector matrix l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { *r = Complex::new(r.re.clone(), i.clone()); From 1d13a3ffdaa2c2590a5cf8e595b5905b3777aecb Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 16 Apr 2022 02:23:51 -0400 Subject: [PATCH 067/112] Add newline --- nalgebra-lapack/src/generalized_eigenvalues.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index 69e5e465..f4f3bc49 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -180,6 +180,7 @@ where } /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues + /// /// Outputs two matrices. /// The first output matrix contains the left eigenvectors of the generalized eigenvalues /// as columns. From ee89f1af002489dfa889fa31bd6ce3a0f3ede360 Mon Sep 17 00:00:00 2001 From: metric-space Date: Sat, 16 Apr 2022 02:37:02 -0400 Subject: [PATCH 068/112] Remove repeated docs --- .../src/generalized_eigenvalues.rs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs index f4f3bc49..5d1e3ace 100644 --- a/nalgebra-lapack/src/generalized_eigenvalues.rs +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -71,19 +71,6 @@ where /// Attempts to compute the generalized eigenvalues, and left and right associated eigenvectors /// via the raw returns from LAPACK's dggev and sggev routines /// - /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 - /// - /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// A * v(j) = lambda(j) * B * v(j). - /// - /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// u(j)**H * A = lambda(j) * u(j)**H * B . - /// where u(j)**H is the conjugate-transpose of u(j). - /// /// Panics if the method did not converge. pub fn new(a: OMatrix, b: OMatrix) -> Self { Self::try_new(a, b).expect("Calculation of generalized eigenvalues failed.") @@ -92,19 +79,6 @@ where /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's /// dggev and sggev routines /// - /// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 - /// - /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// A * v(j) = lambda(j) * B * v(j). - /// - /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// u(j)**H * A = lambda(j) * u(j)**H * B . - /// where u(j)**H is the conjugate-transpose of u(j). - /// /// Returns `None` if the method did not converge. pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { assert!( @@ -186,17 +160,6 @@ where /// as columns. /// The second matrix contains the right eigenvectors of the generalized eigenvalues /// as columns. - /// - /// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// A * v(j) = lambda(j) * B * v(j) - /// - /// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) - /// of (A,B) satisfies - /// - /// u(j)**H * A = lambda(j) * u(j)**H * B - /// where u(j)**H is the conjugate-transpose of u(j). pub fn eigenvectors(&self) -> (OMatrix, D, D>, OMatrix, D, D>) where DefaultAllocator: @@ -262,7 +225,7 @@ where (l, r) } - /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alphai), beta) + /// Outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alphai), beta) /// straight from LAPACK #[must_use] pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> From 8c36ab4cebc92f9701100a3ec9b711ba7823c9b4 Mon Sep 17 00:00:00 2001 From: zyansheep Date: Mon, 21 Mar 2022 12:08:46 -0400 Subject: [PATCH 069/112] upgrade rkyv to 0.7 --- Cargo.toml | 2 +- src/base/array_storage.rs | 16 ++++------------ src/base/dimension.rs | 8 +------- src/base/matrix.rs | 16 ++++------------ src/base/unit.rs | 16 ++++------------ src/geometry/isometry.rs | 23 ++++++----------------- src/geometry/quaternion.rs | 16 ++++------------ src/geometry/scale.rs | 16 ++++------------ src/geometry/translation.rs | 16 ++++------------ 9 files changed, 32 insertions(+), 97 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a629b554..907442cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ alga = { version = "0.9", default-features = false, optional = true } rand_distr = { version = "0.4", default-features = false, optional = true } matrixmultiply = { version = "0.3", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } -rkyv = { version = "~0.6.4", default-features = false, features = ["const_generics"], optional = true } +rkyv = { version = "~0.7.1", optional = true } mint = { version = "0.5", optional = true } quickcheck = { version = "1", optional = true } pest = { version = "2", optional = true } diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index b6bd236a..d6dab40a 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -277,23 +277,15 @@ unsafe impl by #[cfg(feature = "rkyv-serialize-no-std")] mod rkyv_impl { use super::ArrayStorage; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for ArrayStorage { type Archived = ArrayStorage; type Resolver = <[[T; R]; C] as Archive>::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.0.resolve( - pos + offset_of!(Self::Archived, 0), - resolver, - project_struct!(out: Self::Archived => 0), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.0); + self.0.resolve(pos + fp, resolver, fo); } } diff --git a/src/base/dimension.rs b/src/base/dimension.rs index de51339f..39e0459d 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -242,13 +242,7 @@ mod rkyv_impl { type Archived = Self; type Resolver = (); - fn resolve( - &self, - _: usize, - _: Self::Resolver, - _: &mut core::mem::MaybeUninit, - ) { - } + unsafe fn resolve(&self, _: usize, _: Self::Resolver, _: *mut Self::Archived) {} } impl Serialize for Const { diff --git a/src/base/matrix.rs b/src/base/matrix.rs index f12cb3fa..1b598952 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -292,23 +292,15 @@ where mod rkyv_impl { use super::Matrix; use core::marker::PhantomData; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Matrix { type Archived = Matrix; type Resolver = S::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.data.resolve( - pos + offset_of!(Self::Archived, data), - resolver, - project_struct!(out: Self::Archived => data), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.data); + self.data.resolve(pos + fp, resolver, fo); } } diff --git a/src/base/unit.rs b/src/base/unit.rs index bb8b56a1..d82c67ec 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -61,23 +61,15 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Unit { #[cfg(feature = "rkyv-serialize-no-std")] mod rkyv_impl { use super::Unit; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Unit { type Archived = Unit; type Resolver = T::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut ::core::mem::MaybeUninit, - ) { - self.value.resolve( - pos + offset_of!(Self::Archived, value), - resolver, - project_struct!(out: Self::Archived => value), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.value); + self.value.resolve(pos + fp, resolver, fo); } } diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 0179f1ff..1b4eb26f 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -77,7 +77,7 @@ pub struct Isometry { mod rkyv_impl { use super::Isometry; use crate::{base::Scalar, geometry::Translation}; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Isometry where @@ -86,22 +86,11 @@ mod rkyv_impl { type Archived = Isometry; type Resolver = (R::Resolver, as Archive>::Resolver); - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.rotation.resolve( - pos + offset_of!(Self::Archived, rotation), - resolver.0, - project_struct!(out: Self::Archived => rotation), - ); - self.translation.resolve( - pos + offset_of!(Self::Archived, translation), - resolver.1, - project_struct!(out: Self::Archived => translation), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.rotation); + self.rotation.resolve(pos + fp, resolver.0, fo); + let (fp, fo) = out_field!(out.translation); + self.translation.resolve(pos + fp, resolver.1, fo); } } diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 987c9757..43f31e2e 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -101,23 +101,15 @@ where mod rkyv_impl { use super::Quaternion; use crate::base::Vector4; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Quaternion { type Archived = Quaternion; type Resolver = as Archive>::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.coords.resolve( - pos + offset_of!(Self::Archived, coords), - resolver, - project_struct!(out: Self::Archived => coords), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.coords); + self.coords.resolve(pos + fp, resolver, fo); } } diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index abaeeccc..23265bba 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -88,23 +88,15 @@ where mod rkyv_impl { use super::Scale; use crate::base::SVector; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Scale { type Archived = Scale; type Resolver = as Archive>::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.vector.resolve( - pos + offset_of!(Self::Archived, vector), - resolver, - project_struct!(out: Self::Archived => vector), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.vector); + self.vector.resolve(pos + fp, resolver, fo); } } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index e1921d0a..e7dc5ee8 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -88,23 +88,15 @@ where mod rkyv_impl { use super::Translation; use crate::base::SVector; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; + use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; impl Archive for Translation { type Archived = Translation; type Resolver = as Archive>::Resolver; - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.vector.resolve( - pos + offset_of!(Self::Archived, vector), - resolver, - project_struct!(out: Self::Archived => vector), - ); + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let (fp, fo) = out_field!(out.vector); + self.vector.resolve(pos + fp, resolver, fo); } } From 7b5fb956d0c3f721a6726adfe0c1070b6d647a1a Mon Sep 17 00:00:00 2001 From: zyansheep Date: Mon, 21 Mar 2022 12:47:03 -0400 Subject: [PATCH 070/112] add bytecheck for matrix --- Cargo.toml | 3 ++- src/base/matrix.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 907442cf..90869f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ convert-glam020 = [ "glam020" ] ## `serde-serialize`. serde-serialize-no-std = [ "serde", "num-complex/serde" ] serde-serialize = [ "serde-serialize-no-std", "serde/std" ] -rkyv-serialize-no-std = [ "rkyv" ] +rkyv-serialize-no-std = [ "rkyv", "bytecheck" ] rkyv-serialize = [ "rkyv-serialize-no-std", "rkyv/std" ] # Randomness @@ -80,6 +80,7 @@ rand_distr = { version = "0.4", default-features = false, optional = true } matrixmultiply = { version = "0.3", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } rkyv = { version = "~0.7.1", optional = true } +bytecheck = { version = "~0.6.1", optional = true } mint = { version = "0.5", optional = true } quickcheck = { version = "1", optional = true } pest = { version = "2", optional = true } diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 1b598952..63fbd0ce 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -293,6 +293,7 @@ mod rkyv_impl { use super::Matrix; use core::marker::PhantomData; use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; + use bytecheck::CheckBytes; impl Archive for Matrix { type Archived = Matrix; @@ -325,6 +326,22 @@ mod rkyv_impl { }) } } + + impl<__C: ?Sized, T, R, C, S: CheckBytes<__C>> + CheckBytes<__C> + for Matrix + where + S: CheckBytes<__C>, + { + type Error = >::Error; + unsafe fn check_bytes<'a>( + value: *const Matrix, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = S::check_bytes(::core::ptr::addr_of!((*value).data), context)?; + Ok(&*value) + } + } } impl Matrix { From b6c061f6bc70ed6863bc2b3db8f65dfd7b72076b Mon Sep 17 00:00:00 2001 From: zyansheep Date: Mon, 21 Mar 2022 21:03:17 -0400 Subject: [PATCH 071/112] add bytecheck impls --- src/base/array_storage.rs | 22 +++++++++++++++++++ src/base/dimension.rs | 15 +++++++++++++ src/base/matrix.rs | 14 ++++++++----- src/base/unit.rs | 21 +++++++++++++++++++ src/geometry/isometry.rs | 42 +++++++++++++++++++++++++++++++++++++ src/geometry/quaternion.rs | 22 +++++++++++++++++++ src/geometry/scale.rs | 21 +++++++++++++++++++ src/geometry/translation.rs | 21 +++++++++++++++++++ 8 files changed, 173 insertions(+), 5 deletions(-) diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index d6dab40a..78cb6dcd 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -307,3 +307,25 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use std::ptr::addr_of; + + use bytecheck::{ArrayCheckError, CheckBytes}; + + use super::ArrayStorage; + + impl<__C: ?Sized, T, const R: usize, const C: usize> CheckBytes<__C> for ArrayStorage + where + T: CheckBytes<__C>, + { + type Error = ArrayCheckError>::Error>>; + unsafe fn check_bytes<'a>( + value: *const ArrayStorage, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = <[[T; R]; C] as CheckBytes<__C>>::check_bytes(addr_of!((*value).0), context)?; + Ok(&*value) + } + } +} diff --git a/src/base/dimension.rs b/src/base/dimension.rs index 39e0459d..18c12803 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -257,6 +257,21 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use bytecheck::CheckBytes; + + use super::Const; + impl<__C: ?Sized, const R: usize> CheckBytes<__C> for Const { + type Error = core::convert::Infallible; + unsafe fn check_bytes<'a>( + value: *const Const, + _context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + Ok(&*value) + } + } +} pub trait ToConst { type Const: DimName; diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 63fbd0ce..2c481d37 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -293,7 +293,6 @@ mod rkyv_impl { use super::Matrix; use core::marker::PhantomData; use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - use bytecheck::CheckBytes; impl Archive for Matrix { type Archived = Matrix; @@ -326,10 +325,15 @@ mod rkyv_impl { }) } } +} +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use bytecheck::CheckBytes; + use std::ptr::addr_of; - impl<__C: ?Sized, T, R, C, S: CheckBytes<__C>> - CheckBytes<__C> - for Matrix + use super::Matrix; + + impl<__C: ?Sized, T, R, C, S: CheckBytes<__C>> CheckBytes<__C> for Matrix where S: CheckBytes<__C>, { @@ -338,7 +342,7 @@ mod rkyv_impl { value: *const Matrix, context: &mut __C, ) -> Result<&'a Self, Self::Error> { - let _ = S::check_bytes(::core::ptr::addr_of!((*value).data), context)?; + let _ = S::check_bytes(addr_of!((*value).data), context)?; Ok(&*value) } } diff --git a/src/base/unit.rs b/src/base/unit.rs index d82c67ec..6828aa02 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -90,6 +90,27 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use std::ptr::addr_of; + + use bytecheck::CheckBytes; + + use super::Unit; + impl<__C: ?Sized, T: CheckBytes<__C>> CheckBytes<__C> for Unit + where + T: CheckBytes<__C>, + { + type Error = >::Error; + unsafe fn check_bytes<'a>( + value: *const Unit, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = T::check_bytes(addr_of!((*value).value), context)?; + Ok(&*value) + } + } +} #[cfg(feature = "cuda")] unsafe impl cust_core::DeviceCopy for Unit> diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 1b4eb26f..7f7175dd 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -121,6 +121,48 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use crate::{Isometry, Scalar, Translation}; + use bytecheck::CheckBytes; + use std::{error::Error, fmt, ptr::addr_of}; + + #[derive(Debug)] + pub enum IsometryCheckBytesError { + Rotation(R), + Translation(T), + } + impl fmt::Display for IsometryCheckBytesError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Rotation(_) => write!(f, "failed to check bytes of isometry rotation"), + Self::Translation(_) => write!(f, "failed to check bytes of isometry translation"), + } + } + } + impl Error for IsometryCheckBytesError {} + + impl<__C: ?Sized, T: Scalar + CheckBytes<__C>, R: CheckBytes<__C>, const D: usize> + CheckBytes<__C> for Isometry + where + T: CheckBytes<__C>, + { + type Error = IsometryCheckBytesError< + as CheckBytes<__C>>::Error, + >::Error, + >; + unsafe fn check_bytes<'a>( + value: *const Isometry, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = R::check_bytes(addr_of!((*value).rotation), context) + .map_err(|e| IsometryCheckBytesError::Rotation(e))?; + let _ = Translation::::check_bytes(addr_of!((*value).translation), context) + .map_err(|e| IsometryCheckBytesError::Translation(e))?; + Ok(&*value) + } + } +} impl hash::Hash for Isometry where diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 43f31e2e..71e38c4c 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -130,6 +130,28 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use std::ptr::addr_of; + + use bytecheck::CheckBytes; + + use super::Quaternion; + use crate::Vector4; + impl<__C: ?Sized, T: CheckBytes<__C>> CheckBytes<__C> for Quaternion + where + T: CheckBytes<__C>, + { + type Error = as CheckBytes<__C>>::Error; + unsafe fn check_bytes<'a>( + value: *const Quaternion, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = Vector4::check_bytes(addr_of!((*value).coords), context)?; + Ok(&*value) + } + } +} impl Quaternion where diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index 23265bba..333b63cb 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -118,6 +118,27 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use std::ptr::addr_of; + + use bytecheck::CheckBytes; + + use crate::{SVector, Scale}; + impl<__C: ?Sized, T: CheckBytes<__C>, const D: usize> CheckBytes<__C> for Scale + where + T: CheckBytes<__C>, + { + type Error = as CheckBytes<__C>>::Error; + unsafe fn check_bytes<'a>( + value: *const Scale, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = SVector::::check_bytes(addr_of!((*value).vector), context)?; + Ok(&*value) + } + } +} impl Scale { /// Inverts `self`. diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index e7dc5ee8..7af263bf 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -118,6 +118,27 @@ mod rkyv_impl { } } } +#[cfg(feature = "rkyv-serialize")] +mod bytecheck_impl { + use std::ptr::addr_of; + + use bytecheck::CheckBytes; + + use crate::{SVector, Translation}; + impl<__C: ?Sized, T: CheckBytes<__C>, const D: usize> CheckBytes<__C> for Translation + where + T: CheckBytes<__C>, + { + type Error = as CheckBytes<__C>>::Error; + unsafe fn check_bytes<'a>( + value: *const Translation, + context: &mut __C, + ) -> Result<&'a Self, Self::Error> { + let _ = SVector::::check_bytes(addr_of!((*value).vector), context)?; + Ok(&*value) + } + } +} impl Translation { /// Creates a new translation from the given vector. From edb1d1b86f05a37a6acf5711f85fff3ab34d7bbb Mon Sep 17 00:00:00 2001 From: zyansheep Date: Tue, 22 Mar 2022 09:49:32 -0400 Subject: [PATCH 072/112] fix bytecheck feature in cargo.toml --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90869f44..ffd37598 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,8 +52,8 @@ convert-glam020 = [ "glam020" ] ## `serde-serialize`. serde-serialize-no-std = [ "serde", "num-complex/serde" ] serde-serialize = [ "serde-serialize-no-std", "serde/std" ] -rkyv-serialize-no-std = [ "rkyv", "bytecheck" ] -rkyv-serialize = [ "rkyv-serialize-no-std", "rkyv/std" ] +rkyv-serialize-no-std = [ "rkyv" ] +rkyv-serialize = [ "rkyv-serialize-no-std", "rkyv/std", "bytecheck" ] # Randomness ## To use rand in a #[no-std] environment, enable the From 27ce8ee40d89beee7174cfe5e16a1ebd63557a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 30 Apr 2022 10:32:10 +0200 Subject: [PATCH 073/112] Switch to derive macros for rkyv and bytecheck --- src/base/array_storage.rs | 61 ++------------------- src/base/dimension.rs | 50 ++++------------- src/base/matrix.rs | 65 ++-------------------- src/base/unit.rs | 59 ++------------------ src/geometry/dual_quaternion.rs | 5 ++ src/geometry/isometry.rs | 96 ++------------------------------- src/geometry/orthographic.rs | 5 ++ src/geometry/perspective.rs | 5 ++ src/geometry/point.rs | 5 ++ src/geometry/quaternion.rs | 61 ++------------------- src/geometry/rotation.rs | 5 ++ src/geometry/scale.rs | 61 ++------------------- src/geometry/similarity.rs | 5 ++ src/geometry/translation.rs | 61 ++------------------- src/lib.rs | 3 +- 15 files changed, 77 insertions(+), 470 deletions(-) diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index 78cb6dcd..3bc71e1a 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -27,6 +27,11 @@ use std::mem; /// A array-based statically sized matrix data storage. #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct ArrayStorage(pub [[T; R]; C]); @@ -273,59 +278,3 @@ unsafe impl by for ArrayStorage { } - -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::ArrayStorage; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for ArrayStorage { - type Archived = ArrayStorage; - type Resolver = <[[T; R]; C] as Archive>::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.0); - self.0.resolve(pos + fp, resolver, fo); - } - } - - impl, S: Fallible + ?Sized, const R: usize, const C: usize> Serialize - for ArrayStorage - { - fn serialize(&self, serializer: &mut S) -> Result { - self.0.serialize(serializer) - } - } - - impl - Deserialize, D> for ArrayStorage - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(ArrayStorage(self.0.deserialize(deserializer)?)) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use std::ptr::addr_of; - - use bytecheck::{ArrayCheckError, CheckBytes}; - - use super::ArrayStorage; - - impl<__C: ?Sized, T, const R: usize, const C: usize> CheckBytes<__C> for ArrayStorage - where - T: CheckBytes<__C>, - { - type Error = ArrayCheckError>::Error>>; - unsafe fn check_bytes<'a>( - value: *const ArrayStorage, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = <[[T; R]; C] as CheckBytes<__C>>::check_bytes(addr_of!((*value).0), context)?; - Ok(&*value) - } - } -} diff --git a/src/base/dimension.rs b/src/base/dimension.rs index 18c12803..4be97586 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -13,6 +13,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Dim of dynamically-sized algebraic entities. #[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Dynamic { value: usize, @@ -198,6 +203,11 @@ dim_ops!( ); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Const; @@ -233,46 +243,6 @@ impl<'de, const D: usize> Deserialize<'de> for Const { } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Const; - use rkyv::{Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Const { - type Archived = Self; - type Resolver = (); - - unsafe fn resolve(&self, _: usize, _: Self::Resolver, _: *mut Self::Archived) {} - } - - impl Serialize for Const { - fn serialize(&self, _: &mut S) -> Result { - Ok(()) - } - } - - impl Deserialize for Const { - fn deserialize(&self, _: &mut D) -> Result { - Ok(Const) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use bytecheck::CheckBytes; - - use super::Const; - impl<__C: ?Sized, const R: usize> CheckBytes<__C> for Const { - type Error = core::convert::Infallible; - unsafe fn check_bytes<'a>( - value: *const Const, - _context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - Ok(&*value) - } - } -} - pub trait ToConst { type Const: DimName; } diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 2c481d37..8f8786c1 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -150,6 +150,11 @@ pub type MatrixCross = /// some concrete types for `T` and a compatible data storage type `S`). #[repr(C)] #[derive(Clone, Copy)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Matrix { /// The data storage that contains all the matrix components. Disappointed? @@ -288,66 +293,6 @@ where { } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Matrix; - use core::marker::PhantomData; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Matrix { - type Archived = Matrix; - type Resolver = S::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.data); - self.data.resolve(pos + fp, resolver, fo); - } - } - - impl, _S: Fallible + ?Sized> Serialize<_S> - for Matrix - { - fn serialize(&self, serializer: &mut _S) -> Result { - self.data.serialize(serializer) - } - } - - impl - Deserialize, D> - for Matrix - where - S::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Matrix { - data: self.data.deserialize(deserializer)?, - _phantoms: PhantomData, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use bytecheck::CheckBytes; - use std::ptr::addr_of; - - use super::Matrix; - - impl<__C: ?Sized, T, R, C, S: CheckBytes<__C>> CheckBytes<__C> for Matrix - where - S: CheckBytes<__C>, - { - type Error = >::Error; - unsafe fn check_bytes<'a>( - value: *const Matrix, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = S::check_bytes(addr_of!((*value).data), context)?; - Ok(&*value) - } - } -} - impl Matrix { /// Creates a new matrix with the given data without statically checking that the matrix /// dimension matches the storage dimension. diff --git a/src/base/unit.rs b/src/base/unit.rs index 6828aa02..6fc00092 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -21,6 +21,11 @@ use crate::{Dim, Matrix, OMatrix, RealField, Scalar, SimdComplexField, SimdRealF /// in their documentation, read their dedicated pages directly. #[repr(transparent)] #[derive(Clone, Hash, Copy)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] // #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Unit { pub(crate) value: T, @@ -58,60 +63,6 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Unit { } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Unit; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Unit { - type Archived = Unit; - type Resolver = T::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.value); - self.value.resolve(pos + fp, resolver, fo); - } - } - - impl, S: Fallible + ?Sized> Serialize for Unit { - fn serialize(&self, serializer: &mut S) -> Result { - self.value.serialize(serializer) - } - } - - impl Deserialize, D> for Unit - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Unit { - value: self.value.deserialize(deserializer)?, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use std::ptr::addr_of; - - use bytecheck::CheckBytes; - - use super::Unit; - impl<__C: ?Sized, T: CheckBytes<__C>> CheckBytes<__C> for Unit - where - T: CheckBytes<__C>, - { - type Error = >::Error; - unsafe fn check_bytes<'a>( - value: *const Unit, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = T::check_bytes(addr_of!((*value).value), context)?; - Ok(&*value) - } - } -} - #[cfg(feature = "cuda")] unsafe impl cust_core::DeviceCopy for Unit> where diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 719ae13d..6f1b7053 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -40,6 +40,11 @@ use simba::scalar::{ClosedNeg, RealField}; /// See #[repr(C)] #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct DualQuaternion { /// The real component of the quaternion diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 7f7175dd..92169742 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -66,6 +66,11 @@ use crate::geometry::{AbstractRotation, Point, Translation}; Owned>: Deserialize<'de>, T: Scalar")) )] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct Isometry { /// The pure rotational part of this isometry. pub rotation: R, @@ -73,97 +78,6 @@ pub struct Isometry { pub translation: Translation, } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Isometry; - use crate::{base::Scalar, geometry::Translation}; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Isometry - where - T::Archived: Scalar, - { - type Archived = Isometry; - type Resolver = (R::Resolver, as Archive>::Resolver); - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.rotation); - self.rotation.resolve(pos + fp, resolver.0, fo); - let (fp, fo) = out_field!(out.translation); - self.translation.resolve(pos + fp, resolver.1, fo); - } - } - - impl, R: Serialize, S: Fallible + ?Sized, const D: usize> - Serialize for Isometry - where - T::Archived: Scalar, - { - fn serialize(&self, serializer: &mut S) -> Result { - Ok(( - self.rotation.serialize(serializer)?, - self.translation.serialize(serializer)?, - )) - } - } - - impl - Deserialize, _D> for Isometry - where - T::Archived: Scalar + Deserialize, - R::Archived: Scalar + Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Isometry { - rotation: self.rotation.deserialize(deserializer)?, - translation: self.translation.deserialize(deserializer)?, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use crate::{Isometry, Scalar, Translation}; - use bytecheck::CheckBytes; - use std::{error::Error, fmt, ptr::addr_of}; - - #[derive(Debug)] - pub enum IsometryCheckBytesError { - Rotation(R), - Translation(T), - } - impl fmt::Display for IsometryCheckBytesError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Rotation(_) => write!(f, "failed to check bytes of isometry rotation"), - Self::Translation(_) => write!(f, "failed to check bytes of isometry translation"), - } - } - } - impl Error for IsometryCheckBytesError {} - - impl<__C: ?Sized, T: Scalar + CheckBytes<__C>, R: CheckBytes<__C>, const D: usize> - CheckBytes<__C> for Isometry - where - T: CheckBytes<__C>, - { - type Error = IsometryCheckBytesError< - as CheckBytes<__C>>::Error, - >::Error, - >; - unsafe fn check_bytes<'a>( - value: *const Isometry, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = R::check_bytes(addr_of!((*value).rotation), context) - .map_err(|e| IsometryCheckBytesError::Rotation(e))?; - let _ = Translation::::check_bytes(addr_of!((*value).translation), context) - .map_err(|e| IsometryCheckBytesError::Translation(e))?; - Ok(&*value) - } - } -} - impl hash::Hash for Isometry where Owned>: hash::Hash, diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 1119d4e3..7348f676 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -19,6 +19,11 @@ use crate::geometry::{Point3, Projective3}; /// A 3D orthographic projection stored as a homogeneous 4x4 matrix. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Orthographic3 { diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index 8ebab3e4..351960bb 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -20,6 +20,11 @@ use crate::geometry::{Point3, Projective3}; /// A 3D perspective projection stored as a homogeneous 4x4 matrix. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Perspective3 { diff --git a/src/geometry/point.rs b/src/geometry/point.rs index cdc590fa..306c18e5 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -36,6 +36,11 @@ use std::mem::MaybeUninit; /// of said transformations for details. #[repr(C)] #[derive(Clone)] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct OPoint where DefaultAllocator: Allocator, diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 71e38c4c..f38dca6f 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -23,6 +23,11 @@ use crate::geometry::{Point3, Rotation}; /// that may be used as a rotation. #[repr(C)] #[derive(Copy, Clone)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Quaternion { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. @@ -97,62 +102,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Quaternion; - use crate::base::Vector4; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Quaternion { - type Archived = Quaternion; - type Resolver = as Archive>::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.coords); - self.coords.resolve(pos + fp, resolver, fo); - } - } - - impl, S: Fallible + ?Sized> Serialize for Quaternion { - fn serialize(&self, serializer: &mut S) -> Result { - self.coords.serialize(serializer) - } - } - - impl Deserialize, D> for Quaternion - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Quaternion { - coords: self.coords.deserialize(deserializer)?, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use std::ptr::addr_of; - - use bytecheck::CheckBytes; - - use super::Quaternion; - use crate::Vector4; - impl<__C: ?Sized, T: CheckBytes<__C>> CheckBytes<__C> for Quaternion - where - T: CheckBytes<__C>, - { - type Error = as CheckBytes<__C>>::Error; - unsafe fn check_bytes<'a>( - value: *const Quaternion, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = Vector4::check_bytes(addr_of!((*value).coords), context)?; - Ok(&*value) - } - } -} - impl Quaternion where T::Element: SimdRealField, diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index 4dbcfb43..2a8bf427 100755 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -49,6 +49,11 @@ use crate::geometry::Point; /// * [Conversion to a matrix `matrix`, `to_homogeneous`…](#conversion-to-a-matrix) /// #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Rotation { diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index 333b63cb..37da1ef0 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -17,6 +17,11 @@ use crate::geometry::Point; /// A scale which supports non-uniform scaling. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Scale { @@ -84,62 +89,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Scale; - use crate::base::SVector; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Scale { - type Archived = Scale; - type Resolver = as Archive>::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.vector); - self.vector.resolve(pos + fp, resolver, fo); - } - } - - impl, S: Fallible + ?Sized, const D: usize> Serialize for Scale { - fn serialize(&self, serializer: &mut S) -> Result { - self.vector.serialize(serializer) - } - } - - impl Deserialize, _D> - for Scale - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Scale { - vector: self.vector.deserialize(deserializer)?, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use std::ptr::addr_of; - - use bytecheck::CheckBytes; - - use crate::{SVector, Scale}; - impl<__C: ?Sized, T: CheckBytes<__C>, const D: usize> CheckBytes<__C> for Scale - where - T: CheckBytes<__C>, - { - type Error = as CheckBytes<__C>>::Error; - unsafe fn check_bytes<'a>( - value: *const Scale, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = SVector::::check_bytes(addr_of!((*value).vector), context)?; - Ok(&*value) - } - } -} - impl Scale { /// Inverts `self`. /// diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index 9658685e..8c38ff1e 100755 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -34,6 +34,11 @@ use crate::geometry::{AbstractRotation, Isometry, Point, Translation}; DefaultAllocator: Allocator>, Owned>: Deserialize<'de>")) )] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct Similarity { /// The part of this similarity that does not include the scaling factor. pub isometry: Isometry, diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 7af263bf..bef66f68 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -17,6 +17,11 @@ use crate::geometry::Point; /// A translation. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Translation { @@ -84,62 +89,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Translation; - use crate::base::SVector; - use rkyv::{out_field, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Translation { - type Archived = Translation; - type Resolver = as Archive>::Resolver; - - unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { - let (fp, fo) = out_field!(out.vector); - self.vector.resolve(pos + fp, resolver, fo); - } - } - - impl, S: Fallible + ?Sized, const D: usize> Serialize for Translation { - fn serialize(&self, serializer: &mut S) -> Result { - self.vector.serialize(serializer) - } - } - - impl Deserialize, _D> - for Translation - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Translation { - vector: self.vector.deserialize(deserializer)?, - }) - } - } -} -#[cfg(feature = "rkyv-serialize")] -mod bytecheck_impl { - use std::ptr::addr_of; - - use bytecheck::CheckBytes; - - use crate::{SVector, Translation}; - impl<__C: ?Sized, T: CheckBytes<__C>, const D: usize> CheckBytes<__C> for Translation - where - T: CheckBytes<__C>, - { - type Error = as CheckBytes<__C>>::Error; - unsafe fn check_bytes<'a>( - value: *const Translation, - context: &mut __C, - ) -> Result<&'a Self, Self::Error> { - let _ = SVector::::check_bytes(addr_of!((*value).vector), context)?; - Ok(&*value) - } - } -} - impl Translation { /// Creates a new translation from the given vector. #[inline] diff --git a/src/lib.rs b/src/lib.rs index 92b28dcb..1ee1a3ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,12 +78,13 @@ an optimized set of tools for computer graphics and physics. Those features incl unused_mut, unused_parens, unused_qualifications, - unused_results, rust_2018_idioms, rust_2018_compatibility, future_incompatible, missing_copy_implementations )] +#![cfg_attr(feature = "rkyv-serialize-no-std", warn(unused_results))] // TODO: deny this once bytecheck stops generating warnings. +#![cfg_attr(not(feature = "rkyv-serialize-no-std"), deny(unused_results))] #![doc( html_favicon_url = "https://nalgebra.org/img/favicon.ico", html_root_url = "https://docs.rs/nalgebra/0.25.0" From 284494fe5aee0e5d064f6cbcbd8ed96870d63185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 30 Apr 2022 10:59:26 +0200 Subject: [PATCH 074/112] Release v0.31.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.toml | 2 +- examples/cargo/Cargo.toml | 2 +- nalgebra-glm/Cargo.toml | 4 ++-- nalgebra-lapack/Cargo.toml | 6 +++--- nalgebra-macros/Cargo.toml | 2 +- nalgebra-sparse/Cargo.toml | 6 +++--- 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf6b6a0f..c00c01fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,29 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.31.0] (30 Apr. 2022) + +### Breaking changes +- Switch to `cust` 0.3 (for CUDA support). +- Switch to `rkyv` 0.7 +- Remove support for serialization based on `abomonation`. +- Remove support for conversions between `nalgebra` types and `glam` 0.13. + +### Modified +- The aliases for `Const` types have been simplified to help `rust-analyzer`. + +### Added +- Add `TryFrom` conversion between `UnitVector2/3/4` and `glam`’s `Vec2/3/4`. +- `nalgebra-sparse`: added support for serialization of sparse matrices with `serde`. +- `nalgebra-sparse`: add a CSC matrix constructor from unsorted (but valid) data. +- `nalgebra-lapack`: add generalized eigenvalues/eigenvectors calculation + QZ decomposition. + +### Fixed +- Improve stability of SVD. +- Fix slerp for `UnitComplex`. + ## [0.30.1] (09 Jan. 2022) + ### Added - Add conversion from/to types of `glam` 0.19 and 0.20. diff --git a/Cargo.toml b/Cargo.toml index ffd37598..732676ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.30.1" +version = "0.31.0" authors = [ "Sébastien Crozet " ] description = "General-purpose linear algebra library with transformations and statically-sized or dynamically-sized matrices." diff --git a/examples/cargo/Cargo.toml b/examples/cargo/Cargo.toml index 501ae23e..63e70aab 100644 --- a/examples/cargo/Cargo.toml +++ b/examples/cargo/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" authors = [ "You" ] [dependencies] -nalgebra = "0.30.0" +nalgebra = "0.31.0" [[bin]] name = "example" diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index adf05fa3..e700af37 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-glm" -version = "0.16.0" +version = "0.17.0" authors = ["sebcrozet "] description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library." @@ -36,4 +36,4 @@ convert-glam018 = [ "nalgebra/glam018" ] num-traits = { version = "0.2", default-features = false } approx = { version = "0.5", default-features = false } simba = { version = "0.7", default-features = false } -nalgebra = { path = "..", version = "0.30", default-features = false } +nalgebra = { path = "..", version = "0.31", default-features = false } diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index 7cdf9f78..91517a8d 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-lapack" -version = "0.21.0" +version = "0.22.0" authors = [ "Sébastien Crozet ", "Andrew Straw " ] description = "Matrix decompositions using nalgebra matrices and Lapack bindings." @@ -29,7 +29,7 @@ accelerate = ["lapack-src/accelerate"] intel-mkl = ["lapack-src/intel-mkl"] [dependencies] -nalgebra = { version = "0.30", path = ".." } +nalgebra = { version = "0.31", path = ".." } num-traits = "0.2" num-complex = { version = "0.4", default-features = false } simba = "0.7" @@ -39,7 +39,7 @@ lapack-src = { version = "0.8", default-features = false } # clippy = "*" [dev-dependencies] -nalgebra = { version = "0.30", features = [ "arbitrary", "rand" ], path = ".." } +nalgebra = { version = "0.31", features = [ "arbitrary", "rand" ], path = ".." } proptest = { version = "1", default-features = false, features = ["std"] } quickcheck = "1" approx = "0.5" diff --git a/nalgebra-macros/Cargo.toml b/nalgebra-macros/Cargo.toml index bd37b689..6e35f9e9 100644 --- a/nalgebra-macros/Cargo.toml +++ b/nalgebra-macros/Cargo.toml @@ -21,5 +21,5 @@ quote = "1.0" proc-macro2 = "1.0" [dev-dependencies] -nalgebra = { version = "0.30.0", path = ".." } +nalgebra = { version = "0.31.0", path = ".." } trybuild = "1.0.42" diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index bb4fdb8e..c5ab9614 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-sparse" -version = "0.6.0" +version = "0.7.0" authors = [ "Andreas Longva", "Sébastien Crozet " ] edition = "2018" description = "Sparse matrix computation based on nalgebra." @@ -24,7 +24,7 @@ io = [ "pest", "pest_derive" ] slow-tests = [] [dependencies] -nalgebra = { version="0.30", path = "../" } +nalgebra = { version="0.31", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } matrixcompare-core = { version = "0.1.0", optional = true } @@ -34,7 +34,7 @@ serde = { version = "1.0", default-features = false, features = [ "derive" ], op [dev-dependencies] matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } -nalgebra = { version="0.30", path = "../", features = ["compare"] } +nalgebra = { version="0.31", path = "../", features = ["compare"] } serde_json = "1.0" [package.metadata.docs.rs] From f77226b4724fd8c8e60673bd3f1c8e3560698bd6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 3 May 2022 14:19:24 -0600 Subject: [PATCH 075/112] fix failing test --- nalgebra-sparse/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index c5ab9614..7220373e 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -33,6 +33,7 @@ pest_derive = { version = "2", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } [dev-dependencies] +itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.31", path = "../", features = ["compare"] } serde_json = "1.0" From 31fc49818228976e49fb471a4c5896db938fa6e2 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Sat, 7 May 2022 13:40:25 +0200 Subject: [PATCH 076/112] use tempdir to write matrix when running test --- nalgebra-sparse/Cargo.toml | 1 + nalgebra-sparse/src/io/matrix_market.rs | 5 +---- nalgebra-sparse/tests/unit_tests/matrix_market.rs | 10 ++++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 0dfe743f..4e414322 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -34,6 +34,7 @@ pest_derive = { version = "2", optional = true } itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.30", path = "../", features = ["compare"] } +tempfile = "3" [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 5143447c..200aeff2 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1487,10 +1487,7 @@ pub fn write_to_matrix_market_str(&str).unwrap(); -/// // create a temporary file 'temp.mtx' -/// let mut tempdir = std::env::temp_dir(); -/// tempdir.push("temp.mtx"); -/// write_to_matrix_market_file(&matrix,tempdir).unwrap(); +/// write_to_matrix_market_file(&matrix,"path/to/matrix.mtx").unwrap(); /// ``` pub fn write_to_matrix_market_file< T: MatrixMarketScalar, diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 7bf6891c..245fa3f5 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -8,6 +8,7 @@ use nalgebra_sparse::io::{ use nalgebra_sparse::proptest::coo_no_duplicates; use nalgebra_sparse::CooMatrix; use proptest::prelude::*; +use tempfile::tempdir; type C64 = Complex; type C32 = Complex; @@ -457,10 +458,11 @@ proptest! { proptest! { #[test] fn coo_matrix_market_roundtrip_file(coo in coo_no_duplicates(-10 ..= 10, 0 ..= 10, 0..= 10, 100)) { - let mut tempdir = std::env::temp_dir(); - tempdir.push("temp.mtx"); - write_to_matrix_market_file(&coo,&tempdir).unwrap(); - let generated_matrix = load_coo_from_matrix_market_file(tempdir).unwrap(); + let temp_dir = tempdir().expect("Unable to create temporary directory"); + let file_path = temp_dir.path().join("temp.mtx"); + write_to_matrix_market_file(&coo,&file_path).unwrap(); + let generated_matrix = load_coo_from_matrix_market_file(file_path).unwrap(); assert_matrix_eq!(generated_matrix, coo); + temp_dir.close().expect("Unable to delete temporary directory"); } } From ef9a3dd7678f79f37db07a93231f781ab6dbb44f Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 9 May 2022 09:23:42 +0200 Subject: [PATCH 077/112] Update module-level docs --- nalgebra-sparse/src/io/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index f257d7e2..e5412562 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -19,10 +19,12 @@ //! which also uses the Matrix Market file format. //! //! We currently offer functionality for importing a Matrix market file to an instance of a -//! [CooMatrix](crate::CooMatrix) through the function [load_coo_from_matrix_market_file]. It is also possible to load -//! a matrix stored in the matrix market format with the function [load_coo_from_matrix_market_str]. -//! -//! Export is currently not implemented, but [planned](https://github.com/dimforge/nalgebra/issues/1037). +//! [CooMatrix](crate::CooMatrix) through the function [load_coo_from_matrix_market_file], +//! as well as functionality for writing various sparse matrices to the matrix market format +//! through [write_to_matrix_market_file]. It is also possible to load +//! a matrix stored as a string in the matrix market format with the function +//! [load_coo_from_matrix_market_str], or similarly write to a string with +//! [write_to_matrix_market_str]. //! //! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html) //! on the Matrix Market website and the From 9b321955583108149fa97a56fd73314849c5b08b Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 9 May 2022 09:51:55 +0200 Subject: [PATCH 078/112] Change write -> save in order to be consistent with load terminology --- nalgebra-sparse/src/io/matrix_market.rs | 25 +++++++++---------- nalgebra-sparse/src/io/mod.rs | 8 +++--- .../tests/unit_tests/matrix_market.rs | 16 ++++++------ 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 200aeff2..fc9b59b2 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -775,7 +775,7 @@ mm_complex_impl!(f64); mm_pattern_impl!(()); -/// A marker trait for supported sparse matrix types. +/// A marker trait for sparse matrix types that can be exported to the matrix market format. /// /// This is a sealed trait; it cannot be implemented by external crates. This is done in order to prevent leaking /// some of the implementation details we currently rely on. We may relax this restriction in the future. @@ -1439,7 +1439,7 @@ fn next_dense_coordinate( /// -------- /// ``` /// # use nalgebra_sparse::CooMatrix; -/// use nalgebra_sparse::io::{write_to_matrix_market_str}; +/// use nalgebra_sparse::io::{save_to_matrix_market_str}; /// let expected_str = r#"%%matrixmarket matrix coordinate integer general /// % matrixmarket file generated by nalgebra-sparse. /// 5 4 2 @@ -1450,26 +1450,25 @@ fn next_dense_coordinate( /// let col_indices = vec![0,2]; /// let values = vec![10,5]; /// let matrix = CooMatrix::try_from_triplets(5,4,row_indices,col_indices,values).unwrap(); -/// let generated_matrixmarket_str = write_to_matrix_market_str(&matrix); +/// let generated_matrixmarket_str = save_to_matrix_market_str(&matrix); /// assert_eq!(expected_str,generated_matrixmarket_str); /// ``` -pub fn write_to_matrix_market_str>( +pub fn save_to_matrix_market_str>( sparse_matrix: &S, ) -> String { let mut bytes = Vec::::new(); // This will call impl Write for Vec // The vector will grow as needed. // So, unwrap here won't cause any issue. - write_to_matrix_market(&mut bytes, sparse_matrix).unwrap(); + save_to_matrix_market(&mut bytes, sparse_matrix).unwrap(); String::from_utf8(bytes) .expect("Unexpected non UTF-8 data was generated when export to matrix market string") } -/// Write a sparse matrix into Matrix Market format file. -/// -/// The exporter only writes matrix into `coordinate` and `general` format. +/// Save a sparse matrix to a Matrix Market format file. /// +/// The exporter only saves the matrix with the `coordinate` and `general` matrix market formats. /// /// Errors /// -------- @@ -1479,7 +1478,7 @@ pub fn write_to_matrix_market_str(&str).unwrap(); -/// write_to_matrix_market_file(&matrix,"path/to/matrix.mtx").unwrap(); +/// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx").unwrap(); /// ``` -pub fn write_to_matrix_market_file< +pub fn save_to_matrix_market_file< T: MatrixMarketScalar, S: MatrixMarketExport, P: AsRef, @@ -1499,7 +1498,7 @@ pub fn write_to_matrix_market_file< ) -> Result<(), std::io::Error> { let file = File::create(path)?; let mut file = BufWriter::new(file); - write_to_matrix_market(&mut file, sparse_matrix)?; + save_to_matrix_market(&mut file, sparse_matrix)?; // Quote from BufWriter doc. // > It is critical to call flush before BufWriter is dropped. Though dropping will attempt to flush the contents of the buffer, any errors that happen in the process of dropping will be ignored. Calling flush ensures that the buffer is empty and thus dropping will not even attempt file operations. file.flush() @@ -1508,7 +1507,7 @@ pub fn write_to_matrix_market_file< } /// low level implementation of writing sparse matrix into any [std::io::Write] object -pub fn write_to_matrix_market, W: Write>( +pub fn save_to_matrix_market, W: Write>( mut w: W, sparse_matrix: &S, ) -> Result<(), std::io::Error> { diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index e5412562..1f4cb081 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -21,10 +21,10 @@ //! We currently offer functionality for importing a Matrix market file to an instance of a //! [CooMatrix](crate::CooMatrix) through the function [load_coo_from_matrix_market_file], //! as well as functionality for writing various sparse matrices to the matrix market format -//! through [write_to_matrix_market_file]. It is also possible to load +//! through [save_to_matrix_market_file]. It is also possible to load //! a matrix stored as a string in the matrix market format with the function //! [load_coo_from_matrix_market_str], or similarly write to a string with -//! [write_to_matrix_market_str]. +//! [save_to_matrix_market_str]. //! //! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html) //! on the Matrix Market website and the @@ -34,8 +34,8 @@ //! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996). pub use self::matrix_market::{ - load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market, - write_to_matrix_market_file, write_to_matrix_market_str, MatrixMarketError, + load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, save_to_matrix_market, + save_to_matrix_market_file, save_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketExport, MatrixMarketScalar, }; mod matrix_market; diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 245fa3f5..44e27644 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -2,8 +2,8 @@ use matrixcompare::assert_matrix_eq; use nalgebra::matrix; use nalgebra::Complex; use nalgebra_sparse::io::{ - load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file, - write_to_matrix_market_str, + load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, save_to_matrix_market_file, + save_to_matrix_market_str, }; use nalgebra_sparse::proptest::coo_no_duplicates; use nalgebra_sparse::CooMatrix; @@ -374,7 +374,7 @@ fn test_matrixmarket_write_real(){ 1 3 3 2 3 3 "#; - let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + let matrixmarket_str = save_to_matrix_market_str(&coo_matrix); assert_eq!(matrixmarket_str,expected); } @@ -398,7 +398,7 @@ fn test_matrixmarket_write_int() { 1 3 3 2 3 3 "#; - let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + let matrixmarket_str = save_to_matrix_market_str(&coo_matrix); assert_eq!(matrixmarket_str, expected); } @@ -417,7 +417,7 @@ fn test_matrixmarket_write_pattern() { 1 3 2 3 "#; - let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + let matrixmarket_str = save_to_matrix_market_str(&coo_matrix); assert_eq!(matrixmarket_str, expected); } @@ -442,14 +442,14 @@ fn test_matrixmarket_write_complex() { 1 3 4 5 2 3 5 6 "#; - let matrixmarket_str = write_to_matrix_market_str(&coo_matrix); + let matrixmarket_str = save_to_matrix_market_str(&coo_matrix); assert_eq!(matrixmarket_str, expected); } proptest! { #[test] fn coo_matrix_market_roundtrip_str(coo in coo_no_duplicates(-10 ..= 10, 0 ..= 10, 0..= 10, 100)) { - let generated_matrixmarket_string = write_to_matrix_market_str(&coo); + let generated_matrixmarket_string = save_to_matrix_market_str(&coo); let generated_matrix = load_coo_from_matrix_market_str(&generated_matrixmarket_string).unwrap(); assert_matrix_eq!(generated_matrix, coo); } @@ -460,7 +460,7 @@ proptest! { fn coo_matrix_market_roundtrip_file(coo in coo_no_duplicates(-10 ..= 10, 0 ..= 10, 0..= 10, 100)) { let temp_dir = tempdir().expect("Unable to create temporary directory"); let file_path = temp_dir.path().join("temp.mtx"); - write_to_matrix_market_file(&coo,&file_path).unwrap(); + save_to_matrix_market_file(&coo,&file_path).unwrap(); let generated_matrix = load_coo_from_matrix_market_file(file_path).unwrap(); assert_matrix_eq!(generated_matrix, coo); temp_dir.close().expect("Unable to delete temporary directory"); From 59421896cea745843e743006feee1b61c778932f Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 9 May 2022 09:59:09 +0200 Subject: [PATCH 079/112] Polish docs for save_* matrix market methods --- nalgebra-sparse/src/io/matrix_market.rs | 37 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index fc9b59b2..489449a3 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1430,9 +1430,9 @@ fn next_dense_coordinate( } } -/// Write a sparse matrix into Matrix Market format string. +/// Save a sparse matrix as a Matrix Market format string. /// -/// The exporter only writes matrix into `coordinate` and `general` format. +/// The exporter only writes the matrix into `coordinate` and `general` format. /// /// /// Examples @@ -1453,9 +1453,13 @@ fn next_dense_coordinate( /// let generated_matrixmarket_str = save_to_matrix_market_str(&matrix); /// assert_eq!(expected_str,generated_matrixmarket_str); /// ``` -pub fn save_to_matrix_market_str>( +pub fn save_to_matrix_market_str( sparse_matrix: &S, -) -> String { +) -> String +where + T: MatrixMarketScalar, + S: MatrixMarketExport +{ let mut bytes = Vec::::new(); // This will call impl Write for Vec // The vector will grow as needed. @@ -1488,14 +1492,15 @@ pub fn save_to_matrix_market_str /// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); /// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx").unwrap(); /// ``` -pub fn save_to_matrix_market_file< +pub fn save_to_matrix_market_file( + sparse_matrix: &S, + path: P, +) -> Result<(), std::io::Error> +where T: MatrixMarketScalar, S: MatrixMarketExport, P: AsRef, ->( - sparse_matrix: &S, - path: P, -) -> Result<(), std::io::Error> { +{ let file = File::create(path)?; let mut file = BufWriter::new(file); save_to_matrix_market(&mut file, sparse_matrix)?; @@ -1506,11 +1511,19 @@ pub fn save_to_matrix_market_file< Ok(()) } -/// low level implementation of writing sparse matrix into any [std::io::Write] object -pub fn save_to_matrix_market, W: Write>( +/// Save a sparse matrix to an [std::io::Write] instance. +/// +/// This is the most general save functionality. See [save_to_matrix_market_file] and +/// [save_to_matrix_market_str] for higher-level functionality. +pub fn save_to_matrix_market( mut w: W, sparse_matrix: &S, -) -> Result<(), std::io::Error> { +) -> Result<(), std::io::Error> +where + T: MatrixMarketScalar, + S: MatrixMarketExport, + W: Write +{ // write header writeln!( w, From 037226bb1f1a010d5bc3d1c63073e435e1ee80fe Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 9 May 2022 10:02:26 +0200 Subject: [PATCH 080/112] Use ? instead of unwrap() in examples --- nalgebra-sparse/src/io/matrix_market.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 489449a3..36ca5ca4 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1439,6 +1439,7 @@ fn next_dense_coordinate( /// -------- /// ``` /// # use nalgebra_sparse::CooMatrix; +/// # fn main() -> Result<(), Box> { /// use nalgebra_sparse::io::{save_to_matrix_market_str}; /// let expected_str = r#"%%matrixmarket matrix coordinate integer general /// % matrixmarket file generated by nalgebra-sparse. @@ -1449,9 +1450,10 @@ fn next_dense_coordinate( /// let row_indices = vec![0,1]; /// let col_indices = vec![0,2]; /// let values = vec![10,5]; -/// let matrix = CooMatrix::try_from_triplets(5,4,row_indices,col_indices,values).unwrap(); +/// let matrix = CooMatrix::try_from_triplets(5,4,row_indices,col_indices,values)?; /// let generated_matrixmarket_str = save_to_matrix_market_str(&matrix); /// assert_eq!(expected_str,generated_matrixmarket_str); +/// # Ok(()) } /// ``` pub fn save_to_matrix_market_str( sparse_matrix: &S, @@ -1482,6 +1484,7 @@ where /// Examples /// -------- /// ```no_run +/// # fn main() -> Result<(), Box> { /// use nalgebra_sparse::io::{save_to_matrix_market_file,load_coo_from_matrix_market_str}; /// let str = r#" /// %%matrixmarket matrix coordinate integer general @@ -1489,8 +1492,9 @@ where /// 1 1 10 /// 2 3 5 /// "#; -/// let matrix = load_coo_from_matrix_market_str::(&str).unwrap(); -/// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx").unwrap(); +/// let matrix = load_coo_from_matrix_market_str::(&str)?; +/// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx")?; +/// # Ok(()) } /// ``` pub fn save_to_matrix_market_file( sparse_matrix: &S, From 1c7f15e5edf6264cabdd8c13d70b75674f5be5df Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 9 May 2022 14:19:11 -0600 Subject: [PATCH 081/112] remove extra yaml file --- .github/workflows/rust.yml | 139 ------------------------------------- 1 file changed, 139 deletions(-) delete mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 304ecf5f..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: nalgebra CI build - -on: - push: - branches: [ dev, master ] - pull_request: - branches: [ dev, master ] - -env: - CARGO_TERM_COLOR: always - -jobs: - check-fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Check formatting - run: cargo fmt -- --check - clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install clippy - run: rustup component add clippy - - name: Run clippy - run: cargo clippy - build-nalgebra: - runs-on: ubuntu-latest -# env: -# RUSTFLAGS: -D warnings - steps: - - uses: actions/checkout@v2 - - name: Build --no-default-feature - run: cargo build --no-default-features; - - name: Build (default features) - run: cargo build; - - name: Build --features serde-serialize - run: cargo build --features serde-serialize - - name: Build nalgebra-lapack - run: cd nalgebra-lapack; cargo build; - - name: Build nalgebra-sparse --no-default-features - run: cd nalgebra-sparse; cargo build --no-default-features; - - name: Build nalgebra-sparse (default features) - run: cd nalgebra-sparse; cargo build; - - name: Build nalgebra-sparse --all-features - run: cd nalgebra-sparse; cargo build --all-features; - # Run this on it’s own job because it alone takes a lot of time. - # So it’s best to let it run in parallel to the other jobs. - build-nalgebra-all-features: - runs-on: ubuntu-latest - steps: - # Needed because the --all-features build which enables cuda support. - - uses: Jimver/cuda-toolkit@v0.2.4 - - uses: actions/checkout@v2 - - run: cargo build --all-features; - - run: cargo build -p nalgebra-glm --all-features; - test-nalgebra: - runs-on: ubuntu-latest -# env: -# RUSTFLAGS: -D warnings - steps: - - uses: actions/checkout@v2 - - name: test - run: cargo test --features arbitrary,rand,serde-serialize,sparse,debug,io,compare,libm,proptest-support,slow-tests; - test-nalgebra-glm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: test nalgebra-glm - run: cargo test -p nalgebra-glm --features arbitrary,serde-serialize; - test-nalgebra-sparse: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: test nalgebra-sparse - # Manifest-path is necessary because cargo otherwise won't correctly forward features - # We increase number of proptest cases to hopefully catch more potential bugs - run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize - - name: test nalgebra-sparse (slow tests) - # Unfortunately, the "slow-tests" take so much time that we need to run them with --release - run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize,slow-tests slow - test-nalgebra-macros: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: test nalgebra-macros - run: cargo test -p nalgebra-macros - build-wasm: - runs-on: ubuntu-latest -# env: -# RUSTFLAGS: -D warnings - steps: - - uses: actions/checkout@v2 - - run: rustup target add wasm32-unknown-unknown - - name: build nalgebra - run: cargo build --verbose --target wasm32-unknown-unknown; - - name: build nalgebra-glm - run: cargo build -p nalgebra-glm --verbose --target wasm32-unknown-unknown; - build-no-std: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install latest nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rustfmt - - name: install xargo - run: cp .github/Xargo.toml .; rustup component add rust-src; cargo install -f xargo; - - name: build x86_64-unknown-linux-gnu - run: xargo build --verbose --no-default-features --target=x86_64-unknown-linux-gnu; - - name: build x86_64-unknown-linux-gnu --features rand-no-std - run: xargo build --verbose --no-default-features --target=x86_64-unknown-linux-gnu; - - name: build x86_64-unknown-linux-gnu --features alloc - run: xargo build --verbose --no-default-features --features alloc --target=x86_64-unknown-linux-gnu; - - name: build thumbv7em-none-eabihf - run: xargo build --verbose --no-default-features --target=thumbv7em-none-eabihf; - - name: build x86_64-unknown-linux-gnu nalgebra-glm - run: xargo build --verbose --no-default-features -p nalgebra-glm --target=x86_64-unknown-linux-gnu; - - name: build thumbv7em-none-eabihf nalgebra-glm - run: xargo build --verbose --no-default-features -p nalgebra-glm --target=thumbv7em-none-eabihf; - build-cuda: - runs-on: ubuntu-latest - steps: - - uses: Jimver/cuda-toolkit@v0.2.4 - with: - cuda: '11.2.2' - - name: Install nightly-2021-12-04 - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-2021-12-04 - override: true - - uses: actions/checkout@v2 - - run: rustup target add nvptx64-nvidia-cuda - - run: cargo build --no-default-features --features cuda - - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda - env: - CUDA_ARCH: "350" From 4d968da1adc960e4ac51d0f3c39ea0d9ef77e1c1 Mon Sep 17 00:00:00 2001 From: Solra Bizna Date: Wed, 25 May 2022 16:42:18 -0600 Subject: [PATCH 082/112] Make "Point::new" a const fn --- src/geometry/point_construction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geometry/point_construction.rs b/src/geometry/point_construction.rs index 2136080a..ac54b349 100644 --- a/src/geometry/point_construction.rs +++ b/src/geometry/point_construction.rs @@ -202,7 +202,7 @@ impl Point1 { /// assert_eq!(p.x, 1.0); /// ``` #[inline] - pub fn new(x: T) -> Self { + pub const fn new(x: T) -> Self { Point { coords: Vector1::new(x), } @@ -216,7 +216,7 @@ macro_rules! componentwise_constructors_impl( #[doc = $doc] #[doc = "```"] #[inline] - pub fn new($($args: T),*) -> Self { + pub const fn new($($args: T),*) -> Self { Point { coords: $Vector::new($($args),*) } } } From 0cdf3ce4524231c0b9cb66c4ae08512e7af9d2a8 Mon Sep 17 00:00:00 2001 From: Jacob Trueb Date: Wed, 25 May 2022 17:51:19 -0500 Subject: [PATCH 083/112] Fix UnitComplex cast doctest failure on macOS --- src/geometry/unit_complex_construction.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/geometry/unit_complex_construction.rs b/src/geometry/unit_complex_construction.rs index ebf4e81d..9536e87f 100644 --- a/src/geometry/unit_complex_construction.rs +++ b/src/geometry/unit_complex_construction.rs @@ -131,10 +131,11 @@ where /// /// # Example /// ``` + /// #[macro_use] extern crate approx; /// # use nalgebra::UnitComplex; /// let c = UnitComplex::new(1.0f64); /// let c2 = c.cast::(); - /// assert_eq!(c2, UnitComplex::new(1.0f32)); + /// assert_relative_eq!(c2, UnitComplex::new(1.0f32)); /// ``` pub fn cast(self) -> UnitComplex where From 8aa10b819c465eb17b3fd4558512469cfab6cd98 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 11 Jun 2022 11:07:24 -0700 Subject: [PATCH 084/112] Implement `cast` for `Unit>` Currently, `cast` is resolved via `Unit`'s `Deref` impl, which leads to it confusingly stripping the `Unit` from `UnitVector`s. Add an inherent impl which takes precedence, similar to the existing specialization for `UnitQuaternion`. --- src/base/matrix.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 8f8786c1..d9294c9e 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -2186,3 +2186,28 @@ where } } } + +impl Unit> +where + T: Scalar, + D: Dim, + S: RawStorage, +{ + /// Cast the components of `self` to another type. + /// + /// # Example + /// ``` + /// # use nalgebra::Vector3; + /// let v = Vector3::::y_axis(); + /// let v2 = v.cast::(); + /// assert_eq!(v2, Vector3::::y_axis()); + /// ``` + pub fn cast(self) -> Unit> + where + T: Scalar, + OVector: SupersetOf>, + DefaultAllocator: Allocator, + { + Unit::new_unchecked(crate::convert_ref(self.as_ref())) + } +} From 80e77d2f9f0ed0e3d787e972ce598779d5fc599c Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 13 Jun 2022 09:55:16 +0200 Subject: [PATCH 085/112] Fix formatting --- nalgebra-sparse/src/io/matrix_market.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 36ca5ca4..975c586e 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1455,12 +1455,10 @@ fn next_dense_coordinate( /// assert_eq!(expected_str,generated_matrixmarket_str); /// # Ok(()) } /// ``` -pub fn save_to_matrix_market_str( - sparse_matrix: &S, -) -> String +pub fn save_to_matrix_market_str(sparse_matrix: &S) -> String where T: MatrixMarketScalar, - S: MatrixMarketExport + S: MatrixMarketExport, { let mut bytes = Vec::::new(); // This will call impl Write for Vec @@ -1496,10 +1494,7 @@ where /// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx")?; /// # Ok(()) } /// ``` -pub fn save_to_matrix_market_file( - sparse_matrix: &S, - path: P, -) -> Result<(), std::io::Error> +pub fn save_to_matrix_market_file(sparse_matrix: &S, path: P) -> Result<(), std::io::Error> where T: MatrixMarketScalar, S: MatrixMarketExport, @@ -1519,14 +1514,11 @@ where /// /// This is the most general save functionality. See [save_to_matrix_market_file] and /// [save_to_matrix_market_str] for higher-level functionality. -pub fn save_to_matrix_market( - mut w: W, - sparse_matrix: &S, -) -> Result<(), std::io::Error> +pub fn save_to_matrix_market(mut w: W, sparse_matrix: &S) -> Result<(), std::io::Error> where T: MatrixMarketScalar, S: MatrixMarketExport, - W: Write + W: Write, { // write header writeln!( From 1e31e6ba69625cbb5ed9eae14f990953b7290e3e Mon Sep 17 00:00:00 2001 From: devil-ira Date: Sat, 23 Jul 2022 17:39:59 +0200 Subject: [PATCH 086/112] Add feature `convert-glam021`. --- Cargo.toml | 2 ++ src/third_party/glam/mod.rs | 2 ++ src/third_party/glam/v021/mod.rs | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 src/third_party/glam/v021/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 732676ec..faee3029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ convert-glam017 = [ "glam017" ] convert-glam018 = [ "glam018" ] convert-glam019 = [ "glam019" ] convert-glam020 = [ "glam020" ] +convert-glam021 = [ "glam021" ] # Serialization ## To use serde in a #[no-std] environment, enable the @@ -95,6 +96,7 @@ glam017 = { package = "glam", version = "0.17", optional = true } glam018 = { package = "glam", version = "0.18", optional = true } glam019 = { package = "glam", version = "0.19", optional = true } glam020 = { package = "glam", version = "0.20", optional = true } +glam021 = { package = "glam", version = "0.21", optional = true } cust_core = { version = "0.1", optional = true } diff --git a/src/third_party/glam/mod.rs b/src/third_party/glam/mod.rs index ae2c4514..06bdcfb7 100644 --- a/src/third_party/glam/mod.rs +++ b/src/third_party/glam/mod.rs @@ -12,3 +12,5 @@ mod v018; mod v019; #[cfg(feature = "glam020")] mod v020; +#[cfg(feature = "glam021")] +mod v021; diff --git a/src/third_party/glam/v021/mod.rs b/src/third_party/glam/v021/mod.rs new file mode 100644 index 00000000..d0168923 --- /dev/null +++ b/src/third_party/glam/v021/mod.rs @@ -0,0 +1,18 @@ +#[path = "../common/glam_isometry.rs"] +mod glam_isometry; +#[path = "../common/glam_matrix.rs"] +mod glam_matrix; +#[path = "../common/glam_point.rs"] +mod glam_point; +#[path = "../common/glam_quaternion.rs"] +mod glam_quaternion; +#[path = "../common/glam_rotation.rs"] +mod glam_rotation; +#[path = "../common/glam_similarity.rs"] +mod glam_similarity; +#[path = "../common/glam_translation.rs"] +mod glam_translation; +#[path = "../common/glam_unit_complex.rs"] +mod glam_unit_complex; + +pub(self) use glam021 as glam; From 238750fb30a5f3785e37fbf4a4ebc5af48e55dd3 Mon Sep 17 00:00:00 2001 From: devil-ira Date: Sat, 23 Jul 2022 23:55:40 +0200 Subject: [PATCH 087/112] Use `From` trait to convert glam types to arrays. --- src/third_party/glam/common/glam_matrix.rs | 6 +++--- src/third_party/glam/common/glam_point.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/third_party/glam/common/glam_matrix.rs b/src/third_party/glam/common/glam_matrix.rs index fa9f713f..26f58803 100644 --- a/src/third_party/glam/common/glam_matrix.rs +++ b/src/third_party/glam/common/glam_matrix.rs @@ -14,7 +14,7 @@ macro_rules! impl_vec_conversion( impl From<$Vec2> for Vector2<$N> { #[inline] fn from(e: $Vec2) -> Vector2<$N> { - (*e.as_ref()).into() + <[$N;2]>::from(e).into() } } @@ -31,7 +31,7 @@ macro_rules! impl_vec_conversion( impl From<$Vec3> for Vector3<$N> { #[inline] fn from(e: $Vec3) -> Vector3<$N> { - (*e.as_ref()).into() + <[$N;3]>::from(e).into() } } @@ -48,7 +48,7 @@ macro_rules! impl_vec_conversion( impl From<$Vec4> for Vector4<$N> { #[inline] fn from(e: $Vec4) -> Vector4<$N> { - (*e.as_ref()).into() + <[$N;4]>::from(e).into() } } diff --git a/src/third_party/glam/common/glam_point.rs b/src/third_party/glam/common/glam_point.rs index b15a6c6d..205986b8 100644 --- a/src/third_party/glam/common/glam_point.rs +++ b/src/third_party/glam/common/glam_point.rs @@ -9,7 +9,7 @@ macro_rules! impl_point_conversion( impl From<$Vec2> for Point2<$N> { #[inline] fn from(e: $Vec2) -> Point2<$N> { - (*e.as_ref()).into() + <[$N;2]>::from(e).into() } } @@ -23,7 +23,7 @@ macro_rules! impl_point_conversion( impl From<$Vec3> for Point3<$N> { #[inline] fn from(e: $Vec3) -> Point3<$N> { - (*e.as_ref()).into() + <[$N;3]>::from(e).into() } } @@ -37,7 +37,7 @@ macro_rules! impl_point_conversion( impl From<$Vec4> for Point4<$N> { #[inline] fn from(e: $Vec4) -> Point4<$N> { - (*e.as_ref()).into() + <[$N;4]>::from(e).into() } } From f9aa2d76aab6ed1dc2c31f485f20d6557cc86233 Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Thu, 31 Mar 2022 17:28:31 +0200 Subject: [PATCH 088/112] Start from random rotation in from_matrix to prevent issues when calling from_matrix on rotation matrices --- src/geometry/quaternion_construction.rs | 4 +++- src/geometry/rotation_specialization.rs | 10 +++++++--- tests/geometry/rotation.rs | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 6de21bd5..dbd1edbc 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -410,9 +410,11 @@ where /// This is an iterative method. See `.from_matrix_eps` to provide mover /// convergence parameters and starting solution. /// This implements "A Robust Method to Extract the Rotational Part of Deformations" by Müller et al. + #[cfg(feature = "rand-no-std")] pub fn from_matrix(m: &Matrix3) -> Self where - T: RealField, + T: RealField + Scalar, + Standard: Distribution>, { Rotation3::from_matrix(m).into() } diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 41405c87..b57eeb53 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -706,11 +706,15 @@ where /// This is an iterative method. See `.from_matrix_eps` to provide mover /// convergence parameters and starting solution. /// This implements "A Robust Method to Extract the Rotational Part of Deformations" by Müller et al. + #[cfg(feature = "rand-no-std")] pub fn from_matrix(m: &Matrix3) -> Self where - T: RealField, + T: RealField + crate::Scalar, + Standard: Distribution>, { - Self::from_matrix_eps(m, T::default_epsilon(), 0, Self::identity()) + // Starting from a random rotation has almost zero likelihood to end up in a maximum if `m` is already a rotation matrix + let random_rotation: Rotation3 = rand::thread_rng().gen(); + Self::from_matrix_eps(m, T::default_epsilon(), 0, random_rotation) } /// Builds a rotation matrix by extracting the rotation part of the given transformation `m`. @@ -730,7 +734,7 @@ where T: RealField, { if max_iter == 0 { - max_iter = usize::max_value(); + max_iter = usize::MAX; } let mut rot = guess.into_inner(); diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 84bba676..61af83d9 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -1,4 +1,5 @@ -use na::{Quaternion, RealField, UnitQuaternion, Vector2, Vector3}; +use std::f64::consts::PI; +use na::{Matrix3, Quaternion, RealField, Rotation3, UnitQuaternion, UnitVector3, Vector2, Vector3}; #[test] fn angle_2() { @@ -16,6 +17,19 @@ fn angle_3() { assert_eq!(a.angle(&b), 0.0); } +#[test] +fn from_rotation_matrix() { + // Test degenerate case when from_matrix_eps gets stuck in maximum + let identity = Rotation3::from_matrix(&Matrix3::new( + 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, + )); + assert_relative_eq!(identity, &Rotation3::identity(), epsilon = 0.001); + let rotated_z = Rotation3::from_matrix(&Matrix3::new( + 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, + )); + assert_relative_eq!(rotated_z, &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), epsilon = 0.001); +} + #[test] fn quaternion_euler_angles_issue_494() { let quat = UnitQuaternion::from_quaternion(Quaternion::new( From 257d95b3d34b07a1f322076e4e2ff8298679bc92 Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Thu, 31 Mar 2022 18:03:59 +0200 Subject: [PATCH 089/112] Add test case for issue 1078 --- tests/geometry/rotation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 61af83d9..369b5454 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -28,6 +28,10 @@ fn from_rotation_matrix() { 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, )); assert_relative_eq!(rotated_z, &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), epsilon = 0.001); + // Test that issue 1078 is fixed + let m = nalgebra::Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); + assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m)); + assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m.clone()), nalgebra::Rotation3::from_matrix(&m)); } #[test] From ac203fe4fd101e3ae0e0a542f175fcb23f7dabbe Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Thu, 31 Mar 2022 18:07:04 +0200 Subject: [PATCH 090/112] Add test case for issue 628 --- tests/geometry/rotation.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 369b5454..661430a0 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -19,7 +19,7 @@ fn angle_3() { #[test] fn from_rotation_matrix() { - // Test degenerate case when from_matrix_eps gets stuck in maximum + // Test degenerate case when from_matrix gets stuck in Identity rotation let identity = Rotation3::from_matrix(&Matrix3::new( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, )); @@ -28,10 +28,15 @@ fn from_rotation_matrix() { 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, )); assert_relative_eq!(rotated_z, &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), epsilon = 0.001); + // Test that issue 628 is fixed + let m_628 = nalgebra::Matrix3::::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); + assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m_628), epsilon = 0.01); + assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m_628.clone()), nalgebra::Rotation3::from_matrix(&m_628), epsilon = 0.001); + // Test that issue 1078 is fixed - let m = nalgebra::Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); - assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m)); - assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m.clone()), nalgebra::Rotation3::from_matrix(&m)); + let m_1078 = nalgebra::Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); + assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m_1078), epsilon = 0.01); + assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m_1078.clone()), nalgebra::Rotation3::from_matrix(&m_1078), epsilon = 0.001); } #[test] From d515e4f1be71acc31eadb5f26133102e840403fe Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Fri, 1 Apr 2022 14:30:20 +0200 Subject: [PATCH 091/112] Perturbations to check for convergence into maximum. --- src/geometry/rotation_specialization.rs | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index b57eeb53..304f5ee5 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -17,7 +17,7 @@ use std::ops::Neg; use crate::base::dimension::{U1, U2, U3}; use crate::base::storage::Storage; -use crate::base::{Matrix2, Matrix3, SMatrix, SVector, Unit, Vector, Vector1, Vector2, Vector3}; +use crate::base::{Matrix2, Matrix3, SMatrix, SVector, Unit, Vector, Vector1, Vector2, Vector3, UnitVector3}; use crate::geometry::{Rotation2, Rotation3, UnitComplex, UnitQuaternion}; @@ -706,15 +706,12 @@ where /// This is an iterative method. See `.from_matrix_eps` to provide mover /// convergence parameters and starting solution. /// This implements "A Robust Method to Extract the Rotational Part of Deformations" by Müller et al. - #[cfg(feature = "rand-no-std")] pub fn from_matrix(m: &Matrix3) -> Self where - T: RealField + crate::Scalar, - Standard: Distribution>, + T: RealField, { // Starting from a random rotation has almost zero likelihood to end up in a maximum if `m` is already a rotation matrix - let random_rotation: Rotation3 = rand::thread_rng().gen(); - Self::from_matrix_eps(m, T::default_epsilon(), 0, random_rotation) + Self::from_matrix_eps(m, T::default_epsilon(), 0, Rotation3::identity()) } /// Builds a rotation matrix by extracting the rotation part of the given transformation `m`. @@ -737,6 +734,7 @@ where max_iter = usize::MAX; } + let mut perturbation_axes = UnitVector3::new_unchecked(Vector3::new(T::one(), T::zero(), T::zero())); let mut rot = guess.into_inner(); for _ in 0..max_iter { @@ -752,7 +750,25 @@ where if let Some((axis, angle)) = Unit::try_new_and_get(axisangle, eps.clone()) { rot = Rotation3::from_axis_angle(&axis, angle) * rot; } else { - break; + // Check if stuck in a maximum w.r.t. the norm (m - rot).norm() + let mut perturbed = rot.clone(); + let norm_squared = (m - &rot).norm_squared(); + let mut new_norm_squared: T; + // Perturb until the new norm is significantly different + loop { + perturbed *= Rotation3::from_axis_angle(&perturbation_axes, T::frac_pi_8()); + new_norm_squared = (m - &perturbed).norm_squared(); + if relative_ne!(norm_squared, new_norm_squared) { + break; + } + } + // If new norm is larger, it's a minimum + if norm_squared < new_norm_squared { + break; + } + // If not, continue from perturbed rotation, but use a different axes for the next perturbation + perturbation_axes = UnitVector3::new_unchecked(Vector3::new(perturbation_axes.y.clone(), perturbation_axes.z.clone(), perturbation_axes.x.clone())); + rot = perturbed; } } From 26e69863e1bbe6ccc4866dc34f944b49c03603ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 27 Jul 2022 09:45:54 +0200 Subject: [PATCH 092/112] Rotation from matrix: small code cleanups --- src/geometry/quaternion_construction.rs | 3 +-- src/geometry/rotation_specialization.rs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index dbd1edbc..18f7a65c 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -413,8 +413,7 @@ where #[cfg(feature = "rand-no-std")] pub fn from_matrix(m: &Matrix3) -> Self where - T: RealField + Scalar, - Standard: Distribution>, + T: RealField, { Rotation3::from_matrix(m).into() } diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 304f5ee5..f00d8cd7 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -17,7 +17,9 @@ use std::ops::Neg; use crate::base::dimension::{U1, U2, U3}; use crate::base::storage::Storage; -use crate::base::{Matrix2, Matrix3, SMatrix, SVector, Unit, Vector, Vector1, Vector2, Vector3, UnitVector3}; +use crate::base::{ + Matrix2, Matrix3, SMatrix, SVector, Unit, UnitVector3, Vector, Vector1, Vector2, Vector3, +}; use crate::geometry::{Rotation2, Rotation3, UnitComplex, UnitQuaternion}; @@ -710,8 +712,7 @@ where where T: RealField, { - // Starting from a random rotation has almost zero likelihood to end up in a maximum if `m` is already a rotation matrix - Self::from_matrix_eps(m, T::default_epsilon(), 0, Rotation3::identity()) + Self::from_matrix_eps(m, T::default_epsilon(), 0, Self::identity()) } /// Builds a rotation matrix by extracting the rotation part of the given transformation `m`. @@ -734,7 +735,7 @@ where max_iter = usize::MAX; } - let mut perturbation_axes = UnitVector3::new_unchecked(Vector3::new(T::one(), T::zero(), T::zero())); + let mut perturbation_axes = Vector3::x_axis(); let mut rot = guess.into_inner(); for _ in 0..max_iter { @@ -754,20 +755,24 @@ where let mut perturbed = rot.clone(); let norm_squared = (m - &rot).norm_squared(); let mut new_norm_squared: T; + // Perturb until the new norm is significantly different loop { perturbed *= Rotation3::from_axis_angle(&perturbation_axes, T::frac_pi_8()); new_norm_squared = (m - &perturbed).norm_squared(); + if relative_ne!(norm_squared, new_norm_squared) { break; } } + // If new norm is larger, it's a minimum if norm_squared < new_norm_squared { break; } + // If not, continue from perturbed rotation, but use a different axes for the next perturbation - perturbation_axes = UnitVector3::new_unchecked(Vector3::new(perturbation_axes.y.clone(), perturbation_axes.z.clone(), perturbation_axes.x.clone())); + perturbation_axes = UnitVector3::new_unchecked(perturbation_axes.yzx()); rot = perturbed; } } From 18a8a3067101d294b82c95b65a34db5367ee36ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 27 Jul 2022 09:46:02 +0200 Subject: [PATCH 093/112] cargo fmt --- tests/geometry/rotation.rs | 44 +++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 661430a0..883d4c0b 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -1,5 +1,7 @@ +use na::{ + Matrix3, Quaternion, RealField, Rotation3, UnitQuaternion, UnitVector3, Vector2, Vector3, +}; use std::f64::consts::PI; -use na::{Matrix3, Quaternion, RealField, Rotation3, UnitQuaternion, UnitVector3, Vector2, Vector3}; #[test] fn angle_2() { @@ -20,23 +22,41 @@ fn angle_3() { #[test] fn from_rotation_matrix() { // Test degenerate case when from_matrix gets stuck in Identity rotation - let identity = Rotation3::from_matrix(&Matrix3::new( - 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, - )); + let identity = + Rotation3::from_matrix(&Matrix3::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)); assert_relative_eq!(identity, &Rotation3::identity(), epsilon = 0.001); - let rotated_z = Rotation3::from_matrix(&Matrix3::new( - 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, - )); - assert_relative_eq!(rotated_z, &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), epsilon = 0.001); + let rotated_z = + Rotation3::from_matrix(&Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0)); + assert_relative_eq!( + rotated_z, + &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), + epsilon = 0.001 + ); // Test that issue 628 is fixed let m_628 = nalgebra::Matrix3::::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); - assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m_628), epsilon = 0.01); - assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m_628.clone()), nalgebra::Rotation3::from_matrix(&m_628), epsilon = 0.001); + assert_relative_ne!( + identity, + nalgebra::Rotation3::from_matrix(&m_628), + epsilon = 0.01 + ); + assert_relative_eq!( + nalgebra::Rotation3::from_matrix_unchecked(m_628.clone()), + nalgebra::Rotation3::from_matrix(&m_628), + epsilon = 0.001 + ); // Test that issue 1078 is fixed let m_1078 = nalgebra::Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); - assert_relative_ne!(identity, nalgebra::Rotation3::from_matrix(&m_1078), epsilon = 0.01); - assert_relative_eq!(nalgebra::Rotation3::from_matrix_unchecked(m_1078.clone()), nalgebra::Rotation3::from_matrix(&m_1078), epsilon = 0.001); + assert_relative_ne!( + identity, + nalgebra::Rotation3::from_matrix(&m_1078), + epsilon = 0.01 + ); + assert_relative_eq!( + nalgebra::Rotation3::from_matrix_unchecked(m_1078.clone()), + nalgebra::Rotation3::from_matrix(&m_1078), + epsilon = 0.001 + ); } #[test] From 7aadbcf21d2feafbe282fcdcc6b6469bf60dc859 Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Wed, 27 Jul 2022 11:31:34 +0200 Subject: [PATCH 094/112] From_rotation_matrix: Use the larger of eps.sqrt() or eps*eps as disturbance. Add tests for eps > 1.0 --- src/geometry/quaternion_construction.rs | 1 - src/geometry/rotation_specialization.rs | 7 +++-- tests/geometry/rotation.rs | 40 ++++++++++++++++++------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 18f7a65c..6de21bd5 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -410,7 +410,6 @@ where /// This is an iterative method. See `.from_matrix_eps` to provide mover /// convergence parameters and starting solution. /// This implements "A Robust Method to Extract the Rotational Part of Deformations" by Müller et al. - #[cfg(feature = "rand-no-std")] pub fn from_matrix(m: &Matrix3) -> Self where T: RealField, diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index f00d8cd7..fc2c9b93 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -735,6 +735,8 @@ where max_iter = usize::MAX; } + // Using sqrt(eps) ensures we perturb with something larger than eps; clamp to eps to handle the case of eps > 1.0 + let eps_disturbance = eps.clone().sqrt().max(eps.clone() * eps.clone()); let mut perturbation_axes = Vector3::x_axis(); let mut rot = guess.into_inner(); @@ -758,10 +760,9 @@ where // Perturb until the new norm is significantly different loop { - perturbed *= Rotation3::from_axis_angle(&perturbation_axes, T::frac_pi_8()); + perturbed *= Rotation3::from_axis_angle(&perturbation_axes, eps_disturbance.clone()); new_norm_squared = (m - &perturbed).norm_squared(); - - if relative_ne!(norm_squared, new_norm_squared) { + if abs_diff_ne!(norm_squared, new_norm_squared, epsilon = T::default_epsilon()) { break; } } diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 883d4c0b..f5a77b54 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -32,31 +32,51 @@ fn from_rotation_matrix() { &Rotation3::from_axis_angle(&UnitVector3::new_unchecked(Vector3::new(1.0, 0.0, 0.0)), PI), epsilon = 0.001 ); - // Test that issue 628 is fixed - let m_628 = nalgebra::Matrix3::::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); + // Test that issue 627 is fixed + let m_627 = Matrix3::::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); assert_relative_ne!( identity, - nalgebra::Rotation3::from_matrix(&m_628), + Rotation3::from_matrix(&m_627), epsilon = 0.01 ); assert_relative_eq!( - nalgebra::Rotation3::from_matrix_unchecked(m_628.clone()), - nalgebra::Rotation3::from_matrix(&m_628), + Rotation3::from_matrix_unchecked(m_627.clone()), + Rotation3::from_matrix(&m_627), epsilon = 0.001 ); - // Test that issue 1078 is fixed - let m_1078 = nalgebra::Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); + let m_1078 = Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); assert_relative_ne!( identity, - nalgebra::Rotation3::from_matrix(&m_1078), + Rotation3::from_matrix(&m_1078), epsilon = 0.01 ); assert_relative_eq!( - nalgebra::Rotation3::from_matrix_unchecked(m_1078.clone()), - nalgebra::Rotation3::from_matrix(&m_1078), + Rotation3::from_matrix_unchecked(m_1078.clone()), + Rotation3::from_matrix(&m_1078), epsilon = 0.001 ); + // Additional test cases for eps >= 1.0 + assert_relative_ne!( + identity, + Rotation3::from_matrix_eps(&m_627, 1.2, 0, Rotation3::identity()), + epsilon = 0.6 + ); + assert_relative_eq!( + Rotation3::from_matrix_unchecked(m_627.clone()), + Rotation3::from_matrix_eps(&m_627, 1.2, 0, Rotation3::identity()), + epsilon = 0.6 + ); + assert_relative_ne!( + identity, + Rotation3::from_matrix_eps(&m_1078, 1.0, 0, Rotation3::identity()), + epsilon = 0.1 + ); + assert_relative_eq!( + Rotation3::from_matrix_unchecked(m_1078.clone()), + Rotation3::from_matrix_eps(&m_1078, 1.0, 0, Rotation3::identity()), + epsilon = 0.1 + ); } #[test] From 0c2d9deac7da51c91cd5d2b8e08bcf05350cece0 Mon Sep 17 00:00:00 2001 From: Tim Taubner Date: Wed, 27 Jul 2022 11:44:42 +0200 Subject: [PATCH 095/112] cargo fmt --- src/geometry/rotation_specialization.rs | 9 +++++++-- tests/geometry/rotation.rs | 12 ++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index fc2c9b93..c9197fd6 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -760,9 +760,14 @@ where // Perturb until the new norm is significantly different loop { - perturbed *= Rotation3::from_axis_angle(&perturbation_axes, eps_disturbance.clone()); + perturbed *= + Rotation3::from_axis_angle(&perturbation_axes, eps_disturbance.clone()); new_norm_squared = (m - &perturbed).norm_squared(); - if abs_diff_ne!(norm_squared, new_norm_squared, epsilon = T::default_epsilon()) { + if abs_diff_ne!( + norm_squared, + new_norm_squared, + epsilon = T::default_epsilon() + ) { break; } } diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index f5a77b54..2e8d2482 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -34,11 +34,7 @@ fn from_rotation_matrix() { ); // Test that issue 627 is fixed let m_627 = Matrix3::::new(-1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); - assert_relative_ne!( - identity, - Rotation3::from_matrix(&m_627), - epsilon = 0.01 - ); + assert_relative_ne!(identity, Rotation3::from_matrix(&m_627), epsilon = 0.01); assert_relative_eq!( Rotation3::from_matrix_unchecked(m_627.clone()), Rotation3::from_matrix(&m_627), @@ -46,11 +42,7 @@ fn from_rotation_matrix() { ); // Test that issue 1078 is fixed let m_1078 = Matrix3::::new(0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0); - assert_relative_ne!( - identity, - Rotation3::from_matrix(&m_1078), - epsilon = 0.01 - ); + assert_relative_ne!(identity, Rotation3::from_matrix(&m_1078), epsilon = 0.01); assert_relative_eq!( Rotation3::from_matrix_unchecked(m_1078.clone()), Rotation3::from_matrix(&m_1078), From 008511d96eed1adfdb1b3cacda5ff8cf76a4d075 Mon Sep 17 00:00:00 2001 From: Chammika Mannakkara Date: Tue, 4 May 2021 22:23:49 +0900 Subject: [PATCH 096/112] from_row_iterator added --- src/base/allocator.rs | 7 +++ src/base/construction.rs | 41 +++++++++++++++++ src/base/default_allocator.rs | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/src/base/allocator.rs b/src/base/allocator.rs index 29286420..ccbcca37 100644 --- a/src/base/allocator.rs +++ b/src/base/allocator.rs @@ -41,6 +41,13 @@ pub trait Allocator: Any + Sized { ncols: C, iter: I, ) -> Self::Buffer; + + /// Allocates a buffer initialized with the content of the given row-major order iterator. + fn allocate_from_row_iterator>( + nrows: R, + ncols: C, + iter: I, + ) -> Self::Buffer; } /// A matrix reallocator. Changes the size of the memory buffer that initially contains (`RFrom` × diff --git a/src/base/construction.rs b/src/base/construction.rs index fe4e4b08..009d8c76 100644 --- a/src/base/construction.rs +++ b/src/base/construction.rs @@ -86,6 +86,17 @@ where Self::from_data(DefaultAllocator::allocate_from_iterator(nrows, ncols, iter)) } + /// Creates a matrix with all its elements filled by an row-major order iterator. + #[inline] + pub fn from_row_iterator_generic(nrows: R, ncols: C, iter: I) -> Self + where + I: IntoIterator, + { + Self::from_data(DefaultAllocator::allocate_from_row_iterator( + nrows, ncols, iter, + )) + } + /// Creates a matrix with its elements filled with the components provided by a slice in /// row-major order. /// @@ -479,6 +490,36 @@ macro_rules! impl_constructors( Self::from_iterator_generic($($gargs, )* iter) } + /// Creates a matrix or vector with all its elements filled by a row-major iterator. + /// + /// The output matrix is filled row-by-row. + /// + /// ## Example + /// ``` + /// # use nalgebra::{Matrix2x3, Vector3, DVector, DMatrix}; + /// # use std::iter; + /// + /// let v = Vector3::from_row_iterator((0..3).into_iter()); + /// // The additional argument represents the vector dimension. + /// let dv = DVector::from_row_iterator(3, (0..3).into_iter()); + /// let m = Matrix2x3::from_row_iterator((0..6).into_iter()); + /// // The two additional arguments represent the matrix dimensions. + /// let dm = DMatrix::from_row_iterator(2, 3, (0..6).into_iter()); + /// + /// // For Vectors from_row_iterator is identical to from_iterator + /// assert!(v.x == 0 && v.y == 1 && v.z == 2); + /// assert!(dv[0] == 0 && dv[1] == 1 && dv[2] == 2); + /// assert!(m.m11 == 0 && m.m12 == 1 && m.m13 == 2 && + /// m.m21 == 3 && m.m22 == 4 && m.m23 == 5); + /// assert!(dm[(0, 0)] == 0 && dm[(0, 1)] == 1 && dm[(0, 2)] == 2 && + /// dm[(1, 0)] == 3 && dm[(1, 1)] == 4 && dm[(1, 2)] == 5); + /// ``` + #[inline] + pub fn from_row_iterator($($args: usize,)* iter: I) -> Self + where I: IntoIterator { + Self::from_row_iterator_generic($($gargs, )* iter) + } + /// Creates a matrix or vector filled with the results of a function applied to each of its /// component coordinates. /// diff --git a/src/base/default_allocator.rs b/src/base/default_allocator.rs index 09197bbd..e055604c 100644 --- a/src/base/default_allocator.rs +++ b/src/base/default_allocator.rs @@ -80,6 +80,40 @@ impl Allocator, Const> // yielded enough elements to initialize our matrix. unsafe { , Const>>::assume_init(res) } } + + #[inline] + fn allocate_from_row_iterator>( + nrows: Const, + ncols: Const, + iter: I, + ) -> Self::Buffer { + #[cfg(feature = "no_unsound_assume_init")] + let mut res: Self::Buffer = unimplemented!(); + #[cfg(not(feature = "no_unsound_assume_init"))] + let mut res = unsafe { Self::allocate_uninitialized(nrows, ncols).assume_init() }; + let mut count = 0; + let res_ptr = res.as_mut_slice(); + + for (i, e) in iter + .into_iter() + .take(ncols.value() * nrows.value()) + .enumerate() + { + unsafe { + *res_ptr + .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; + } + // res_ptr[(i % ncols.value()) * nrows.value() + i / ncols.value()] = e; + count += 1; + } + + assert!( + count == nrows.value() * ncols.value(), + "Matrix init. from row iterator: iterator not long enough." + ); + + res + } } // Dynamic - Static @@ -128,6 +162,32 @@ impl Allocator for DefaultAllocator { VecStorage::new(nrows, ncols, res) } + + #[inline] + fn allocate_from_row_iterator>( + nrows: Dynamic, + ncols: C, + iter: I, + ) -> Self::Buffer { + let it = iter.into_iter().take(nrows.value() * ncols.value()); + let mut res: Vec = Vec::with_capacity(nrows.value() * ncols.value()); + let res_ptr = res.as_mut_ptr(); + let mut count = 0; + + unsafe { + for (i, e) in it.enumerate() { + *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; + count += 1; + } + res.set_len(nrows.value() * ncols.value()); + } + assert!( + count == nrows.value() * ncols.value(), + "Matrix init. from row iterator: iterator not long enough." + ); + + VecStorage::new(nrows, ncols, res) + } } // Static - Dynamic @@ -176,6 +236,32 @@ impl Allocator for DefaultAllocator { VecStorage::new(nrows, ncols, res) } + + #[inline] + fn allocate_from_row_iterator>( + nrows: R, + ncols: Dynamic, + iter: I, + ) -> Self::Buffer { + let it = iter.into_iter().take(nrows.value() * ncols.value()); + let mut res: Vec = Vec::with_capacity(nrows.value() * ncols.value()); + let res_ptr = res.as_mut_ptr(); + let mut count = 0; + + unsafe { + for (i, e) in it.enumerate() { + *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; + count += 1; + } + res.set_len(nrows.value() * ncols.value()); + } + assert!( + count == nrows.value() * ncols.value(), + "Matrix init. from row iterator: iterator not long enough." + ); + + VecStorage::new(nrows, ncols, res) + } } /* From 8588ef8fb20f6e3fb0f3dbbf99f32e850638a451 Mon Sep 17 00:00:00 2001 From: Gianluca Oldani Date: Wed, 2 Mar 2022 00:52:01 +0100 Subject: [PATCH 097/112] Sound implementation for from_row_iterator --- src/base/default_allocator.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/base/default_allocator.rs b/src/base/default_allocator.rs index e055604c..b048ba24 100644 --- a/src/base/default_allocator.rs +++ b/src/base/default_allocator.rs @@ -87,10 +87,7 @@ impl Allocator, Const> ncols: Const, iter: I, ) -> Self::Buffer { - #[cfg(feature = "no_unsound_assume_init")] - let mut res: Self::Buffer = unimplemented!(); - #[cfg(not(feature = "no_unsound_assume_init"))] - let mut res = unsafe { Self::allocate_uninitialized(nrows, ncols).assume_init() }; + let mut res = Self::allocate_uninit(nrows, ncols); let mut count = 0; let res_ptr = res.as_mut_slice(); @@ -101,7 +98,7 @@ impl Allocator, Const> { unsafe { *res_ptr - .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; + .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = MaybeUninit::new(e); } // res_ptr[(i % ncols.value()) * nrows.value() + i / ncols.value()] = e; count += 1; @@ -112,7 +109,7 @@ impl Allocator, Const> "Matrix init. from row iterator: iterator not long enough." ); - res + unsafe { , Const>>::assume_init(res) } } } @@ -250,7 +247,7 @@ impl Allocator for DefaultAllocator { unsafe { for (i, e) in it.enumerate() { - *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; + *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = MaybeUninit::new(e).assume_init(); count += 1; } res.set_len(nrows.value() * ncols.value()); From 59b01e955f2d6901eba972c58c2ffcb4c9b5781d Mon Sep 17 00:00:00 2001 From: Gianluca Oldani Date: Thu, 3 Mar 2022 13:33:04 +0100 Subject: [PATCH 098/112] Fixed formatting --- src/base/default_allocator.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/base/default_allocator.rs b/src/base/default_allocator.rs index b048ba24..d9bc2c6b 100644 --- a/src/base/default_allocator.rs +++ b/src/base/default_allocator.rs @@ -98,7 +98,8 @@ impl Allocator, Const> { unsafe { *res_ptr - .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = MaybeUninit::new(e); + .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = + MaybeUninit::new(e); } // res_ptr[(i % ncols.value()) * nrows.value() + i / ncols.value()] = e; count += 1; @@ -247,7 +248,8 @@ impl Allocator for DefaultAllocator { unsafe { for (i, e) in it.enumerate() { - *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = MaybeUninit::new(e).assume_init(); + *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = + MaybeUninit::new(e).assume_init(); count += 1; } res.set_len(nrows.value() * ncols.value()); From 89767ee9f33106f874127fd2ee018dc259ad070b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 30 Jul 2022 18:06:47 +0200 Subject: [PATCH 099/112] Reduce code duplication for allocating a storage from a raw iterator. --- src/base/allocator.rs | 30 ++++++++++++- src/base/default_allocator.rs | 85 ----------------------------------- 2 files changed, 29 insertions(+), 86 deletions(-) diff --git a/src/base/allocator.rs b/src/base/allocator.rs index ccbcca37..bb227d3b 100644 --- a/src/base/allocator.rs +++ b/src/base/allocator.rs @@ -42,12 +42,40 @@ pub trait Allocator: Any + Sized { iter: I, ) -> Self::Buffer; + #[inline] /// Allocates a buffer initialized with the content of the given row-major order iterator. fn allocate_from_row_iterator>( nrows: R, ncols: C, iter: I, - ) -> Self::Buffer; + ) -> Self::Buffer { + let mut res = Self::allocate_uninit(nrows, ncols); + let mut count = 0; + + unsafe { + // OK because the allocated buffer is guaranteed to be contiguous. + let res_ptr = res.as_mut_slice_unchecked(); + + for (k, e) in iter + .into_iter() + .take(ncols.value() * nrows.value()) + .enumerate() + { + let i = k / ncols.value(); + let j = k % ncols.value(); + // result[(i, j)] = e; + *res_ptr.get_unchecked_mut(i + j * nrows.value()) = MaybeUninit::new(e); + count += 1; + } + + assert!( + count == nrows.value() * ncols.value(), + "Matrix init. from row iterator: iterator not long enough." + ); + + >::assume_init(res) + } + } } /// A matrix reallocator. Changes the size of the memory buffer that initially contains (`RFrom` × diff --git a/src/base/default_allocator.rs b/src/base/default_allocator.rs index d9bc2c6b..09197bbd 100644 --- a/src/base/default_allocator.rs +++ b/src/base/default_allocator.rs @@ -80,38 +80,6 @@ impl Allocator, Const> // yielded enough elements to initialize our matrix. unsafe { , Const>>::assume_init(res) } } - - #[inline] - fn allocate_from_row_iterator>( - nrows: Const, - ncols: Const, - iter: I, - ) -> Self::Buffer { - let mut res = Self::allocate_uninit(nrows, ncols); - let mut count = 0; - let res_ptr = res.as_mut_slice(); - - for (i, e) in iter - .into_iter() - .take(ncols.value() * nrows.value()) - .enumerate() - { - unsafe { - *res_ptr - .get_unchecked_mut((i % ncols.value()) * nrows.value() + i / ncols.value()) = - MaybeUninit::new(e); - } - // res_ptr[(i % ncols.value()) * nrows.value() + i / ncols.value()] = e; - count += 1; - } - - assert!( - count == nrows.value() * ncols.value(), - "Matrix init. from row iterator: iterator not long enough." - ); - - unsafe { , Const>>::assume_init(res) } - } } // Dynamic - Static @@ -160,32 +128,6 @@ impl Allocator for DefaultAllocator { VecStorage::new(nrows, ncols, res) } - - #[inline] - fn allocate_from_row_iterator>( - nrows: Dynamic, - ncols: C, - iter: I, - ) -> Self::Buffer { - let it = iter.into_iter().take(nrows.value() * ncols.value()); - let mut res: Vec = Vec::with_capacity(nrows.value() * ncols.value()); - let res_ptr = res.as_mut_ptr(); - let mut count = 0; - - unsafe { - for (i, e) in it.enumerate() { - *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = e; - count += 1; - } - res.set_len(nrows.value() * ncols.value()); - } - assert!( - count == nrows.value() * ncols.value(), - "Matrix init. from row iterator: iterator not long enough." - ); - - VecStorage::new(nrows, ncols, res) - } } // Static - Dynamic @@ -234,33 +176,6 @@ impl Allocator for DefaultAllocator { VecStorage::new(nrows, ncols, res) } - - #[inline] - fn allocate_from_row_iterator>( - nrows: R, - ncols: Dynamic, - iter: I, - ) -> Self::Buffer { - let it = iter.into_iter().take(nrows.value() * ncols.value()); - let mut res: Vec = Vec::with_capacity(nrows.value() * ncols.value()); - let res_ptr = res.as_mut_ptr(); - let mut count = 0; - - unsafe { - for (i, e) in it.enumerate() { - *res_ptr.add((i % ncols.value()) * nrows.value() + i / ncols.value()) = - MaybeUninit::new(e).assume_init(); - count += 1; - } - res.set_len(nrows.value() * ncols.value()); - } - assert!( - count == nrows.value() * ncols.value(), - "Matrix init. from row iterator: iterator not long enough." - ); - - VecStorage::new(nrows, ncols, res) - } } /* From d5284a2a86ac05d40720d14ecf71896abdcb7241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 30 Jul 2022 18:16:18 +0200 Subject: [PATCH 100/112] Remove unused extern crate. --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1ee1a3ba..8da86531 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,8 +109,6 @@ extern crate alloc; #[cfg(not(feature = "std"))] extern crate core as std; -#[cfg(feature = "io")] -extern crate pest; #[macro_use] #[cfg(feature = "io")] extern crate pest_derive; From 54ab62d9715831384a4ea618f1cf641bd8f4c6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 30 Jul 2022 18:19:17 +0200 Subject: [PATCH 101/112] Remove another unused extern crate. --- nalgebra-sparse/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index 8567261a..c62677c3 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -143,8 +143,6 @@ )] pub extern crate nalgebra as na; -#[cfg(feature = "io")] -extern crate pest; #[macro_use] #[cfg(feature = "io")] extern crate pest_derive; From 11ff2ed69b42ba4b77482dca4ebde7513290429b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 31 Jul 2022 09:42:02 +0200 Subject: [PATCH 102/112] Release v0.31.1 --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 6 +++--- nalgebra-sparse/Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c00c01fc..2950a1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.31.1] (31 July 2022) + +### Modified +- Improve performances of multiplication of two sparse matrices. + +### Added +- Add `Matrix::from_row_iterator` to build a matrix from an iterator yielding components in row-major order. +- Add support for conversion from/to types of `glam` 0.21. +- `nalgebra-sparse`: add support for the matrix-market export of sparse matrices. +- `nalgebra-lapack`: add a `GE` for solving the generalized eigenvalues problem. + +### Fixed +- Fix `Rotation3::from_matrix` and `UnitQuaternion::from_matrix` when the input matrix is already a valid + rotation matrix. + ## [0.31.0] (30 Apr. 2022) ### Breaking changes diff --git a/Cargo.toml b/Cargo.toml index faee3029..5b9940df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.31.0" +version = "0.31.1" authors = [ "Sébastien Crozet " ] description = "General-purpose linear algebra library with transformations and statically-sized or dynamically-sized matrices." @@ -132,6 +132,6 @@ required-features = ["rand"] lto = true [package.metadata.docs.rs] -# Enable certain features when building docs for docs.rs -features = [ "proptest-support", "compare", "macros", "rand" ] +# Enable all the features when building the docs on docs.rs +all-features = true diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 390f594e..b9c343e4 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-sparse" -version = "0.7.0" +version = "0.7.1" authors = [ "Andreas Longva", "Sébastien Crozet " ] edition = "2018" description = "Sparse matrix computation based on nalgebra." From d3b7acce4d97823d37b7d652293765dcc6e1562f Mon Sep 17 00:00:00 2001 From: Ritoban Roy-Chowdhury Date: Mon, 8 Aug 2022 17:48:10 -0700 Subject: [PATCH 103/112] update DefaultAllocator docs to say ArrayStorage instead of GenericArray --- src/base/default_allocator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/default_allocator.rs b/src/base/default_allocator.rs index 09197bbd..6aaadfc7 100644 --- a/src/base/default_allocator.rs +++ b/src/base/default_allocator.rs @@ -26,7 +26,7 @@ use std::mem::{ManuallyDrop, MaybeUninit}; * Allocator. * */ -/// An allocator based on `GenericArray` and `VecStorage` for statically-sized and dynamically-sized +/// An allocator based on [`ArrayStorage`] and [`VecStorage`] for statically-sized and dynamically-sized /// matrices respectively. #[derive(Copy, Clone, Debug)] pub struct DefaultAllocator; From 84c44984b1fd8975bdd195e3b5c9077c4c9c5e55 Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Fri, 12 Aug 2022 01:33:11 -0400 Subject: [PATCH 104/112] Use #[inline] to improve opt-level 1 performance --- src/base/dimension.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/base/dimension.rs b/src/base/dimension.rs index 4be97586..b1b700d7 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -252,14 +252,17 @@ pub trait ToTypenum { } unsafe impl Dim for Const { + #[inline] fn try_to_usize() -> Option { Some(T) } + #[inline] fn value(&self) -> usize { T } + #[inline] fn from_usize(dim: usize) -> Self { assert_eq!(dim, T); Self From b90dc7c042e9278786ba54c5b0fc8d85db8412d7 Mon Sep 17 00:00:00 2001 From: lukas Date: Mon, 15 Aug 2022 19:14:38 -0700 Subject: [PATCH 105/112] Add `clear_triplet()` --- nalgebra-sparse/src/coo.rs | 20 +++++++++++++++++ nalgebra-sparse/tests/unit_tests/coo.rs | 29 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 2b302e37..e047745a 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -211,6 +211,26 @@ impl CooMatrix { self.values.push(v); } + /// Remove a single triplet from the matrix. + /// + /// This removes the value `v` from the `i`th row and `j`th column in the matrix. + pub fn clear_triplet(&mut self, i: usize, j: usize, v: T) -> Option<(usize, usize, T)> + where + T: PartialEq, + { + let triple_idx = self + .triplet_iter() + .position(|triplet| triplet == (i, j, &v)); + if let Some(triple_idx) = triple_idx { + self.row_indices.remove(triple_idx); + self.col_indices.remove(triple_idx); + let retv = self.values.remove(triple_idx); + Some((i, j, retv)) + } else { + None + } + } + /// The number of rows in the matrix. #[inline] #[must_use] diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index c70c5f97..6ec88fb1 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -226,6 +226,35 @@ fn coo_push_valid_entries() { ); } +#[test] +fn coo_clear_triplet_valid_entries() { + let mut coo = CooMatrix::new(3, 3); + + coo.push(0, 0, 1); + coo.push(0, 0, 2); + coo.push(2, 2, 3); + + // clear a triplet that is not included + let triplet = coo.clear_triplet(0, 0, 0); + assert_eq!(triplet, None); + assert_eq!( + coo.triplet_iter().collect::>(), + vec![(0, 0, &1), (0, 0, &2), (2, 2, &3)] + ); + let triplet = coo.clear_triplet(0, 0, 1); + assert_eq!(triplet, Some((0, 0, 1))); + assert_eq!( + coo.triplet_iter().collect::>(), + vec![(0, 0, &2), (2, 2, &3)] + ); + let triplet = coo.clear_triplet(0, 0, 2); + assert_eq!(triplet, Some((0, 0, 2))); + assert_eq!(coo.triplet_iter().collect::>(), vec![(2, 2, &3)]); + let triplet = coo.clear_triplet(2, 2, 3); + assert_eq!(triplet, Some((2, 2, 3))); + assert_eq!(coo.triplet_iter().collect::>(), vec![]); +} + #[test] fn coo_push_out_of_bounds_entries() { { From bcc5527baac743df259b85c9a61a5793a63ce8fc Mon Sep 17 00:00:00 2001 From: lukas Date: Mon, 15 Aug 2022 19:28:58 -0700 Subject: [PATCH 106/112] Switch return type to just T --- nalgebra-sparse/src/coo.rs | 6 +++--- nalgebra-sparse/tests/unit_tests/coo.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index e047745a..197ef6f4 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -214,7 +214,7 @@ impl CooMatrix { /// Remove a single triplet from the matrix. /// /// This removes the value `v` from the `i`th row and `j`th column in the matrix. - pub fn clear_triplet(&mut self, i: usize, j: usize, v: T) -> Option<(usize, usize, T)> + pub fn clear_triplet(&mut self, i: usize, j: usize, v: T) -> Option where T: PartialEq, { @@ -224,8 +224,8 @@ impl CooMatrix { if let Some(triple_idx) = triple_idx { self.row_indices.remove(triple_idx); self.col_indices.remove(triple_idx); - let retv = self.values.remove(triple_idx); - Some((i, j, retv)) + let removed_value = self.values.remove(triple_idx); + Some(removed_value) } else { None } diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index 6ec88fb1..4b640596 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -242,16 +242,16 @@ fn coo_clear_triplet_valid_entries() { vec![(0, 0, &1), (0, 0, &2), (2, 2, &3)] ); let triplet = coo.clear_triplet(0, 0, 1); - assert_eq!(triplet, Some((0, 0, 1))); + assert_eq!(triplet, Some(1)); assert_eq!( coo.triplet_iter().collect::>(), vec![(0, 0, &2), (2, 2, &3)] ); let triplet = coo.clear_triplet(0, 0, 2); - assert_eq!(triplet, Some((0, 0, 2))); + assert_eq!(triplet, Some(2)); assert_eq!(coo.triplet_iter().collect::>(), vec![(2, 2, &3)]); let triplet = coo.clear_triplet(2, 2, 3); - assert_eq!(triplet, Some((2, 2, 3))); + assert_eq!(triplet, Some(3)); assert_eq!(coo.triplet_iter().collect::>(), vec![]); } From bdfa643e3cc749b44058ab467068e4aa96287513 Mon Sep 17 00:00:00 2001 From: lukas Date: Tue, 16 Aug 2022 00:15:03 -0700 Subject: [PATCH 107/112] clear_triplets --- nalgebra-sparse/src/coo.rs | 20 +++++--------------- nalgebra-sparse/tests/unit_tests/coo.rs | 20 +++----------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 197ef6f4..9793fa1e 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -211,24 +211,14 @@ impl CooMatrix { self.values.push(v); } - /// Remove a single triplet from the matrix. - /// - /// This removes the value `v` from the `i`th row and `j`th column in the matrix. - pub fn clear_triplet(&mut self, i: usize, j: usize, v: T) -> Option + /// Clear all triplets from the matrix. + pub fn clear_triplets(&mut self, i: usize, j: usize, v: T) -> Option where T: PartialEq, { - let triple_idx = self - .triplet_iter() - .position(|triplet| triplet == (i, j, &v)); - if let Some(triple_idx) = triple_idx { - self.row_indices.remove(triple_idx); - self.col_indices.remove(triple_idx); - let removed_value = self.values.remove(triple_idx); - Some(removed_value) - } else { - None - } + self.col_indices.clear(); + self.row_indices.clear(); + self.values.clear(); } /// The number of rows in the matrix. diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index 4b640596..b9c22885 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -227,32 +227,18 @@ fn coo_push_valid_entries() { } #[test] -fn coo_clear_triplet_valid_entries() { +fn coo_clear_triplets_valid_entries() { let mut coo = CooMatrix::new(3, 3); coo.push(0, 0, 1); coo.push(0, 0, 2); coo.push(2, 2, 3); - - // clear a triplet that is not included - let triplet = coo.clear_triplet(0, 0, 0); - assert_eq!(triplet, None); assert_eq!( coo.triplet_iter().collect::>(), vec![(0, 0, &1), (0, 0, &2), (2, 2, &3)] ); - let triplet = coo.clear_triplet(0, 0, 1); - assert_eq!(triplet, Some(1)); - assert_eq!( - coo.triplet_iter().collect::>(), - vec![(0, 0, &2), (2, 2, &3)] - ); - let triplet = coo.clear_triplet(0, 0, 2); - assert_eq!(triplet, Some(2)); - assert_eq!(coo.triplet_iter().collect::>(), vec![(2, 2, &3)]); - let triplet = coo.clear_triplet(2, 2, 3); - assert_eq!(triplet, Some(3)); - assert_eq!(coo.triplet_iter().collect::>(), vec![]); + coo.clear_triplets(); + assert_eq(coo.triplet_iter.collect::>(), vec![]); } #[test] From 41e1cc0db223bdbd4fc5547212760d68fd262f30 Mon Sep 17 00:00:00 2001 From: lukas Date: Tue, 16 Aug 2022 00:20:13 -0700 Subject: [PATCH 108/112] extend test case --- nalgebra-sparse/tests/unit_tests/coo.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index b9c22885..16ee5647 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -239,6 +239,14 @@ fn coo_clear_triplets_valid_entries() { ); coo.clear_triplets(); assert_eq(coo.triplet_iter.collect::>(), vec![]); + // making sure everyhting works after clearing + coo.push(0, 0, 1); + coo.push(0, 0, 2); + coo.push(2, 2, 3); + assert_eq!( + coo.triplet_iter().collect::>(), + vec![(0, 0, &1), (0, 0, &2), (2, 2, &3)] + ); } #[test] From b9483ab545621517974390a7d4ff1d846318683c Mon Sep 17 00:00:00 2001 From: Lukas Hermann Date: Tue, 16 Aug 2022 22:06:43 +0000 Subject: [PATCH 109/112] remove bad return type --- nalgebra-sparse/src/coo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 9793fa1e..240e2c57 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -212,7 +212,7 @@ impl CooMatrix { } /// Clear all triplets from the matrix. - pub fn clear_triplets(&mut self, i: usize, j: usize, v: T) -> Option + pub fn clear_triplets(&mut self, i: usize, j: usize, v: T) where T: PartialEq, { From 0eb1f5c125b65847fea06f5d0f01f030096c3450 Mon Sep 17 00:00:00 2001 From: lukas Date: Tue, 16 Aug 2022 20:10:17 -0700 Subject: [PATCH 110/112] 'fix error's --- nalgebra-sparse/src/coo.rs | 2 +- nalgebra-sparse/tests/unit_tests/coo.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 240e2c57..1a1720ed 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -212,7 +212,7 @@ impl CooMatrix { } /// Clear all triplets from the matrix. - pub fn clear_triplets(&mut self, i: usize, j: usize, v: T) + pub fn clear_triplets(&mut self) where T: PartialEq, { diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index 16ee5647..d232b041 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -238,7 +238,7 @@ fn coo_clear_triplets_valid_entries() { vec![(0, 0, &1), (0, 0, &2), (2, 2, &3)] ); coo.clear_triplets(); - assert_eq(coo.triplet_iter.collect::>(), vec![]); + assert_eq!(coo.triplet_iter().collect::>(), vec![]); // making sure everyhting works after clearing coo.push(0, 0, 1); coo.push(0, 0, 2); From 498fbf51b13d3af325c2f4c546fa4b394d53818f Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 20 Aug 2022 00:09:12 -0700 Subject: [PATCH 111/112] remove unnecessary trait bounds --- nalgebra-sparse/src/coo.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 1a1720ed..25dc07fb 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -212,10 +212,7 @@ impl CooMatrix { } /// Clear all triplets from the matrix. - pub fn clear_triplets(&mut self) - where - T: PartialEq, - { + pub fn clear_triplets(&mut self) { self.col_indices.clear(); self.row_indices.clear(); self.values.clear(); From 3aca9af616620aa54f63ae1f5c876ca99c1a2890 Mon Sep 17 00:00:00 2001 From: Patiga Date: Fri, 19 Aug 2022 13:34:21 +0200 Subject: [PATCH 112/112] Link listed types in lib.rs to their docs Helpful because lib.rs is the 'main page' for docs.rs This allows for easy/direct access to the mentioned types Currently you need to look up mentioned types via the search bar --- src/lib.rs | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8da86531..86a5dc6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,28 +46,34 @@ fn main() { **nalgebra** is meant to be a general-purpose, low-dimensional, linear algebra library, with an optimized set of tools for computer graphics and physics. Those features include: -* A single parametrizable type `Matrix` for vectors, (square or rectangular) matrices, and slices - with dimensions known either at compile-time (using type-level integers) or at runtime. +* A single parametrizable type [`Matrix`](Matrix) for vectors, (square or rectangular) matrices, and + slices with dimensions known either at compile-time (using type-level integers) or at runtime. * Matrices and vectors with compile-time sizes are statically allocated while dynamic ones are allocated on the heap. -* Convenient aliases for low-dimensional matrices and vectors: `Vector1` to `Vector6` and - `Matrix1x1` to `Matrix6x6`, including rectangular matrices like `Matrix2x5`. -* Points sizes known at compile time, and convenience aliases: `Point1` to `Point6`. -* Translation (seen as a transformation that composes by multiplication): `Translation2`, - `Translation3`. -* Rotation matrices: `Rotation2`, `Rotation3`. -* Quaternions: `Quaternion`, `UnitQuaternion` (for 3D rotation). -* Unit complex numbers can be used for 2D rotation: `UnitComplex`. -* Algebraic entities with a norm equal to one: `Unit`, e.g., `Unit>`. -* Isometries (translation ⨯ rotation): `Isometry2`, `Isometry3` -* Similarity transformations (translation ⨯ rotation ⨯ uniform scale): `Similarity2`, `Similarity3`. -* Affine transformations stored as a homogeneous matrix: `Affine2`, `Affine3`. -* Projective (i.e. invertible) transformations stored as a homogeneous matrix: `Projective2`, - `Projective3`. +* Convenient aliases for low-dimensional matrices and vectors: [`Vector1`](Vector1) to + [`Vector6`](Vector6) and [`Matrix1x1`](Matrix1) to [`Matrix6x6`](Matrix6), including rectangular + matrices like [`Matrix2x5`](Matrix2x5). +* Points sizes known at compile time, and convenience aliases: [`Point1`](Point1) to + [`Point6`](Point6). +* Translation (seen as a transformation that composes by multiplication): + [`Translation2`](Translation2), [`Translation3`](Translation3). +* Rotation matrices: [`Rotation2`](Rotation2), [`Rotation3`](Rotation3). +* Quaternions: [`Quaternion`](Quaternion), [`UnitQuaternion`](UnitQuaternion) (for 3D rotation). +* Unit complex numbers can be used for 2D rotation: [`UnitComplex`](UnitComplex). +* Algebraic entities with a norm equal to one: [`Unit`](Unit), e.g., `Unit>`. +* Isometries (translation ⨯ rotation): [`Isometry2`](Isometry2), [`Isometry3`](Isometry3) +* Similarity transformations (translation ⨯ rotation ⨯ uniform scale): + [`Similarity2`](Similarity2), [`Similarity3`](Similarity3). +* Affine transformations stored as a homogeneous matrix: + [`Affine2`](Affine2), [`Affine3`](Affine3). +* Projective (i.e. invertible) transformations stored as a homogeneous matrix: + [`Projective2`](Projective2), [`Projective3`](Projective3). * General transformations that does not have to be invertible, stored as a homogeneous matrix: - `Transform2`, `Transform3`. -* 3D projections for computer graphics: `Perspective3`, `Orthographic3`. -* Matrix factorizations: `Cholesky`, `QR`, `LU`, `FullPivLU`, `SVD`, `Schur`, `Hessenberg`, `SymmetricEigen`. + [`Transform2`](Transform2), [`Transform3`](Transform3). +* 3D projections for computer graphics: [`Perspective3`](Perspective3), + [`Orthographic3`](Orthographic3). +* Matrix factorizations: [`Cholesky`](Cholesky), [`QR`](QR), [`LU`](LU), [`FullPivLU`](FullPivLU), + [`SVD`](SVD), [`Schur`](Schur), [`Hessenberg`](Hessenberg), [`SymmetricEigen`](SymmetricEigen). * Insertion and removal of rows of columns of a matrix. */