added reading matrix market data to sparse coomatrix
This commit is contained in:
parent
8ea8ac70d5
commit
7f9128c92d
|
@ -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"
|
||||
|
|
|
@ -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?)*
|
||||
}
|
|
@ -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::<f64>(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::<f64>(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::<f64>(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::<i32>(str), or any other integer type;
|
||||
/// 3 3 1
|
||||
/// 2 2 2
|
||||
/// 2 3 2
|
||||
/// "#;
|
||||
/// let matrix_error = load_coo_from_mm_str::<f64>(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::<f64>(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::<f64>(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::<f64>(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::<Complex<f64>>(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::<f64,_>(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::<i32>(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<T: fmt::Debug + std::hash::Hash + std::marker::Copy + Ord> From<pest::error::Error<T>>
|
||||
for MMError
|
||||
{
|
||||
fn from(err: pest::error::Error<T>) -> Self {
|
||||
Self::from_kind_and_message(
|
||||
MMErrorKind::ParsingError,
|
||||
format!("Can't parse the data.\n Error: {}", err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> 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<SparseFormatError> for MMError {
|
||||
fn from(err: SparseFormatError) -> Self {
|
||||
Self::from_kind_and_message(
|
||||
MMErrorKind::SparseFormatError(*err.kind()),
|
||||
format!("{}", &err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> 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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<Self, MMError>;
|
||||
/// When matrix is a Real matrix, it will convert a [f64] number to this type.
|
||||
fn from_f64(f: f64) -> Result<Self, MMError>;
|
||||
/// When matrix is a Complx matrix, it will convert a [Complex<f64>] number to this type.
|
||||
fn from_c64(c: Complex<f64>) -> Result<Self, MMError>;
|
||||
/// When matrix is a Pattern matrix, it will convert a unit type [unit] to this type.
|
||||
fn from_pattern(p: ()) -> Result<Self, MMError>;
|
||||
/// When matrix is a Skew-symmetric matrix, it will convert itself to its negative.
|
||||
fn negative(self) -> Result<Self, MMError>;
|
||||
/// When matrix is a Hermitian matrix, it will convert itself to its conjugate.
|
||||
fn conjugate(self) -> Result<Self, MMError>;
|
||||
}
|
||||
/// Implement MMType for primitive integer types.
|
||||
macro_rules! mm_int_impl {
|
||||
($T:ty) => {
|
||||
impl MMType for $T {
|
||||
#[inline]
|
||||
fn from_i128(i: i128) -> Result<Self, MMError> {
|
||||
Ok(i as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(_f: f64) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Int type can't parse from f64"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_c64(_c: Complex<f64>) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Int type can't parse from Complex<f64>"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_pattern(_p: ()) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Int type can't parse from ()"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn conjugate(self) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Int type has no conjugate"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn negative(self) -> Result<Self, MMError>
|
||||
where
|
||||
Self: Neg<Output = Self>,
|
||||
{
|
||||
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<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Float type can't parse from i128"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(f: f64) -> Result<Self, MMError> {
|
||||
Ok(f as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn from_c64(_c: Complex<f64>) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Float type can't parse from Complex<f64>"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_pattern(_p: ()) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Float type can't parse from ()"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn conjugate(self) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Float type has no conjugate"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn negative(self) -> Result<Self, MMError>
|
||||
where
|
||||
Self: Neg<Output = Self>,
|
||||
{
|
||||
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<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Complex type can't parse from i128"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(_f: f64) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Complex type can't parse from f64"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_c64(c: Complex<f64>) -> Result<Self, MMError> {
|
||||
Ok(Self {
|
||||
re: c.re as $T,
|
||||
im: c.im as $T,
|
||||
})
|
||||
}
|
||||
#[inline]
|
||||
fn from_pattern(_p: ()) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Complex type can't parse from ()"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn conjugate(self) -> Result<Self, MMError> {
|
||||
Ok(self.conj())
|
||||
}
|
||||
#[inline]
|
||||
fn negative(self) -> Result<Self, MMError>
|
||||
where
|
||||
Self: Neg<Output = Self>,
|
||||
{
|
||||
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<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Pattern type can't parse from i128"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(_f: f64) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Pattern type can't parse from f64"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_c64(_c: Complex<f64>) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Pattern type can't parse from Complex<f64>"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn from_pattern(p: ()) -> Result<Self, MMError> {
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn conjugate(self) -> Result<Self, MMError> {
|
||||
Err(MMError::from_kind_and_message(
|
||||
MMErrorKind::TypeUnmatched,
|
||||
format!("Pattern type has no conjugate"),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
fn negative(self) -> Result<Self, MMError> {
|
||||
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::<Sparsity>()
|
||||
.unwrap();
|
||||
header_type.dt = inner
|
||||
.next()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_ascii_lowercase()
|
||||
.parse::<DataType>()
|
||||
.unwrap();
|
||||
header_type.ss = inner
|
||||
.next()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_ascii_lowercase()
|
||||
.parse::<StorageScheme>()
|
||||
.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::<usize>().unwrap();
|
||||
shape.1 = inner.next().unwrap().as_str().parse::<usize>().unwrap();
|
||||
shape.2 = inner.next().unwrap().as_str().parse::<usize>().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<T, P: AsRef<Path>>(path: P) -> Result<CooMatrix<T>, 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<T>(data: &str) -> Result<CooMatrix<T>, 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<usize> = Vec::new();
|
||||
let mut cols: Vec<usize> = Vec::new();
|
||||
let mut data: Vec<T> = 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::<usize>().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::<usize>().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::<i128>()?;
|
||||
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::<f64>().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::<f64>().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::<f64>().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::<f64>::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,
|
||||
)?)
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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::<f32>(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::<i32>(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::<Complex<f64>>(file_str).unwrap();
|
||||
let expected = dmatrix![
|
||||
Complex::<f64>{re:1.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0},Complex::<f64>{re:0.0,im:0.0};
|
||||
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:10.5,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:-22.22},Complex::<f64>{re:0.0,im:0.0};
|
||||
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.015,im:0.0}, Complex::<f64>{re:0.0,im:0.0},Complex::<f64>{re:0.0,im:0.0};
|
||||
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:22.22}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:-280.0,im:0.0},Complex::<f64>{re:0.0,im:-33.32};
|
||||
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:33.32},Complex::<f64>{re:12.0,im:0.0};
|
||||
];
|
||||
assert_matrix_eq!(sparse_mat, expected);
|
||||
}
|
||||
|
||||
#[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::<f64>(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);
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue