2020-11-23 17:16:18 +08:00
|
|
|
#[test]
|
|
|
|
#[ignore]
|
|
|
|
fn coo_no_duplicates_generates_admissible_matrices() {
|
|
|
|
//TODO
|
|
|
|
}
|
2020-11-18 20:54:14 +08:00
|
|
|
|
|
|
|
#[cfg(feature = "slow-tests")]
|
2020-11-23 17:16:18 +08:00
|
|
|
mod slow {
|
2021-01-20 23:07:43 +08:00
|
|
|
use nalgebra_sparse::proptest::{coo_with_duplicates, coo_no_duplicates, csr, csc, sparsity_pattern};
|
2020-11-23 17:16:18 +08:00
|
|
|
use nalgebra::DMatrix;
|
|
|
|
|
|
|
|
use proptest::test_runner::TestRunner;
|
|
|
|
use proptest::strategy::ValueTree;
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::iter::repeat;
|
|
|
|
use std::ops::RangeInclusive;
|
2021-01-20 23:07:43 +08:00
|
|
|
use nalgebra_sparse::csr::CsrMatrix;
|
2020-11-23 17:16:18 +08:00
|
|
|
|
|
|
|
fn generate_all_possible_matrices(value_range: RangeInclusive<i32>,
|
|
|
|
rows_range: RangeInclusive<usize>,
|
|
|
|
cols_range: RangeInclusive<usize>)
|
|
|
|
-> HashSet<DMatrix<i32>>
|
|
|
|
{
|
|
|
|
// Enumerate all possible combinations
|
|
|
|
let mut all_combinations = HashSet::new();
|
|
|
|
for nrows in rows_range {
|
|
|
|
for ncols in cols_range.clone() {
|
|
|
|
// For the given number of rows and columns
|
|
|
|
let n_values = nrows * ncols;
|
|
|
|
|
|
|
|
if n_values == 0 {
|
|
|
|
// If we have zero rows or columns, the set of matrices with the given
|
|
|
|
// rows and columns is a single element: an empty matrix
|
|
|
|
all_combinations.insert(DMatrix::from_row_slice(nrows, ncols, &[]));
|
|
|
|
} else {
|
|
|
|
// Otherwise, we need to sample all possible matrices.
|
|
|
|
// To do this, we generate the values as the (multi) Cartesian product
|
|
|
|
// of the value sets. For example, for a 2x2 matrices, we consider
|
|
|
|
// all possible 4-element arrays that the matrices can take by
|
|
|
|
// considering all elements in the cartesian product
|
|
|
|
// V x V x V x V
|
|
|
|
// where V is the set of eligible values, e.g. V := -1 ..= 1
|
|
|
|
let values_iter = repeat(value_range.clone())
|
|
|
|
.take(n_values)
|
|
|
|
.multi_cartesian_product();
|
|
|
|
for matrix_values in values_iter {
|
|
|
|
all_combinations.insert(DMatrix::from_row_slice(nrows, ncols, &matrix_values));
|
|
|
|
}
|
2020-11-18 20:54:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 17:16:18 +08:00
|
|
|
all_combinations
|
2020-11-18 20:54:14 +08:00
|
|
|
}
|
|
|
|
|
2020-11-23 17:16:18 +08:00
|
|
|
#[cfg(feature = "slow-tests")]
|
|
|
|
#[test]
|
|
|
|
fn coo_no_duplicates_samples_all_admissible_outputs() {
|
|
|
|
// Note: This test basically mirrors a similar test for `matrix` in the `nalgebra` repo.
|
2020-11-18 20:54:14 +08:00
|
|
|
|
2020-11-23 17:16:18 +08:00
|
|
|
// Test that the proptest generation covers all possible outputs for a small space of inputs
|
|
|
|
// given enough samples.
|
2020-11-18 20:54:14 +08:00
|
|
|
|
2020-11-23 17:16:18 +08:00
|
|
|
// We use a deterministic test runner to make the test "stable".
|
|
|
|
let mut runner = TestRunner::deterministic();
|
2020-11-18 20:54:14 +08:00
|
|
|
|
2020-11-23 17:16:18 +08:00
|
|
|
// This number needs to be high enough so that we with high probability sample
|
|
|
|
// all possible cases
|
|
|
|
let num_generated_matrices = 500000;
|
2020-11-18 20:54:14 +08:00
|
|
|
|
2020-11-23 17:16:18 +08:00
|
|
|
let values = -1..=1;
|
|
|
|
let rows = 0..=2;
|
|
|
|
let cols = 0..=3;
|
2021-01-20 23:07:43 +08:00
|
|
|
let max_nnz = rows.end() * cols.end();
|
|
|
|
let strategy = coo_no_duplicates(values.clone(), rows.clone(), cols.clone(), max_nnz);
|
2020-11-23 17:16:18 +08:00
|
|
|
|
|
|
|
// Enumerate all possible combinations
|
|
|
|
let all_combinations = generate_all_possible_matrices(values, rows, cols);
|
2020-11-18 20:54:14 +08:00
|
|
|
|
2021-01-20 23:07:43 +08:00
|
|
|
let visited_combinations = sample_matrix_output_space(strategy,
|
|
|
|
&mut runner,
|
|
|
|
num_generated_matrices);
|
2020-11-23 17:16:18 +08:00
|
|
|
|
|
|
|
assert_eq!(visited_combinations.len(), all_combinations.len());
|
|
|
|
assert_eq!(visited_combinations, all_combinations, "Did not sample all possible values.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "slow-tests")]
|
|
|
|
#[test]
|
|
|
|
fn coo_with_duplicates_samples_all_admissible_outputs() {
|
|
|
|
// This is almost the same as the test for coo_no_duplicates, except that we need
|
|
|
|
// a different "success" criterion, since coo_with_duplicates is able to generate
|
|
|
|
// matrices with values outside of the value constraints. See below for details.
|
|
|
|
|
|
|
|
// We use a deterministic test runner to make the test "stable".
|
|
|
|
let mut runner = TestRunner::deterministic();
|
|
|
|
|
|
|
|
// This number needs to be high enough so that we with high probability sample
|
|
|
|
// all possible cases
|
|
|
|
let num_generated_matrices = 500000;
|
|
|
|
|
|
|
|
let values = -1..=1;
|
|
|
|
let rows = 0..=2;
|
|
|
|
let cols = 0..=3;
|
2021-01-20 23:07:43 +08:00
|
|
|
let max_nnz = rows.end() * cols.end();
|
|
|
|
let strategy = coo_with_duplicates(values.clone(), rows.clone(), cols.clone(), max_nnz, 2);
|
2020-11-23 17:16:18 +08:00
|
|
|
|
|
|
|
// Enumerate all possible combinations that fit the constraints
|
|
|
|
// (note: this is only a subset of the matrices that can be generated by
|
|
|
|
// `coo_with_duplicates`)
|
|
|
|
let all_combinations = generate_all_possible_matrices(values, rows, cols);
|
|
|
|
|
2021-01-20 23:07:43 +08:00
|
|
|
let visited_combinations = sample_matrix_output_space(strategy,
|
|
|
|
&mut runner,
|
|
|
|
num_generated_matrices);
|
2020-11-23 17:16:18 +08:00
|
|
|
|
|
|
|
// Here we cannot verify that the set of visited combinations is *equal* to
|
|
|
|
// all possible outcomes with the given constraints, however the
|
|
|
|
// strategy should be able to generate all matrices that fit the constraints.
|
|
|
|
// In other words, we need to determine that set of all admissible matrices
|
|
|
|
// is contained in the set of visited matrices
|
|
|
|
assert!(all_combinations.is_subset(&visited_combinations));
|
|
|
|
}
|
2020-11-25 00:34:19 +08:00
|
|
|
|
2021-01-20 23:07:43 +08:00
|
|
|
#[cfg(feature = "slow-tests")]
|
|
|
|
#[test]
|
|
|
|
fn csr_samples_all_admissible_outputs() {
|
|
|
|
// We use a deterministic test runner to make the test "stable".
|
|
|
|
let mut runner = TestRunner::deterministic();
|
|
|
|
|
|
|
|
// This number needs to be high enough so that we with high probability sample
|
|
|
|
// all possible cases
|
|
|
|
let num_generated_matrices = 500000;
|
|
|
|
|
|
|
|
let values = -1..=1;
|
|
|
|
let rows = 0..=2;
|
|
|
|
let cols = 0..=3;
|
|
|
|
let max_nnz = rows.end() * cols.end();
|
|
|
|
let strategy = csr(values.clone(), rows.clone(), cols.clone(), max_nnz);
|
|
|
|
|
|
|
|
let all_combinations = generate_all_possible_matrices(values, rows, cols);
|
|
|
|
|
|
|
|
let visited_combinations = sample_matrix_output_space(strategy,
|
|
|
|
&mut runner,
|
|
|
|
num_generated_matrices);
|
|
|
|
|
|
|
|
assert_eq!(visited_combinations.len(), all_combinations.len());
|
|
|
|
assert_eq!(visited_combinations, all_combinations, "Did not sample all possible values.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "slow-tests")]
|
|
|
|
#[test]
|
|
|
|
fn csc_samples_all_admissible_outputs() {
|
|
|
|
// We use a deterministic test runner to make the test "stable".
|
|
|
|
let mut runner = TestRunner::deterministic();
|
|
|
|
|
|
|
|
// This number needs to be high enough so that we with high probability sample
|
|
|
|
// all possible cases
|
|
|
|
let num_generated_matrices = 500000;
|
|
|
|
|
|
|
|
let values = -1..=1;
|
|
|
|
let rows = 0..=2;
|
|
|
|
let cols = 0..=3;
|
|
|
|
let max_nnz = rows.end() * cols.end();
|
|
|
|
let strategy = csc(values.clone(), rows.clone(), cols.clone(), max_nnz);
|
|
|
|
|
|
|
|
let all_combinations = generate_all_possible_matrices(values, rows, cols);
|
|
|
|
|
|
|
|
let visited_combinations = sample_matrix_output_space(strategy,
|
|
|
|
&mut runner,
|
|
|
|
num_generated_matrices);
|
|
|
|
|
|
|
|
assert_eq!(visited_combinations.len(), all_combinations.len());
|
|
|
|
assert_eq!(visited_combinations, all_combinations, "Did not sample all possible values.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "slow-tests")]
|
|
|
|
#[test]
|
|
|
|
fn sparsity_pattern_samples_all_admissible_outputs() {
|
|
|
|
let mut runner = TestRunner::deterministic();
|
|
|
|
|
|
|
|
let num_generated_patterns = 50000;
|
|
|
|
|
|
|
|
let major_dims = 0..=2;
|
|
|
|
let minor_dims = 0..=3;
|
|
|
|
let max_nnz = major_dims.end() * minor_dims.end();
|
|
|
|
let strategy = sparsity_pattern(major_dims.clone(), minor_dims.clone(), max_nnz);
|
|
|
|
|
|
|
|
let visited_patterns: HashSet<_> = sample_strategy(strategy, &mut runner)
|
|
|
|
.take(num_generated_patterns)
|
|
|
|
.map(|pattern| {
|
|
|
|
// We represent patterns as dense matrices with 1 if an entry is occupied,
|
|
|
|
// 0 otherwise
|
|
|
|
let values = vec![1; pattern.nnz()];
|
|
|
|
CsrMatrix::try_from_pattern_and_values(pattern, values).unwrap()
|
|
|
|
})
|
|
|
|
.map(|csr| DMatrix::from(&csr))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let all_possible_patterns = generate_all_possible_matrices(0..=1, major_dims, minor_dims);
|
|
|
|
|
|
|
|
assert_eq!(visited_patterns.len(), all_possible_patterns.len());
|
|
|
|
assert_eq!(visited_patterns, all_possible_patterns);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sample_matrix_output_space<S>(strategy: S,
|
|
|
|
runner: &mut TestRunner,
|
|
|
|
num_samples: usize)
|
|
|
|
-> HashSet<DMatrix<i32>>
|
|
|
|
where
|
|
|
|
S: Strategy,
|
|
|
|
DMatrix<i32>: for<'b> From<&'b S::Value>
|
|
|
|
{
|
|
|
|
sample_strategy(strategy, runner)
|
|
|
|
.take(num_samples)
|
|
|
|
.map(|matrix| DMatrix::from(&matrix))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sample_strategy<'a, S: 'a + Strategy>(strategy: S, runner: &'a mut TestRunner)
|
|
|
|
-> impl 'a + Iterator<Item=S::Value> {
|
|
|
|
repeat(()).map(move |_| {
|
|
|
|
let tree = strategy
|
|
|
|
.new_tree(runner)
|
|
|
|
.expect("Tree generation should not fail");
|
|
|
|
let value = tree.current();
|
|
|
|
value
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|