diff --git a/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/standard_basis_helper.rs b/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/standard_basis_helper.rs index c329566f1..37b2d5202 100644 --- a/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/standard_basis_helper.rs +++ b/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/standard_basis_helper.rs @@ -184,3 +184,523 @@ pub(crate) fn fold_dynamic_standard_basis_tensors + From std::ops::AddAssign<&'a mut S> + Copy>( + mut vec: &mut [S], + fold_factors: &[S], + ) { + let nu = fold_factors.len(); + assert_eq!(vec.len(), 1 << fold_factors.len()); + for i in (0..nu).rev() { + let (lo, hi) = vec.split_at_mut(vec.len() / 2); + lo.iter_mut().zip(hi).for_each(|(l, h)| { + *l *= fold_factors[i]; + *l += h; + }); + vec = lo; + } + } + + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_0_point() { + let mut lo_vec = vec![::ZERO; 2]; + let mut hi_vec = vec![::ZERO; 2]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![]; + let lo_vec_expected = vec![F::from(2), ::ZERO]; + let hi_vec_expected = vec![F::from(3), F::from(3)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_1_point() { + let mut lo_vec = vec![::ZERO; 2]; + let mut hi_vec = vec![::ZERO; 2]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![F::from(5)]; + let lo_vec_expected = vec![F::from(2), F::from(2 * 5)]; + let hi_vec_expected = vec![F::from(3), F::from(3)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_2_point() { + let mut lo_vec = vec![::ZERO; 4]; + let mut hi_vec = vec![::ZERO; 4]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![F::from(5), F::from(7)]; + let lo_vec_expected = vec![ + F::from(2), + F::from(2 * 5), + F::from(2 * 7), + F::from(2 * 5 * 7), + ]; + let hi_vec_expected = vec![ + F::from(3), + F::from(3), + F::from(3 * 7), + ::ZERO, + ]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_3_point() { + let mut lo_vec = vec![::ZERO; 4]; + let mut hi_vec = vec![::ZERO; 4]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![F::from(5), F::from(7), F::from(11)]; + let lo_vec_expected = vec![ + F::from(2), + F::from(2 * 5), + F::from(2 * 7), + F::from(2 * 5 * 7), + ]; + let hi_vec_expected = vec![F::from(3), F::from(3), F::from(3 * 7), F::from(3 * 11)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_4_point() { + let mut lo_vec = vec![::ZERO; 8]; + let mut hi_vec = vec![::ZERO; 8]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![F::from(5), F::from(7), F::from(11), F::from(13)]; + let lo_vec_expected = vec![ + F::from(2), + F::from(2 * 5), + F::from(2 * 7), + F::from(2 * 5 * 7), + F::from(2 * 11), + F::from(2 * 5 * 11), + F::from(2 * 7 * 11), + F::from(2 * 5 * 7 * 11), + ]; + let hi_vec_expected = vec![ + F::from(3), + F::from(3), + F::from(3 * 7), + F::from(3 * 11), + F::from(3 * 13), + F::from(3 * 11 * 13), + ::ZERO, + ::ZERO, + ]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + #[test] + fn we_can_compute_dynamic_standard_basis_vecs_from_length_5_point() { + let mut lo_vec = vec![::ZERO; 8]; + let mut hi_vec = vec![::ZERO; 8]; + lo_vec[0] = F::from(2); + hi_vec[0] = F::from(3); + let point = vec![ + F::from(5), + F::from(7), + F::from(11), + F::from(13), + F::from(17), + ]; + let lo_vec_expected = vec![ + F::from(2), + F::from(2 * 5), + F::from(2 * 7), + F::from(2 * 5 * 7), + F::from(2 * 11), + F::from(2 * 5 * 11), + F::from(2 * 7 * 11), + F::from(2 * 5 * 7 * 11), + ]; + let hi_vec_expected = vec![ + F::from(3), + F::from(3), + F::from(3 * 7), + F::from(3 * 11), + F::from(3 * 13), + F::from(3 * 11 * 13), + F::from(3 * 17), + F::from(3 * 13 * 17), + ]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + assert_eq!(lo_vec, lo_vec_expected); + assert_eq!(hi_vec, hi_vec_expected); + } + + /// Computes the evaluation of a basis monomial at the given point. + /// + /// In other words, the result is `prod point[i]^(b[i])` where + /// `index = sum 2^i*b[i]` and `b[i]` is `0` or `1`. (i.e. `b` is the binary representation of `index`.) + /// Note: `point` is padded with zeros as needed. + /// + /// This method is primarily to test the `build_standard_basis_vecs` method. + fn get_binary_eval(index: usize, point: &[F]) -> F { + core::iter::successors(Some(index), |&k| match k >> 1 { + 0 => None, + k => Some(k), + }) + .enumerate() + .filter_map(|(i, b)| { + if b % 2 == 0 { + None + } else { + Some(point.get(i).copied().unwrap_or(::ZERO)) + } + }) + .product() + } + + #[test] + fn we_can_compute_dynamic_random_standard_basis_vecs() { + use ark_std::{test_rng, UniformRand}; + use itertools::Itertools; + let mut rng = test_rng(); + for num_vars in 0..10 { + let point = core::iter::repeat_with(|| F::rand(&mut rng)) + .take(num_vars) + .collect_vec(); + let alpha = F::rand(&mut rng); + let beta = F::rand(&mut rng); + let nu = point.len() / 2 + 1; + let mut lo_vec = vec![::ZERO; 1 << nu]; + let mut hi_vec = vec![::ZERO; 1 << nu]; + lo_vec[0] = alpha; + hi_vec[0] = beta; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + for i in 0..1 << nu { + assert_eq!(lo_vec[i], alpha * get_binary_eval(i, &point)); + assert_eq!( + hi_vec[i], + beta * get_binary_eval(row_start_index(i), &point) + ); + } + } + } + + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_0_point() { + let mut lo_vec = vec![::ZERO; 2]; + let mut hi_vec = vec![::ZERO; 2]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = vec![]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200)]; + let alpha_invs = vec![F::from(300)]; + let lo_fold_expected = lo_vec[0] * F::from(200) + lo_vec[1]; + let hi_fold_expected = hi_vec[0] * F::from(300) + hi_vec[1]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_1_point() { + let mut lo_vec = vec![::ZERO; 2]; + let mut hi_vec = vec![::ZERO; 2]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = vec![F::from(5)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200)]; + let alpha_invs = vec![F::from(300)]; + let lo_fold_expected = lo_vec[0] * F::from(200) + lo_vec[1]; + let hi_fold_expected = hi_vec[0] * F::from(300) + hi_vec[1]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_2_point() { + let mut lo_vec = vec![::ZERO; 4]; + let mut hi_vec = vec![::ZERO; 4]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = vec![F::from(5), F::from(7)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200), F::from(201)]; + let alpha_invs = vec![F::from(300), F::from(301)]; + let lo_fold_expected = lo_vec[0] * F::from(200 * 201) + + lo_vec[1] * F::from(201) + + lo_vec[2] * F::from(200) + + lo_vec[3]; + let hi_fold_expected = hi_vec[0] * F::from(300 * 301) + + hi_vec[1] * F::from(301) + + hi_vec[2] * F::from(300) + + hi_vec[3]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_3_point() { + let mut lo_vec = vec![::ZERO; 4]; + let mut hi_vec = vec![::ZERO; 4]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = vec![F::from(5), F::from(7), F::from(11)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200), F::from(201)]; + let alpha_invs = vec![F::from(300), F::from(301)]; + let lo_fold_expected = lo_vec[0] * F::from(200 * 201) + + lo_vec[1] * F::from(201) + + lo_vec[2] * F::from(200) + + lo_vec[3]; + let hi_fold_expected = hi_vec[0] * F::from(300 * 301) + + hi_vec[1] * F::from(301) + + hi_vec[2] * F::from(300) + + hi_vec[3]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_4_point() { + let mut lo_vec = vec![::ZERO; 8]; + let mut hi_vec = vec![::ZERO; 8]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = vec![F::from(5), F::from(7), F::from(11), F::from(13)]; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200), F::from(201), F::from(202)]; + let alpha_invs = vec![F::from(300), F::from(301), F::from(302)]; + let lo_fold_expected = lo_vec[0] * F::from(200 * 201 * 202) + + lo_vec[1] * F::from(201 * 202) + + lo_vec[2] * F::from(200 * 202) + + lo_vec[3] * F::from(202) + + lo_vec[4] * F::from(200 * 201) + + lo_vec[5] * F::from(201) + + lo_vec[6] * F::from(200) + + lo_vec[7]; + let hi_fold_expected = hi_vec[0] * F::from(300 * 301 * 302) + + hi_vec[1] * F::from(301 * 302) + + hi_vec[2] * F::from(300 * 302) + + hi_vec[3] * F::from(302) + + hi_vec[4] * F::from(300 * 301) + + hi_vec[5] * F::from(301) + + hi_vec[6] * F::from(300) + + hi_vec[7]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_fold_dynamic_standard_basis_tensors_of_length_5_point() { + let mut lo_vec = vec![::ZERO; 8]; + let mut hi_vec = vec![::ZERO; 8]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + let point = [5, 7, 11, 13, 17].map(F::from); + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = vec![F::from(200), F::from(201), F::from(202)]; + let alpha_invs = vec![F::from(300), F::from(301), F::from(302)]; + let lo_fold_expected = lo_vec[0] * F::from(200 * 201 * 202) + + lo_vec[1] * F::from(201 * 202) + + lo_vec[2] * F::from(200 * 202) + + lo_vec[3] * F::from(202) + + lo_vec[4] * F::from(200 * 201) + + lo_vec[5] * F::from(201) + + lo_vec[6] * F::from(200) + + lo_vec[7]; + let hi_fold_expected = hi_vec[0] * F::from(300 * 301 * 302) + + hi_vec[1] * F::from(301 * 302) + + hi_vec[2] * F::from(300 * 302) + + hi_vec[3] * F::from(302) + + hi_vec[4] * F::from(300 * 301) + + hi_vec[5] * F::from(301) + + hi_vec[6] * F::from(300) + + hi_vec[7]; + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + assert_eq!(lo_fold, lo_fold_expected); + assert_eq!(hi_fold, hi_fold_expected); + } + #[test] + fn we_can_naive_fold_length_0_fold_factors() { + let fold_factors = vec![]; + let mut vec = vec![F::from(100)]; + naive_fold(&mut vec, &fold_factors); + assert_eq!(vec[0], F::from(100)); + } + #[test] + fn we_can_naive_fold_length_1_fold_factors() { + let fold_factors = vec![F::from(2)]; + let mut vec = vec![F::from(100), F::from(101)]; + naive_fold(&mut vec, &fold_factors); + assert_eq!(vec[0], F::from(100 * 2 + 101)); + } + #[test] + fn we_can_naive_fold_length_2_fold_factors() { + let fold_factors = vec![F::from(2), F::from(3)]; + let mut vec = vec![F::from(100), F::from(101), F::from(102), F::from(103)]; + naive_fold(&mut vec, &fold_factors); + assert_eq!(vec[0], F::from(100 * 2 * 3 + 101 * 3 + 102 * 2 + 103)); + } + #[test] + fn we_can_naive_fold_length_3_fold_factors() { + let fold_factors = vec![F::from(2), F::from(3), F::from(5)]; + let mut vec = [100, 101, 102, 103, 104, 105, 106, 107].map(F::from); + naive_fold(&mut vec, &fold_factors); + assert_eq!( + vec[0], + F::from( + 100 * 2 * 3 * 5 + + 101 * 3 * 5 + + 102 * 2 * 5 + + 103 * 5 + + 104 * 2 * 3 + + 105 * 3 + + 106 * 2 + + 107 + ) + ); + } + + #[test] + fn we_can_fold_dynamic_random_standard_basis_tensors() { + use ark_std::{test_rng, UniformRand}; + use itertools::Itertools; + let mut rng = test_rng(); + for num_vars in 0..10 { + let point = core::iter::repeat_with(|| F::rand(&mut rng)) + .take(num_vars) + .collect_vec(); + let nu = point.len() / 2 + 1; + let mut lo_vec = vec![::ZERO; 1 << nu]; + let mut hi_vec = vec![::ZERO; 1 << nu]; + lo_vec[0] = ::ONE; + hi_vec[0] = ::ONE; + compute_dynamic_standard_basis_vecs::(&point, &mut lo_vec, &mut hi_vec); + + let alphas = core::iter::repeat_with(|| F::rand(&mut rng)) + .take(nu) + .collect_vec(); + let alpha_invs = core::iter::repeat_with(|| F::rand(&mut rng)) + .take(nu) + .collect_vec(); + let (lo_fold, hi_fold) = + fold_dynamic_standard_basis_tensors::(&point, &alphas, &alpha_invs); + naive_fold(&mut lo_vec, &alphas); + naive_fold(&mut hi_vec, &alpha_invs); + assert_eq!(lo_fold, lo_vec[0]); + assert_eq!(hi_fold, hi_vec[0]); + } + } + + #[test] + fn we_can_compute_dynamic_vecs_for_length_0_point() { + let point = vec![]; + let expected_lo_vec = vec![F::from(1), F::from(0)]; + let expected_hi_vec = vec![F::from(1), F::from(1)]; + let (lo_vec, hi_vec) = compute_dynamic_vecs::(&point); + assert_eq!(expected_lo_vec, lo_vec); + assert_eq!(expected_hi_vec, hi_vec); + } + + #[test] + fn we_can_compute_dynamic_vecs_for_length_1_point() { + let point = vec![F::from(2)]; + let expected_lo_vec = vec![F::from(1 - 2), F::from(2)]; + let expected_hi_vec = vec![F::from(1), F::from(1)]; + let (lo_vec, hi_vec) = compute_dynamic_vecs::(&point); + assert_eq!(expected_lo_vec, lo_vec); + assert_eq!(expected_hi_vec, hi_vec); + } + + #[test] + fn we_can_compute_dynamic_vecs_for_length_2_point() { + let point = vec![F::from(2), F::from(3)]; + let expected_lo_vec = vec![ + F::from((1 - 2) * (1 - 3)), + F::from(2 * (1 - 3)), + F::from((1 - 2) * 3), + F::from(2 * 3), + ]; + let expected_hi_vec = vec![ + F::from(1), + F::from(1), + F::from(3) / F::from(1 - 3), + F::from(0), + ]; + let (lo_vec, hi_vec) = compute_dynamic_vecs::(&point); + assert_eq!(expected_lo_vec, lo_vec); + assert_eq!(expected_hi_vec, hi_vec); + } + + #[test] + fn we_can_compute_dynamic_vecs_for_length_3_point() { + let point = vec![F::from(2), F::from(3), F::from(5)]; + let expected_lo_vec = vec![ + F::from((1 - 2) * (1 - 3)), + F::from(2 * (1 - 3)), + F::from((1 - 2) * 3), + F::from(2 * 3), + ]; + let expected_hi_vec = vec![ + F::from(1 - 5), + F::from(1 - 5), + F::from((1 - 5) * 3) / F::from(1 - 3), + F::from(5), + ]; + let (lo_vec, hi_vec) = compute_dynamic_vecs::(&point); + assert_eq!(expected_lo_vec, lo_vec); + assert_eq!(expected_hi_vec, hi_vec); + } + + #[test] + fn we_can_compute_dynamic_vecs_that_matches_evaluation_vec() { + use ark_std::UniformRand; + let mut rng = ark_std::test_rng(); + for num_vars in 0..20 { + let point: Vec<_> = core::iter::repeat_with(|| F::rand(&mut rng)) + .take(num_vars) + .collect(); + let (lo_vec, hi_vec) = compute_dynamic_vecs::(&point); + let mut eval_vec = vec![::ZERO; 1 << num_vars]; + compute_evaluation_vector(&mut eval_vec, &point); + for (i, val) in eval_vec.into_iter().enumerate() { + let (row, column) = row_and_column_from_index(i); + assert_eq!(hi_vec[row] * lo_vec[column], val); + } + } + } +} diff --git a/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/structure.rs b/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/structure.rs index 89f8c59d6..35487144a 100644 --- a/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/structure.rs +++ b/crates/proof-of-sql/src/proof_primitive/dynamic_dory_and_hyrax_common_utils/structure.rs @@ -73,3 +73,204 @@ pub(crate) const fn matrix_size(data_len: usize, offset: usize) -> (usize, usize let width_of_last_row = full_width_of_row(last_row); (last_row + 1, width_of_last_row) } + +#[cfg(test)] +mod tests { + use super::*; + /// Creates a 2^nu by 2^nu matrix that is partially filled with the indexes that belong in each position. + fn create_position_matrix(nu: usize) -> Vec>> { + let max_index = 1 << (2 * nu - 1); + let mut matrix = vec![]; + for i in 0..max_index { + let (row, column) = row_and_column_from_index(i); + if matrix.len() <= row { + matrix.resize_with(row + 1, Vec::new); + } + if matrix[row].len() <= column { + matrix[row].resize(column + 1, None); + } + matrix[row][column] = Some(i); + } + matrix + } + #[test] + fn we_can_compute_positions_from_indexes() { + assert_eq!( + create_position_matrix(2), + vec![ + vec![Some(0)], // length 1 + vec![None, Some(1)], // length "1" + vec![Some(2), Some(3)], // length 2 + vec![Some(4), Some(5), Some(6), Some(7)], // length 4 + ], + ); + assert_eq!( + create_position_matrix(4), + vec![ + vec![Some(0)], // length 1 + vec![None, Some(1)], // length "1" + vec![Some(2), Some(3)], // length 2 + vec![Some(4), Some(5), Some(6), Some(7)], // length 4 + vec![Some(8), Some(9), Some(10), Some(11)], // length 4 + vec![Some(12), Some(13), Some(14), Some(15)], // length 4 + (16..=23).map(Some).collect(), // length 8 + (24..=31).map(Some).collect(), // length 8 + (32..=39).map(Some).collect(), // length 8 + (40..=47).map(Some).collect(), // length 8 + (48..=55).map(Some).collect(), // length 8 + (56..=63).map(Some).collect(), // length 8 + (64..=79).map(Some).collect(), // length 16 + (80..=95).map(Some).collect(), // length 16 + (96..=111).map(Some).collect(), // length 16 + (112..=127).map(Some).collect() // length 16 + ], + ); + } + #[test] + fn we_can_fill_matrix_with_no_collisions_and_correct_size_and_row_starts() { + for nu in 1..10 { + let num_vars = nu * 2 - 1; + let matrix = create_position_matrix(nu); + // Every position should be unique. + assert_eq!( + matrix.iter().flatten().filter(|&x| x.is_some()).count(), + 1 << num_vars + ); + // The matrix should have 2^nu rows. + assert_eq!(matrix.len(), 1 << nu); + // The matrix should have 2^nu columns. + assert_eq!(matrix.iter().map(Vec::len).max().unwrap(), 1 << nu); + for (index, row) in matrix.iter().enumerate() { + // The start of each row should match with `row_start_index`. + assert_eq!( + row_start_index(index), + match index { + 1 => 0, // Row 1 starts at 0, even though nothing is put in the 0th position. This is because the 0th index is at position (0, 0) + _ => row[0] + .expect("Every row except 1 should have a value in the 0th position."), + } + ); + } + } + } + #[test] + fn we_can_find_the_full_width_of_row() { + // This corresponds to a matrix with 2^(N+1) rows. + let n = 20; + let mut expected_widths = Vec::new(); + + // First three rows are defined by the dynamic Dory structure. + expected_widths.extend(std::iter::repeat(1).take(1)); + expected_widths.extend(std::iter::repeat(2).take(2)); + + // The rest of the rows are defined by the pattern 3*2^n rows of length 4*2^n. + for n in 0..n { + let repeat_count = 3 * 2_usize.pow(n); + let value = 4 * 2_usize.pow(n); + expected_widths.extend(std::iter::repeat(value).take(repeat_count)); + } + + // Verify the widths. + for (row, width) in expected_widths.iter().enumerate() { + let width_of_row = full_width_of_row(row); + assert_eq!( + width_of_row, *width, + "row: {row} does not produce expected width" + ); + } + } + #[test] + fn we_can_produce_the_correct_matrix_size() { + // NOTE: returned tuple is (height, width). + assert_eq!(matrix_size(0, 0), (0, 0)); + assert_eq!(matrix_size(1, 0), (1, 1)); + assert_eq!(matrix_size(2, 0), (2, 2)); + assert_eq!(matrix_size(3, 0), (3, 2)); + assert_eq!(matrix_size(4, 0), (3, 2)); + assert_eq!(matrix_size(5, 0), (4, 4)); + assert_eq!(matrix_size(6, 0), (4, 4)); + assert_eq!(matrix_size(7, 0), (4, 4)); + assert_eq!(matrix_size(8, 0), (4, 4)); + assert_eq!(matrix_size(9, 0), (5, 4)); + assert_eq!(matrix_size(10, 0), (5, 4)); + assert_eq!(matrix_size(11, 0), (5, 4)); + assert_eq!(matrix_size(12, 0), (5, 4)); + assert_eq!(matrix_size(13, 0), (6, 4)); + assert_eq!(matrix_size(14, 0), (6, 4)); + assert_eq!(matrix_size(15, 0), (6, 4)); + assert_eq!(matrix_size(16, 0), (6, 4)); + assert_eq!(matrix_size(17, 0), (7, 8)); + + assert_eq!(matrix_size(64, 0), (12, 8)); + assert_eq!(matrix_size(71, 0), (13, 16)); + assert_eq!(matrix_size(81, 0), (14, 16)); + assert_eq!(matrix_size(98, 0), (15, 16)); + assert_eq!(matrix_size(115, 0), (16, 16)); + } + #[test] + fn we_can_produce_the_correct_matrix_size_with_offset() { + // NOTE: returned tuple is (height, width). + assert_eq!(matrix_size(0, 0), (0, 0)); + assert_eq!(matrix_size(0, 1), (1, 1)); + assert_eq!(matrix_size(0, 2), (2, 2)); + assert_eq!(matrix_size(1, 1), (2, 2)); + assert_eq!(matrix_size(1, 2), (3, 2)); + assert_eq!(matrix_size(1, 3), (3, 2)); + assert_eq!(matrix_size(1, 4), (4, 4)); + assert_eq!(matrix_size(1, 5), (4, 4)); + assert_eq!(matrix_size(1, 6), (4, 4)); + assert_eq!(matrix_size(1, 7), (4, 4)); + assert_eq!(matrix_size(1, 8), (5, 4)); + assert_eq!(matrix_size(1, 9), (5, 4)); + assert_eq!(matrix_size(1, 10), (5, 4)); + assert_eq!(matrix_size(1, 11), (5, 4)); + assert_eq!(matrix_size(1, 12), (6, 4)); + assert_eq!(matrix_size(1, 13), (6, 4)); + assert_eq!(matrix_size(1, 14), (6, 4)); + assert_eq!(matrix_size(1, 15), (6, 4)); + assert_eq!(matrix_size(1, 16), (7, 8)); + + assert_eq!(matrix_size(1, 63), (12, 8)); + assert_eq!(matrix_size(1, 70), (13, 16)); + assert_eq!(matrix_size(1, 80), (14, 16)); + assert_eq!(matrix_size(1, 97), (15, 16)); + assert_eq!(matrix_size(1, 114), (16, 16)); + } + #[test] + fn we_can_find_the_index_for_row_column_pairs() { + use std::collections::HashSet; + + const MAX_INDEX: usize = 1 << 16; + let mut valid_pairs = HashSet::new(); + + // Collect all valid (row, column) pairs + for i in 0..MAX_INDEX { + let (row, column) = row_and_column_from_index(i); + valid_pairs.insert((row, column)); + } + + let (max_row, max_column) = valid_pairs + .iter() + .fold((0, 0), |(max_row, max_column), &(row, column)| { + (max_row.max(row), max_column.max(column)) + }); + + // Check that all valid pairs are valid and all invalid pairs are invalid + for row in 0..max_row { + for column in 0..max_column { + let index = index_from_row_and_column(row, column); + if valid_pairs.contains(&(row, column)) { + assert!( + index.is_some(), + "Valid pair ({row}, {column}) generated no index" + ); + } else { + assert!( + index.is_none(), + "Invalid pair ({row}, {column}) generated a valid index" + ); + } + } + } + } +}