sprs_rssn/
io.rs

1//! Serialization and deserialization of sparse matrices
2
3use std::error::Error;
4use std::fmt;
5use std::fs::File;
6use std::io;
7use std::io::{Seek, SeekFrom, Write};
8use std::ops::Neg;
9use std::path::Path;
10
11use crate::indexing::SpIndex;
12use crate::num_kinds::{NumKind, PrimitiveKind};
13use crate::num_matrixmarket::*;
14use crate::sparse::{SparseMat, TriMatI};
15
16#[derive(Debug)]
17pub enum IoError {
18    Io(io::Error),
19    BadMatrixMarketFile,
20    MismatchedMatrixMarketRead(NumKind, NumKind),
21    UnsupportedMatrixMarketFormat,
22}
23
24use self::IoError::*;
25
26impl fmt::Display for IoError {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        match *self {
29            Self::Io(ref err) => err.fmt(f),
30            Self::BadMatrixMarketFile | Self::UnsupportedMatrixMarketFormat => {
31                write!(f, "Bad matrix market file.")
32            }
33            Self::MismatchedMatrixMarketRead(matrix_kind, file_kind) => {
34                write!(
35                    f,
36                    "Tried to load {file_kind} file into {matrix_kind} matrix."
37                )
38            }
39        }
40    }
41}
42
43impl Error for IoError {}
44
45impl From<io::Error> for IoError {
46    fn from(err: io::Error) -> Self {
47        Self::Io(err)
48    }
49}
50
51impl PartialEq for IoError {
52    fn eq(&self, rhs: &Self) -> bool {
53        match (self, rhs) {
54            (Self::BadMatrixMarketFile, Self::BadMatrixMarketFile)
55            | (
56                Self::UnsupportedMatrixMarketFormat,
57                Self::UnsupportedMatrixMarketFormat,
58            ) => true,
59            (
60                Self::MismatchedMatrixMarketRead(a1, a2),
61                Self::MismatchedMatrixMarketRead(b1, b2),
62            ) => a1 == a2 && b1 == b2,
63            _ => false,
64        }
65    }
66}
67
68#[derive(Debug, PartialEq, Eq)]
69enum DataType {
70    Integer,
71    Real,
72    Complex,
73    Pattern,
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
77pub enum SymmetryMode {
78    General,
79    Hermitian,
80    Symmetric,
81    SkewSymmetric,
82}
83
84fn parse_header(header: &str) -> Result<(SymmetryMode, DataType), IoError> {
85    if !header.starts_with("%%matrixmarket matrix coordinate") {
86        return Err(BadMatrixMarketFile);
87    }
88    let data_type = if header.contains("real") {
89        DataType::Real
90    } else if header.contains("integer") {
91        DataType::Integer
92    } else if header.contains("complex") {
93        DataType::Complex
94    } else if header.contains("pattern") {
95        DataType::Pattern
96    } else {
97        return Err(BadMatrixMarketFile);
98    };
99    let sym_mode = if header.contains("general") {
100        SymmetryMode::General
101    } else if header.contains("skew-symmetric") {
102        SymmetryMode::SkewSymmetric
103    } else if header.contains("symmetric") {
104        SymmetryMode::Symmetric
105    } else if header.contains("hermitian") {
106        SymmetryMode::Hermitian
107    } else {
108        return Err(BadMatrixMarketFile);
109    };
110    Ok((sym_mode, data_type))
111}
112
113/// Read a sparse matrix file in the Matrix Market format and return a
114/// corresponding triplet matrix.
115///
116/// Presently, only general matrices are supported, but symmetric and hermitian
117/// matrices should be supported in the future.
118pub fn read_matrix_market<N, I, P>(mm_file: P) -> Result<TriMatI<N, I>, IoError>
119where
120    I: SpIndex,
121    N: PrimitiveKind
122        + Clone
123        + MatrixMarketRead
124        + MatrixMarketConjugate
125        + Neg<Output = N>,
126    P: AsRef<Path>,
127{
128    let mm_file = mm_file.as_ref();
129    let f = File::open(mm_file)?;
130    let mut reader = io::BufReader::new(f);
131    read_matrix_market_from_bufread(&mut reader)
132}
133
134/// Read a sparse matrix in the Matrix Market format from an `io::BufRead` and return a
135/// corresponding triplet matrix.
136///
137/// Presently, only general matrices are supported, but symmetric and hermitian
138/// matrices should be supported in the future.
139pub fn read_matrix_market_from_bufread<N, I, R>(
140    reader: &mut R,
141) -> Result<TriMatI<N, I>, IoError>
142where
143    I: SpIndex,
144    N: PrimitiveKind
145        + Clone
146        + Neg<Output = N>
147        + MatrixMarketRead
148        + MatrixMarketConjugate,
149    R: io::BufRead + ?Sized,
150{
151    // MatrixMarket format specifies lines of at most 1024 chars
152    let mut line = String::with_capacity(1024);
153
154    // Parse the header line, all tags are case insensitive.
155    reader.read_line(&mut line)?;
156    let header = line.to_lowercase();
157    let (sym_mode, data_type) = parse_header(&header)?;
158    let data_num_kind = match data_type {
159        DataType::Integer => NumKind::Integer,
160        DataType::Real => NumKind::Float,
161        DataType::Complex => NumKind::Complex,
162        DataType::Pattern => NumKind::Pattern,
163    };
164    // any type can be convert to pattern
165    if N::num_kind() != NumKind::Pattern && N::num_kind() != data_num_kind {
166        return Err(MismatchedMatrixMarketRead(N::num_kind(), data_num_kind));
167    }
168    // we are going to ignore the data that the martix has
169    let droping_data =
170        N::num_kind() == NumKind::Pattern && data_num_kind != NumKind::Pattern;
171
172    // The header is followed by any number of comment or empty lines, skip
173    'header: loop {
174        line.clear();
175        let len = reader.read_line(&mut line)?;
176        if len == 0 || line.starts_with('%') {
177            continue 'header;
178        }
179        break;
180    }
181    // read shape and number of entries
182    // this is a line like:
183    // rows cols entries
184    // with arbitrary amounts of whitespace
185    let (rows, cols, entries) = {
186        let mut infos = line
187            .split_whitespace()
188            .filter_map(|s| s.parse::<usize>().ok());
189        let rows = infos.next().ok_or(BadMatrixMarketFile)?;
190        let cols = infos.next().ok_or(BadMatrixMarketFile)?;
191        let entries = infos.next().ok_or(BadMatrixMarketFile)?;
192        if infos.next().is_some() {
193            return Err(BadMatrixMarketFile);
194        }
195        (rows, cols, entries)
196    };
197    let nnz_max = if sym_mode == SymmetryMode::General {
198        entries
199    } else {
200        2 * entries
201    };
202    let mut row_inds = Vec::with_capacity(nnz_max);
203    let mut col_inds = Vec::with_capacity(nnz_max);
204    let mut data = Vec::with_capacity(nnz_max);
205    // one non-zero entry per non-empty line
206    for _ in 0..entries {
207        // skip empty lines (no comment line should appear)
208        'empty_lines: loop {
209            line.clear();
210            let len = reader.read_line(&mut line)?;
211            // check for an all whitespace line
212            if len != 0 && line.split_whitespace().next().is_none() {
213                continue 'empty_lines;
214            }
215            break;
216        }
217        // Non-zero entries are lines of the form:
218        // row col value
219        // if the data type is integer of real, and
220        // row col real imag
221        // if the data type is complex.
222        // Again, this is with arbitrary amounts of whitespace
223        let mut entry = line.split_whitespace();
224        let row = entry
225            .next()
226            .ok_or(BadMatrixMarketFile)
227            .and_then(|s| s.parse::<usize>().or(Err(BadMatrixMarketFile)))?;
228        let col = entry
229            .next()
230            .ok_or(BadMatrixMarketFile)
231            .and_then(|s| s.parse::<usize>().or(Err(BadMatrixMarketFile)))?;
232        // MatrixMarket indices are 1-based
233        let row = row.checked_sub(1).ok_or(BadMatrixMarketFile)?;
234        let col = col.checked_sub(1).ok_or(BadMatrixMarketFile)?;
235        let val: N = MatrixMarketRead::mm_read(&mut entry)?;
236        row_inds.push(I::from_usize(row));
237        col_inds.push(I::from_usize(col));
238        data.push(val.clone());
239        if sym_mode != SymmetryMode::General && row != col {
240            if sym_mode == SymmetryMode::Hermitian {
241                row_inds.push(I::from_usize(col));
242                col_inds.push(I::from_usize(row));
243                let conj =
244                    val.mm_conj().ok_or(UnsupportedMatrixMarketFormat)?;
245                data.push(conj);
246            } else if sym_mode == SymmetryMode::SkewSymmetric {
247                row_inds.push(I::from_usize(col));
248                col_inds.push(I::from_usize(row));
249                data.push(-val);
250            } else {
251                row_inds.push(I::from_usize(col));
252                col_inds.push(I::from_usize(row));
253                data.push(val);
254            }
255        }
256        if sym_mode == SymmetryMode::SkewSymmetric && row == col {
257            return Err(BadMatrixMarketFile);
258        }
259        if droping_data {
260            // the mtx file has data, but we are ignoring it
261            if entry.next().is_none() {
262                return Err(BadMatrixMarketFile);
263            }
264        } else {
265            // we are not ignoring data, so all data should be comsumed
266            if entry.next().is_some() {
267                return Err(BadMatrixMarketFile);
268            }
269        }
270    }
271
272    Ok(TriMatI::from_triplets(
273        (rows, cols),
274        row_inds,
275        col_inds,
276        data,
277    ))
278}
279
280/// Write a sparse matrix into the matrix market format.
281///
282/// # Example
283///
284/// ```rust,no_run
285/// use sprs::{CsMat};
286/// # use std::io;
287/// # fn save_id5() -> Result<(), io::Error> {
288/// let save_path = "/tmp/identity5.mm";
289/// let eye : CsMat<f64> = CsMat::eye(5);
290/// sprs::io::write_matrix_market(&save_path, &eye)?;
291/// # Ok(())
292/// # }
293/// ```
294pub fn write_matrix_market<'a, N, I, M, P>(
295    path: P,
296    mat: M,
297) -> Result<(), io::Error>
298where
299    I: 'a + SpIndex + fmt::Display,
300    N: 'a + PrimitiveKind + MatrixMarketDisplay,
301    for<'n> Displayable<&'n N>: std::fmt::Display,
302    M: IntoIterator<Item = (&'a N, (I, I))> + SparseMat,
303    P: AsRef<Path>,
304{
305    let f = File::create(path)?;
306    let mut writer = io::BufWriter::new(f);
307    write_matrix_market_to_bufwrite(&mut writer, mat)
308}
309pub fn write_matrix_market_to_bufwrite<'a, N, I, M, W>(
310    writer: &mut W,
311    mat: M,
312) -> Result<(), io::Error>
313where
314    I: 'a + SpIndex + fmt::Display,
315    N: 'a + PrimitiveKind + MatrixMarketDisplay,
316    for<'n> Displayable<&'n N>: std::fmt::Display,
317    M: IntoIterator<Item = (&'a N, (I, I))> + SparseMat,
318    W: io::Write + ?Sized,
319{
320    let (rows, cols, nnz) = (mat.rows(), mat.cols(), mat.nnz());
321
322    // header
323    let data_type = match N::num_kind() {
324        NumKind::Integer => "integer",
325        NumKind::Float => "real",
326        NumKind::Complex => "complex",
327        NumKind::Pattern => "pattern",
328    };
329    writeln!(
330        writer,
331        "%%MatrixMarket matrix coordinate {data_type} general"
332    )?;
333    writeln!(writer, "% written by sprs")?;
334
335    // dimensions and nnz
336    writeln!(writer, "{rows} {cols} {nnz}")?;
337
338    // entries
339    for (val, (row, col)) in mat {
340        writeln!(
341            writer,
342            "{} {} {}",
343            row.index() + 1,
344            col.index() + 1,
345            val.mm_display()
346        )?;
347    }
348    Ok(())
349}
350
351/// Write a symmetric sparse matrix into the matrix market format.
352///
353/// This function does not enforce the actual symmetry of the matrix,
354/// instead only the elements below the diagonal are written.
355///
356/// If `sym` is `SymmetryMode::SkewSymmetric`, the diagonal elements
357/// are also ignored.
358///
359/// Note that this method can also be used to write general sparse
360/// matrices, but this would be slightly less efficient than using
361/// `write_matrix_market`.
362pub fn write_matrix_market_sym<'a, N, I, M, P>(
363    path: P,
364    mat: M,
365    sym: SymmetryMode,
366) -> Result<(), io::Error>
367where
368    I: 'a + SpIndex + fmt::Display,
369    N: 'a + PrimitiveKind + MatrixMarketDisplay,
370    for<'n> Displayable<&'n N>: std::fmt::Display,
371    M: IntoIterator<Item = (&'a N, (I, I))> + SparseMat,
372    P: AsRef<Path>,
373{
374    let (rows, cols, nnz) = (mat.rows(), mat.cols(), mat.nnz());
375    let f = File::create(path)?;
376    let mut writer = io::BufWriter::new(f);
377
378    // header
379    let data_type = match N::num_kind() {
380        NumKind::Integer => "integer",
381        NumKind::Float => "real",
382        NumKind::Complex => "complex",
383        NumKind::Pattern => "pattern",
384    };
385    let mode = match sym {
386        SymmetryMode::General => "general",
387        SymmetryMode::Symmetric => "symmetric",
388        SymmetryMode::SkewSymmetric => "skew-symmetric",
389        SymmetryMode::Hermitian => "hermitian",
390    };
391    writeln!(
392        writer,
393        "%%MatrixMarket matrix coordinate {data_type} {mode}"
394    )?;
395    writeln!(writer, "% written by sprs")?;
396
397    // We cannot know in advance how many entries will be written since
398    // this is affected by the symmetry mode. However, we do know that it
399    // can't be greater than the current nnz. Thus, the text size required
400    // to store the number of entries can only decrease. We record the position
401    // where we wrote the header and will later rewrite the number of entries,
402    // replacing possible extra digits by spaces.
403    let dim_header_pos = writer.stream_position()?;
404    // dimensions and nnz
405    writeln!(writer, "{rows} {cols} {nnz}")?;
406
407    // entries
408    let mut entries = 0;
409    match sym {
410        SymmetryMode::General => {
411            for (val, (row, col)) in mat {
412                writeln!(
413                    writer,
414                    "{} {} {}",
415                    row.index() + 1,
416                    col.index() + 1,
417                    val.mm_display()
418                )?;
419                entries += 1;
420            }
421        }
422        SymmetryMode::SkewSymmetric => {
423            for (val, (row, col)) in
424                mat.into_iter().filter(|&(_, (r, c))| r < c)
425            {
426                writeln!(
427                    writer,
428                    "{} {} {}",
429                    row.index() + 1,
430                    col.index() + 1,
431                    val.mm_display()
432                )?;
433                entries += 1;
434            }
435        }
436        _ => {
437            for (val, (row, col)) in
438                mat.into_iter().filter(|&(_, (r, c))| r <= c)
439            {
440                writeln!(
441                    writer,
442                    "{} {} {}",
443                    row.index() + 1,
444                    col.index() + 1,
445                    val.mm_display()
446                )?;
447                entries += 1;
448            }
449        }
450    };
451    assert!(entries <= nnz);
452    writer.seek(SeekFrom::Start(dim_header_pos))?;
453    write!(writer, "{rows} {cols} {entries}")?;
454    let dim_header_size = format!("{rows} {cols} {nnz}").len();
455    let new_size = format!("{rows} {cols} {entries}").len();
456    if new_size < dim_header_size {
457        let nb_spaces = dim_header_size - new_size;
458        for _ in 0..nb_spaces {
459            writer.write_all(b" ")?;
460        }
461    }
462    Ok(())
463}
464
465#[cfg(test)]
466mod test {
467    use super::{
468        read_matrix_market, read_matrix_market_from_bufread,
469        write_matrix_market, write_matrix_market_sym, IoError, SymmetryMode,
470    };
471    use crate::{num_kinds::Pattern, CsMat};
472    use ndarray::{arr2, Array2};
473    use num_complex::{Complex32, Complex64};
474    use tempfile::tempdir;
475    #[cfg_attr(miri, ignore)]
476    #[test]
477    fn simple_matrix_market_read() {
478        let path = "data/matrix_market/simple.mm";
479        let mat = read_matrix_market::<f64, usize, _>(path).unwrap();
480        assert_eq!(mat.rows(), 5);
481        assert_eq!(mat.cols(), 5);
482        assert_eq!(mat.nnz(), 8);
483        assert_eq!(mat.row_inds(), &[0, 1, 2, 0, 3, 3, 3, 4]);
484        assert_eq!(mat.col_inds(), &[0, 1, 2, 3, 1, 3, 4, 4]);
485        assert_eq!(
486            mat.data(),
487            &[1., 10.5, 1.5e-02, 6., 2.505e2, -2.8e2, 3.332e1, 1.2e+1]
488        );
489    }
490
491    #[cfg_attr(miri, ignore)]
492    #[test]
493    fn failing_matrix_market_reads() {
494        let complex_mm_path = "data/matrix_market/complex/simple.mtx";
495        let float_mm_path = "data/matrix_market/simple.mm";
496        let int_mm_path = "data/matrix_market/simple_int.mm";
497        let hermitian_int_mm_path =
498            "data/matrix_market/complex/hermitian-int.mtx";
499        assert!(read_matrix_market::<num_complex::Complex64, usize, _>(
500            complex_mm_path
501        )
502        .is_ok());
503        assert!(read_matrix_market::<i64, usize, _>(int_mm_path).is_ok());
504        assert!(read_matrix_market::<f64, usize, _>(float_mm_path).is_ok());
505
506        assert!(read_matrix_market::<f64, usize, _>(complex_mm_path).is_err());
507        assert!(read_matrix_market::<i64, usize, _>(complex_mm_path).is_err());
508
509        assert!(read_matrix_market::<num_complex::Complex64, usize, _>(
510            float_mm_path
511        )
512        .is_err());
513        assert!(read_matrix_market::<i64, usize, _>(float_mm_path).is_err());
514
515        assert!(read_matrix_market::<num_complex::Complex64, usize, _>(
516            int_mm_path
517        )
518        .is_err());
519        assert!(read_matrix_market::<f64, usize, _>(int_mm_path).is_err());
520
521        let err =
522            read_matrix_market::<f64, usize, _>(complex_mm_path).unwrap_err();
523        assert_eq!(
524            format!("{}", err),
525            "Tried to load complex file into real matrix."
526        );
527
528        assert_eq!(
529            read_matrix_market::<i64, usize, _>(hermitian_int_mm_path),
530            Err(crate::io::IoError::UnsupportedMatrixMarketFormat),
531        );
532    }
533
534    #[cfg_attr(miri, ignore)]
535    #[test]
536    fn simple_matrix_market_read_complex64() {
537        let path = "data/matrix_market/complex/simple.mtx";
538        let mat = read_matrix_market::<num_complex::Complex64, usize, _>(path)
539            .unwrap();
540        assert_eq!(mat.rows(), 2);
541        assert_eq!(mat.cols(), 2);
542        assert_eq!(mat.nnz(), 3);
543        assert_eq!(mat.row_inds(), &[0, 0, 1]);
544        assert_eq!(mat.col_inds(), &[0, 1, 0]);
545        assert_eq!(
546            mat.data(),
547            &[
548                Complex64::new(1.0, 2.0),
549                Complex64::new(3.0, 4.0),
550                Complex64::new(5.0, 6.0),
551            ]
552        );
553    }
554
555    #[cfg_attr(miri, ignore)]
556    #[test]
557    fn simple_matrix_market_read_complex64_hermitian() {
558        let path = "data/matrix_market/complex/hermitian.mtx";
559        let mat = read_matrix_market::<num_complex::Complex64, usize, _>(path)
560            .unwrap();
561        assert_eq!(mat.rows(), 2);
562        assert_eq!(mat.cols(), 2);
563        assert_eq!(mat.nnz(), 3);
564        assert_eq!(mat.row_inds(), &[0, 1, 0]);
565        assert_eq!(mat.col_inds(), &[0, 0, 1]);
566        assert_eq!(
567            mat.data(),
568            &[
569                Complex64::new(1.0, 2.0),
570                Complex64::new(5.0, 6.0),
571                Complex64::new(5.0, -6.0),
572            ]
573        );
574        let expected_dm: Array2<Complex64> = arr2(&[
575            [Complex64::new(1.0, 2.0), Complex64::new(5.0, -6.0)],
576            [Complex64::new(5.0, 6.0), Complex64::new(0.0, 0.0)],
577        ]);
578        let csr: CsMat<Complex64> = mat.to_csr();
579        let dm: Array2<Complex64> = csr.to_dense();
580        assert_eq!(dm, expected_dm);
581        println!("{}", dm);
582    }
583
584    #[cfg_attr(miri, ignore)]
585    #[test]
586    fn simple_matrix_market_read_complex32() {
587        let path = "data/matrix_market/complex/simple.mtx";
588        let mat = read_matrix_market::<num_complex::Complex32, usize, _>(path)
589            .unwrap();
590        assert_eq!(mat.rows(), 2);
591        assert_eq!(mat.cols(), 2);
592        assert_eq!(mat.nnz(), 3);
593        assert_eq!(mat.row_inds(), &[0, 0, 1]);
594        assert_eq!(mat.col_inds(), &[0, 1, 0]);
595        assert_eq!(
596            mat.data(),
597            &[
598                Complex32::new(1.0, 2.0),
599                Complex32::new(3.0, 4.0),
600                Complex32::new(5.0, 6.0),
601            ]
602        );
603    }
604
605    #[test]
606    #[cfg_attr(miri, ignore)]
607    fn simple_matrix_market_read_from_bufread() {
608        let path = "data/matrix_market/simple.mm";
609        let f = std::fs::File::open(path).unwrap();
610        let mut reader = std::io::BufReader::new(f);
611
612        let mat = read_matrix_market_from_bufread::<f64, usize, _>(&mut reader)
613            .unwrap();
614        assert_eq!(mat.rows(), 5);
615        assert_eq!(mat.cols(), 5);
616        assert_eq!(mat.nnz(), 8);
617        assert_eq!(mat.row_inds(), &[0, 1, 2, 0, 3, 3, 3, 4]);
618        assert_eq!(mat.col_inds(), &[0, 1, 2, 3, 1, 3, 4, 4]);
619        assert_eq!(
620            mat.data(),
621            &[1., 10.5, 1.5e-02, 6., 2.505e2, -2.8e2, 3.332e1, 1.2e+1]
622        );
623    }
624
625    #[test]
626    #[cfg_attr(miri, ignore)]
627    fn int_matrix_market_read() {
628        let path = "data/matrix_market/simple_int.mm";
629        let mat = read_matrix_market::<i32, usize, _>(path).unwrap();
630        assert_eq!(mat.rows(), 5);
631        assert_eq!(mat.cols(), 5);
632        assert_eq!(mat.nnz(), 8);
633        assert_eq!(mat.row_inds(), &[0, 1, 2, 0, 3, 3, 3, 4]);
634        assert_eq!(mat.col_inds(), &[0, 1, 2, 3, 1, 3, 4, 4]);
635        assert_eq!(mat.data(), &[1, 1, 1, 6, 2, -2, 3, 1]);
636    }
637
638    #[test]
639    #[cfg_attr(miri, ignore)]
640    fn matrix_market_read_fail_too_many_in_entry() {
641        let path = "data/matrix_market/bad_files/too_many_elems_in_entry.mm";
642        let res = read_matrix_market::<f64, i32, _>(path);
643        assert_eq!(res.unwrap_err(), IoError::BadMatrixMarketFile);
644    }
645
646    #[test]
647    #[cfg_attr(miri, ignore)]
648    fn matrix_market_read_fail_not_enough_entries() {
649        let path = "data/matrix_market/bad_files/not_enough_entries.mm";
650        let res = read_matrix_market::<f64, i32, _>(path);
651        assert_eq!(res.unwrap_err(), IoError::BadMatrixMarketFile);
652    }
653
654    #[test]
655    #[cfg_attr(miri, ignore)]
656    fn read_write_read_matrix_market() {
657        let path = "data/matrix_market/simple.mm";
658        let mat = read_matrix_market::<f64, usize, _>(path).unwrap();
659        let tmp_dir = tempdir().unwrap();
660        let save_path = tmp_dir.path().join("simple.mm");
661        write_matrix_market(&save_path, mat.view()).unwrap();
662        let mat2 = read_matrix_market::<f64, usize, _>(&save_path).unwrap();
663        assert_eq!(mat, mat2);
664        write_matrix_market(&save_path, &mat2).unwrap();
665        let mat3 = read_matrix_market::<f64, usize, _>(&save_path).unwrap();
666        assert_eq!(mat, mat3);
667    }
668
669    #[test]
670    #[cfg_attr(miri, ignore)]
671    fn read_write_read_matrix_market_via_csc() {
672        let path = "data/matrix_market/simple.mm";
673        let mat = read_matrix_market::<f64, usize, _>(path).unwrap();
674        let csc: CsMat<_> = mat.to_csc();
675        let tmp_dir = tempdir().unwrap();
676        let save_path = tmp_dir.path().join("simple_csc.mm");
677        write_matrix_market(&save_path, &csc).unwrap();
678        let mat2 = read_matrix_market::<f64, usize, _>(&save_path).unwrap();
679        assert_eq!(csc, mat2.to_csc());
680    }
681
682    #[test]
683    #[cfg_attr(miri, ignore)]
684    fn read_symmetric_matrix_market() {
685        let path = "data/matrix_market/symmetric.mm";
686        let mat = read_matrix_market::<f64, usize, _>(path).unwrap();
687        let csc = mat.to_csc();
688        let expected = CsMat::new_csc(
689            (5, 5),
690            vec![0, 1, 3, 4, 6, 8],
691            vec![0, 1, 3, 2, 1, 4, 3, 4],
692            vec![1., 10.5, 2.505e2, 1.5e-2, 2.505e2, 3.332e1, 3.332e1, 1.2e1],
693        );
694        assert_eq!(csc, expected);
695        let tmp_dir = tempdir().unwrap();
696        let save_path = tmp_dir.path().join("symmetric.mm");
697        write_matrix_market_sym(&save_path, &csc, SymmetryMode::Symmetric)
698            .unwrap();
699        let mat2 = read_matrix_market::<f64, usize, _>(&save_path).unwrap();
700        assert_eq!(csc, mat2.to_csc());
701    }
702
703    #[test]
704    #[cfg_attr(miri, ignore)]
705    fn read_symmetric_matrix_market_complex() {
706        let path = "data/matrix_market/complex/symmetric.mtx";
707        let mat = read_matrix_market::<Complex64, usize, _>(path).unwrap();
708        let csc = mat.to_csc();
709        let expected = CsMat::new_csc(
710            (2, 2),
711            vec![0, 2, 3],
712            vec![0, 1, 0],
713            vec![
714                Complex64::new(1.0, 2.0),
715                Complex64::new(3.0, 4.0),
716                Complex64::new(3.0, 4.0),
717            ],
718        );
719        assert_eq!(csc, expected);
720        let tmp_dir = tempdir().unwrap();
721        let save_path = tmp_dir.path().join("symmetric.mm");
722        write_matrix_market_sym(&save_path, &csc, SymmetryMode::Symmetric)
723            .unwrap();
724        let mat2 =
725            read_matrix_market::<Complex64, usize, _>(&save_path).unwrap();
726        assert_eq!(csc, mat2.to_csc());
727    }
728
729    #[test]
730    #[cfg_attr(miri, ignore)]
731    fn read_skew_symmetric_matrix_market_complex() {
732        let path = "data/matrix_market/complex/skew-symmetric.mtx";
733        let mat = read_matrix_market::<Complex64, usize, _>(path).unwrap();
734        println!("trimat: {:?}", mat);
735        let csc = mat.to_csc();
736        assert_eq!(csc.get(0, 0), None);
737        assert_eq!(csc.get(1, 1), None);
738        assert_eq!(csc.get(0, 1), Some(&Complex64::new(3.0, 4.0)));
739        let expected = CsMat::new_csc(
740            (2, 2),
741            vec![0, 1, 2],
742            vec![1, 0],
743            vec![Complex64::new(-3.0, -4.0), Complex64::new(3.0, 4.0)],
744        );
745        assert_eq!(expected.get(0, 0), None);
746        assert_eq!(expected.get(1, 1), None);
747        assert_eq!(expected.get(0, 1), Some(&Complex64::new(3.0, 4.0)));
748        assert_eq!(csc, expected);
749        let tmp_dir = tempdir().unwrap();
750        let save_path = tmp_dir.path().join("skew-symmetric.mm");
751        write_matrix_market_sym(&save_path, &csc, SymmetryMode::SkewSymmetric)
752            .unwrap();
753        let mat2 =
754            read_matrix_market::<Complex64, usize, _>(&save_path).unwrap();
755        assert_eq!(csc, mat2.to_csc());
756    }
757
758    #[test]
759    #[cfg_attr(miri, ignore)]
760    /// Test whether the seek and replace strategy in the symmetric write
761    /// works.
762    fn tricky_symmetric_matrix_market() {
763        // design a 5x5 symmetric matrix such that the number
764        // of nonzeros has more digits than the number of symmetric entries
765        // We take the matrix
766        // | .  2  .  .  1 |
767        // | 2  .  3  .  . |
768        // | .  3  .  5  . |
769        // | .  .  5  .  4 |
770        // | 1  .  .  4  . |
771        let mat = CsMat::new(
772            (5, 5),
773            vec![0, 2, 4, 6, 8, 10],
774            vec![1, 4, 0, 2, 1, 3, 2, 4, 0, 3],
775            vec![2, 1, 2, 3, 3, 5, 5, 4, 1, 4],
776        );
777        let tmp_dir = tempdir().unwrap();
778        let save_path = tmp_dir.path().join("symmetric.mm");
779        write_matrix_market_sym(&save_path, &mat, SymmetryMode::Symmetric)
780            .unwrap();
781        let mat2 = read_matrix_market::<i32, usize, _>(&save_path).unwrap();
782        assert_eq!(mat, mat2.to_csr());
783    }
784
785    #[test]
786    #[cfg_attr(miri, ignore)]
787    fn skew_symmetric_matrix_market() {
788        let mat = CsMat::new(
789            (5, 5),
790            vec![0, 2, 4, 6, 8, 10],
791            vec![1, 4, 0, 2, 1, 3, 2, 4, 0, 3],
792            vec![2, 1, -2, 3, -3, 5, -5, 4, -1, -4],
793        );
794        let tmp_dir = tempdir().unwrap();
795        let save_path = tmp_dir.path().join("skew_symmetric.mm");
796        write_matrix_market_sym(&save_path, &mat, SymmetryMode::SkewSymmetric)
797            .unwrap();
798        let mat2 = read_matrix_market::<i32, usize, _>(&save_path).unwrap();
799        assert_eq!(mat, mat2.to_csr());
800    }
801
802    #[test]
803    #[cfg_attr(miri, ignore)]
804    fn general_matrix_via_symmetric_save() {
805        let mat = CsMat::new(
806            (5, 5),
807            vec![0, 2, 4, 6, 8, 10],
808            vec![0, 3, 0, 2, 1, 3, 2, 4, 0, 3],
809            vec![2, -1, 2, 3, 3, 5, 5, 4, 1, 4],
810        );
811        let tmp_dir = tempdir().unwrap();
812        let save_path = tmp_dir.path().join("general.mm");
813        write_matrix_market_sym(&save_path, &mat, SymmetryMode::General)
814            .unwrap();
815        let mat2 = read_matrix_market::<i32, usize, _>(&save_path).unwrap();
816        assert_eq!(mat, mat2.to_csr());
817    }
818
819    #[test]
820    #[cfg_attr(miri, ignore)]
821    fn read_pattern_matrix() {
822        let path = "data/matrix_market/pattern.mm";
823        let mat = read_matrix_market::<Pattern, usize, _>(path).unwrap();
824        println!("trimat: {:?}", mat);
825        let csc = mat.to_csc();
826        assert_eq!(csc.get(0, 0), Some(&Pattern {}));
827        assert_eq!(csc.get(1, 1), Some(&Pattern {}));
828        assert_eq!(csc.get(2, 2), Some(&Pattern {}));
829        assert_eq!(csc.get(3, 3), Some(&Pattern {}));
830        assert_eq!(csc.get(0, 1), None);
831        let expected = CsMat::new_csc(
832            (4, 4),
833            vec![0, 1, 2, 3, 4],
834            vec![0, 1, 2, 3],
835            vec![Pattern {}; 4],
836        );
837        assert_eq!(csc.get(0, 0), Some(&Pattern {}));
838        assert_eq!(csc.get(1, 1), Some(&Pattern {}));
839        assert_eq!(csc.get(2, 2), Some(&Pattern {}));
840        assert_eq!(csc.get(3, 3), Some(&Pattern {}));
841        assert_eq!(csc.get(0, 1), None);
842        assert_eq!(csc, expected);
843        let tmp_dir = tempdir().unwrap();
844        let save_path = tmp_dir.path().join("pattern_write.mm");
845        write_matrix_market(&save_path, &csc).unwrap();
846        let mat2 = read_matrix_market::<Pattern, usize, _>(&save_path).unwrap();
847        assert_eq!(csc, mat2.to_csc());
848    }
849
850    #[test]
851    #[cfg_attr(miri, ignore)]
852    fn read_non_pattern_mtx_to_pattern() {
853        let path = "data/matrix_market/simple.mm";
854        let mat = read_matrix_market::<Pattern, usize, _>(path).unwrap();
855        println!("trimat: {:?}", mat);
856        let csc = mat.to_csc();
857        assert_eq!(csc.get(1, 1), Some(&Pattern {}));
858        assert_eq!(csc.get(2, 2), Some(&Pattern {}));
859        assert_eq!(csc.get(3, 3), Some(&Pattern {}));
860        assert_eq!(csc.get(0, 1), None);
861        let expected = CsMat::new_csc(
862            (5, 5),
863            vec![0, 1, 3, 4, 6, 8],
864            vec![0, 1, 3, 2, 0, 3, 3, 4],
865            vec![Pattern {}; 8],
866        );
867        assert_eq!(csc.get(0, 0), Some(&Pattern {}));
868        assert_eq!(csc.get(1, 1), Some(&Pattern {}));
869        assert_eq!(csc.get(2, 2), Some(&Pattern {}));
870        assert_eq!(csc.get(3, 3), Some(&Pattern {}));
871        assert_eq!(csc.get(0, 1), None);
872        assert_eq!(csc, expected);
873        let tmp_dir = tempdir().unwrap();
874        let save_path = tmp_dir.path().join("non_pattern_write.mm");
875        write_matrix_market(&save_path, &csc).unwrap();
876        let mat2 = read_matrix_market::<Pattern, usize, _>(&save_path).unwrap();
877        assert_eq!(csc, mat2.to_csc());
878    }
879
880    #[test]
881    #[cfg_attr(miri, ignore)]
882    fn read_csc_to_csr_trans() {
883        let path = "data/matrix_market/simple.mm";
884        let mat = read_matrix_market::<Pattern, usize, _>(path).unwrap();
885        println!("trimat: {:?}", mat);
886        let csc: CsMat<Pattern> = mat.to_csc();
887        let csr = csc.to_csr();
888        let csc_copy = csr.to_csc();
889        let csr_copy = csc_copy.to_csr();
890
891        assert_eq!(csc, csc_copy);
892        assert_eq!(csr, csr_copy);
893    }
894}