Merge pull request #1069 from losanc/matrixmarket-io

add support for matrix market export
This commit is contained in:
Andreas Borgen Longva 2022-06-13 10:13:38 +02:00 committed by GitHub
commit 446b52d08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 397 additions and 50 deletions

View File

@ -36,8 +36,9 @@ serde = { version = "1.0", default-features = false, features = [ "derive" ], op
itertools = "0.10" itertools = "0.10"
matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] }
nalgebra = { version="0.31", path = "../", features = ["compare"] } nalgebra = { version="0.31", path = "../", features = ["compare"] }
tempfile = "3.3"
serde_json = "1.0" serde_json = "1.0"
[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,9 @@
//! 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::SparseFormatError; use crate::SparseFormatError;
use crate::SparseFormatErrorKind; use crate::SparseFormatErrorKind;
use crate::{CooMatrix, CscMatrix, CsrMatrix};
use nalgebra::Complex; use nalgebra::Complex;
use pest::iterators::Pairs; use pest::iterators::Pairs;
use pest::Parser; use pest::Parser;
@ -12,7 +12,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::{BufWriter, 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 +268,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 +289,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 +507,21 @@ 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;
/// Write the data self to w
fn write_matrix_market<W: std::fmt::Write>(&self, w: W) -> Result<(), std::fmt::Error>;
}
pub trait SupportedMatrixMarketExport<T: SupportedMatrixMarketScalar> {
/// iterate over triplets
fn triplet_iter(&self) -> Box<dyn Iterator<Item = (usize, usize, &T)> + '_>;
/// number of rows
fn nrows(&self) -> usize;
/// number of columns
fn ncols(&self) -> usize;
/// number of non-zeros
fn nnz(&self) -> usize;
} }
} }
@ -557,6 +573,17 @@ 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 write_matrix_market<W: std::fmt::Write>(
&self,
mut w: W,
) -> Result<(), std::fmt::Error> {
write!(w, "{}", self)
}
} }
}; };
} }
@ -602,6 +629,17 @@ 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 write_matrix_market<W: std::fmt::Write>(
&self,
mut w: W,
) -> Result<(), std::fmt::Error> {
write!(w, "{}", self)
}
} }
}; };
} }
@ -648,6 +686,17 @@ 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 write_matrix_market<W: std::fmt::Write>(
&self,
mut w: W,
) -> Result<(), std::fmt::Error> {
write!(w, "{} {}", self.re, self.im)
}
} }
}; };
} }
@ -697,6 +746,17 @@ 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 write_matrix_market<W: std::fmt::Write>(
&self,
mut _w: W,
) -> Result<(), std::fmt::Error> {
Ok(())
}
} }
}; };
} }
@ -715,6 +775,46 @@ mm_complex_impl!(f64);
mm_pattern_impl!(()); mm_pattern_impl!(());
/// 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.
pub trait MatrixMarketExport<T: MatrixMarketScalar>:
internal::SupportedMatrixMarketExport<T>
{
}
macro_rules! mm_matrix_impl {
($T_MATRIX:ty) => {
impl<T: MatrixMarketScalar> MatrixMarketExport<T> for $T_MATRIX {}
impl<T: internal::SupportedMatrixMarketScalar> internal::SupportedMatrixMarketExport<T>
for $T_MATRIX
{
#[inline]
fn triplet_iter(&self) -> Box<dyn Iterator<Item = (usize, usize, &T)> + '_> {
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<T>);
mm_matrix_impl!(CsrMatrix<T>);
mm_matrix_impl!(CscMatrix<T>);
#[derive(Parser)] #[derive(Parser)]
#[grammar = "io/matrix_market.pest"] #[grammar = "io/matrix_market.pest"]
struct MatrixMarketParser; struct MatrixMarketParser;
@ -1329,3 +1429,123 @@ fn next_dense_coordinate(
} }
} }
} }
/// Save a sparse matrix as a Matrix Market format string.
///
/// The exporter only writes the matrix into `coordinate` and `general` format.
///
///
/// Examples
/// --------
/// ```
/// # use nalgebra_sparse::CooMatrix;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// 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
/// 1 1 10
/// 2 3 5
/// "#;
/// 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)?;
/// 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<T, S>(sparse_matrix: &S) -> String
where
T: MatrixMarketScalar,
S: MatrixMarketExport<T>,
{
let mut bytes = Vec::<u8>::new();
// This will call impl<A: Allocator> Write for Vec<u8, A>
// The vector will grow as needed.
// So, unwrap here won't cause any issue.
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")
}
/// 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
/// --------
///
/// See [MatrixMarketErrorKind] for a list of possible error conditions.
///
/// Examples
/// --------
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use nalgebra_sparse::io::{save_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)?;
/// save_to_matrix_market_file(&matrix,"path/to/matrix.mtx")?;
/// # Ok(()) }
/// ```
pub fn save_to_matrix_market_file<T, S, P>(sparse_matrix: &S, path: P) -> Result<(), std::io::Error>
where
T: MatrixMarketScalar,
S: MatrixMarketExport<T>,
P: AsRef<Path>,
{
let file = File::create(path)?;
let mut file = BufWriter::new(file);
save_to_matrix_market(&mut file, sparse_matrix)?;
// Quote from BufWriter doc.
// > It is critical to call flush before BufWriter<W> 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(())
}
/// 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<T, S, W>(mut w: W, sparse_matrix: &S) -> Result<(), std::io::Error>
where
T: MatrixMarketScalar,
S: MatrixMarketExport<T>,
W: Write,
{
// write header
writeln!(
w,
"%%matrixmarket matrix coordinate {} general",
T::typename()
)?;
//write comment
writeln!(w, "% matrixmarket file generated by nalgebra-sparse.")?;
// write shape information
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() {
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(())
}

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
//! //!
@ -19,10 +19,12 @@
//! which also uses the Matrix Market file format. //! which also uses the Matrix Market file format.
//! //!
//! We currently offer functionality for importing a Matrix market file to an instance of a //! 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 //! [CooMatrix](crate::CooMatrix) through the function [load_coo_from_matrix_market_file],
//! a matrix stored in the matrix market format with the function [load_coo_from_matrix_market_str]. //! as well as functionality for writing various sparse matrices to the matrix market format
//! //! through [save_to_matrix_market_file]. It is also possible to load
//! Export is currently not implemented, but [planned](https://github.com/dimforge/nalgebra/issues/1037). //! 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
//! [save_to_matrix_market_str].
//! //!
//! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html) //! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html)
//! on the Matrix Market website and the //! on the Matrix Market website and the
@ -32,7 +34,8 @@
//! > "*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, save_to_matrix_market,
MatrixMarketErrorKind, MatrixMarketScalar, save_to_matrix_market_file, save_to_matrix_market_str, MatrixMarketError,
MatrixMarketErrorKind, MatrixMarketExport, MatrixMarketScalar,
}; };
mod matrix_market; mod matrix_market;

