From d511e372de5a5555ea44f3f681f98f17dd498e09 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Mon, 24 Jan 2022 23:17:30 +0100 Subject: [PATCH] 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); }