From 7f9128c92dc9518589d7368bcb9fdb9e71e151fe Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Mon, 1 Nov 2021 17:25:16 +0100 Subject: [PATCH 01/15] added reading matrix market data to sparse coomatrix --- nalgebra-sparse/Cargo.toml | 3 + nalgebra-sparse/src/io/matrix_market.pest | 44 + nalgebra-sparse/src/io/matrix_market.rs | 861 ++++++++++++++++++ nalgebra-sparse/src/io/mod.rs | 8 + nalgebra-sparse/src/lib.rs | 8 + .../tests/unit_tests/matrix_market.rs | 165 ++++ nalgebra-sparse/tests/unit_tests/mod.rs | 2 + 7 files changed, 1091 insertions(+) create mode 100644 nalgebra-sparse/src/io/matrix_market.pest create mode 100644 nalgebra-sparse/src/io/matrix_market.rs create mode 100644 nalgebra-sparse/src/io/mod.rs create mode 100644 nalgebra-sparse/tests/unit_tests/matrix_market.rs diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 76fb408a..b85fea2c 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -15,6 +15,7 @@ license = "Apache-2.0" [features] proptest-support = ["proptest", "nalgebra/proptest-support"] compare = [ "matrixcompare-core" ] +io = [ "pest", "pest_derive" ] # Enable to enable running some tests that take a lot of time to run slow-tests = [] @@ -24,6 +25,8 @@ nalgebra = { version="0.29", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } matrixcompare-core = { version = "0.1.0", optional = true } +pest = { version = "2", optional = true } +pest_derive = { version = "2", optional = true } [dev-dependencies] itertools = "0.10" diff --git a/nalgebra-sparse/src/io/matrix_market.pest b/nalgebra-sparse/src/io/matrix_market.pest new file mode 100644 index 00000000..73809166 --- /dev/null +++ b/nalgebra-sparse/src/io/matrix_market.pest @@ -0,0 +1,44 @@ +WHITESPACE = _{ " " } + +// + +// dense matrix(array) not supported here +Sparsity = {^"coordinate"} +DataType = {^"real" | ^"complex" | ^"pattern" | ^"integer" } +StorageScheme = {^"symmetric" | ^"general" | ^"skew-symmetric" | ^"hermitian"} +// Only consider matrices here. +Header = { ^"%%matrixmarket matrix" ~ Sparsity ~ DataType ~ StorageScheme } + +// + +Comments = _{ "%" ~ (!NEWLINE ~ ANY)* } + +// + +Dimension = @{ ASCII_DIGIT+ } +Shape = { Dimension ~ Dimension ~ Dimension } + +// + +// grammar from https://doc.rust-lang.org/std/primitive.f64.html#grammar + +Sign = {("+" | "-")} +Exp = @{ ^"e" ~ Sign? ~ ASCII_DIGIT+} +Number = @{ ((ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT*) | (ASCII_DIGIT* ~ "." ~ASCII_DIGIT+) | ASCII_DIGIT+ ) ~ Exp? } +Value = @{ Sign? ~ ("inf" | "NaN" | Number) } + +// zero value: pattern +// one Value: real or int number +// two values: complex number +Entry = { Dimension ~ Dimension ~ Value? ~ Value? } + +// + +Document = { + SOI ~ + NEWLINE* ~ + Header ~ + (NEWLINE ~ Comments)* ~ + (NEWLINE ~ Shape) ~ + (NEWLINE ~ Entry?)* +} \ No newline at end of file diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs new file mode 100644 index 00000000..9ff4abec --- /dev/null +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -0,0 +1,861 @@ +//! use mm(or MM) to represent martrix market +use crate::coo::CooMatrix; +use crate::SparseFormatError; +use crate::SparseFormatErrorKind; +use nalgebra::Complex; +use pest::iterators::Pairs; +use pest::Parser; +use std::cmp::PartialEq; +use std::fmt; +use std::fmt::Formatter; +use std::fs; +use std::num::ParseIntError; +use std::ops::Neg; +use std::path::Path; +use std::str::FromStr; + +/// A description of the error that occurred during importing a matrix from a matrix market format data. +#[derive(Debug)] +pub struct MMError { + error_kind: MMErrorKind, + message: String, +} + +/// Errors produced by functions that expect well-formed matrix market format data. +/// > _NOTE1:_ Since the matrix market design didn't mention if duplicate entries are allowed or not, so, it's allowed here. +/// +/// > _NOTE2:_ Dense matrices are not supported here. For example, `%%matrixmarket matrix array real general` will give `MMErrorKind::ParsingError`. +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum MMErrorKind { + /// Indicates that some word is not known to MM format + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%MatrixMarket whatever whatever whatever whatever + /// 1 1 1 + /// 1 1 5 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::ParsingError); + /// ``` + ParsingError, + + /// Indicates that header is not valid + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%MatrixMarket matrix coordinate real hermitian + /// % a real matrix can't be hermitian + /// 1 1 1 + /// 1 1 5 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::InvalidHeader); + /// ``` + InvalidHeader, + + /// Indicates that the data entries in .mtx file are more or less than entries specified in .mtx file + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate real general + /// % it has one more data entry than specified. + /// 3 3 1 + /// 2 2 2 + /// 2 3 2 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::EntryNumUnmatched); + /// ``` + EntryNumUnmatched, + + /// Indicates that the type T is not matched with the function it called. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate integer general + /// % it should be called by load_coo_from_mm_str::(str), or any other integer type; + /// 3 3 1 + /// 2 2 2 + /// 2 3 2 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::TypeUnmatched); + /// ``` + TypeUnmatched, + + /// Indicates that zero has been used as an index in the data, which is not allowed. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate real general + /// 1 1 1 + /// 0 0 10 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::ZeroIndexed); + /// ``` + ZeroIndexed, + + /// Indicates [SparseFormatError], while creating the sparse matrix. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::SparseFormatErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate real general + /// 1 1 1 + /// 4 2 10 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::SparseFormatError(SparseFormatErrorKind::IndexOutOfBounds)); + /// ``` + SparseFormatError(SparseFormatErrorKind), + + /// Indicates that a wrong diagonal element has been provieded to the matrix + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra::Complex; + /// let str = r#" + /// %%matrixmarket matrix coordinate real skew-symmetric + /// % skew-symmetric matrix can't have element on diagonal + /// 5 5 2 + /// 1 1 10 + /// 2 1 5 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::DiagonalError); + /// + /// let str = r#" + /// %%matrixmarket matrix coordinate complex hermitian + /// % hermitian matrix diagonal element must be a real number + /// 5 5 2 + /// 1 1 10 2 + /// 2 1 5 2 + /// "#; + /// let matrix_error = load_coo_from_mm_str::>(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::DiagonalError); + /// ``` + /// Here the skew matrix shouldn't have an element on the diagonal + DiagonalError, + + /// Indicates [io error](`std::io::Error`), while reading the data from file. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_file; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let file_name = "whatever.mtx"; + /// let matrix_error = load_coo_from_mm_file::(file_name); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::IOError(std::io::ErrorKind::NotFound)); + /// ``` + IOError(std::io::ErrorKind), + + /// Indicates (skew-)symmetric (or hermitian) matrix is not lower triangle matrix. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_mm_str; + /// # use nalgebra_sparse::io::MMErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate integer symmetric + /// 5 5 2 + /// 1 1 10 + /// 2 3 5 + /// "#; + /// let matrix_error = load_coo_from_mm_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::LowerTriangleError); + /// ``` + LowerTriangleError, +} + +impl MMError { + fn from_kind_and_message(error_type: MMErrorKind, message: String) -> Self { + Self { + error_kind: error_type, + message, + } + } + + /// The operation error kind. + #[must_use] + pub fn kind(&self) -> &MMErrorKind { + &self.error_kind + } + + /// The underlying error message. + #[must_use] + pub fn message(&self) -> &str { + self.message.as_str() + } +} + +impl fmt::Display for MMError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Matrix Market load error: ")?; + match self.kind() { + MMErrorKind::ParsingError => { + write!(f, "ParsingError,")?; + } + MMErrorKind::InvalidHeader => { + write!(f, "InvalidHeader,")?; + } + MMErrorKind::EntryNumUnmatched => { + write!(f, "EntryNumUnmatched,")?; + } + MMErrorKind::TypeUnmatched => { + write!(f, "TypeUnmatched,")?; + } + MMErrorKind::SparseFormatError(_) => { + write!(f, "SparseFormatError,")?; + } + MMErrorKind::ZeroIndexed => { + write!(f, "ZeroIndexed,")?; + } + MMErrorKind::IOError(_) => { + write!(f, "IOError,")?; + } + MMErrorKind::DiagonalError => { + write!(f, "DiagonalError,")?; + } + MMErrorKind::LowerTriangleError => { + write!(f, "LowerTriangleError,")?; + } + } + write!(f, " Message: {}", self.message) + } +} + +impl std::error::Error for MMError {} + +impl From> + for MMError +{ + fn from(err: pest::error::Error) -> Self { + Self::from_kind_and_message( + MMErrorKind::ParsingError, + format!("Can't parse the data.\n Error: {}", err), + ) + } +} + +impl From for MMError { + fn from(err: ParseIntError) -> Self { + Self::from_kind_and_message( + MMErrorKind::ParsingError, + format!("Can't parse data as i128.\n Error: {}", err), + ) + } +} + +impl From for MMError { + fn from(err: SparseFormatError) -> Self { + Self::from_kind_and_message( + MMErrorKind::SparseFormatError(*err.kind()), + format!("{}", &err), + ) + } +} + +impl From for MMError { + fn from(err: std::io::Error) -> Self { + Self::from_kind_and_message(MMErrorKind::IOError(err.kind()), format!("{}", &err)) + } +} + +#[derive(Debug, PartialEq)] +enum Sparsity { + Sparse, +} +#[derive(Debug, PartialEq)] +enum DataType { + Real, + Complex, + Pattern, + Integer, +} +#[derive(Debug, PartialEq)] +enum StorageScheme { + Symmetric, + General, + Skew, + Hermitian, +} +#[derive(Debug, PartialEq)] +struct Typecode { + sp: Sparsity, + dt: DataType, + ss: StorageScheme, +} + +impl FromStr for Sparsity { + type Err = MMError; + /// Assumes that `word` is already lower case. + fn from_str(word: &str) -> Result { + match word { + "coordinate" => Ok(Sparsity::Sparse), + _ => Err(MMError::from_kind_and_message( + MMErrorKind::ParsingError, + format!("keyword {} is unknown", word), + )), + } + } +} + +impl FromStr for DataType { + type Err = MMError; + /// Assumes that `word` is already lower case. + fn from_str(word: &str) -> Result { + match word { + "real" => Ok(DataType::Real), + "complex" => Ok(DataType::Complex), + "integer" => Ok(DataType::Integer), + "pattern" => Ok(DataType::Pattern), + _ => Err(MMError::from_kind_and_message( + MMErrorKind::ParsingError, + format!("keyword {} is unknown", word), + )), + } + } +} + +impl FromStr for StorageScheme { + type Err = MMError; + /// Assumes that `word` is already lower case. + fn from_str(word: &str) -> Result { + match word { + "skew-symmetric" => Ok(StorageScheme::Skew), + "general" => Ok(StorageScheme::General), + "symmetric" => Ok(StorageScheme::Symmetric), + "hermitian" => Ok(StorageScheme::Hermitian), + _ => Err(MMError::from_kind_and_message( + MMErrorKind::ParsingError, + format!("keyword {} is unknown", word), + )), + } + } +} + +/// Precheck if it's a valid header. +/// +/// For more details, please check Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington. The matrix market formats: Initial design. Technical report, Applied and Computational Mathematics Division, NIST, 1996. Section 3. +fn typecode_precheck(tc: &Typecode) -> Result<(), MMError> { + match tc { + Typecode { + dt: DataType::Real, + ss: StorageScheme::Hermitian, + .. + } => Err(MMError::from_kind_and_message( + MMErrorKind::InvalidHeader, + String::from("Real matrix can't be hermitian."), + )), + Typecode { + dt: DataType::Integer, + ss: StorageScheme::Hermitian, + .. + } => Err(MMError::from_kind_and_message( + MMErrorKind::InvalidHeader, + String::from("Integer matrix can't be hermitian."), + )), + Typecode { + dt: DataType::Pattern, + ss: StorageScheme::Hermitian, + .. + } => Err(MMError::from_kind_and_message( + MMErrorKind::InvalidHeader, + String::from("Pattern matrix can't be hermitian."), + )), + Typecode { + dt: DataType::Pattern, + ss: StorageScheme::Skew, + .. + } => Err(MMError::from_kind_and_message( + MMErrorKind::InvalidHeader, + String::from("Pattern matrix can't be skew-symmetric."), + )), + // precheck success + _ => Ok(()), + } +} + +/// Base trait for matrix market types. +pub trait MMType: Sized + Clone { + /// When matrix is an Integer matrix, it will convert a [i128] number to this type. + fn from_i128(i: i128) -> Result; + /// When matrix is a Real matrix, it will convert a [f64] number to this type. + fn from_f64(f: f64) -> Result; + /// When matrix is a Complx matrix, it will convert a [Complex] number to this type. + fn from_c64(c: Complex) -> Result; + /// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type. + fn from_pattern(p: ()) -> Result; + /// When matrix is a Skew-symmetric matrix, it will convert itself to its negative. + fn negative(self) -> Result; + /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. + fn conjugate(self) -> Result; +} +/// Implement MMType for primitive integer types. +macro_rules! mm_int_impl { + ($T:ty) => { + impl MMType for $T { + #[inline] + fn from_i128(i: i128) -> Result { + Ok(i as Self) + } + #[inline] + fn from_f64(_f: f64) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Int type can't parse from f64"), + )) + } + #[inline] + fn from_c64(_c: Complex) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Int type can't parse from Complex"), + )) + } + #[inline] + fn from_pattern(_p: ()) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Int type can't parse from ()"), + )) + } + #[inline] + fn conjugate(self) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Int type has no conjugate"), + )) + } + #[inline] + fn negative(self) -> Result + where + Self: Neg, + { + Ok(-self) + } + } + }; +} +/// Implement MMType for primitive float types. +macro_rules! mm_float_impl { + ($T:ty) => { + impl MMType for $T { + #[inline] + fn from_i128(_i: i128) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Float type can't parse from i128"), + )) + } + #[inline] + fn from_f64(f: f64) -> Result { + Ok(f as Self) + } + #[inline] + fn from_c64(_c: Complex) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Float type can't parse from Complex"), + )) + } + #[inline] + fn from_pattern(_p: ()) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Float type can't parse from ()"), + )) + } + #[inline] + fn conjugate(self) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Float type has no conjugate"), + )) + } + #[inline] + fn negative(self) -> Result + where + Self: Neg, + { + Ok(-self) + } + } + }; +} +/// Implement MMType for primitive complex types. +macro_rules! mm_complex_impl { + ($T:ty) => { + impl MMType for Complex<$T> { + #[inline] + fn from_i128(_i: i128) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Complex type can't parse from i128"), + )) + } + #[inline] + fn from_f64(_f: f64) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Complex type can't parse from f64"), + )) + } + #[inline] + fn from_c64(c: Complex) -> Result { + Ok(Self { + re: c.re as $T, + im: c.im as $T, + }) + } + #[inline] + fn from_pattern(_p: ()) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Complex type can't parse from ()"), + )) + } + #[inline] + fn conjugate(self) -> Result { + Ok(self.conj()) + } + #[inline] + fn negative(self) -> Result + where + Self: Neg, + { + Ok(-self) + } + } + }; +} +/// Implement MMType for primitive unit types. +macro_rules! mm_pattern_impl { + ($T:ty) => { + impl MMType for $T { + #[inline] + fn from_i128(_i: i128) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Pattern type can't parse from i128"), + )) + } + #[inline] + fn from_f64(_f: f64) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Pattern type can't parse from f64"), + )) + } + #[inline] + fn from_c64(_c: Complex) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Pattern type can't parse from Complex"), + )) + } + #[inline] + fn from_pattern(p: ()) -> Result { + Ok(p) + } + + #[inline] + fn conjugate(self) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Pattern type has no conjugate"), + )) + } + #[inline] + fn negative(self) -> Result { + Err(MMError::from_kind_and_message( + MMErrorKind::TypeUnmatched, + format!("Pattern type has no negative"), + )) + } + } + }; +} + +mm_int_impl!(i8); +mm_int_impl!(i16); +mm_int_impl!(i32); +mm_int_impl!(i64); +mm_int_impl!(i128); + +mm_float_impl!(f32); +mm_float_impl!(f64); + +mm_complex_impl!(f32); +mm_complex_impl!(f64); + +mm_pattern_impl!(()); + +#[derive(Parser)] +#[grammar = "io/matrix_market.pest"] +struct MMParser; + +/// Parsing a pest structure to a Typecode of the matrix. +fn parsing_header(inner: &mut Pairs<'_, Rule>, header_type: &mut Typecode) -> Result<(), MMError> { + // once the data can be parsed, all unwrap() in this function here will not panic. + header_type.sp = inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(); + header_type.dt = inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(); + header_type.ss = inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(); + typecode_precheck(&header_type) +} + +/// Parsing a pest structure to 3 int, which are number of rols, cols and non-zeros. +fn parsing_shape(inner: &mut Pairs<'_, Rule>, shape: &mut (usize, usize, usize)) { + // once the file can be parsed, all unwrap() in this function here will not panic. + shape.0 = inner.next().unwrap().as_str().parse::().unwrap(); + shape.1 = inner.next().unwrap().as_str().parse::().unwrap(); + shape.2 = inner.next().unwrap().as_str().parse::().unwrap(); +} + +#[inline] +/// Do a precheck of remaing data. Because in matrix_market.pest file, either 0, or 1 or 2 float number(s) as data is valid, however, for example, for Real matrix, only 1 float is valid. And 2 float for Complex matrix. +fn check_value_length(inner: &Pairs<'_, Rule>, l: usize) -> Result<(), MMError> { + let copy = inner.clone(); + let c = copy.count(); + if l != c { + return Err(MMError::from_kind_and_message( + MMErrorKind::ParsingError, + format!("{} data required, but {} provided.", l, c), + )); + } + Ok(()) +} + +#[inline] +// do a quick check it the entry is in the lower triangle part of the matrix +fn check_lower_triangle(r: usize, c: usize) -> Result<(), MMError> { + if c > r { + return Err(MMError::from_kind_and_message( + MMErrorKind::LowerTriangleError, + format!( + "Entry: row {} col {} should be put into lower triangle", + r, c + ), + )); + } + Ok(()) +} + +/// Parses a Matrix Market file at the given path, and returns the corresponding sparse matrix as CooMatrix format. +pub fn load_coo_from_mm_file>(path: P) -> Result, MMError> +where + T: MMType, +{ + let file = fs::read_to_string(path)?; + load_coo_from_mm_str(&file) +} + +/// Parses a Matrix Market file described by the given string, and returns the corresponding as CooMatrix format. +pub fn load_coo_from_mm_str(data: &str) -> Result, MMError> +where + T: MMType, +{ + // unwrap() here guaranteed when data can be parsed + let file = MMParser::parse(Rule::Document, data)?.next().unwrap(); + // Default typecode + let mut header_type = Typecode { + sp: Sparsity::Sparse, + dt: DataType::Real, + ss: StorageScheme::General, + }; + let mut shape = (0, 0, 0); + let mut rows: Vec = Vec::new(); + let mut cols: Vec = Vec::new(); + let mut data: Vec = Vec::new(); + + let mut count = 0; + for line in file.into_inner() { + match line.as_rule() { + Rule::Header => { + let mut inner = line.into_inner(); + parsing_header(&mut inner, &mut header_type)?; + } + Rule::Shape => { + let mut inner = line.into_inner(); + parsing_shape(&mut inner, &mut shape); + } + Rule::Entry => { + count += 1; + let mut inner = line.into_inner(); + // NOTE: indices are 1-based. + // unwrap() here guaranteed when data can be parsed + let r = inner.next().unwrap().as_str().parse::().unwrap(); + if r == 0 { + return Err(MMError::from_kind_and_message( + MMErrorKind::ZeroIndexed, + String::from("The data has to be one-indixed"), + )); + } + let r = r - 1; + // unwrap() here guaranteed when data can be parsed + let c = inner.next().unwrap().as_str().parse::().unwrap(); + if c == 0 { + return Err(MMError::from_kind_and_message( + MMErrorKind::ZeroIndexed, + String::from("The data has to be one-indixed"), + )); + } + let c = c - 1; + let d: T; + match header_type.dt { + DataType::Integer => { + check_value_length(&inner, 1)?; + // unwrap() here guaranteed by check_value_length + let i = inner.next().unwrap().as_str().parse::()?; + d = T::from_i128(i)?; + } + DataType::Real => { + check_value_length(&inner, 1)?; + // first unwrap() here guaranteed by check_value_length + // second unwrap() here guaranteed by parsing the data + let i = inner.next().unwrap().as_str().parse::().unwrap(); + d = T::from_f64(i)?; + } + DataType::Complex => { + check_value_length(&inner, 2)?; + // first unwrap() here guaranteed by check_value_length + // second unwrap() here guaranteed by parsing the data + let real = inner.next().unwrap().as_str().parse::().unwrap(); + // first unwrap() here guaranteed by check_value_length + // second unwrap() here guaranteed by parsing the data + let imag = inner.next().unwrap().as_str().parse::().unwrap(); + // only complex could be hermitian, and check diagonal element is a real number + if header_type.ss == StorageScheme::Hermitian && r == c && imag != 0.0 { + return Err(MMError::from_kind_and_message(MMErrorKind::DiagonalError,format!("There is a diagonal element in hermitian matrix, in row(and column) {}, but imaginary part is not zero",r))); + } + d = T::from_c64(Complex::::new(real, imag))?; + } + DataType::Pattern => { + check_value_length(&inner, 0)?; + d = T::from_pattern(())?; + } + } + + match header_type.ss { + StorageScheme::General => { + rows.push(r); + cols.push(c); + data.push(d); + } + StorageScheme::Symmetric => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + // don't need to add twice if the element in on diagonal + if r != c { + rows.push(c); + cols.push(r); + data.push(d); + } + } + StorageScheme::Skew => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + // skew-symmetric matrix shouldn't have diagonal element + if r == c { + return Err(MMError::from_kind_and_message(MMErrorKind::DiagonalError,format!("There is a diagonal element in skew matrix, in row(and column) {}",r))); + } + rows.push(c); + cols.push(r); + data.push(d.negative()?); + } + StorageScheme::Hermitian => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + // don't need to add twice if the element in on diagonal + // diagonal element has been checked before to make sure the imaginary part is zero + if r != c { + rows.push(c); + cols.push(r); + data.push(d.conjugate()?); + } + } + } + } + _ => {} + } + } + if count != shape.2 { + return Err(MMError::from_kind_and_message( + MMErrorKind::EntryNumUnmatched, + format!( + "expected {} entries in matrix market file, but found {}", + shape.2, count + ), + )); + } + + Ok(CooMatrix::try_from_triplets( + shape.0, shape.1, rows, cols, data, + )?) +} diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs new file mode 100644 index 00000000..51b62873 --- /dev/null +++ b/nalgebra-sparse/src/io/mod.rs @@ -0,0 +1,8 @@ +//! Parsers for various matrix formats. +//! +//! Use mm(or MM) to represent matrix market. + +pub use self::matrix_market::{ + load_coo_from_mm_file, load_coo_from_mm_str, MMError, MMErrorKind, MMType, +}; +mod matrix_market; diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index bf845757..a588787f 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -142,11 +142,19 @@ )] pub extern crate nalgebra as na; +#[cfg(feature = "io")] +extern crate pest; +#[macro_use] +#[cfg(feature = "io")] +extern crate pest_derive; + pub mod convert; pub mod coo; pub mod csc; pub mod csr; pub mod factorization; +#[cfg(feature = "io")] +pub mod io; pub mod ops; pub mod pattern; diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs new file mode 100644 index 00000000..82ac6e2b --- /dev/null +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -0,0 +1,165 @@ +use matrixcompare::assert_matrix_eq; +use nalgebra::dmatrix; +use nalgebra::Complex; +use nalgebra_sparse::io::load_coo_from_mm_str; +use nalgebra_sparse::CooMatrix; + +#[test] +#[rustfmt::skip] +fn test_mm_sparse_real_general() { + let file_str = r#" +%%MatrixMarket matrix CoOrdinate real general +% This is also an example of free-format features. +%================================================================================= +% +% This ASCII file represents a sparse MxN matrix with L +% nonzeros in the following Matrix Market format: +% +% +----------------------------------------------+ +% |%%MatrixMarket matrix coordinate real general | <--- header line +% |% | <--+ +% |% comments | |-- 0 or more comment lines +% |% | <--+ +% | M T L | <--- rows, columns, entries +% | I1 J1 A(I1, J1) | <--+ +% | I2 J2 A(I2, J2) | | +% | I3 J3 A(I3, J3) | |-- L lines +% | . . . | | +% | IL JL A(IL, JL) | <--+ +% +----------------------------------------------+ +% +% Indices are 1-based, i.e. A(1,1) is the first element. +% +%================================================================================= + 5 5 8 + 1 1 1 + 2 2 1.050e+01 + 3 3 1.500e-02 + 1 4 6.000e+00 + 4 2 2.505e+02 +4 4 -2.800e+02 +4 5 3.332e+01 + 5 5 1.200e+01 +"#; + let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let expected = dmatrix![ + 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; + 0.0, 250.5, 0.0, -280.0, 33.32; + 0.0, 0.0, 0.0, 0.0, 12.0; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_mm_sparse_int_symmetric() { + let file_str = r#" +%%MatrixMarket matrix coordinate integer symmetric +% + 5 5 9 + 1 1 11 + 2 2 22 + 3 2 23 + 3 3 33 + 4 2 24 + 4 4 44 + 5 1 -15 + 5 3 35 + 5 5 55 +"#; + let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let expected = dmatrix![ + 11, 0, 0, 0, -15; + 0, 22, 23, 24, 0; + 0, 23, 33, 0, 35; + 0, 24, 0, 44, 0; + -15, 0, 35, 0, 55; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_mm_sparse_complex_hermitian() { + let file_str = r#" +%%MatrixMarket matrix coordinate complex hermitian +% + 5 5 7 + 1 1 1.0 0.0 + 2 2 10.5 0.0 + 4 2 250.5 22.22 + 3 3 0.015 0.0 + 4 4 -2.8e2 0.0 + 5 5 12.0 0.0 + 5 4 0.0 33.32 + +"#; + let sparse_mat = load_coo_from_mm_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}; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_mm_sparse_real_skew() { + let file_str = r#" +%%MatrixMarket matrix coordinate real skew-symmetric +% + 5 5 4 + 3 2 -23.0 + 4 2 -24.0 + 5 1 -15.0 + 5 3 -35.0 +"#; + let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let expected = dmatrix![ + 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; + 0.0, -24.0, 0.0, 0.0, 0.0; + -15.0, 0.0, -35.0, 0.0, 0.0; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_mm_sparse_pattern_general() { + let file_str = r#" +%%MatrixMarket matrix coordinate pattern general +% + 5 5 10 + 1 1 + 1 5 + 2 3 + 2 4 + 3 2 + 3 5 + 4 1 + 5 2 + 5 4 + 5 5 +"#; + let pattern_matrix = load_coo_from_mm_str::<()>(file_str).unwrap(); + let nrows = pattern_matrix.nrows(); + let ncols = pattern_matrix.ncols(); + let (row_idx, col_idx, val) = pattern_matrix.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![ + 1, 0, 0, 0, 1; + 0, 0, 1, 1, 0; + 0, 1, 0, 0, 1; + 1, 0, 0, 0, 0; + 0, 1, 0, 1, 1; + ]; + assert_matrix_eq!(sparse_mat, expected); +} diff --git a/nalgebra-sparse/tests/unit_tests/mod.rs b/nalgebra-sparse/tests/unit_tests/mod.rs index 0099a246..e97731e1 100644 --- a/nalgebra-sparse/tests/unit_tests/mod.rs +++ b/nalgebra-sparse/tests/unit_tests/mod.rs @@ -3,6 +3,8 @@ mod convert_serial; mod coo; mod csc; mod csr; +#[cfg(feature = "io")] +mod matrix_market; mod ops; mod pattern; mod proptest; From 332fe8c0e49f0b413762d245946aeab52adbda62 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Wed, 17 Nov 2021 16:32:57 +0100 Subject: [PATCH 02/15] update loading matrix market, including: 1. Several renamings 2. removed unnecessary traits 3. added support for dense matrix 4. removed unnecessary comparison when reading data line by line. (e.g. only read header line once) --- nalgebra-sparse/src/io/matrix_market.pest | 31 +- nalgebra-sparse/src/io/matrix_market.rs | 1232 +++++++++++------ nalgebra-sparse/src/io/mod.rs | 6 +- .../tests/unit_tests/matrix_market.rs | 206 ++- 4 files changed, 1031 insertions(+), 444 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.pest b/nalgebra-sparse/src/io/matrix_market.pest index 73809166..aeeba406 100644 --- a/nalgebra-sparse/src/io/matrix_market.pest +++ b/nalgebra-sparse/src/io/matrix_market.pest @@ -1,9 +1,8 @@ -WHITESPACE = _{ " " } +WHITESPACE = _{ " "|"\t" } // -// dense matrix(array) not supported here -Sparsity = {^"coordinate"} +Sparsity = {^"coordinate" | ^"array"} DataType = {^"real" | ^"complex" | ^"pattern" | ^"integer" } StorageScheme = {^"symmetric" | ^"general" | ^"skew-symmetric" | ^"hermitian"} // Only consider matrices here. @@ -16,7 +15,9 @@ Comments = _{ "%" ~ (!NEWLINE ~ ANY)* } // Dimension = @{ ASCII_DIGIT+ } -Shape = { Dimension ~ Dimension ~ Dimension } +SparseShape = { Dimension ~ Dimension ~ Dimension} +DenseShape = { Dimension ~ Dimension} +Shape = {SparseShape | DenseShape } // @@ -25,14 +26,21 @@ Shape = { Dimension ~ Dimension ~ Dimension } Sign = {("+" | "-")} Exp = @{ ^"e" ~ Sign? ~ ASCII_DIGIT+} Number = @{ ((ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT*) | (ASCII_DIGIT* ~ "." ~ASCII_DIGIT+) | ASCII_DIGIT+ ) ~ Exp? } -Value = @{ Sign? ~ ("inf" | "NaN" | Number) } +Real = @{ Sign? ~ ("inf" | "NaN" | Number) } -// zero value: pattern -// one Value: real or int number -// two values: complex number -Entry = { Dimension ~ Dimension ~ Value? ~ Value? } -// +SparseReal = {Dimension~ Dimension~ Real } +SparseComplex = {Dimension ~ Dimension ~ Real ~ Real} +SparsePattern = {Dimension ~ Dimension} + +DenseReal = {Real} +DenseComplex = {Real ~ Real} + + +Entry = { SparseComplex | SparseReal | SparsePattern | DenseComplex | DenseReal } + +// end of file, a silent way, see https://github.com/pest-parser/pest/issues/304#issuecomment-427198507 +eoi = _{ !ANY } Document = { SOI ~ @@ -40,5 +48,6 @@ Document = { Header ~ (NEWLINE ~ Comments)* ~ (NEWLINE ~ Shape) ~ - (NEWLINE ~ Entry?)* + (NEWLINE ~ Entry?)* ~ + eoi } \ 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 9ff4abec..3be7a8ac 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1,48 +1,51 @@ -//! use mm(or MM) to represent martrix market +//! 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::SparseFormatError; use crate::SparseFormatErrorKind; +use nalgebra::base::Scalar; use nalgebra::Complex; use pest::iterators::Pairs; use pest::Parser; use std::cmp::PartialEq; +use std::convert::Infallible; +use std::convert::TryFrom; use std::fmt; use std::fmt::Formatter; use std::fs; use std::num::ParseIntError; -use std::ops::Neg; +use std::num::TryFromIntError; use std::path::Path; use std::str::FromStr; /// A description of the error that occurred during importing a matrix from a matrix market format data. #[derive(Debug)] -pub struct MMError { - error_kind: MMErrorKind, +pub struct MatrixMarketError { + error_kind: MatrixMarketErrorKind, message: String, } /// Errors produced by functions that expect well-formed matrix market format data. -/// > _NOTE1:_ Since the matrix market design didn't mention if duplicate entries are allowed or not, so, it's allowed here. -/// -/// > _NOTE2:_ Dense matrices are not supported here. For example, `%%matrixmarket matrix array real general` will give `MMErrorKind::ParsingError`. +/// > _NOTE:_ Since the matrix market design didn't mention if multiple sparse entries with the same coordiantes are allowed or not, so, it's allowed here. #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq)] -pub enum MMErrorKind { +pub enum MatrixMarketErrorKind { /// Indicates that some word is not known to MM format /// /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%MatrixMarket whatever whatever whatever whatever /// 1 1 1 /// 1 1 5 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::ParsingError); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::ParsingError); /// ``` ParsingError, @@ -51,17 +54,17 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%MatrixMarket matrix coordinate real hermitian /// % a real matrix can't be hermitian /// 1 1 1 /// 1 1 5 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::InvalidHeader); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::InvalidHeader); /// ``` InvalidHeader, @@ -70,8 +73,8 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%matrixmarket matrix coordinate real general /// % it has one more data entry than specified. @@ -79,9 +82,9 @@ pub enum MMErrorKind { /// 2 2 2 /// 2 3 2 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::EntryNumUnmatched); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::EntryNumUnmatched); /// ``` EntryNumUnmatched, @@ -90,55 +93,55 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" - /// %%matrixmarket matrix coordinate integer general - /// % it should be called by load_coo_from_mm_str::(str), or any other integer type; - /// 3 3 1 - /// 2 2 2 - /// 2 3 2 + /// %%matrixmarket matrix coordinate real general + /// % it should be called by load_coo_from_matrix_market_str::(str), or f32; + /// 3 3 2 + /// 2 2 2.22 + /// 2 3 2.22 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::TypeUnmatched); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::TypeUnmatched); /// ``` TypeUnmatched, - /// Indicates that zero has been used as an index in the data, which is not allowed. + /// Indicates that zero has been used as an index in the data, or the shape of the matrix, which is not allowed. /// /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%matrixmarket matrix coordinate real general /// 1 1 1 /// 0 0 10 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::ZeroIndexed); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::ZeroError); /// ``` - ZeroIndexed, + ZeroError, /// Indicates [SparseFormatError], while creating the sparse matrix. /// /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// # use nalgebra_sparse::SparseFormatErrorKind; /// let str = r#" /// %%matrixmarket matrix coordinate real general /// 1 1 1 /// 4 2 10 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::SparseFormatError(SparseFormatErrorKind::IndexOutOfBounds)); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::SparseFormatError(SparseFormatErrorKind::IndexOutOfBounds)); /// ``` SparseFormatError(SparseFormatErrorKind), @@ -147,8 +150,8 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// # use nalgebra::Complex; /// let str = r#" /// %%matrixmarket matrix coordinate real skew-symmetric @@ -157,9 +160,9 @@ pub enum MMErrorKind { /// 1 1 10 /// 2 1 5 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::DiagonalError); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); /// /// let str = r#" /// %%matrixmarket matrix coordinate complex hermitian @@ -168,9 +171,9 @@ pub enum MMErrorKind { /// 1 1 10 2 /// 2 1 5 2 /// "#; - /// let matrix_error = load_coo_from_mm_str::>(str); + /// let matrix_error = load_coo_from_matrix_market_str::>(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::DiagonalError); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); /// ``` /// Here the skew matrix shouldn't have an element on the diagonal DiagonalError, @@ -180,12 +183,12 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_file; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_file; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let file_name = "whatever.mtx"; - /// let matrix_error = load_coo_from_mm_file::(file_name); + /// let matrix_error = load_coo_from_matrix_market_file::(file_name); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::IOError(std::io::ErrorKind::NotFound)); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::IOError(std::io::ErrorKind::NotFound)); /// ``` IOError(std::io::ErrorKind), @@ -194,23 +197,42 @@ pub enum MMErrorKind { /// Examples /// -------- /// ```rust - /// # use nalgebra_sparse::io::load_coo_from_mm_str; - /// # use nalgebra_sparse::io::MMErrorKind; + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%matrixmarket matrix coordinate integer symmetric /// 5 5 2 /// 1 1 10 /// 2 3 5 /// "#; - /// let matrix_error = load_coo_from_mm_str::(str); + /// let matrix_error = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(*matrix_error.unwrap_err().kind(),MMErrorKind::LowerTriangleError); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::NotLowerTriangle); /// ``` - LowerTriangleError, + NotLowerTriangle, + + /// Indicates (skew-)symmetric (or hermitian) matrix is not square matrix. + /// + /// Examples + /// -------- + /// ```rust + /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; + /// # use nalgebra_sparse::io::MatrixMarketErrorKind; + /// let str = r#" + /// %%matrixmarket matrix coordinate integer symmetric + /// 5 4 2 + /// 1 1 10 + /// 2 3 5 + /// "#; + /// let matrix_error = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_error.is_err(), true); + /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::NotSquareMatrix); + /// ``` + NotSquareMatrix, } -impl MMError { - fn from_kind_and_message(error_type: MMErrorKind, message: String) -> Self { +impl MatrixMarketError { + fn from_kind_and_message(error_type: MatrixMarketErrorKind, message: String) -> Self { Self { error_kind: error_type, message, @@ -219,8 +241,8 @@ impl MMError { /// The operation error kind. #[must_use] - pub fn kind(&self) -> &MMErrorKind { - &self.error_kind + pub fn kind(&self) -> MatrixMarketErrorKind { + self.error_kind } /// The underlying error message. @@ -230,82 +252,112 @@ impl MMError { } } -impl fmt::Display for MMError { +impl fmt::Display for MatrixMarketError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Matrix Market load error: ")?; match self.kind() { - MMErrorKind::ParsingError => { + MatrixMarketErrorKind::ParsingError => { write!(f, "ParsingError,")?; } - MMErrorKind::InvalidHeader => { + MatrixMarketErrorKind::InvalidHeader => { write!(f, "InvalidHeader,")?; } - MMErrorKind::EntryNumUnmatched => { + MatrixMarketErrorKind::EntryNumUnmatched => { write!(f, "EntryNumUnmatched,")?; } - MMErrorKind::TypeUnmatched => { + MatrixMarketErrorKind::TypeUnmatched => { write!(f, "TypeUnmatched,")?; } - MMErrorKind::SparseFormatError(_) => { + MatrixMarketErrorKind::SparseFormatError(_) => { write!(f, "SparseFormatError,")?; } - MMErrorKind::ZeroIndexed => { - write!(f, "ZeroIndexed,")?; + MatrixMarketErrorKind::ZeroError => { + write!(f, "ZeroError,")?; } - MMErrorKind::IOError(_) => { + MatrixMarketErrorKind::IOError(_) => { write!(f, "IOError,")?; } - MMErrorKind::DiagonalError => { + MatrixMarketErrorKind::DiagonalError => { write!(f, "DiagonalError,")?; } - MMErrorKind::LowerTriangleError => { - write!(f, "LowerTriangleError,")?; + MatrixMarketErrorKind::NotLowerTriangle => { + write!(f, "NotLowerTriangle,")?; + } + MatrixMarketErrorKind::NotSquareMatrix => { + write!(f, "NotSquareMatrix,")?; } } write!(f, " Message: {}", self.message) } } -impl std::error::Error for MMError {} +impl std::error::Error for MatrixMarketError {} impl From> - for MMError + for MatrixMarketError { fn from(err: pest::error::Error) -> Self { Self::from_kind_and_message( - MMErrorKind::ParsingError, + MatrixMarketErrorKind::ParsingError, format!("Can't parse the data.\n Error: {}", err), ) } } -impl From for MMError { +impl From for MatrixMarketError { fn from(err: ParseIntError) -> Self { Self::from_kind_and_message( - MMErrorKind::ParsingError, + MatrixMarketErrorKind::ParsingError, format!("Can't parse data as i128.\n Error: {}", err), ) } } -impl From for MMError { +impl From for MatrixMarketError { fn from(err: SparseFormatError) -> Self { Self::from_kind_and_message( - MMErrorKind::SparseFormatError(*err.kind()), + MatrixMarketErrorKind::SparseFormatError(*err.kind()), format!("{}", &err), ) } } -impl From for MMError { +impl From for MatrixMarketError { fn from(err: std::io::Error) -> Self { - Self::from_kind_and_message(MMErrorKind::IOError(err.kind()), format!("{}", &err)) + Self::from_kind_and_message( + MatrixMarketErrorKind::IOError(err.kind()), + format!("{}", &err), + ) + } +} + +impl From for MatrixMarketError { + fn from(err: TryFromIntError) -> Self { + Self::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!( + "Please consider using a larger integery type. Error message: {}", + &err + ), + ) + } +} + +// This is needed when calling `i128::try_from(i: i128)` +// but it won't happen +impl From for MatrixMarketError { + fn from(_err: Infallible) -> Self { + Self::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("This won't happen"), + ) } } #[derive(Debug, PartialEq)] enum Sparsity { Sparse, + Dense, } #[derive(Debug, PartialEq)] enum DataType { @@ -323,19 +375,20 @@ enum StorageScheme { } #[derive(Debug, PartialEq)] struct Typecode { - sp: Sparsity, - dt: DataType, - ss: StorageScheme, + sparsity: Sparsity, + datatype: DataType, + storagescheme: StorageScheme, } impl FromStr for Sparsity { - type Err = MMError; + type Err = MatrixMarketError; /// Assumes that `word` is already lower case. fn from_str(word: &str) -> Result { match word { "coordinate" => Ok(Sparsity::Sparse), - _ => Err(MMError::from_kind_and_message( - MMErrorKind::ParsingError, + "array" => Ok(Sparsity::Dense), + _ => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, format!("keyword {} is unknown", word), )), } @@ -343,7 +396,7 @@ impl FromStr for Sparsity { } impl FromStr for DataType { - type Err = MMError; + type Err = MatrixMarketError; /// Assumes that `word` is already lower case. fn from_str(word: &str) -> Result { match word { @@ -351,8 +404,8 @@ impl FromStr for DataType { "complex" => Ok(DataType::Complex), "integer" => Ok(DataType::Integer), "pattern" => Ok(DataType::Pattern), - _ => Err(MMError::from_kind_and_message( - MMErrorKind::ParsingError, + _ => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, format!("keyword {} is unknown", word), )), } @@ -360,7 +413,7 @@ impl FromStr for DataType { } impl FromStr for StorageScheme { - type Err = MMError; + type Err = MatrixMarketError; /// Assumes that `word` is already lower case. fn from_str(word: &str) -> Result { match word { @@ -368,8 +421,8 @@ impl FromStr for StorageScheme { "general" => Ok(StorageScheme::General), "symmetric" => Ok(StorageScheme::Symmetric), "hermitian" => Ok(StorageScheme::Hermitian), - _ => Err(MMError::from_kind_and_message( - MMErrorKind::ParsingError, + _ => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, format!("keyword {} is unknown", word), )), } @@ -379,239 +432,238 @@ impl FromStr for StorageScheme { /// Precheck if it's a valid header. /// /// For more details, please check Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington. The matrix market formats: Initial design. Technical report, Applied and Computational Mathematics Division, NIST, 1996. Section 3. -fn typecode_precheck(tc: &Typecode) -> Result<(), MMError> { +fn typecode_precheck(tc: &Typecode) -> Result<(), MatrixMarketError> { match tc { Typecode { - dt: DataType::Real, - ss: StorageScheme::Hermitian, + datatype: DataType::Real, + storagescheme: StorageScheme::Hermitian, .. - } => Err(MMError::from_kind_and_message( - MMErrorKind::InvalidHeader, + } => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::InvalidHeader, String::from("Real matrix can't be hermitian."), )), Typecode { - dt: DataType::Integer, - ss: StorageScheme::Hermitian, + datatype: DataType::Integer, + storagescheme: StorageScheme::Hermitian, .. - } => Err(MMError::from_kind_and_message( - MMErrorKind::InvalidHeader, + } => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::InvalidHeader, String::from("Integer matrix can't be hermitian."), )), Typecode { - dt: DataType::Pattern, - ss: StorageScheme::Hermitian, + datatype: DataType::Pattern, + storagescheme: StorageScheme::Hermitian, .. - } => Err(MMError::from_kind_and_message( - MMErrorKind::InvalidHeader, + } => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::InvalidHeader, String::from("Pattern matrix can't be hermitian."), )), Typecode { - dt: DataType::Pattern, - ss: StorageScheme::Skew, + datatype: DataType::Pattern, + storagescheme: StorageScheme::Skew, .. - } => Err(MMError::from_kind_and_message( - MMErrorKind::InvalidHeader, + } => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::InvalidHeader, String::from("Pattern matrix can't be skew-symmetric."), )), + Typecode { + datatype: DataType::Pattern, + sparsity: Sparsity::Dense, + .. + } => Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::InvalidHeader, + String::from("Dense matrix can't be pattern matrix."), + )), // precheck success _ => Ok(()), } } /// Base trait for matrix market types. -pub trait MMType: Sized + Clone { +pub trait MatrixMarketScalar: Scalar { /// When matrix is an Integer matrix, it will convert a [i128] number to this type. - fn from_i128(i: i128) -> Result; + fn from_i128(i: i128) -> Result; /// When matrix is a Real matrix, it will convert a [f64] number to this type. - fn from_f64(f: f64) -> Result; + fn from_f64(f: f64) -> Result; /// When matrix is a Complx matrix, it will convert a [Complex] number to this type. - fn from_c64(c: Complex) -> Result; + fn from_c64(c: Complex) -> Result; /// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type. - fn from_pattern(p: ()) -> Result; + fn from_pattern(p: ()) -> Result; /// When matrix is a Skew-symmetric matrix, it will convert itself to its negative. - fn negative(self) -> Result; + fn negative(self) -> Result; /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. - fn conjugate(self) -> Result; + fn conjugate(self) -> Result; } -/// Implement MMType for primitive integer types. +/// Implement MatrixMarketScalar for primitive integer types. macro_rules! mm_int_impl { ($T:ty) => { - impl MMType for $T { + impl MatrixMarketScalar for $T { #[inline] - fn from_i128(i: i128) -> Result { - Ok(i as Self) + fn from_i128(i: i128) -> Result { + Ok(Self::try_from(i)?) } #[inline] - fn from_f64(_f: f64) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Int type can't parse from f64"), + fn from_f64(_f: f64) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Int type can't be parsed from f64"), )) } #[inline] - fn from_c64(_c: Complex) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Int type can't parse from Complex"), + fn from_c64(_c: Complex) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Int type can't be parsed from Complex"), )) } #[inline] - fn from_pattern(_p: ()) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Int type can't parse from ()"), + fn from_pattern(_p: ()) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Int type can't be parsed from ()"), )) } #[inline] - fn conjugate(self) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, + fn conjugate(self) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, format!("Int type has no conjugate"), )) } #[inline] - fn negative(self) -> Result - where - Self: Neg, - { + fn negative(self) -> Result { Ok(-self) } } }; } -/// Implement MMType for primitive float types. -macro_rules! mm_float_impl { +/// Implement MatrixMarketScalar for primitive real types. +macro_rules! mm_real_impl { ($T:ty) => { - impl MMType for $T { + impl MatrixMarketScalar for $T { #[inline] - fn from_i128(_i: i128) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Float type can't parse from i128"), + fn from_i128(_i: i128) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("real type can't be parsed from i128"), )) } #[inline] - fn from_f64(f: f64) -> Result { + fn from_f64(f: f64) -> Result { Ok(f as Self) } #[inline] - fn from_c64(_c: Complex) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Float type can't parse from Complex"), + fn from_c64(_c: Complex) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("real type can't be parsed from Complex"), )) } #[inline] - fn from_pattern(_p: ()) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Float type can't parse from ()"), + fn from_pattern(_p: ()) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("real type can't be parsed from ()"), )) } #[inline] - fn conjugate(self) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Float type has no conjugate"), + fn conjugate(self) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("real type has no conjugate"), )) } #[inline] - fn negative(self) -> Result - where - Self: Neg, - { + fn negative(self) -> Result { Ok(-self) } } }; } -/// Implement MMType for primitive complex types. +/// Implement MatrixMarketScalar for primitive complex types. macro_rules! mm_complex_impl { ($T:ty) => { - impl MMType for Complex<$T> { + impl MatrixMarketScalar for Complex<$T> { #[inline] - fn from_i128(_i: i128) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Complex type can't parse from i128"), + fn from_i128(_i: i128) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Complex type can't be parsed from i128"), )) } #[inline] - fn from_f64(_f: f64) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Complex type can't parse from f64"), + fn from_f64(_f: f64) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Complex type can't be parsed from f64"), )) } #[inline] - fn from_c64(c: Complex) -> Result { + fn from_c64(c: Complex) -> Result { Ok(Self { re: c.re as $T, im: c.im as $T, }) } #[inline] - fn from_pattern(_p: ()) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Complex type can't parse from ()"), + fn from_pattern(_p: ()) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Complex type can't be parsed from ()"), )) } #[inline] - fn conjugate(self) -> Result { + fn conjugate(self) -> Result { Ok(self.conj()) } #[inline] - fn negative(self) -> Result - where - Self: Neg, - { + fn negative(self) -> Result { Ok(-self) } } }; } -/// Implement MMType for primitive unit types. +/// Implement MatrixMarketScalar for primitive unit types. macro_rules! mm_pattern_impl { ($T:ty) => { - impl MMType for $T { + impl MatrixMarketScalar for $T { #[inline] - fn from_i128(_i: i128) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Pattern type can't parse from i128"), + fn from_i128(_i: i128) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Pattern type can't be parsed from i128"), )) } #[inline] - fn from_f64(_f: f64) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Pattern type can't parse from f64"), + fn from_f64(_f: f64) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Pattern type can't be parsed from f64"), )) } #[inline] - fn from_c64(_c: Complex) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, - format!("Pattern type can't parse from Complex"), + fn from_c64(_c: Complex) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, + format!("Pattern type can't be parsed from Complex"), )) } #[inline] - fn from_pattern(p: ()) -> Result { + fn from_pattern(p: ()) -> Result { Ok(p) } #[inline] - fn conjugate(self) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, + fn conjugate(self) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, format!("Pattern type has no conjugate"), )) } #[inline] - fn negative(self) -> Result { - Err(MMError::from_kind_and_message( - MMErrorKind::TypeUnmatched, + fn negative(self) -> Result { + Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::TypeUnmatched, format!("Pattern type has no negative"), )) } @@ -625,8 +677,8 @@ mm_int_impl!(i32); mm_int_impl!(i64); mm_int_impl!(i128); -mm_float_impl!(f32); -mm_float_impl!(f64); +mm_real_impl!(f32); +mm_real_impl!(f64); mm_complex_impl!(f32); mm_complex_impl!(f64); @@ -637,61 +689,240 @@ mm_pattern_impl!(()); #[grammar = "io/matrix_market.pest"] struct MMParser; -/// Parsing a pest structure to a Typecode of the matrix. -fn parsing_header(inner: &mut Pairs<'_, Rule>, header_type: &mut Typecode) -> Result<(), MMError> { - // once the data can be parsed, all unwrap() in this function here will not panic. - header_type.sp = inner - .next() - .unwrap() - .as_str() - .to_ascii_lowercase() - .parse::() - .unwrap(); - header_type.dt = inner - .next() - .unwrap() - .as_str() - .to_ascii_lowercase() - .parse::() - .unwrap(); - header_type.ss = inner - .next() - .unwrap() - .as_str() - .to_ascii_lowercase() - .parse::() - .unwrap(); - typecode_precheck(&header_type) +/// Parses a Matrix Market file at the given path, and returns the corresponding sparse matrix as CooMatrix format. +/// +/// Errors +/// -------- +/// +/// See [MatrixMarketErrorKind] for a list of possible error conditions. +/// +/// > _NOTE:_ Here uses strong type requirements, which means if the matrix is an integer matrix, e.g. `%%matrixmarket matrix cooridnate integer general`, then you have to load it by `load_coo_from_matrix_market_file`, where T is an integer type. Trying `load_coo_from_matrix_market_file` will give [TypeUnmatched](MatrixMarketErrorKind::TypeUnmatched) Error. After loading it, you can cast it into a `f64` matrix, by calling [cast](`nalgebra::base::Matrix::cast()`), but be aware of accuracy lose. +pub fn load_coo_from_matrix_market_file>( + path: P, +) -> Result, MatrixMarketError> +where + T: MatrixMarketScalar, +{ + let file = fs::read_to_string(path)?; + load_coo_from_matrix_market_str(&file) } -/// Parsing a pest structure to 3 int, which are number of rols, cols and non-zeros. -fn parsing_shape(inner: &mut Pairs<'_, Rule>, shape: &mut (usize, usize, usize)) { - // once the file can be parsed, all unwrap() in this function here will not panic. - shape.0 = inner.next().unwrap().as_str().parse::().unwrap(); - shape.1 = inner.next().unwrap().as_str().parse::().unwrap(); - shape.2 = inner.next().unwrap().as_str().parse::().unwrap(); -} +/// Parses a Matrix Market file described by the given string, and returns the corresponding as CooMatrix format. +/// +/// Errors +/// -------- +/// +/// See [MatrixMarketErrorKind] for a list of possible error conditions. +/// +/// > _NOTE:_ Here uses strong type requirements, which means if the matrix is an integer matrix, e.g. `%%matrixmarket matrix cooridnate integer general`, then you have to load it by `load_coo_from_matrix_market_str`, where T is an integer type. Trying `load_coo_from_matrix_market_str` will give [TypeUnmatched](MatrixMarketErrorKind::TypeUnmatched) Error. After loading it, you can cast it into a `f64` matrix, by calling [cast](`nalgebra::base::Matrix::cast()`),but be aware of accuracy lose. -#[inline] -/// Do a precheck of remaing data. Because in matrix_market.pest file, either 0, or 1 or 2 float number(s) as data is valid, however, for example, for Real matrix, only 1 float is valid. And 2 float for Complex matrix. -fn check_value_length(inner: &Pairs<'_, Rule>, l: usize) -> Result<(), MMError> { - let copy = inner.clone(); - let c = copy.count(); - if l != c { - return Err(MMError::from_kind_and_message( - MMErrorKind::ParsingError, - format!("{} data required, but {} provided.", l, c), +pub fn load_coo_from_matrix_market_str(data: &str) -> Result, MatrixMarketError> +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let file = MMParser::parse(Rule::Document, data)?.next().unwrap(); + + let mut rows: Vec = Vec::new(); + let mut cols: Vec = Vec::new(); + let mut data: Vec = Vec::new(); + let mut lines = file.into_inner(); + + let header_line = lines.next().unwrap(); + let header_type = parse_header(&mut header_line.into_inner()); + typecode_precheck(&header_type)?; + + let shape_line = lines.next().unwrap(); + // shape here is number of rows, columns, non-zeros + let shape: (usize, usize, usize); + match header_type.sparsity { + Sparsity::Sparse => { + shape = parse_sparse_shape(&mut shape_line.into_inner(), &header_type.storagescheme)?; + } + Sparsity::Dense => { + shape = parse_dense_shape(&mut shape_line.into_inner(), &header_type.storagescheme)?; + } + } + + // used when constructing dense matrix. + // If it's sparse matrix, it has no effect. + let mut current_dense_coordiante: (usize, usize) = (0, 0); + if header_type.storagescheme == StorageScheme::Skew { + // for skew dense matrix, the first element starts from (1,0) + current_dense_coordiante = (1, 0); + } + // count how many entries in the matrix data + let count = lines.clone().count(); + if count != shape.2 { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::EntryNumUnmatched, + format!( + "{} entries required for the matrix, but {} was provided", + shape.2, count, + ), )); } - Ok(()) + + for data_line in lines { + let entry: (usize, usize, T); + match header_type { + Typecode { + sparsity: Sparsity::Sparse, + datatype: DataType::Real, + .. + } => { + entry = parse_sparse_real::(&mut data_line.into_inner())?; + } + Typecode { + sparsity: Sparsity::Sparse, + datatype: DataType::Integer, + .. + } => { + entry = parse_sparse_int::(&mut data_line.into_inner())?; + } + Typecode { + sparsity: Sparsity::Sparse, + datatype: DataType::Pattern, + .. + } => { + entry = parse_sparse_pattern::(&mut data_line.into_inner())?; + } + Typecode { + sparsity: Sparsity::Sparse, + datatype: DataType::Complex, + .. + } => { + entry = parse_sparse_complex::(&mut data_line.into_inner())?; + } + Typecode { + sparsity: Sparsity::Dense, + datatype: DataType::Complex, + .. + } => { + entry = ( + current_dense_coordiante.0, + current_dense_coordiante.1, + parse_dense_complex::(&mut data_line.into_inner())?, + ); + next_dense_coordiante( + &mut current_dense_coordiante, + shape, + &header_type.storagescheme, + ); + } + Typecode { + sparsity: Sparsity::Dense, + datatype: DataType::Real, + .. + } => { + entry = ( + current_dense_coordiante.0, + current_dense_coordiante.1, + parse_dense_real::(&mut data_line.into_inner())?, + ); + + next_dense_coordiante( + &mut current_dense_coordiante, + shape, + &header_type.storagescheme, + ); + } + Typecode { + sparsity: Sparsity::Dense, + datatype: DataType::Integer, + .. + } => { + entry = ( + current_dense_coordiante.0, + current_dense_coordiante.1, + parse_dense_int::(&mut data_line.into_inner())?, + ); + next_dense_coordiante( + &mut current_dense_coordiante, + shape, + &header_type.storagescheme, + ); + } + _ => { + // it shouldn't happen here, because dense matrix can't be pattern. And it will give InvalidHeader error beforehand. + entry = (1, 1, T::from_i128(1)?) + } + } + + let (r, c, d) = entry; + + match header_type.storagescheme { + StorageScheme::General => { + rows.push(r); + cols.push(c); + data.push(d); + } + StorageScheme::Symmetric => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + // don't need to add twice if the element in on diagonal + if r != c { + rows.push(c); + cols.push(r); + data.push(d); + } + } + StorageScheme::Skew => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + // skew-symmetric matrix shouldn't have diagonal element + if r == c { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::DiagonalError, + format!( + "There is a diagonal element in skew matrix, in row(and column) {}", + r + 1 + ), + )); + } + rows.push(c); + cols.push(r); + data.push(d.negative()?); + } + StorageScheme::Hermitian => { + check_lower_triangle(r, c)?; + rows.push(r); + cols.push(c); + data.push(d.clone()); + + if r == c && d != d.clone().conjugate()? { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::DiagonalError, + format!( + "There is a diagonal element in hermitian matrix, which is not a real number, in row(and column) {}", + r + 1 + ), + )); + } + // don't need to add twice if the element in on diagonal + if r != c { + rows.push(c); + cols.push(r); + data.push(d.conjugate()?); + } + } + } + } + Ok(CooMatrix::try_from_triplets( + shape.0, shape.1, rows, cols, data, + )?) } #[inline] -// do a quick check it the entry is in the lower triangle part of the matrix -fn check_lower_triangle(r: usize, c: usize) -> Result<(), MMError> { +/// do a quick check it the entry is in the lower triangle part of the matrix +fn check_lower_triangle(r: usize, c: usize) -> Result<(), MatrixMarketError> { if c > r { - return Err(MMError::from_kind_and_message( - MMErrorKind::LowerTriangleError, + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::NotLowerTriangle, format!( "Entry: row {} col {} should be put into lower triangle", r, c @@ -701,161 +932,362 @@ fn check_lower_triangle(r: usize, c: usize) -> Result<(), MMError> { Ok(()) } -/// Parses a Matrix Market file at the given path, and returns the corresponding sparse matrix as CooMatrix format. -pub fn load_coo_from_mm_file>(path: P) -> Result, MMError> -where - T: MMType, -{ - let file = fs::read_to_string(path)?; - load_coo_from_mm_str(&file) +#[inline] +/// Parse a pest structure to a Typecode of the matrix. +fn parse_header(inner: &mut Pairs<'_, Rule>) -> Typecode { + // unwrap() in this function are guaranteed by pasing the data + Typecode { + sparsity: inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(), + datatype: inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(), + storagescheme: inner + .next() + .unwrap() + .as_str() + .to_ascii_lowercase() + .parse::() + .unwrap(), + } } -/// Parses a Matrix Market file described by the given string, and returns the corresponding as CooMatrix format. -pub fn load_coo_from_mm_str(data: &str) -> Result, MMError> -where - T: MMType, -{ - // unwrap() here guaranteed when data can be parsed - let file = MMParser::parse(Rule::Document, data)?.next().unwrap(); - // Default typecode - let mut header_type = Typecode { - sp: Sparsity::Sparse, - dt: DataType::Real, - ss: StorageScheme::General, - }; - let mut shape = (0, 0, 0); - let mut rows: Vec = Vec::new(); - let mut cols: Vec = Vec::new(); - let mut data: Vec = Vec::new(); +// Parse shape starts here------------------------------------------------- - let mut count = 0; - for line in file.into_inner() { - match line.as_rule() { - Rule::Header => { - let mut inner = line.into_inner(); - parsing_header(&mut inner, &mut header_type)?; - } - Rule::Shape => { - let mut inner = line.into_inner(); - parsing_shape(&mut inner, &mut shape); - } - Rule::Entry => { - count += 1; - let mut inner = line.into_inner(); - // NOTE: indices are 1-based. - // unwrap() here guaranteed when data can be parsed - let r = inner.next().unwrap().as_str().parse::().unwrap(); - if r == 0 { - return Err(MMError::from_kind_and_message( - MMErrorKind::ZeroIndexed, - String::from("The data has to be one-indixed"), - )); - } - let r = r - 1; - // unwrap() here guaranteed when data can be parsed - let c = inner.next().unwrap().as_str().parse::().unwrap(); - if c == 0 { - return Err(MMError::from_kind_and_message( - MMErrorKind::ZeroIndexed, - String::from("The data has to be one-indixed"), - )); - } - let c = c - 1; - let d: T; - match header_type.dt { - DataType::Integer => { - check_value_length(&inner, 1)?; - // unwrap() here guaranteed by check_value_length - let i = inner.next().unwrap().as_str().parse::()?; - d = T::from_i128(i)?; - } - DataType::Real => { - check_value_length(&inner, 1)?; - // first unwrap() here guaranteed by check_value_length - // second unwrap() here guaranteed by parsing the data - let i = inner.next().unwrap().as_str().parse::().unwrap(); - d = T::from_f64(i)?; - } - DataType::Complex => { - check_value_length(&inner, 2)?; - // first unwrap() here guaranteed by check_value_length - // second unwrap() here guaranteed by parsing the data - let real = inner.next().unwrap().as_str().parse::().unwrap(); - // first unwrap() here guaranteed by check_value_length - // second unwrap() here guaranteed by parsing the data - let imag = inner.next().unwrap().as_str().parse::().unwrap(); - // only complex could be hermitian, and check diagonal element is a real number - if header_type.ss == StorageScheme::Hermitian && r == c && imag != 0.0 { - return Err(MMError::from_kind_and_message(MMErrorKind::DiagonalError,format!("There is a diagonal element in hermitian matrix, in row(and column) {}, but imaginary part is not zero",r))); - } - d = T::from_c64(Complex::::new(real, imag))?; - } - DataType::Pattern => { - check_value_length(&inner, 0)?; - d = T::from_pattern(())?; - } - } - - match header_type.ss { - StorageScheme::General => { - rows.push(r); - cols.push(c); - data.push(d); - } - StorageScheme::Symmetric => { - check_lower_triangle(r, c)?; - rows.push(r); - cols.push(c); - data.push(d.clone()); - // don't need to add twice if the element in on diagonal - if r != c { - rows.push(c); - cols.push(r); - data.push(d); - } - } - StorageScheme::Skew => { - check_lower_triangle(r, c)?; - rows.push(r); - cols.push(c); - data.push(d.clone()); - // skew-symmetric matrix shouldn't have diagonal element - if r == c { - return Err(MMError::from_kind_and_message(MMErrorKind::DiagonalError,format!("There is a diagonal element in skew matrix, in row(and column) {}",r))); - } - rows.push(c); - cols.push(r); - data.push(d.negative()?); - } - StorageScheme::Hermitian => { - check_lower_triangle(r, c)?; - rows.push(r); - cols.push(c); - data.push(d.clone()); - // don't need to add twice if the element in on diagonal - // diagonal element has been checked before to make sure the imaginary part is zero - if r != c { - rows.push(c); - cols.push(r); - data.push(d.conjugate()?); - } - } - } - } - _ => {} - } +/// Parse a pest structure to sparse shape information, including 3 int, which are number of rols, cols and non-zeros. +fn parse_sparse_shape( + inner: &mut Pairs<'_, Rule>, + storagescheme: &StorageScheme, +) -> Result<(usize, usize, usize), MatrixMarketError> { + // unwrap() in this function are guaranteed by pasing the data + let shape_inner = inner.next().unwrap(); + if shape_inner.as_rule() != Rule::SparseShape { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" + Shape shape line requires 3 int numbers as number of rows, columns and non-zeros, but line {} was provided here. + ",shape_inner.as_str()))); } - if count != shape.2 { - return Err(MMError::from_kind_and_message( - MMErrorKind::EntryNumUnmatched, - format!( - "expected {} entries in matrix market file, but found {}", - shape.2, count + + let mut inner = shape_inner.into_inner(); + + let r = inner.next().unwrap().as_str().parse::().unwrap(); + let c = inner.next().unwrap().as_str().parse::().unwrap(); + let nnz = inner.next().unwrap().as_str().parse::().unwrap(); + + // shape information can't use 0 as dimension + if r * c * nnz == 0 { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ZeroError, + String::from( + " + Matrix can't have 0 as shape dimensions. + ", ), )); } - Ok(CooMatrix::try_from_triplets( - shape.0, shape.1, rows, cols, data, - )?) + // check for square matirx, when it's not a general matrix + if *storagescheme != StorageScheme::General && r != c { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NotSquareMatrix,format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}",r,c))); + } + + Ok((r, c, nnz)) +} + +/// Parse a pest structure to dense shape information, including 2 int, which are number of rols, cols. +fn parse_dense_shape( + inner: &mut Pairs<'_, Rule>, + storagescheme: &StorageScheme, +) -> Result<(usize, usize, usize), MatrixMarketError> { + // unwrap() in this function are guaranteed by pasing the data + let shape_inner = inner.next().unwrap(); + if shape_inner.as_rule() != Rule::DenseShape { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" + Shape shape line requires 2 int numbers as number of rows, columns, but line {} was provided here. + ",shape_inner.as_str()))); + } + + let mut inner = shape_inner.into_inner(); + let r = inner.next().unwrap().as_str().parse::().unwrap(); + let c = inner.next().unwrap().as_str().parse::().unwrap(); + // shape information can't use 0 as dimension + if r * c == 0 { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ZeroError, + String::from( + " + Matrix can't have 0 as shape dimensions. + ", + ), + )); + } + + // check for square matirx, when it's not a general matrix + if *storagescheme != StorageScheme::General && r != c { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NotSquareMatrix,format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}",r,c))); + } + + let n: usize; + // Calculate the number of entries in the dense matrix + match storagescheme { + StorageScheme::General => { + // general matrix should contain r*c entries + n = r * c; + } + StorageScheme::Symmetric | StorageScheme::Hermitian => { + // it must be square matrix, so r==c is true here + // Symmetric or Hermitian should contain 1+2...+r = r*(r+1)/2 entries + n = r * (r + 1) / 2; + } + StorageScheme::Skew => { + // it must be square matrix, so r==c is true here + // Skew-Symmetric should contain 1+2...+r-1 = r*(r-1)/2 entries + n = r * (r - 1) / 2; + } + } + + Ok((r, c, n)) +} + +// Parse shape ends here------------------------------------------------- + +// Parse entry starts here------------------------------------------------- + +/// Parse a pest structure to sparse real entry, including 2 int, which are number of rols, cols, and a real number as data +fn parse_sparse_real(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T), MatrixMarketError> +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + if entry_inner.as_rule() != Rule::SparseReal { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" + Spare real matrix requires 2 int number as coordiantes and 1 real number as data, but line {} was provided. + ",entry_inner.as_str() ))); + } + + let mut inner = entry_inner.into_inner(); + let (r, c) = parse_sparse_coordiante(&mut inner)?; + let d = inner.next().unwrap().as_str().parse::().unwrap(); + Ok((r, c, T::from_f64(d)?)) +} + +/// Parse a pest structure to sparse integer entry, including 2 int, which are number of rols, cols, and a int number as data +fn parse_sparse_int(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T), MatrixMarketError> +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + // Because integer numbers can also be parsed as float numbers, it will be checked again in `parse::()?` + if entry_inner.as_rule() != Rule::SparseReal { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, + format!( + " + Spare real matrix requires 3 int number as coordiantes and data, but line {} was provided. + ", + entry_inner.as_str() + ), + )); + } + let mut inner = entry_inner.into_inner(); + let (r, c) = parse_sparse_coordiante(&mut inner)?; + // Here to guarantee it is an integer number + let d = inner.next().unwrap().as_str().parse::()?; + Ok((r, c, T::from_i128(d)?)) +} + +/// Parse a pest structure to sparse pattern entry, including 2 int, which are number of rols, cols +fn parse_sparse_pattern( + inner: &mut Pairs<'_, Rule>, +) -> Result<(usize, usize, T), MatrixMarketError> +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + if entry_inner.as_rule() != Rule::SparsePattern { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, + format!( + " + Spare real matrix requires 2 int number as coordiantes, but line {} was provided. + ", + entry_inner.as_str() + ), + )); + } + let mut inner = entry_inner.into_inner(); + let (r, c) = parse_sparse_coordiante(&mut inner)?; + Ok((r, c, T::from_pattern(())?)) +} + +/// Parse a pest structure to sparse complex entry, including 2 int, which are number of rols, cols, and 2 real number as complex data +fn parse_sparse_complex( + inner: &mut Pairs<'_, Rule>, +) -> Result<(usize, usize, T), MatrixMarketError> +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + if entry_inner.as_rule() != Rule::SparseComplex { + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" + Spare real matrix requires 2 int number as coordiantes and 2 real number as complex data, but line {} was provided. + ",entry_inner.as_str() ))); + } + let mut inner = entry_inner.into_inner(); + let (r, c) = parse_sparse_coordiante(&mut inner)?; + let real = inner.next().unwrap().as_str().parse::().unwrap(); + let imag = inner.next().unwrap().as_str().parse::().unwrap(); + let complex = Complex::::new(real, imag); + Ok((r, c, T::from_c64(complex)?)) +} + +/// Parse a pest structure to dense real entry, including a real number as data +fn parse_dense_real(inner: &mut Pairs<'_, Rule>) -> Result +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + if entry_inner.as_rule() != Rule::DenseReal { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, + format!( + " + Dense real matrix requires 1 real number as data, but line {} was provided. + ", + entry_inner.as_str() + ), + )); + } + let mut inner = entry_inner.into_inner(); + let d = inner.next().unwrap().as_str().parse::().unwrap(); + Ok(T::from_f64(d)?) +} + +/// Parse a pest structure to dense integer entry, including a integer number as data +fn parse_dense_int(inner: &mut Pairs<'_, Rule>) -> Result +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + // Because integer numbers can also be parsed as float numbers, it will be checked again in `parse::()?` + if entry_inner.as_rule() != Rule::DenseReal { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, + format!( + " + Dense real matrix requires 1 int number as data, but line {} was provided. + ", + entry_inner.as_str() + ), + )); + } + let mut inner = entry_inner.into_inner(); + // Here to guarantee it is an integer number + let d = inner.next().unwrap().as_str().parse::()?; + Ok(T::from_i128(d)?) +} + +/// Parse a pest structure to dense complex entry, including 2 real number as complex data +fn parse_dense_complex(inner: &mut Pairs<'_, Rule>) -> Result +where + T: MatrixMarketScalar, +{ + // unwrap() in this function are guaranteed by pasing the data + let entry_inner = inner.next().unwrap(); + // Note: theoretically, 2 positive integers could also become the complex number, + // but it would be parsed as SparsePattern, because SparsePattern has higher priority. + // But DenseComplex can't have higher priority, + // because, it's more often to deal with "normal" SparsePattern, rather than "unnormal" DenseComplex + if entry_inner.as_rule() != Rule::DenseComplex && entry_inner.as_rule() != Rule::SparsePattern { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ParsingError, + format!( + " + Dense real matrix requires 2 real number as complex data, but line {} was provided. + ", + entry_inner.as_str() + ), + )); + } + let mut inner = entry_inner.into_inner(); + let real = inner.next().unwrap().as_str().parse::().unwrap(); + let imag = inner.next().unwrap().as_str().parse::().unwrap(); + let complex = Complex::::new(real, imag); + Ok(T::from_c64(complex)?) +} + +// Parse entry ends here------------------------------------------------- + +/// Parse the coordiantes information used for sparse matrix +fn parse_sparse_coordiante( + inner: &mut Pairs<'_, Rule>, +) -> Result<(usize, usize), MatrixMarketError> { + // unwrap() in this function are guaranteed by pasing the data + let r = inner.next().unwrap().as_str().parse::().unwrap(); + let c = inner.next().unwrap().as_str().parse::().unwrap(); + if r * c == 0 { + return Err(MatrixMarketError::from_kind_and_message( + MatrixMarketErrorKind::ZeroError, + String::from("The data has to be one-indixed"), + )); + } + // The coordiantes in matrix market is one-based, but in CooMatrix is zero-based. + Ok((r - 1, c - 1)) +} + +/// Calculate the next coordiantes used for dense matrix +fn next_dense_coordiante( + current_dense_coordiante: &mut (usize, usize), + shape: (usize, usize, usize), + storagescheme: &StorageScheme, +) { + // matrix market is column based format. + // so it follows the order (0,0) -> (1,0) -> ... -> (row, 0) -> (0,1) -> ... ->(row,col) + // current_dense_coordiante is (row, column) + match storagescheme { + StorageScheme::General => { + if current_dense_coordiante.0 < shape.0 - 1 { + current_dense_coordiante.0 += 1 + } else { + // jump to next column, reset row to 1, column add 1 + current_dense_coordiante.0 = 0; + current_dense_coordiante.1 += 1; + } + } + StorageScheme::Symmetric | StorageScheme::Hermitian => { + if current_dense_coordiante.0 < shape.0 - 1 { + current_dense_coordiante.0 += 1 + } else { + // jump to next column, column add 1, then set row equals to current column + // for example (0,0) -> (1,0) -> ... -> (row, 0) -> (1,1) -> ... + current_dense_coordiante.1 += 1; + current_dense_coordiante.0 = current_dense_coordiante.1; + } + } + StorageScheme::Skew => { + if current_dense_coordiante.0 < shape.0 - 1 { + current_dense_coordiante.0 += 1; + } else { + // jump to next column, set row equals to current column, then column add 1 + // skew matrix doesn't have element on diagonal + // for example (1,0) -> (2,0) -> ... -> (row, 0) -> (2,1) -> ... + current_dense_coordiante.1 += 1; + current_dense_coordiante.0 = current_dense_coordiante.1 + 1; + } + } + } } diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index 51b62873..2d74a35c 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -1,8 +1,10 @@ //! Parsers for various matrix formats. //! -//! Use mm(or MM) to represent matrix market. +//! ## 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. pub use self::matrix_market::{ - load_coo_from_mm_file, load_coo_from_mm_str, MMError, MMErrorKind, MMType, + load_coo_from_matrix_market_file, load_coo_from_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 82ac6e2b..316ac02f 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -1,12 +1,12 @@ use matrixcompare::assert_matrix_eq; use nalgebra::dmatrix; use nalgebra::Complex; -use nalgebra_sparse::io::load_coo_from_mm_str; +use nalgebra_sparse::io::load_coo_from_matrix_market_str; use nalgebra_sparse::CooMatrix; #[test] #[rustfmt::skip] -fn test_mm_sparse_real_general() { +fn test_matrixmarket_sparse_real_general() { let file_str = r#" %%MatrixMarket matrix CoOrdinate real general % This is also an example of free-format features. @@ -32,16 +32,27 @@ fn test_mm_sparse_real_general() { % %================================================================================= 5 5 8 - 1 1 1 - 2 2 1.050e+01 - 3 3 1.500e-02 + 1 1 1 + + 2 2 1.050e+01 + 3 3 1.500e-02 + + + + + + + + 1 4 6.000e+00 + 4 2 2.505e+02 + 4 4 -2.800e+02 4 5 3.332e+01 5 5 1.200e+01 "#; - let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); let expected = dmatrix![ 1.0, 0.0, 0.0, 6.0, 0.0; 0.0, 10.5, 0.0, 0.0, 0.0; @@ -54,22 +65,22 @@ fn test_mm_sparse_real_general() { #[test] #[rustfmt::skip] -fn test_mm_sparse_int_symmetric() { +fn test_matrixmarket_sparse_int_symmetric() { let file_str = r#" %%MatrixMarket matrix coordinate integer symmetric % 5 5 9 1 1 11 - 2 2 22 - 3 2 23 - 3 3 33 - 4 2 24 - 4 4 44 + 2 2 22 + 3 2 23 + 3 3 33 + 4 2 24 + 4 4 44 5 1 -15 5 3 35 - 5 5 55 + 5 5 55 "#; - let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); let expected = dmatrix![ 11, 0, 0, 0, -15; 0, 22, 23, 24, 0; @@ -82,21 +93,21 @@ fn test_mm_sparse_int_symmetric() { #[test] #[rustfmt::skip] -fn test_mm_sparse_complex_hermitian() { +fn test_matrixmarket_sparse_complex_hermitian() { let file_str = r#" %%MatrixMarket matrix coordinate complex hermitian % 5 5 7 - 1 1 1.0 0.0 - 2 2 10.5 0.0 - 4 2 250.5 22.22 - 3 3 0.015 0.0 - 4 4 -2.8e2 0.0 - 5 5 12.0 0.0 + 1 1 1.0 0.0 + 2 2 10.5 0.0 + 4 2 250.5 22.22 + 3 3 0.015 0.0 + 4 4 -2.8e2 0.0 + 5 5 12.0 0.0 5 4 0.0 33.32 - + "#; - let sparse_mat = load_coo_from_mm_str::>(file_str).unwrap(); + 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}; @@ -109,17 +120,17 @@ fn test_mm_sparse_complex_hermitian() { #[test] #[rustfmt::skip] -fn test_mm_sparse_real_skew() { +fn test_matrixmarket_sparse_real_skew() { let file_str = r#" %%MatrixMarket matrix coordinate real skew-symmetric % 5 5 4 - 3 2 -23.0 - 4 2 -24.0 + 3 2 -23.0 + 4 2 -24.0 5 1 -15.0 5 3 -35.0 "#; - let sparse_mat = load_coo_from_mm_str::(file_str).unwrap(); + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); let expected = dmatrix![ 0.0, 0.0, 0.0, 0.0, 15.0; 0.0, 0.0, 23.0, 24.0, 0.0; @@ -132,12 +143,12 @@ fn test_mm_sparse_real_skew() { #[test] #[rustfmt::skip] -fn test_mm_sparse_pattern_general() { +fn test_matrixmarket_sparse_pattern_general() { let file_str = r#" %%MatrixMarket matrix coordinate pattern general % 5 5 10 - 1 1 + 1 1 1 5 2 3 2 4 @@ -146,9 +157,9 @@ fn test_mm_sparse_pattern_general() { 4 1 5 2 5 4 - 5 5 + 5 5 "#; - let pattern_matrix = load_coo_from_mm_str::<()>(file_str).unwrap(); + 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(); @@ -163,3 +174,136 @@ fn test_mm_sparse_pattern_general() { ]; assert_matrix_eq!(sparse_mat, expected); } + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_dense_real_general() { + let file_str = r#" +%%MatrixMarket matrix array real general +% +4 3 +1.0 +2.0 +3.0 +4.0 +5.0 +6.0 +7.0 +8.0 +9.0 +10.0 +11.0 +12.0 + +"#; + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); + let expected = dmatrix![ + 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); +} + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_dense_real_symmetric() { + let file_str = r#" +%%MatrixMarket matrix array real symmetric +% +4 4 +1.0 +2.0 +3.0 +4.0 +5.0 +6.0 +7.0 +8.0 +9.0 +10.0 + +"#; + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); + let expected = dmatrix![ + 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); +} + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_dense_complex_hermitian() { + let file_str = r#" +%%MatrixMarket matrix array complex hermitian +% +4 4 +1.0 0.0 +2.0 2.0 +3.0 3.0 +4.0 4.0 +5.0 0.0 +6.0 6.0 +7.0 7.0 +8.0 0.0 +9.0 9.0 +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}; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_dense_int_skew() { + let file_str = r#" +%%MatrixMarket matrix array integer skew-symmetric +% +4 4 +1 +2 +3 +4 +5 +6 +"#; + let sparse_mat = load_coo_from_matrix_market_str::(file_str).unwrap(); + let expected = dmatrix![ + 0,-1,-2,-3; + 1, 0,-4,-5; + 2, 4, 0,-6; + 3, 5, 6, 0; + ]; + assert_matrix_eq!(sparse_mat, expected); +} + +#[test] +#[rustfmt::skip] +fn test_matrixmarket_dense_complex_general() { + let file_str = r#" +%%MatrixMarket matrix array complex general +% +2 2 +1 0 +1 0 +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}; + ]; + assert_matrix_eq!(sparse_mat, expected); +} From 920bd75b820278fc233936708b482114d247270b Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Wed, 17 Nov 2021 17:04:24 +0100 Subject: [PATCH 03/15] fix typo --- nalgebra-sparse/src/io/matrix_market.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 3be7a8ac..6b008517 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -720,7 +720,7 @@ pub fn load_coo_from_matrix_market_str(data: &str) -> Result, Ma where T: MatrixMarketScalar, { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let file = MMParser::parse(Rule::Document, data)?.next().unwrap(); let mut rows: Vec = Vec::new(); @@ -935,7 +935,7 @@ fn check_lower_triangle(r: usize, c: usize) -> Result<(), MatrixMarketError> { #[inline] /// Parse a pest structure to a Typecode of the matrix. fn parse_header(inner: &mut Pairs<'_, Rule>) -> Typecode { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data Typecode { sparsity: inner .next() @@ -968,7 +968,7 @@ fn parse_sparse_shape( inner: &mut Pairs<'_, Rule>, storagescheme: &StorageScheme, ) -> Result<(usize, usize, usize), MatrixMarketError> { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let shape_inner = inner.next().unwrap(); if shape_inner.as_rule() != Rule::SparseShape { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" @@ -1007,7 +1007,7 @@ fn parse_dense_shape( inner: &mut Pairs<'_, Rule>, storagescheme: &StorageScheme, ) -> Result<(usize, usize, usize), MatrixMarketError> { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let shape_inner = inner.next().unwrap(); if shape_inner.as_rule() != Rule::DenseShape { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" @@ -1066,7 +1066,7 @@ fn parse_sparse_real(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T) where T: MatrixMarketScalar, { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let entry_inner = inner.next().unwrap(); if entry_inner.as_rule() != Rule::SparseReal { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" @@ -1085,7 +1085,7 @@ fn parse_sparse_int(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T), where T: MatrixMarketScalar, { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let entry_inner = inner.next().unwrap(); // Because integer numbers can also be parsed as float numbers, it will be checked again in `parse::()?` if entry_inner.as_rule() != Rule::SparseReal { @@ -1113,7 +1113,7 @@ fn parse_sparse_pattern( where T: MatrixMarketScalar, { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let entry_inner = inner.next().unwrap(); if entry_inner.as_rule() != Rule::SparsePattern { return Err(MatrixMarketError::from_kind_and_message( @@ -1138,7 +1138,7 @@ fn parse_sparse_complex( where T: MatrixMarketScalar, { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let entry_inner = inner.next().unwrap(); if entry_inner.as_rule() != Rule::SparseComplex { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" @@ -1158,7 +1158,7 @@ fn parse_dense_real(inner: &mut Pairs<'_, Rule>) -> Result(inner: &mut Pairs<'_, Rule>) -> Result()?` if entry_inner.as_rule() != Rule::DenseReal { @@ -1206,7 +1206,7 @@ fn parse_dense_complex(inner: &mut Pairs<'_, Rule>) -> Result, ) -> Result<(usize, usize), MatrixMarketError> { - // unwrap() in this function are guaranteed by pasing the data + // unwrap() in this function are guaranteed by parsing the data let r = inner.next().unwrap().as_str().parse::().unwrap(); let c = inner.next().unwrap().as_str().parse::().unwrap(); if r * c == 0 { From 3b67afcd9bca508e97fe2ab13f5bd77a1af9071a Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:07:13 +0100 Subject: [PATCH 04/15] Matrix market: Extend and reword documentation, rename some types --- nalgebra-sparse/src/io/matrix_market.rs | 213 ++++++++++++++---------- nalgebra-sparse/src/io/mod.rs | 34 +++- 2 files changed, 155 insertions(+), 92 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 6b008517..518675cd 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -27,11 +27,12 @@ pub struct MatrixMarketError { } /// Errors produced by functions that expect well-formed matrix market format data. -/// > _NOTE:_ Since the matrix market design didn't mention if multiple sparse entries with the same coordiantes are allowed or not, so, it's allowed here. #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq)] pub enum MatrixMarketErrorKind { - /// Indicates that some word is not known to MM format + /// Parsing failure. + /// + /// Indicates that the parser failed, for example due to an unexpected string. /// /// Examples /// -------- @@ -39,17 +40,17 @@ pub enum MatrixMarketErrorKind { /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" - /// %%MatrixMarket whatever whatever whatever whatever + /// %%MatrixMarket invalid invalid invalid invalid /// 1 1 1 /// 1 1 5 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::ParsingError); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(), MatrixMarketErrorKind::ParsingError); /// ``` ParsingError, - /// Indicates that header is not valid + /// Indicates that the matrix market header is invalid. /// /// Examples /// -------- @@ -62,13 +63,13 @@ pub enum MatrixMarketErrorKind { /// 1 1 1 /// 1 1 5 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::InvalidHeader); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::InvalidHeader); /// ``` InvalidHeader, - /// Indicates that the data entries in .mtx file are more or less than entries specified in .mtx file + /// Indicates that the number of data entries in the matrix market file does not match the header. /// /// Examples /// -------- @@ -82,13 +83,14 @@ pub enum MatrixMarketErrorKind { /// 2 2 2 /// 2 3 2 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::EntryNumUnmatched); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::EntryMismatch); /// ``` - EntryNumUnmatched, + EntryMismatch, - /// Indicates that the type T is not matched with the function it called. + /// Indicates that the scalar type requested is not compatible with the scalar type stored + /// in the matrix market file. /// /// Examples /// -------- @@ -97,18 +99,20 @@ pub enum MatrixMarketErrorKind { /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" /// %%matrixmarket matrix coordinate real general - /// % it should be called by load_coo_from_matrix_market_str::(str), or f32; + /// % it should be loaded with load_coo_from_matrix_market_str::(str) (or f32) /// 3 3 2 /// 2 2 2.22 /// 2 3 2.22 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::TypeUnmatched); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::TypeMismatch); /// ``` - TypeUnmatched, + TypeMismatch, - /// Indicates that zero has been used as an index in the data, or the shape of the matrix, which is not allowed. + /// Indicates that zero has been used as an index in the data. + /// + /// **Note**: The matrix market format uses 1-based indexing. /// /// Examples /// -------- @@ -120,13 +124,14 @@ pub enum MatrixMarketErrorKind { /// 1 1 1 /// 0 0 10 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::ZeroError); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::ZeroError); /// ``` ZeroError, - /// Indicates [SparseFormatError], while creating the sparse matrix. + /// Indicates [SparseFormatError] while creating the sparse matrix. + /// /// /// Examples /// -------- @@ -139,13 +144,14 @@ pub enum MatrixMarketErrorKind { /// 1 1 1 /// 4 2 10 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::SparseFormatError(SparseFormatErrorKind::IndexOutOfBounds)); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(), + /// MatrixMarketErrorKind::SparseFormatError(SparseFormatErrorKind::IndexOutOfBounds)); /// ``` SparseFormatError(SparseFormatErrorKind), - /// Indicates that a wrong diagonal element has been provieded to the matrix + /// Indicates that a wrong diagonal element has been provided to the matrix. /// /// Examples /// -------- @@ -160,9 +166,9 @@ pub enum MatrixMarketErrorKind { /// 1 1 10 /// 2 1 5 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); /// /// let str = r#" /// %%matrixmarket matrix coordinate complex hermitian @@ -171,28 +177,27 @@ pub enum MatrixMarketErrorKind { /// 1 1 10 2 /// 2 1 5 2 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::>(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); + /// let matrix_result = load_coo_from_matrix_market_str::>(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::DiagonalError); /// ``` - /// Here the skew matrix shouldn't have an element on the diagonal + /// Here the skew matrix shouldn't have an element on the diagonal. DiagonalError, - /// Indicates [io error](`std::io::Error`), while reading the data from file. + /// Indicates an [IO error](`std::io::Error`) while reading the data from file. /// /// Examples /// -------- /// ```rust /// # use nalgebra_sparse::io::load_coo_from_matrix_market_file; /// # use nalgebra_sparse::io::MatrixMarketErrorKind; - /// let file_name = "whatever.mtx"; - /// let matrix_error = load_coo_from_matrix_market_file::(file_name); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::IOError(std::io::ErrorKind::NotFound)); + /// let matrix_result = load_coo_from_matrix_market_file::("matrix.mtx"); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::IOError(std::io::ErrorKind::NotFound)); /// ``` IOError(std::io::ErrorKind), - /// Indicates (skew-)symmetric (or hermitian) matrix is not lower triangle matrix. + /// Indicates that a (skew-)symmetric (or hermitian) matrix is not a lower triangular matrix. /// /// Examples /// -------- @@ -205,13 +210,13 @@ pub enum MatrixMarketErrorKind { /// 1 1 10 /// 2 3 5 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::NotLowerTriangle); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::NotLowerTriangle); /// ``` NotLowerTriangle, - /// Indicates (skew-)symmetric (or hermitian) matrix is not square matrix. + /// Indicates that a (skew-)symmetric (or hermitian) matrix is not a square matrix. /// /// Examples /// -------- @@ -224,11 +229,11 @@ pub enum MatrixMarketErrorKind { /// 1 1 10 /// 2 3 5 /// "#; - /// let matrix_error = load_coo_from_matrix_market_str::(str); - /// assert_eq!(matrix_error.is_err(), true); - /// assert_eq!(matrix_error.unwrap_err().kind(),MatrixMarketErrorKind::NotSquareMatrix); + /// let matrix_result = load_coo_from_matrix_market_str::(str); + /// assert_eq!(matrix_result.is_err(), true); + /// assert_eq!(matrix_result.unwrap_err().kind(),MatrixMarketErrorKind::NonSquare); /// ``` - NotSquareMatrix, + NonSquare, } impl MatrixMarketError { @@ -239,7 +244,7 @@ impl MatrixMarketError { } } - /// The operation error kind. + /// The matrix market error kind. #[must_use] pub fn kind(&self) -> MatrixMarketErrorKind { self.error_kind @@ -254,7 +259,7 @@ impl MatrixMarketError { impl fmt::Display for MatrixMarketError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Matrix Market load error: ")?; + write!(f, "Matrix Market error: ")?; match self.kind() { MatrixMarketErrorKind::ParsingError => { write!(f, "ParsingError,")?; @@ -262,11 +267,11 @@ impl fmt::Display for MatrixMarketError { MatrixMarketErrorKind::InvalidHeader => { write!(f, "InvalidHeader,")?; } - MatrixMarketErrorKind::EntryNumUnmatched => { + MatrixMarketErrorKind::EntryMismatch => { write!(f, "EntryNumUnmatched,")?; } - MatrixMarketErrorKind::TypeUnmatched => { - write!(f, "TypeUnmatched,")?; + MatrixMarketErrorKind::TypeMismatch => { + write!(f, "TypeMismatch,")?; } MatrixMarketErrorKind::SparseFormatError(_) => { write!(f, "SparseFormatError,")?; @@ -283,11 +288,11 @@ impl fmt::Display for MatrixMarketError { MatrixMarketErrorKind::NotLowerTriangle => { write!(f, "NotLowerTriangle,")?; } - MatrixMarketErrorKind::NotSquareMatrix => { + MatrixMarketErrorKind::NonSquare => { write!(f, "NotSquareMatrix,")?; } } - write!(f, " Message: {}", self.message) + write!(f, " message: {}", self.message) } } @@ -334,9 +339,9 @@ impl From for MatrixMarketError { impl From for MatrixMarketError { fn from(err: TryFromIntError) -> Self { Self::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!( - "Please consider using a larger integery type. Error message: {}", + "Please consider using a larger integer type. Error message: {}", &err ), ) @@ -348,7 +353,7 @@ impl From for MatrixMarketError { impl From for MatrixMarketError { fn from(_err: Infallible) -> Self { Self::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("This won't happen"), ) } @@ -479,9 +484,9 @@ fn typecode_precheck(tc: &Typecode) -> Result<(), MatrixMarketError> { } } -/// Base trait for matrix market types. +/// Scalar types supported by the matrix market parser. pub trait MatrixMarketScalar: Scalar { - /// When matrix is an Integer matrix, it will convert a [i128] number to this type. + /// When the matrix is an integer matrix, it will convert a [i128] number to this type. fn from_i128(i: i128) -> Result; /// When matrix is a Real matrix, it will convert a [f64] number to this type. fn from_f64(f: f64) -> Result; @@ -494,6 +499,7 @@ pub trait MatrixMarketScalar: Scalar { /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. fn conjugate(self) -> Result; } + /// Implement MatrixMarketScalar for primitive integer types. macro_rules! mm_int_impl { ($T:ty) => { @@ -505,28 +511,28 @@ macro_rules! mm_int_impl { #[inline] fn from_f64(_f: f64) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Int type can't be parsed from f64"), )) } #[inline] fn from_c64(_c: Complex) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Int type can't be parsed from Complex"), )) } #[inline] fn from_pattern(_p: ()) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Int type can't be parsed from ()"), )) } #[inline] fn conjugate(self) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Int type has no conjugate"), )) } @@ -544,7 +550,7 @@ macro_rules! mm_real_impl { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("real type can't be parsed from i128"), )) } @@ -555,21 +561,21 @@ macro_rules! mm_real_impl { #[inline] fn from_c64(_c: Complex) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("real type can't be parsed from Complex"), )) } #[inline] fn from_pattern(_p: ()) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("real type can't be parsed from ()"), )) } #[inline] fn conjugate(self) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("real type has no conjugate"), )) } @@ -580,6 +586,7 @@ macro_rules! mm_real_impl { } }; } + /// Implement MatrixMarketScalar for primitive complex types. macro_rules! mm_complex_impl { ($T:ty) => { @@ -587,14 +594,14 @@ macro_rules! mm_complex_impl { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Complex type can't be parsed from i128"), )) } #[inline] fn from_f64(_f: f64) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Complex type can't be parsed from f64"), )) } @@ -608,7 +615,7 @@ macro_rules! mm_complex_impl { #[inline] fn from_pattern(_p: ()) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Complex type can't be parsed from ()"), )) } @@ -630,21 +637,21 @@ macro_rules! mm_pattern_impl { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Pattern type can't be parsed from i128"), )) } #[inline] fn from_f64(_f: f64) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Pattern type can't be parsed from f64"), )) } #[inline] fn from_c64(_c: Complex) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Pattern type can't be parsed from Complex"), )) } @@ -656,14 +663,14 @@ macro_rules! mm_pattern_impl { #[inline] fn conjugate(self) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Pattern type has no conjugate"), )) } #[inline] fn negative(self) -> Result { Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::TypeUnmatched, + MatrixMarketErrorKind::TypeMismatch, format!("Pattern type has no negative"), )) } @@ -687,16 +694,31 @@ mm_pattern_impl!(()); #[derive(Parser)] #[grammar = "io/matrix_market.pest"] -struct MMParser; +struct MatrixMarketParser; -/// Parses a Matrix Market file at the given path, and returns the corresponding sparse matrix as CooMatrix format. +/// Parses a Matrix Market file at the given path as a `CooMatrix`. +/// +/// The matrix market format specification does not clarify whether duplicate entries are allowed. Our importer +/// assumes that this is permitted and produces a `CooMatrix` with possibly duplicate entries. +/// +/// **Note**: A current restriction of the importer is that you must use a compatible scalar type when importing. +/// For example, in order to import a matrix stored as `integer` in the matrix market format, you must +/// import it as an integer matrix, otherwise a [TypeMismatch](MatrixMarketErrorKind::TypeMismatch) error +/// will be returned. This restriction may be lifted in the future, and is +/// tracked by issue [#1038](https://github.com/dimforge/nalgebra/issues/1038). /// /// Errors /// -------- /// /// See [MatrixMarketErrorKind] for a list of possible error conditions. /// -/// > _NOTE:_ Here uses strong type requirements, which means if the matrix is an integer matrix, e.g. `%%matrixmarket matrix cooridnate integer general`, then you have to load it by `load_coo_from_matrix_market_file`, where T is an integer type. Trying `load_coo_from_matrix_market_file` will give [TypeUnmatched](MatrixMarketErrorKind::TypeUnmatched) Error. After loading it, you can cast it into a `f64` matrix, by calling [cast](`nalgebra::base::Matrix::cast()`), but be aware of accuracy lose. +/// Examples +/// -------- +/// ``` +/// use nalgebra_sparse::io::load_coo_from_matrix_market_file; +/// // Use e.g. `f64` for floating-point matrices +/// let matrix = load_coo_from_matrix_market_file::("path/to/matrix.mtx")?; +/// ``` pub fn load_coo_from_matrix_market_file>( path: P, ) -> Result, MatrixMarketError> @@ -707,21 +729,34 @@ where load_coo_from_matrix_market_str(&file) } -/// Parses a Matrix Market file described by the given string, and returns the corresponding as CooMatrix format. +/// Parses a Matrix Market file described by the given string as a `CooMatrix`. +/// +/// See [load_coo_from_matrix_market_file] for more information. /// /// Errors /// -------- /// /// See [MatrixMarketErrorKind] for a list of possible error conditions. /// -/// > _NOTE:_ Here uses strong type requirements, which means if the matrix is an integer matrix, e.g. `%%matrixmarket matrix cooridnate integer general`, then you have to load it by `load_coo_from_matrix_market_str`, where T is an integer type. Trying `load_coo_from_matrix_market_str` will give [TypeUnmatched](MatrixMarketErrorKind::TypeUnmatched) Error. After loading it, you can cast it into a `f64` matrix, by calling [cast](`nalgebra::base::Matrix::cast()`),but be aware of accuracy lose. - +/// Examples +/// -------- +/// ``` +/// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; +/// # use nalgebra_sparse::io::MatrixMarketErrorKind; +/// let str = r#" +/// %%matrixmarket matrix coordinate integer symmetric +/// 5 4 2 +/// 1 1 10 +/// 2 3 5 +/// "#; +/// let matrix = load_coo_from_matrix_market_str::(str)?; +/// ``` pub fn load_coo_from_matrix_market_str(data: &str) -> Result, MatrixMarketError> where T: MatrixMarketScalar, { // unwrap() in this function are guaranteed by parsing the data - let file = MMParser::parse(Rule::Document, data)?.next().unwrap(); + let file = MatrixMarketParser::parse(Rule::Document, data)?.next().unwrap(); let mut rows: Vec = Vec::new(); let mut cols: Vec = Vec::new(); @@ -755,7 +790,7 @@ where let count = lines.clone().count(); if count != shape.2 { return Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::EntryNumUnmatched, + MatrixMarketErrorKind::EntryMismatch, format!( "{} entries required for the matrix, but {} was provided", shape.2, count, @@ -996,7 +1031,7 @@ fn parse_sparse_shape( // check for square matirx, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { - return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NotSquareMatrix,format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}",r,c))); + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NonSquare, format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}", r, c))); } Ok((r, c, nnz)) @@ -1032,7 +1067,7 @@ fn parse_dense_shape( // check for square matirx, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { - return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NotSquareMatrix,format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}",r,c))); + return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NonSquare, format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}", r, c))); } let n: usize; diff --git a/nalgebra-sparse/src/io/mod.rs b/nalgebra-sparse/src/io/mod.rs index 2d74a35c..89b21ffb 100644 --- a/nalgebra-sparse/src/io/mod.rs +++ b/nalgebra-sparse/src/io/mod.rs @@ -1,7 +1,35 @@ -//! Parsers for various matrix formats. +//! Functionality for importing and exporting sparse matrices to and from files. //! -//! ## 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. +//! **Available only when the `io` feature is enabled.** +//! +//! The following formats are currently supported: +//! +//! | Format | Import | Export | +//! | ------------------------------------------------|------------|------------| +//! | [Matrix market](#matrix-market-format) | Yes | No | +//! +//! [Matrix market]: https://math.nist.gov/MatrixMarket/formats.html +//! +//! ## Matrix Market format +//! +//! The Matrix Market format is a simple ASCII-based file format for sparse matrices, and was initially developed for +//! the [NIST Matrix Market](https://math.nist.gov/MatrixMarket/), a repository of example sparse matrices. +//! In later years it has largely been superseded by the +//! [SuiteSparse Matrix Collection](https://sparse.tamu.edu/) (formerly University of Florida Sparse Matrix Collection), +//! 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). +//! +//! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html) +//! on the Matrix Market website and the +//! [following NIST whitepaper](https://math.nist.gov/MatrixMarket/reports/MMformat.ps): +//! +//! > Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington.
+//! > "*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, From 1b73b2f9919e54cdaf870061fdc50c1c61a3d582 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:07:47 +0100 Subject: [PATCH 05/15] Link to matrix market IO in lib.rs --- nalgebra-sparse/Cargo.toml | 2 ++ nalgebra-sparse/src/lib.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index b85fea2c..7692984e 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -15,6 +15,8 @@ license = "Apache-2.0" [features] proptest-support = ["proptest", "nalgebra/proptest-support"] compare = [ "matrixcompare-core" ] + +# Enable matrix market I/O io = [ "pest", "pest_derive" ] # Enable to enable running some tests that take a lot of time to run diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index a588787f..edbf83bd 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -19,6 +19,7 @@ //! - Sparsity patterns in CSR and CSC matrices are explicitly represented by the //! [SparsityPattern](pattern::SparsityPattern) type, which encodes the invariants of the //! associated index data structures. +//! - [Matrix market format support](`io`) when the `io` feature is enabled. //! - [proptest strategies](`proptest`) for sparse matrices when the feature //! `proptest-support` is enabled. //! - [matrixcompare support](https://crates.io/crates/matrixcompare) for effortless From 4c039573f2c1e698cc06824662c4b1c580758d70 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:26:51 +0100 Subject: [PATCH 06/15] Make nalgebra-sparse unit tests require io feature --- nalgebra-sparse/tests/unit.rs | 11 +++++++++-- nalgebra-sparse/tests/unit_tests/mod.rs | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/nalgebra-sparse/tests/unit.rs b/nalgebra-sparse/tests/unit.rs index 73a95cd7..1e61467c 100644 --- a/nalgebra-sparse/tests/unit.rs +++ b/nalgebra-sparse/tests/unit.rs @@ -1,6 +1,13 @@ //! Unit tests -#[cfg(any(not(feature = "proptest-support"), not(feature = "compare")))] -compile_error!("Tests must be run with features `proptest-support` and `compare`"); +#[cfg(not(all( +feature = "proptest-support", +feature = "compare", +feature = "io", +)))] +compile_error!( + "Please enable the `proptest-support`, `compare` and `io` features in order to compile and run the tests. + Example: `cargo test -p nalgebra-sparse --features proptest-support,compare,io`" +); mod unit_tests; diff --git a/nalgebra-sparse/tests/unit_tests/mod.rs b/nalgebra-sparse/tests/unit_tests/mod.rs index e97731e1..7090a493 100644 --- a/nalgebra-sparse/tests/unit_tests/mod.rs +++ b/nalgebra-sparse/tests/unit_tests/mod.rs @@ -3,7 +3,6 @@ mod convert_serial; mod coo; mod csc; mod csr; -#[cfg(feature = "io")] mod matrix_market; mod ops; mod pattern; From 4d0f401882648cceea0bf6e0ea88f496cafe0bfd Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:27:03 +0100 Subject: [PATCH 07/15] Add (failing) test for empty matrix market matrix --- .../tests/unit_tests/matrix_market.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 316ac02f..f0a9d217 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -4,6 +4,24 @@ use nalgebra::Complex; use nalgebra_sparse::io::load_coo_from_matrix_market_str; use nalgebra_sparse::CooMatrix; +#[test] +#[rustfmt::skip] +fn test_matrixmarket_sparse_real_general_empty() { + // Test several valid zero-shapes of a matrix + let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; + let strings: Vec = shapes + .into_iter() + .map(|(m, n)| format!("%%MatrixMarket matrix coordinate real general\n {} {} 0", m, n)) + .collect(); + + for string in &strings { + let sparse_mat = load_coo_from_matrix_market_str::(string).unwrap(); + assert_eq!(sparse_mat.nrows(), 0); + assert_eq!(sparse_mat.ncols(), 0); + assert_eq!(sparse_mat.nnz(), 0); + } +} + #[test] #[rustfmt::skip] fn test_matrixmarket_sparse_real_general() { From 93f3d600050f458298a6e89b0ccd17804a5658b5 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:33:03 +0100 Subject: [PATCH 08/15] Remove From for MatrixMarketError We want pest to remain an internal implementation detail, so it should not leak into the public API. --- nalgebra-sparse/src/io/matrix_market.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 518675cd..dd823bc9 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -298,13 +298,14 @@ impl fmt::Display for MatrixMarketError { impl std::error::Error for MatrixMarketError {} -impl From> - for MatrixMarketError -{ - fn from(err: pest::error::Error) -> Self { +impl MatrixMarketError { + fn from_pest_error(error: pest::error::Error) -> Self + where + T: fmt::Debug + std::hash::Hash + std::marker::Copy + Ord + { Self::from_kind_and_message( MatrixMarketErrorKind::ParsingError, - format!("Can't parse the data.\n Error: {}", err), + format!("Can't parse the data.\n Error: {}", error), ) } } @@ -756,7 +757,9 @@ where T: MatrixMarketScalar, { // unwrap() in this function are guaranteed by parsing the data - let file = MatrixMarketParser::parse(Rule::Document, data)?.next().unwrap(); + let file = MatrixMarketParser::parse(Rule::Document, data) + .map_err(MatrixMarketError::from_pest_error)? + .next().unwrap(); let mut rows: Vec = Vec::new(); let mut cols: Vec = Vec::new(); From e3d1119bffba0c6cd9de66ab6228566a6ace2d4b Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 11:45:06 +0100 Subject: [PATCH 09/15] Hide MatrixMarketScalar implementation details for now The existing MatrixMarketScalar is relatively closely tied to the way oru parser is implemented. I've moved these internals into an internal trait and sealed the public trait. This makes it less flexible for users for now, but gives us more freedom to change internals in the future. --- nalgebra-sparse/src/io/matrix_market.rs | 54 ++++++++++++++++--------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index dd823bc9..19434eb6 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -4,7 +4,6 @@ use crate::coo::CooMatrix; use crate::SparseFormatError; use crate::SparseFormatErrorKind; -use nalgebra::base::Scalar; use nalgebra::Complex; use pest::iterators::Pairs; use pest::Parser; @@ -486,25 +485,38 @@ fn typecode_precheck(tc: &Typecode) -> Result<(), MatrixMarketError> { } /// Scalar types supported by the matrix market parser. -pub trait MatrixMarketScalar: Scalar { - /// When the matrix is an integer matrix, it will convert a [i128] number to this type. - fn from_i128(i: i128) -> Result; - /// When matrix is a Real matrix, it will convert a [f64] number to this type. - fn from_f64(f: f64) -> Result; - /// When matrix is a Complx matrix, it will convert a [Complex] number to this type. - fn from_c64(c: Complex) -> Result; - /// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type. - fn from_pattern(p: ()) -> Result; - /// When matrix is a Skew-symmetric matrix, it will convert itself to its negative. - fn negative(self) -> Result; - /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. - fn conjugate(self) -> Result; +mod internal { + use crate::io::MatrixMarketError; + use na::{Complex, Scalar}; + + pub trait SupportedMatrixMarketScalar: Scalar { + /// When the matrix is an integer matrix, it will convert a [i128] number to this type. + fn from_i128(i: i128) -> Result; + /// When matrix is a Real matrix, it will convert a [f64] number to this type. + fn from_f64(f: f64) -> Result; + /// When matrix is a Complx matrix, it will convert a [Complex] number to this type. + fn from_c64(c: Complex) -> Result; + /// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type. + fn from_pattern(p: ()) -> Result; + /// When matrix is a Skew-symmetric matrix, it will convert itself to its negative. + fn negative(self) -> Result; + /// When matrix is a Hermitian matrix, it will convert itself to its conjugate. + fn conjugate(self) -> Result; + } } +/// A marker trait for supported matrix market scalars. +/// +/// 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 MatrixMarketScalar: internal::SupportedMatrixMarketScalar {} + /// Implement MatrixMarketScalar for primitive integer types. macro_rules! mm_int_impl { ($T:ty) => { - impl MatrixMarketScalar for $T { + impl MatrixMarketScalar for $T {} + + impl internal::SupportedMatrixMarketScalar for $T { #[inline] fn from_i128(i: i128) -> Result { Ok(Self::try_from(i)?) @@ -547,7 +559,9 @@ macro_rules! mm_int_impl { /// Implement MatrixMarketScalar for primitive real types. macro_rules! mm_real_impl { ($T:ty) => { - impl MatrixMarketScalar for $T { + impl MatrixMarketScalar for $T {} + + impl internal::SupportedMatrixMarketScalar for $T { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( @@ -591,7 +605,9 @@ macro_rules! mm_real_impl { /// Implement MatrixMarketScalar for primitive complex types. macro_rules! mm_complex_impl { ($T:ty) => { - impl MatrixMarketScalar for Complex<$T> { + impl MatrixMarketScalar for Complex<$T> {} + + impl internal::SupportedMatrixMarketScalar for Complex<$T> { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( @@ -634,7 +650,9 @@ macro_rules! mm_complex_impl { /// Implement MatrixMarketScalar for primitive unit types. macro_rules! mm_pattern_impl { ($T:ty) => { - impl MatrixMarketScalar for $T { + impl MatrixMarketScalar for $T {} + + impl internal::SupportedMatrixMarketScalar for $T { #[inline] fn from_i128(_i: i128) -> Result { Err(MatrixMarketError::from_kind_and_message( From 4569484aa027aaf9f5f90258f3f43c8546950115 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 12:08:42 +0100 Subject: [PATCH 10/15] Line breaks --- nalgebra-sparse/src/io/matrix_market.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 19434eb6..e65620df 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -436,7 +436,11 @@ impl FromStr for StorageScheme { /// Precheck if it's a valid header. /// -/// For more details, please check Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington. The matrix market formats: Initial design. Technical report, Applied and Computational Mathematics Division, NIST, 1996. Section 3. +/// For more details, please check +/// +/// Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington. +/// The matrix market formats: Initial design. +/// Technical report, Applied and Computational Mathematics Division, NIST, 1996. Section 3. fn typecode_precheck(tc: &Typecode) -> Result<(), MatrixMarketError> { match tc { Typecode { From e2820316a8d991c42951e92d480aaad9448e6735 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 12:17:47 +0100 Subject: [PATCH 11/15] Fix typos --- nalgebra-sparse/src/io/matrix_market.rs | 102 ++++++++++++------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index e65620df..1429d814 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -498,7 +498,7 @@ mod internal { fn from_i128(i: i128) -> Result; /// When matrix is a Real matrix, it will convert a [f64] number to this type. fn from_f64(f: f64) -> Result; - /// When matrix is a Complx matrix, it will convert a [Complex] number to this type. + /// When matrix is a Complex matrix, it will convert a [Complex] number to this type. fn from_c64(c: Complex) -> Result; /// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type. fn from_pattern(p: ()) -> Result; @@ -806,10 +806,10 @@ where // used when constructing dense matrix. // If it's sparse matrix, it has no effect. - let mut current_dense_coordiante: (usize, usize) = (0, 0); + let mut current_dense_coordinate: (usize, usize) = (0, 0); if header_type.storagescheme == StorageScheme::Skew { // for skew dense matrix, the first element starts from (1,0) - current_dense_coordiante = (1, 0); + current_dense_coordinate = (1, 0); } // count how many entries in the matrix data let count = lines.clone().count(); @@ -860,12 +860,12 @@ where .. } => { entry = ( - current_dense_coordiante.0, - current_dense_coordiante.1, + current_dense_coordinate.0, + current_dense_coordinate.1, parse_dense_complex::(&mut data_line.into_inner())?, ); - next_dense_coordiante( - &mut current_dense_coordiante, + next_dense_coordinate( + &mut current_dense_coordinate, shape, &header_type.storagescheme, ); @@ -876,13 +876,13 @@ where .. } => { entry = ( - current_dense_coordiante.0, - current_dense_coordiante.1, + current_dense_coordinate.0, + current_dense_coordinate.1, parse_dense_real::(&mut data_line.into_inner())?, ); - next_dense_coordiante( - &mut current_dense_coordiante, + next_dense_coordinate( + &mut current_dense_coordinate, shape, &header_type.storagescheme, ); @@ -893,12 +893,12 @@ where .. } => { entry = ( - current_dense_coordiante.0, - current_dense_coordiante.1, + current_dense_coordinate.0, + current_dense_coordinate.1, parse_dense_int::(&mut data_line.into_inner())?, ); - next_dense_coordiante( - &mut current_dense_coordiante, + next_dense_coordinate( + &mut current_dense_coordinate, shape, &header_type.storagescheme, ); @@ -1023,7 +1023,7 @@ fn parse_header(inner: &mut Pairs<'_, Rule>) -> Typecode { // Parse shape starts here------------------------------------------------- -/// Parse a pest structure to sparse shape information, including 3 int, which are number of rols, cols and non-zeros. +/// Parse a pest structure to sparse shape information, including 3 int, which are number of rows, cols and non-zeros. fn parse_sparse_shape( inner: &mut Pairs<'_, Rule>, storagescheme: &StorageScheme, @@ -1054,7 +1054,7 @@ fn parse_sparse_shape( )); } - // check for square matirx, when it's not a general matrix + // check for square matrix, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NonSquare, format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}", r, c))); } @@ -1062,7 +1062,7 @@ fn parse_sparse_shape( Ok((r, c, nnz)) } -/// Parse a pest structure to dense shape information, including 2 int, which are number of rols, cols. +/// Parse a pest structure to dense shape information, including 2 int, which are number of rows, cols. fn parse_dense_shape( inner: &mut Pairs<'_, Rule>, storagescheme: &StorageScheme, @@ -1090,7 +1090,7 @@ fn parse_dense_shape( )); } - // check for square matirx, when it's not a general matrix + // check for square matrix, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NonSquare, format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}", r, c))); } @@ -1121,7 +1121,7 @@ fn parse_dense_shape( // Parse entry starts here------------------------------------------------- -/// Parse a pest structure to sparse real entry, including 2 int, which are number of rols, cols, and a real number as data +/// Parse a pest structure to sparse real entry, including 2 int, which are number of rows, cols, and a real number as data fn parse_sparse_real(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T), MatrixMarketError> where T: MatrixMarketScalar, @@ -1130,17 +1130,17 @@ where let entry_inner = inner.next().unwrap(); if entry_inner.as_rule() != Rule::SparseReal { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" - Spare real matrix requires 2 int number as coordiantes and 1 real number as data, but line {} was provided. + Spare real matrix requires 2 int number as coordinates and 1 real number as data, but line {} was provided. ",entry_inner.as_str() ))); } let mut inner = entry_inner.into_inner(); - let (r, c) = parse_sparse_coordiante(&mut inner)?; + let (r, c) = parse_sparse_coordinate(&mut inner)?; let d = inner.next().unwrap().as_str().parse::().unwrap(); Ok((r, c, T::from_f64(d)?)) } -/// Parse a pest structure to sparse integer entry, including 2 int, which are number of rols, cols, and a int number as data +/// Parse a pest structure to sparse integer entry, including 2 int, which are number of rows, cols, and a int number as data fn parse_sparse_int(inner: &mut Pairs<'_, Rule>) -> Result<(usize, usize, T), MatrixMarketError> where T: MatrixMarketScalar, @@ -1153,20 +1153,20 @@ where MatrixMarketErrorKind::ParsingError, format!( " - Spare real matrix requires 3 int number as coordiantes and data, but line {} was provided. + Spare real matrix requires 3 int number as coordinates and data, but line {} was provided. ", entry_inner.as_str() ), )); } let mut inner = entry_inner.into_inner(); - let (r, c) = parse_sparse_coordiante(&mut inner)?; + let (r, c) = parse_sparse_coordinate(&mut inner)?; // Here to guarantee it is an integer number let d = inner.next().unwrap().as_str().parse::()?; Ok((r, c, T::from_i128(d)?)) } -/// Parse a pest structure to sparse pattern entry, including 2 int, which are number of rols, cols +/// Parse a pest structure to sparse pattern entry, including 2 int, which are number of rows, cols fn parse_sparse_pattern( inner: &mut Pairs<'_, Rule>, ) -> Result<(usize, usize, T), MatrixMarketError> @@ -1180,18 +1180,18 @@ where MatrixMarketErrorKind::ParsingError, format!( " - Spare real matrix requires 2 int number as coordiantes, but line {} was provided. + Spare real matrix requires 2 int number as coordinates, but line {} was provided. ", entry_inner.as_str() ), )); } let mut inner = entry_inner.into_inner(); - let (r, c) = parse_sparse_coordiante(&mut inner)?; + let (r, c) = parse_sparse_coordinate(&mut inner)?; Ok((r, c, T::from_pattern(())?)) } -/// Parse a pest structure to sparse complex entry, including 2 int, which are number of rols, cols, and 2 real number as complex data +/// Parse a pest structure to sparse complex entry, including 2 int, which are number of rows, cols, and 2 real number as complex data fn parse_sparse_complex( inner: &mut Pairs<'_, Rule>, ) -> Result<(usize, usize, T), MatrixMarketError> @@ -1202,11 +1202,11 @@ where let entry_inner = inner.next().unwrap(); if entry_inner.as_rule() != Rule::SparseComplex { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::ParsingError,format!(" - Spare real matrix requires 2 int number as coordiantes and 2 real number as complex data, but line {} was provided. + Spare real matrix requires 2 int number as coordinates and 2 real number as complex data, but line {} was provided. ",entry_inner.as_str() ))); } let mut inner = entry_inner.into_inner(); - let (r, c) = parse_sparse_coordiante(&mut inner)?; + let (r, c) = parse_sparse_coordinate(&mut inner)?; let real = inner.next().unwrap().as_str().parse::().unwrap(); let imag = inner.next().unwrap().as_str().parse::().unwrap(); let complex = Complex::::new(real, imag); @@ -1292,8 +1292,8 @@ where // Parse entry ends here------------------------------------------------- -/// Parse the coordiantes information used for sparse matrix -fn parse_sparse_coordiante( +/// Parse the coordinates information used for sparse matrix +fn parse_sparse_coordinate( inner: &mut Pairs<'_, Rule>, ) -> Result<(usize, usize), MatrixMarketError> { // unwrap() in this function are guaranteed by parsing the data @@ -1302,51 +1302,51 @@ fn parse_sparse_coordiante( if r * c == 0 { return Err(MatrixMarketError::from_kind_and_message( MatrixMarketErrorKind::ZeroError, - String::from("The data has to be one-indixed"), + String::from("The data has to be one-indexed"), )); } - // The coordiantes in matrix market is one-based, but in CooMatrix is zero-based. + // The coordinates in matrix market is one-based, but in CooMatrix is zero-based. Ok((r - 1, c - 1)) } -/// Calculate the next coordiantes used for dense matrix -fn next_dense_coordiante( - current_dense_coordiante: &mut (usize, usize), +/// Calculate the next coordinates used for dense matrix +fn next_dense_coordinate( + current_dense_coordinate: &mut (usize, usize), shape: (usize, usize, usize), storagescheme: &StorageScheme, ) { // matrix market is column based format. // so it follows the order (0,0) -> (1,0) -> ... -> (row, 0) -> (0,1) -> ... ->(row,col) - // current_dense_coordiante is (row, column) + // current_dense_coordinate is (row, column) match storagescheme { StorageScheme::General => { - if current_dense_coordiante.0 < shape.0 - 1 { - current_dense_coordiante.0 += 1 + if current_dense_coordinate.0 < shape.0 - 1 { + current_dense_coordinate.0 += 1 } else { // jump to next column, reset row to 1, column add 1 - current_dense_coordiante.0 = 0; - current_dense_coordiante.1 += 1; + current_dense_coordinate.0 = 0; + current_dense_coordinate.1 += 1; } } StorageScheme::Symmetric | StorageScheme::Hermitian => { - if current_dense_coordiante.0 < shape.0 - 1 { - current_dense_coordiante.0 += 1 + if current_dense_coordinate.0 < shape.0 - 1 { + current_dense_coordinate.0 += 1 } else { // jump to next column, column add 1, then set row equals to current column // for example (0,0) -> (1,0) -> ... -> (row, 0) -> (1,1) -> ... - current_dense_coordiante.1 += 1; - current_dense_coordiante.0 = current_dense_coordiante.1; + current_dense_coordinate.1 += 1; + current_dense_coordinate.0 = current_dense_coordinate.1; } } StorageScheme::Skew => { - if current_dense_coordiante.0 < shape.0 - 1 { - current_dense_coordiante.0 += 1; + if current_dense_coordinate.0 < shape.0 - 1 { + current_dense_coordinate.0 += 1; } else { // jump to next column, set row equals to current column, then column add 1 // skew matrix doesn't have element on diagonal // for example (1,0) -> (2,0) -> ... -> (row, 0) -> (2,1) -> ... - current_dense_coordiante.1 += 1; - current_dense_coordiante.0 = current_dense_coordiante.1 + 1; + current_dense_coordinate.1 += 1; + current_dense_coordinate.0 = current_dense_coordinate.1 + 1; } } } From 9ddd09017dc82ecfb4d1327ac71bb82ef5b0579f Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Wed, 1 Dec 2021 12:18:45 +0100 Subject: [PATCH 12/15] Update add IO feature to CI config for nalgebra-sparse --- .github/workflows/nalgebra-ci-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nalgebra-ci-build.yml b/.github/workflows/nalgebra-ci-build.yml index fd3ec273..b553deaf 100644 --- a/.github/workflows/nalgebra-ci-build.yml +++ b/.github/workflows/nalgebra-ci-build.yml @@ -65,10 +65,10 @@ jobs: - 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 + run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io - 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,slow-tests slow + run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,slow-tests slow test-nalgebra-macros: runs-on: ubuntu-latest steps: From 92b324c007a3184ed9cbf3b3fd9554df93316166 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Sun, 12 Dec 2021 12:10:20 +0100 Subject: [PATCH 13/15] code fmt; fix failing unit test and doc test --- nalgebra-sparse/src/io/matrix_market.rs | 30 ++++++++----------- nalgebra-sparse/tests/unit.rs | 6 +--- .../tests/unit_tests/matrix_market.rs | 8 ++--- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 1429d814..2620eea6 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -300,7 +300,7 @@ impl std::error::Error for MatrixMarketError {} impl MatrixMarketError { fn from_pest_error(error: pest::error::Error) -> Self where - T: fmt::Debug + std::hash::Hash + std::marker::Copy + Ord + T: fmt::Debug + std::hash::Hash + std::marker::Copy + Ord, { Self::from_kind_and_message( MatrixMarketErrorKind::ParsingError, @@ -739,8 +739,10 @@ struct MatrixMarketParser; /// -------- /// ``` /// use nalgebra_sparse::io::load_coo_from_matrix_market_file; -/// // Use e.g. `f64` for floating-point matrices -/// let matrix = load_coo_from_matrix_market_file::("path/to/matrix.mtx")?; +/// // Use e.g. `i32` for integer matrices +/// let matrix = load_coo_from_matrix_market_file::("path/to/matrix.mtx"); +/// // extract the real matrix here by +/// // let matrix = matrix.unwrap(); /// ``` pub fn load_coo_from_matrix_market_file>( path: P, @@ -767,12 +769,15 @@ where /// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; /// # use nalgebra_sparse::io::MatrixMarketErrorKind; /// let str = r#" -/// %%matrixmarket matrix coordinate integer symmetric +/// %%matrixmarket matrix coordinate integer general /// 5 4 2 /// 1 1 10 /// 2 3 5 /// "#; -/// let matrix = load_coo_from_matrix_market_str::(str)?; +/// // Use e.g. `i32` for integer matrices +/// let matrix = load_coo_from_matrix_market_str::(str); +/// // extract the real matrix here by +/// // let matrix = matrix.unwrap(); /// ``` pub fn load_coo_from_matrix_market_str(data: &str) -> Result, MatrixMarketError> where @@ -781,7 +786,8 @@ where // unwrap() in this function are guaranteed by parsing the data let file = MatrixMarketParser::parse(Rule::Document, data) .map_err(MatrixMarketError::from_pest_error)? - .next().unwrap(); + .next() + .unwrap(); let mut rows: Vec = Vec::new(); let mut cols: Vec = Vec::new(); @@ -1042,18 +1048,6 @@ fn parse_sparse_shape( let c = inner.next().unwrap().as_str().parse::().unwrap(); let nnz = inner.next().unwrap().as_str().parse::().unwrap(); - // shape information can't use 0 as dimension - if r * c * nnz == 0 { - return Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::ZeroError, - String::from( - " - Matrix can't have 0 as shape dimensions. - ", - ), - )); - } - // check for square matrix, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { return Err(MatrixMarketError::from_kind_and_message(MatrixMarketErrorKind::NonSquare, format!("(Skew-)Symmetric or hermitian matrix should be square matrix, but it has dimension {} and {}", r, c))); diff --git a/nalgebra-sparse/tests/unit.rs b/nalgebra-sparse/tests/unit.rs index 1e61467c..74d32a40 100644 --- a/nalgebra-sparse/tests/unit.rs +++ b/nalgebra-sparse/tests/unit.rs @@ -1,9 +1,5 @@ //! Unit tests -#[cfg(not(all( -feature = "proptest-support", -feature = "compare", -feature = "io", -)))] +#[cfg(not(all(feature = "proptest-support", feature = "compare", feature = "io",)))] compile_error!( "Please enable the `proptest-support`, `compare` and `io` features in order to compile and run the tests. Example: `cargo test -p nalgebra-sparse --features proptest-support,compare,io`" diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index f0a9d217..58db6e02 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -10,14 +10,14 @@ fn test_matrixmarket_sparse_real_general_empty() { // Test several valid zero-shapes of a matrix let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let strings: Vec = shapes - .into_iter() + .iter() .map(|(m, n)| format!("%%MatrixMarket matrix coordinate real general\n {} {} 0", m, n)) .collect(); - for string in &strings { + for (shape,string) in shapes.iter().zip(strings.iter()) { let sparse_mat = load_coo_from_matrix_market_str::(string).unwrap(); - assert_eq!(sparse_mat.nrows(), 0); - assert_eq!(sparse_mat.ncols(), 0); + assert_eq!(sparse_mat.nrows(), shape.0); + assert_eq!(sparse_mat.ncols(), shape.1); assert_eq!(sparse_mat.nnz(), 0); } } From 656180f40ea7670cb3242859fa1c6ad77ddd694e Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Mon, 13 Dec 2021 09:26:54 +0100 Subject: [PATCH 14/15] fix for empty dense matrix --- nalgebra-sparse/src/io/matrix_market.rs | 11 ---------- .../tests/unit_tests/matrix_market.rs | 20 ++++++++++++++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 2620eea6..446f045d 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -1072,17 +1072,6 @@ fn parse_dense_shape( let mut inner = shape_inner.into_inner(); let r = inner.next().unwrap().as_str().parse::().unwrap(); let c = inner.next().unwrap().as_str().parse::().unwrap(); - // shape information can't use 0 as dimension - if r * c == 0 { - return Err(MatrixMarketError::from_kind_and_message( - MatrixMarketErrorKind::ZeroError, - String::from( - " - Matrix can't have 0 as shape dimensions. - ", - ), - )); - } // check for square matrix, when it's not a general matrix if *storagescheme != StorageScheme::General && r != c { diff --git a/nalgebra-sparse/tests/unit_tests/matrix_market.rs b/nalgebra-sparse/tests/unit_tests/matrix_market.rs index 58db6e02..48ff1a78 100644 --- a/nalgebra-sparse/tests/unit_tests/matrix_market.rs +++ b/nalgebra-sparse/tests/unit_tests/matrix_market.rs @@ -7,7 +7,7 @@ use nalgebra_sparse::CooMatrix; #[test] #[rustfmt::skip] fn test_matrixmarket_sparse_real_general_empty() { - // Test several valid zero-shapes of a matrix + // Test several valid zero-shapes of a sparse matrix let shapes = vec![ (0, 0), (1, 0), (0, 1) ]; let strings: Vec = shapes .iter() @@ -22,6 +22,24 @@ fn test_matrixmarket_sparse_real_general_empty() { } } +#[test] +#[rustfmt::skip] +fn test_matrixmarket_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 + .iter() + .map(|(m, n)| format!("%%MatrixMarket matrix array real general\n {} {}", m, n)) + .collect(); + + for (shape,string) in shapes.iter().zip(strings.iter()) { + let sparse_mat = load_coo_from_matrix_market_str::(string).unwrap(); + assert_eq!(sparse_mat.nrows(), shape.0); + assert_eq!(sparse_mat.ncols(), shape.1); + assert_eq!(sparse_mat.nnz(), 0); + } +} + #[test] #[rustfmt::skip] fn test_matrixmarket_sparse_real_general() { From 96b65c430feec0ffe0e64dcfdfe5bf6e3d0710a5 Mon Sep 17 00:00:00 2001 From: Hantao Hui Date: Fri, 17 Dec 2021 14:48:14 +0100 Subject: [PATCH 15/15] using no_run in doc code example --- nalgebra-sparse/src/io/matrix_market.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/nalgebra-sparse/src/io/matrix_market.rs b/nalgebra-sparse/src/io/matrix_market.rs index 446f045d..dea284ee 100644 --- a/nalgebra-sparse/src/io/matrix_market.rs +++ b/nalgebra-sparse/src/io/matrix_market.rs @@ -226,7 +226,7 @@ pub enum MatrixMarketErrorKind { /// %%matrixmarket matrix coordinate integer symmetric /// 5 4 2 /// 1 1 10 - /// 2 3 5 + /// 3 2 5 /// "#; /// let matrix_result = load_coo_from_matrix_market_str::(str); /// assert_eq!(matrix_result.is_err(), true); @@ -737,12 +737,10 @@ struct MatrixMarketParser; /// /// Examples /// -------- -/// ``` +/// ```no_run /// use nalgebra_sparse::io::load_coo_from_matrix_market_file; /// // Use e.g. `i32` for integer matrices -/// let matrix = load_coo_from_matrix_market_file::("path/to/matrix.mtx"); -/// // extract the real matrix here by -/// // let matrix = matrix.unwrap(); +/// let matrix = load_coo_from_matrix_market_file::("path/to/matrix.mtx").unwrap(); /// ``` pub fn load_coo_from_matrix_market_file>( path: P, @@ -766,8 +764,7 @@ where /// Examples /// -------- /// ``` -/// # use nalgebra_sparse::io::load_coo_from_matrix_market_str; -/// # use nalgebra_sparse::io::MatrixMarketErrorKind; +/// use nalgebra_sparse::io::load_coo_from_matrix_market_str; /// let str = r#" /// %%matrixmarket matrix coordinate integer general /// 5 4 2 @@ -775,9 +772,7 @@ where /// 2 3 5 /// "#; /// // Use e.g. `i32` for integer matrices -/// let matrix = load_coo_from_matrix_market_str::(str); -/// // extract the real matrix here by -/// // let matrix = matrix.unwrap(); +/// let matrix = load_coo_from_matrix_market_str::(str).unwrap(); /// ``` pub fn load_coo_from_matrix_market_str(data: &str) -> Result, MatrixMarketError> where