Skip to content

Commit

Permalink
feat: add helper methods to dynamic Dory structure (PROOF-919) (#198)
Browse files Browse the repository at this point in the history
# Rationale for this change
The GPU implementation of the dynamic Dory commitment computation
requires a number of helper methods in the dynamic Dory structure. This
PR implements methods that will be used by the GPU implementation.

# What changes are included in this PR?
- Add `index_from_row_and_column` method which is the inverse of
`row_and_column_from_index` method. Given a `row` and `column` it will
provide the `index` of the original data. The method also handles cases
where `row` and `column` do not provide a valid `index`.
- Add `matrix_size` method which returns the width and height of the
dynamic Dory matrix structure that will hold the given data.
- Made helper methods `const` for compile-time evaluation.
- Add temporary `#[allow(dead_code)]` to address `cargo clippy` warnings
- these will be removed when the dynamic Dory commit computation is
implemented for the GPU.

# Are these changes tested?
Yes
  • Loading branch information
jacobtrombetta authored Oct 2, 2024
1 parent be2fbf2 commit 3fb460f
Showing 1 changed file with 159 additions and 3 deletions.
162 changes: 159 additions & 3 deletions crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,53 @@
//! ...
//! ```

/// Returns the full width of a row in the matrix.
///
/// Note: when row = 1, this correctly returns 2, even though no data belongs at position 0.
#[allow(dead_code)]
pub(crate) const fn full_width_of_row(row: usize) -> usize {
((2 * row + 4) / 3).next_power_of_two()
}

/// Returns the index that belongs in the first column in a particular row.
///
/// Note: when row = 1, this correctly returns 0, even though no data belongs there.
#[cfg(test)]
pub(crate) fn row_start_index(row: usize) -> usize {
let width_of_row = ((2 * row + 4) / 3).next_power_of_two();
pub(crate) const fn row_start_index(row: usize) -> usize {
let width_of_row = full_width_of_row(row);
width_of_row * (row - width_of_row / 2)
}

/// Returns the (row, column) in the matrix where the data with the given index belongs.
pub(crate) fn row_and_column_from_index(index: usize) -> (usize, usize) {
pub(crate) const fn row_and_column_from_index(index: usize) -> (usize, usize) {
let width_of_row = 1 << (((2 * index + 1).ilog2() + 1) / 2);
let row = index / width_of_row + width_of_row / 2;
let column = index % width_of_row;
(row, column)
}

/// Returns the index of data where the (row, column) belongs.
#[allow(dead_code)]
pub(crate) fn index_from_row_and_column(row: usize, column: usize) -> Option<usize> {
let width_of_row = full_width_of_row(row);
(column < width_of_row && (row, column) != (1, 0))
.then_some(width_of_row * (row - width_of_row / 2) + column)
}

/// Returns a matrix size, (height, width), that can hold the given number of data points being committed with respect to an offset.
///
/// Note: when data_len = 0 and offset = 0, this function returns an empty matrix with size (0, 0).
#[allow(dead_code)]
pub(crate) const fn matrix_size(data_len: usize, offset: usize) -> (usize, usize) {
if data_len == 0 && offset == 0 {
return (0, 0);
}

let (last_row, _) = row_and_column_from_index(offset + data_len - 1);
let width_of_last_row = full_width_of_row(last_row);
(last_row + 1, width_of_last_row)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -125,4 +156,129 @@ mod tests {
}
}
}
#[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: {} does not produce expected width",
row
);
}
}
#[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 ({}, {}) generated no index",
row,
column
);
} else {
assert!(
index.is_none(),
"Invalid pair ({}, {}) generated a valid index",
row,
column
);
}
}
}
}
}

0 comments on commit 3fb460f

Please sign in to comment.