use super::*;
use crate::sparse_io::*;
type Data = Box<dyn SparseIo<IndexIter = Vec<usize>>>;
const ENTRIES: [(u64, u64, f32); 7] = [
(0, 0, 10.0),
(2, 0, 20.0),
(1, 1, 30.0),
(3, 1, 40.0),
(0, 3, 50.0),
(2, 3, 60.0),
(3, 4, 70.0),
];
fn fixture(dir: &std::path::Path) -> Data {
let path = dir.join("fixture.zarr");
create_sparse_from_triplets(
&ENTRIES,
(4, 5, ENTRIES.len()),
path.to_str(),
Some(&SparseIoBackend::Zarr),
)
.expect("fixture")
}
fn entries_at(path: &str) -> Vec<(u64, u64, f32)> {
let data = open_sparse_matrix(path, &SparseIoBackend::Zarr).expect("open");
let ncol = data.num_columns().expect("ncol");
let (_, _, mut triplets) = data
.read_triplets_by_columns((0..ncol).collect())
.expect("read");
triplets.sort_by_key(|&(r, c, _)| (c, r));
triplets
}
#[test]
fn the_row_index_counts_the_entries_of_the_rows_kept() {
let dir = tempfile::tempdir().unwrap();
let data = fixture(dir.path());
assert_eq!(data.row_indptr().len(), 5, "the fixture has a by-row index");
let rows = RowMap::keeping(&[3, 0], 4).unwrap();
assert_eq!(nnz_of_kept_rows(data.row_indptr(), &rows.to), Some(4));
let every_row = RowMap::keeping(&[0, 1, 2, 3], 4).unwrap();
assert_eq!(nnz_of_kept_rows(data.row_indptr(), &every_row.to), Some(7));
let other_rows = RowMap::keeping(&[0, 1], 3).unwrap();
assert_eq!(nnz_of_kept_rows(data.row_indptr(), &other_rows.to), None);
}
#[test]
fn a_selection_is_counted_and_written_in_blocks_cut_by_the_budget() {
let dir = tempfile::tempdir().unwrap();
let data = fixture(dir.path());
let cols = [3usize, 1, 0, 4];
let rows = RowMap::keeping(&[3, 0], 4).unwrap();
let plan = plan_within(data.as_ref(), &cols, Some(&rows), TRIPLET_BYTES).unwrap();
assert_eq!(plan.slabs, [(0, 1), (1, 2), (2, 3), (3, 4)]);
assert_eq!((plan.nrow, plan.nnz), (2, 4));
let out = dir.path().join("out.zarr");
let out = out.to_str().unwrap();
let mut written = create_sparse_streaming_empty(Some(out), Some(&SparseIoBackend::Zarr))
.expect("empty output");
let shape =
write_csc_selection(data.as_ref(), &cols, Some(&rows), &plan, &mut *written).unwrap();
drop(written);
assert_eq!(shape, (2, 4, 4));
assert_eq!(
entries_at(out),
[(1, 0, 50.0), (0, 1, 40.0), (1, 2, 10.0), (0, 3, 70.0)]
);
}
#[test]
fn a_column_past_the_data_is_refused_before_the_output_exists() {
let dir = tempfile::tempdir().unwrap();
let data = fixture(dir.path());
let out = dir.path().join("out.zarr");
let names =
|p: &str, n: usize| -> Vec<Box<str>> { (0..n).map(|i| format!("{p}{i}").into()).collect() };
let err = stream_mapped_selection(
data.as_ref(),
&[1, 5],
None,
&names("r", 4),
&names("c", 2),
out.to_str().unwrap(),
&SparseIoBackend::Zarr,
)
.unwrap_err();
assert!(
format!("{err:#}").contains("column 5 is past the 5 columns"),
"{err:#}"
);
assert!(!out.exists(), "{} was created", out.display());
let rows = RowMap {
to: vec![Some(0), Some(0), None, None],
nrow: 1,
};
let err = stream_mapped_selection(
data.as_ref(),
&[1],
Some(&rows),
&names("r", 1),
&names("c", 1),
out.to_str().unwrap(),
&SparseIoBackend::Zarr,
)
.unwrap_err();
assert!(
format!("{err:#}").contains("land on output row 0"),
"{err:#}"
);
assert!(!out.exists(), "{} was created", out.display());
}
#[test]
fn a_selection_names_each_index_once_and_within_range() {
assert!(distinct_within(&[2, 0, 1], 3, "row").is_ok());
assert!(distinct_within(&[], 3, "row").is_ok());
let err = distinct_within(&[0, 3], 3, "column").unwrap_err();
assert_eq!(err.to_string(), "column 3 is past the 3 columns");
let err = distinct_within(&[1, 0, 1], 3, "row").unwrap_err();
assert_eq!(err.to_string(), "the row selection repeats row 1");
let err = RowMap::keeping(&[1, 1], 3).err().expect("row 1 twice");
assert_eq!(err.to_string(), "the row selection repeats row 1");
}
#[test]
fn rows_are_placed_by_name_and_ambiguous_names_refused() {
let names = |xs: &[&str]| -> Vec<Box<str>> { xs.iter().map(|&x| x.into()).collect() };
let old = names(&["a", "b", "c", "b"]);
let map = RowMap::by_names(&old, &names(&["c", "x", "a"]), 4).unwrap();
assert_eq!(map.to, [Some(2), None, Some(0), None]);
assert_eq!(map.nrow, 3);
let map = RowMap::by_names(&old, &names(&["c", "a"]), 6).unwrap();
assert_eq!(map.to, [Some(1), None, Some(0), None, None, None]);
let map = RowMap::by_names(&old, &names(&["b", "c"]), 2).unwrap();
assert_eq!(map.to, [None, Some(0)]);
assert_eq!(map.nrow, 2);
let err = RowMap::by_names(&old, &names(&["a", "c", "a"]), 4)
.err()
.expect("a listed twice");
assert!(err.to_string().contains("lists `a` twice"), "{err}");
let err = RowMap::by_names(&old, &names(&["b", "a"]), 4)
.err()
.expect("two rows named b");
assert!(
err.to_string().contains("rows 1 and 3 are both named `b`"),
"{err}"
);
}
#[test]
fn a_slab_is_laid_out_by_column_with_rows_ascending() {
let triplets = vec![(2, 1, 5.0), (3, 0, 1.0), (0, 0, 2.0)];
let (colptr, rows, values) = csc_slab(triplets, 3, None);
assert_eq!(colptr, [0, 2, 3]);
assert_eq!(rows, [0, 3, 2]);
assert_eq!(values, [2.0, 1.0, 5.0]);
let triplets: Vec<(u64, u64, f32)> = vec![
(2, 3, 1.0),
(0, 0, 2.0),
(4, 3, 3.0),
(1, 3, 4.0),
(3, 0, 5.0),
(1, 3, 6.0),
(0, 1, 7.0),
];
let mut sorted = triplets.clone();
sorted.sort_by_key(|&(row, col, _)| (col, row));
let (colptr, rows, values) = csc_slab(triplets, 4, None);
assert_eq!(colptr, [0, 2, 3, 3]);
assert_eq!(rows, sorted.iter().map(|t| t.0).collect::<Vec<_>>());
assert_eq!(values, sorted.iter().map(|t| t.2).collect::<Vec<_>>());
let to = [Some(1), None, Some(0), Some(0)];
let triplets = vec![(0, 0, 1.0), (1, 0, 2.0), (2, 0, 3.0), (3, 0, 4.0)];
let (colptr, rows, values) = csc_slab(triplets, 1, Some(&to));
assert_eq!(colptr, [0]);
assert_eq!(rows, [0, 0, 1]);
assert_eq!(values, [3.0, 4.0, 1.0]);
}