View File

@ -1,12 +1,21 @@
use matrixcompare::assert_matrix_eq; use matrixcompare::assert_matrix_eq;
use nalgebra::dmatrix; use nalgebra::matrix;
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_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; use nalgebra_sparse::CooMatrix;
use proptest::prelude::*;
use tempfile::tempdir;
type C64 = Complex<f64>;
type C32 = Complex<f32>;
#[test] #[test]
#[rustfmt::skip] #[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 // Test several valid zero-shapes of a sparse matrix
let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let shapes = vec![ (0, 0), (1, 0), (0, 1) ];
let strings: Vec<String> = shapes let strings: Vec<String> = shapes
@ -24,7 +33,7 @@ fn test_matrixmarket_sparse_real_general_empty() {
#[test] #[test]
#[rustfmt::skip] #[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 // Test several valid zero-shapes of a dense matrix
let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let shapes = vec![ (0, 0), (1, 0), (0, 1) ];
let strings: Vec<String> = shapes let strings: Vec<String> = shapes
@ -42,7 +51,7 @@ fn test_matrixmarket_dense_real_general_empty() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_sparse_real_general() { fn test_matrixmarket_load_sparse_real_general() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix CoOrdinate real general %%MatrixMarket matrix CoOrdinate real general
% This is also an example of free-format features. % This is also an example of free-format features.
@ -89,7 +98,7 @@ fn test_matrixmarket_sparse_real_general() {
5 5 1.200e+01 5 5 1.200e+01
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
1.0, 0.0, 0.0, 6.0, 0.0; 1.0, 0.0, 0.0, 6.0, 0.0;
0.0, 10.5, 0.0, 0.0, 0.0; 0.0, 10.5, 0.0, 0.0, 0.0;
0.0, 0.0, 0.015, 0.0, 0.0; 0.0, 0.0, 0.015, 0.0, 0.0;
@ -101,7 +110,7 @@ fn test_matrixmarket_sparse_real_general() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_sparse_int_symmetric() { fn test_matrixmarket_load_sparse_int_symmetric() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix coordinate integer symmetric %%MatrixMarket matrix coordinate integer symmetric
% %
@ -117,7 +126,7 @@ fn test_matrixmarket_sparse_int_symmetric() {
5 5 55 5 5 55
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<i128>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<i128>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
11, 0, 0, 0, -15; 11, 0, 0, 0, -15;
0, 22, 23, 24, 0; 0, 22, 23, 24, 0;
0, 23, 33, 0, 35; 0, 23, 33, 0, 35;
@ -129,7 +138,7 @@ fn test_matrixmarket_sparse_int_symmetric() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_sparse_complex_hermitian() { fn test_matrixmarket_load_sparse_complex_hermitian() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix coordinate complex hermitian %%MatrixMarket matrix coordinate complex hermitian
% %
@ -144,19 +153,19 @@ fn test_matrixmarket_sparse_complex_hermitian() {
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f64>>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<Complex<f64>>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
Complex::<f64>{re:1.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:0.0}; 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};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:10.5,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:-22.22},Complex::<f64>{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};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.015,im:0.0}, Complex::<f64>{re:0.0,im:0.0},Complex::<f64>{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};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:22.22}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:-280.0,im:0.0},Complex::<f64>{re:0.0,im:-33.32}; 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};
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}; 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); assert_matrix_eq!(sparse_mat, expected);
} }
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_sparse_real_skew() { fn test_matrixmarket_load_sparse_real_skew() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix coordinate real skew-symmetric %%MatrixMarket matrix coordinate real skew-symmetric
% %
@ -167,7 +176,7 @@ fn test_matrixmarket_sparse_real_skew() {
5 3 -35.0 5 3 -35.0
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<f64>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<f64>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
0.0, 0.0, 0.0, 0.0, 15.0; 0.0, 0.0, 0.0, 0.0, 15.0;
0.0, 0.0, 23.0, 24.0, 0.0; 0.0, 0.0, 23.0, 24.0, 0.0;
0.0, -23.0, 0.0, 0.0, 35.0; 0.0, -23.0, 0.0, 0.0, 35.0;
@ -179,7 +188,7 @@ fn test_matrixmarket_sparse_real_skew() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_sparse_pattern_general() { fn test_matrixmarket_load_sparse_pattern_general() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix coordinate pattern general %%MatrixMarket matrix coordinate pattern general
% %
@ -198,10 +207,10 @@ 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 = matrix![
1, 0, 0, 0, 1; 1, 0, 0, 0, 1;
0, 0, 1, 1, 0; 0, 0, 1, 1, 0;
0, 1, 0, 0, 1; 0, 1, 0, 0, 1;
@ -213,7 +222,7 @@ fn test_matrixmarket_sparse_pattern_general() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_dense_real_general() { fn test_matrixmarket_load_dense_real_general() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix array real general %%MatrixMarket matrix array real general
% %
@ -233,7 +242,7 @@ fn test_matrixmarket_dense_real_general() {
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
1.0, 5.0, 9.0; 1.0, 5.0, 9.0;
2.0, 6.0, 10.0; 2.0, 6.0, 10.0;
3.0, 7.0, 11.0; 3.0, 7.0, 11.0;
@ -244,7 +253,7 @@ fn test_matrixmarket_dense_real_general() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_dense_real_symmetric() { fn test_matrixmarket_load_dense_real_symmetric() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix array real symmetric %%MatrixMarket matrix array real symmetric
% %
@ -262,7 +271,7 @@ fn test_matrixmarket_dense_real_symmetric() {
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
1.0, 2.0, 3.0, 4.0; 1.0, 2.0, 3.0, 4.0;
2.0, 5.0, 6.0, 7.0; 2.0, 5.0, 6.0, 7.0;
3.0, 6.0, 8.0, 9.0; 3.0, 6.0, 8.0, 9.0;
@ -273,7 +282,7 @@ fn test_matrixmarket_dense_real_symmetric() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_dense_complex_hermitian() { fn test_matrixmarket_load_dense_complex_hermitian() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix array complex hermitian %%MatrixMarket matrix array complex hermitian
% %
@ -290,19 +299,19 @@ fn test_matrixmarket_dense_complex_hermitian() {
10.0 0.0 10.0 0.0
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f64>>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<C64>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
Complex::<f64>{re:1.0,im:0.0}, Complex::<f64>{re:2.0,im:-2.0} ,Complex::<f64>{re:3.0,im:-3.0} ,Complex::<f64>{re:4.0,im:-4.0}; 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};
Complex::<f64>{re:2.0,im:2.0}, Complex::<f64>{re:5.0,im:0.0} ,Complex::<f64>{re:6.0,im:-6.0} ,Complex::<f64>{re:7.0,im:-7.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};
Complex::<f64>{re:3.0,im:3.0}, Complex::<f64>{re:6.0,im:6.0} ,Complex::<f64>{re:8.0,im:0.0} ,Complex::<f64>{re:9.0,im:-9.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};
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}; 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); assert_matrix_eq!(sparse_mat, expected);
} }
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_dense_int_skew() { fn test_matrixmarket_load_dense_int_skew() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix array integer skew-symmetric %%MatrixMarket matrix array integer skew-symmetric
% %
@ -315,7 +324,7 @@ fn test_matrixmarket_dense_int_skew() {
6 6
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<i32>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<i32>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
0,-1,-2,-3; 0,-1,-2,-3;
1, 0,-4,-5; 1, 0,-4,-5;
2, 4, 0,-6; 2, 4, 0,-6;
@ -326,7 +335,7 @@ fn test_matrixmarket_dense_int_skew() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_matrixmarket_dense_complex_general() { fn test_matrixmarket_load_dense_complex_general() {
let file_str = r#" let file_str = r#"
%%MatrixMarket matrix array complex general %%MatrixMarket matrix array complex general
% %
@ -336,10 +345,124 @@ fn test_matrixmarket_dense_complex_general() {
1 0 1 0
1 0 1 0
"#; "#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f32>>(file_str).unwrap(); let sparse_mat = load_coo_from_matrix_market_str::<C32>(file_str).unwrap();
let expected = dmatrix![ let expected = matrix![
Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{re:1.0,im:0.0}; C32{re:1.0,im:0.0},C32{re:1.0,im:0.0};
Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{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); assert_matrix_eq!(sparse_mat, expected);
} }
#[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 = save_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 = save_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 = save_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 = 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 = 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);
}
}
proptest! {
#[test]
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");
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");
}
}