use data_beans::sparse_io::*;
fn named_fixture() -> Box<dyn SparseIo<IndexIter = Vec<usize>>> {
let mut arr = ndarray::Array2::<f32>::zeros((4, 5));
arr[(0, 0)] = 10.0;
arr[(2, 0)] = 20.0;
arr[(1, 1)] = 30.0;
arr[(3, 1)] = 40.0;
arr[(0, 3)] = 50.0;
arr[(2, 3)] = 60.0;
arr[(3, 4)] = 70.0;
let mut data = create_sparse_from_ndarray(&arr, None, None).expect("fixture");
let rows: Vec<Box<str>> = ["r0", "r1", "r2", "r3"].map(Box::from).to_vec();
let cols: Vec<Box<str>> = ["c0", "c1", "c2", "c3", "c4"].map(Box::from).to_vec();
data.register_row_names_vec(&rows);
data.register_column_names_vec(&cols);
data
}
fn dense(data: &dyn SparseIo<IndexIter = Vec<usize>>) -> nalgebra::DMatrix<f32> {
let ncol = data.num_columns().expect("ncol");
data.read_columns_dmatrix((0..ncol).collect())
.expect("read")
}
#[test]
fn a_column_subset_keeps_values_names_and_order() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(Some(&vec![0usize, 3, 4]), None)?;
assert_eq!(data.num_rows(), Some(4));
assert_eq!(data.num_columns(), Some(3));
assert_eq!(data.num_non_zeros(), Some(5));
let col_names = data.column_names()?;
assert_eq!(
col_names.iter().map(AsRef::as_ref).collect::<Vec<_>>(),
vec!["c0", "c3", "c4"]
);
let m = dense(&*data);
assert_eq!(m[(0, 0)], 10.0);
assert_eq!(m[(2, 0)], 20.0);
assert_eq!(m[(0, 1)], 50.0);
assert_eq!(m[(2, 1)], 60.0);
assert_eq!(m[(3, 2)], 70.0);
assert_eq!(m.sum(), 10.0 + 20.0 + 50.0 + 60.0 + 70.0, "nothing extra");
Ok(())
}
#[test]
fn a_row_subset_renumbers_without_aliasing() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(None, Some(&vec![2usize, 3]))?;
assert_eq!(data.num_rows(), Some(2));
assert_eq!(data.num_columns(), Some(5));
assert_eq!(data.num_non_zeros(), Some(4), "20, 40, 60, 70 survive");
let row_names = data.row_names()?;
assert_eq!(
row_names.iter().map(AsRef::as_ref).collect::<Vec<_>>(),
vec!["r2", "r3"]
);
let m = dense(&*data);
assert_eq!(m[(0, 0)], 20.0, "old r2 is new row 0");
assert_eq!(m[(1, 1)], 40.0, "old r3 is new row 1");
assert_eq!(m[(0, 3)], 60.0);
assert_eq!(m[(1, 4)], 70.0);
Ok(())
}
#[test]
fn rows_and_columns_together_compose() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(Some(&vec![1usize, 3]), Some(&vec![0usize, 3]))?;
assert_eq!(data.num_rows(), Some(2));
assert_eq!(data.num_columns(), Some(2));
assert_eq!(data.num_non_zeros(), Some(2));
let m = dense(&*data);
assert_eq!(m[(1, 0)], 40.0);
assert_eq!(m[(0, 1)], 50.0);
Ok(())
}
#[test]
fn the_csr_side_survives_the_rewrite() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(Some(&vec![0usize, 3]), None)?;
let (_, _, by_rows) = data.read_triplets_by_rows((0..4).collect())?;
let mut got: Vec<_> = by_rows.iter().map(|&(i, j, x)| (i, j, x)).collect();
got.sort_by(|a, b| (a.0, a.1).partial_cmp(&(b.0, b.1)).expect("ord"));
assert_eq!(
got,
vec![(0, 0, 10.0), (0, 1, 50.0), (2, 0, 20.0), (2, 1, 60.0)]
);
Ok(())
}
#[test]
fn an_empty_selection_is_refused_not_written() -> anyhow::Result<()> {
let mut data = named_fixture();
assert!(data.subset_columns_rows(Some(&vec![]), None).is_err());
Ok(())
}
#[test]
fn a_permuted_column_selection_defines_the_output_order() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(Some(&vec![3usize, 0]), None)?;
let col_names = data.column_names()?;
assert_eq!(
col_names.iter().map(AsRef::as_ref).collect::<Vec<_>>(),
vec!["c3", "c0"]
);
let m = dense(&*data);
assert_eq!(m[(0, 0)], 50.0, "new column 0 is old c3");
assert_eq!(m[(0, 1)], 10.0, "new column 1 is old c0");
Ok(())
}
#[test]
fn a_reordering_row_selection_keeps_columns_sorted() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(None, Some(&vec![3usize, 0]))?;
let row_names = data.row_names()?;
assert_eq!(
row_names.iter().map(AsRef::as_ref).collect::<Vec<_>>(),
vec!["r3", "r0"]
);
let m = dense(&*data);
assert_eq!(m[(1, 0)], 10.0, "old r0 is new row 1");
assert_eq!(m[(0, 1)], 40.0, "old r3 is new row 0");
assert_eq!(m[(1, 3)], 50.0);
assert_eq!(m[(0, 4)], 70.0);
assert_eq!(data.num_non_zeros(), Some(4));
Ok(())
}
#[test]
fn a_cold_read_after_the_swap_serves_the_new_contents() -> anyhow::Result<()> {
let mut data = named_fixture();
data.subset_columns_rows(None, Some(&vec![2usize, 3]))?;
let (_, _, triplets) = data.read_triplets_by_columns((0..5).collect())?;
let nrow = data.num_rows().expect("nrow") as u64;
assert!(
triplets.iter().all(|&(r, _, _)| r < nrow),
"a stale chunk cache leaks pre-swap rows: {triplets:?}"
);
assert_eq!(triplets.len(), 4);
Ok(())
}
#[test]
fn a_duplicated_selection_is_refused_by_name() -> anyhow::Result<()> {
let mut data = named_fixture();
let err = data
.subset_columns_rows(Some(&vec![0usize, 3, 0]), None)
.expect_err("duplicates must be refused");
assert!(
err.to_string().contains("repeats an index"),
"the error must name the cause, got: {err}"
);
let mut data = named_fixture();
assert!(data
.subset_columns_rows(None, Some(&vec![1usize, 1]))
.is_err());
Ok(())
}