add support for matrix market export

This commit is contained in:
Hantao Hui 2022-01-24 23:17:30 +01:00
parent 6cc633474d
commit d511e372de
4 changed files with 190 additions and 10 deletions

View File

@ -37,4 +37,4 @@ nalgebra = { version="0.30", path = "../", features = ["compare"] }
[package.metadata.docs.rs] [package.metadata.docs.rs]
# Enable certain features when building docs for docs.rs # Enable certain features when building docs for docs.rs
features = [ "proptest-support", "compare" ] features = [ "proptest-support", "compare" , "io"]

View File

@ -1,9 +1,10 @@
//! Implementation of matrix market io code. //! 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. //! 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::SparseFormatError;
use crate::SparseFormatErrorKind; use crate::SparseFormatErrorKind;
use matrixcompare_core::SparseAccess;
use nalgebra::Complex; use nalgebra::Complex;
use pest::iterators::Pairs; use pest::iterators::Pairs;
use pest::Parser; use pest::Parser;
@ -12,7 +13,8 @@ use std::convert::Infallible;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::fs; use std::fs::{self, File};
use std::io::Write;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::num::TryFromIntError; use std::num::TryFromIntError;
use std::path::Path; use std::path::Path;
@ -267,7 +269,7 @@ impl fmt::Display for MatrixMarketError {
write!(f, "InvalidHeader,")?; write!(f, "InvalidHeader,")?;
} }
MatrixMarketErrorKind::EntryMismatch => { MatrixMarketErrorKind::EntryMismatch => {
write!(f, "EntryNumUnmatched,")?; write!(f, "EntryMismatch,")?;
} }
MatrixMarketErrorKind::TypeMismatch => { MatrixMarketErrorKind::TypeMismatch => {
write!(f, "TypeMismatch,")?; write!(f, "TypeMismatch,")?;
@ -288,7 +290,7 @@ impl fmt::Display for MatrixMarketError {
write!(f, "NotLowerTriangle,")?; write!(f, "NotLowerTriangle,")?;
} }
MatrixMarketErrorKind::NonSquare => { MatrixMarketErrorKind::NonSquare => {
write!(f, "NotSquareMatrix,")?; write!(f, "NonSquare,")?;
} }
} }
write!(f, " message: {}", self.message) write!(f, " message: {}", self.message)
@ -506,6 +508,10 @@ mod internal {
fn negative(self) -> Result<Self, MatrixMarketError>; fn negative(self) -> Result<Self, MatrixMarketError>;
/// When matrix is a Hermitian matrix, it will convert itself to its conjugate. /// When matrix is a Hermitian matrix, it will convert itself to its conjugate.
fn conjugate(self) -> Result<Self, MatrixMarketError>; fn conjugate(self) -> Result<Self, MatrixMarketError>;
/// 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<Self, MatrixMarketError> { fn negative(self) -> Result<Self, MatrixMarketError> {
Ok(-self) 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<Self, MatrixMarketError> { fn negative(self) -> Result<Self, MatrixMarketError> {
Ok(-self) 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<Self, MatrixMarketError> { fn negative(self) -> Result<Self, MatrixMarketError> {
Ok(-self) 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"), 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::<i32>(&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::<i32>(&generated_matrixmarket_string).unwrap();
/// assert_matrix_eq!(matrix,generated_matrixmarket);
/// ```
pub fn write_to_matrix_market_str<T: MatrixMarketScalar, S: SparseAccess<T>>(
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::<i32>(&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<T: MatrixMarketScalar, S: SparseAccess<T>, P: AsRef<Path>>(
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(())
}

View File

@ -6,7 +6,7 @@
//! //!
//! | Format | Import | Export | //! | 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 //! [Matrix market]: https://math.nist.gov/MatrixMarket/formats.html
//! //!
@ -32,7 +32,7 @@
//! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996). //! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996).
pub use self::matrix_market::{ pub use self::matrix_market::{
load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, MatrixMarketError, load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, write_to_matrix_market_file,
MatrixMarketErrorKind, MatrixMarketScalar, write_to_matrix_market_str, MatrixMarketError, MatrixMarketErrorKind, MatrixMarketScalar,
}; };
mod matrix_market; mod matrix_market;

View File

@ -1,7 +1,7 @@
use matrixcompare::assert_matrix_eq; use matrixcompare::assert_matrix_eq;
use nalgebra::dmatrix; use nalgebra::dmatrix;
use nalgebra::Complex; 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; use nalgebra_sparse::CooMatrix;
#[test] #[test]
@ -19,6 +19,10 @@ fn test_matrixmarket_sparse_real_general_empty() {
assert_eq!(sparse_mat.nrows(), shape.0); assert_eq!(sparse_mat.nrows(), shape.0);
assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.ncols(), shape.1);
assert_eq!(sparse_mat.nnz(), 0); 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::<f32>(&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.nrows(), shape.0);
assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.ncols(), shape.1);
assert_eq!(sparse_mat.nnz(), 0); 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::<f32>(&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; 0.0, 0.0, 0.0, 0.0, 12.0;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<f32>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -125,6 +137,10 @@ fn test_matrixmarket_sparse_int_symmetric() {
-15, 0, 35, 0, 55; -15, 0, 35, 0, 55;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<i128>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -152,6 +168,10 @@ fn test_matrixmarket_sparse_complex_hermitian() {
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:33.32},Complex::<f64>{re:12.0,im:0.0}; Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:33.32},Complex::<f64>{re:12.0,im:0.0};
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<Complex<f64>>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -175,6 +195,10 @@ fn test_matrixmarket_sparse_real_skew() {
-15.0, 0.0, -35.0, 0.0, 0.0; -15.0, 0.0, -35.0, 0.0, 0.0;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<f64>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -198,7 +222,7 @@ fn test_matrixmarket_sparse_pattern_general() {
let pattern_matrix = load_coo_from_matrix_market_str::<()>(file_str).unwrap(); let pattern_matrix = load_coo_from_matrix_market_str::<()>(file_str).unwrap();
let nrows = pattern_matrix.nrows(); let nrows = pattern_matrix.nrows();
let ncols = pattern_matrix.ncols(); 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 values = vec![1; val.len()];
let sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap(); let sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap();
let expected = dmatrix![ let expected = dmatrix![
@ -209,6 +233,17 @@ fn test_matrixmarket_sparse_pattern_general() {
0, 1, 0, 1, 1; 0, 1, 0, 1, 1;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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] #[test]
@ -240,6 +275,10 @@ fn test_matrixmarket_dense_real_general() {
4.0, 8.0, 12.0; 4.0, 8.0, 12.0;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<f32>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -269,6 +308,10 @@ fn test_matrixmarket_dense_real_symmetric() {
4.0, 7.0, 9.0, 10.0; 4.0, 7.0, 9.0, 10.0;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<f32>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -298,6 +341,10 @@ fn test_matrixmarket_dense_complex_hermitian() {
Complex::<f64>{re:4.0,im:4.0}, Complex::<f64>{re:7.0,im:7.0} ,Complex::<f64>{re:9.0,im:9.0} ,Complex::<f64>{re:10.0,im:0.0}; Complex::<f64>{re:4.0,im:4.0}, Complex::<f64>{re:7.0,im:7.0} ,Complex::<f64>{re:9.0,im:9.0} ,Complex::<f64>{re:10.0,im:0.0};
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<Complex<f64>>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -322,6 +369,10 @@ fn test_matrixmarket_dense_int_skew() {
3, 5, 6, 0; 3, 5, 6, 0;
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<i32>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }
#[test] #[test]
@ -342,4 +393,8 @@ fn test_matrixmarket_dense_complex_general() {
Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{re:1.0,im:0.0}; Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{re:1.0,im:0.0};
]; ];
assert_matrix_eq!(sparse_mat, expected); 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::<Complex<f32>>(&generated_matrixmarket_str).unwrap();
assert_matrix_eq!(expected, generated_matrix);
} }