#[test] #[ignore] fn coo_no_duplicates_generates_admissible_matrices() { //TODO } #[cfg(feature = "slow-tests")] mod slow { use nalgebra::DMatrix; use nalgebra_sparse::proptest::{ coo_no_duplicates, coo_with_duplicates, csc, csr, sparsity_pattern, }; use itertools::Itertools; use proptest::strategy::ValueTree; use proptest::test_runner::TestRunner; use proptest::prelude::*; use nalgebra_sparse::csr::CsrMatrix; use std::collections::HashSet; use std::iter::repeat; use std::ops::RangeInclusive; fn generate_all_possible_matrices( value_range: RangeInclusive, rows_range: RangeInclusive, cols_range: RangeInclusive, ) -> HashSet> { // 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, )); } } } } all_combinations } #[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. // Test that the proptest generation covers all possible outputs for a small space of inputs // given enough samples. // 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 = coo_no_duplicates(values.clone(), rows.clone(), cols.clone(), max_nnz); // Enumerate all possible combinations 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 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; let max_nnz = rows.end() * cols.end(); let strategy = coo_with_duplicates(values.clone(), rows.clone(), cols.clone(), max_nnz, 2); // 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); let visited_combinations = sample_matrix_output_space(strategy, &mut runner, num_generated_matrices); // 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)); } #[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( strategy: S, runner: &mut TestRunner, num_samples: usize, ) -> HashSet> where S: Strategy, DMatrix: 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 { repeat(()).map(move |_| { let tree = strategy .new_tree(runner) .expect("Tree generation should not fail"); let value = tree.current(); value }) } }