forked from IrreducibleOSS/binius
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example: Projected / Repeated columns usage (#6)
* example: Add example of Projected column usage * example: Add example of Repeated column usage * example: Add example of ZeroPadded column usage
- Loading branch information
Showing
4 changed files
with
429 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
use binius_circuits::{builder::ConstraintSystemBuilder, unconstrained::unconstrained}; | ||
use binius_core::{constraint_system::validate::validate_witness, oracle::ProjectionVariant}; | ||
use binius_field::{arch::OptimalUnderlier, BinaryField128b, BinaryField8b}; | ||
|
||
type U = OptimalUnderlier; | ||
type F128 = BinaryField128b; | ||
type F8 = BinaryField8b; | ||
|
||
#[derive(Clone)] | ||
struct U8U128ProjectionInfo { | ||
log_size: usize, | ||
decimal: usize, | ||
binary: Vec<F128>, | ||
variant: ProjectionVariant, | ||
} | ||
|
||
// The idea behind projection is that data from a column of some given field (F8) | ||
// can be interpreted as a data of some or greater field (F128) and written to another column with equal or smaller length, | ||
// which depends on LOG_SIZE and values of projection. Also two possible variants of projections are available, which | ||
// has significant impact on input data processing. | ||
// In the following example we have input column with bytes (u8) projected to the output column with u128 values. | ||
fn projection( | ||
builder: &mut ConstraintSystemBuilder<U, F128>, | ||
projection_info: U8U128ProjectionInfo, | ||
namespace: &str, | ||
) { | ||
builder.push_namespace(format!("projection {}", namespace)); | ||
|
||
let input = | ||
unconstrained::<U, F128, F8>(builder, "in", projection_info.clone().log_size).unwrap(); | ||
|
||
let projected = builder | ||
.add_projected( | ||
"projected", | ||
input, | ||
projection_info.clone().binary, | ||
projection_info.clone().variant, | ||
) | ||
.unwrap(); | ||
|
||
if let Some(witness) = builder.witness() { | ||
let input_values = witness.get::<F8>(input).unwrap().as_slice::<u8>(); | ||
let mut projected_witness = witness.new_column::<F128>(projected); | ||
let projected_values = projected_witness.as_mut_slice::<F128>(); | ||
|
||
assert_eq!(projected_values.len(), projection_info.expected_projection_len()); | ||
|
||
match projection_info.variant { | ||
ProjectionVariant::FirstVars => { | ||
// Quite elaborated regularity, on my opinion | ||
for idx in 0..projected_values.len() { | ||
projected_values[idx] = F128::new( | ||
input_values[(idx | ||
* 2usize.pow(projection_info.clone().binary.len() as u32)) | ||
+ projection_info.clone().decimal] as u128, | ||
); | ||
} | ||
} | ||
ProjectionVariant::LastVars => { | ||
// decimal representation of the binary values is used as a simple offset | ||
for idx in 0..projected_values.len() { | ||
projected_values[idx] = | ||
F128::new(input_values[projection_info.clone().decimal + idx] as u128); | ||
} | ||
} | ||
}; | ||
} | ||
builder.pop_namespace(); | ||
} | ||
|
||
impl U8U128ProjectionInfo { | ||
fn new( | ||
log_size: usize, | ||
decimal: usize, | ||
binary: Vec<F128>, | ||
variant: ProjectionVariant, | ||
) -> U8U128ProjectionInfo { | ||
assert!(log_size >= binary.len()); | ||
|
||
if variant == ProjectionVariant::LastVars { | ||
// Pad with zeroes to LOG_SIZE len iterator. | ||
// In this case we interpret binary values in a reverse order, meaning that the very first | ||
// element is elder byte, so zeroes must be explicitly appended | ||
let mut binary_clone = binary.clone(); | ||
let mut zeroes = vec![F128::new(0u128); log_size - binary.len()]; | ||
binary_clone.append(&mut zeroes); | ||
|
||
let coefficients = (0..binary_clone.len()) | ||
.map(|degree| F128::new(2usize.pow(degree as u32) as u128)) | ||
.collect::<Vec<F128>>(); | ||
|
||
let value = binary_clone | ||
.iter() | ||
.zip(coefficients.iter().rev()) | ||
.fold(F128::new(0u128), |acc, (byte, coefficient)| acc + (*byte) * (*coefficient)); | ||
|
||
assert_eq!(decimal as u128, value.val()); | ||
} | ||
|
||
U8U128ProjectionInfo { | ||
log_size, | ||
decimal, | ||
binary, | ||
variant, | ||
} | ||
} | ||
|
||
fn expected_projection_len(&self) -> usize { | ||
2usize.pow((self.log_size - self.binary.len()) as u32) | ||
} | ||
} | ||
|
||
fn main() { | ||
let allocator = bumpalo::Bump::new(); | ||
let mut builder = ConstraintSystemBuilder::<U, F128>::new_with_witness(&allocator); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
4usize, | ||
9usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(0u128), | ||
F128::from(0u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::FirstVars, | ||
); | ||
projection(&mut builder, projection_data, "test_1"); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
16usize, | ||
34816usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(0u128), | ||
F128::from(0u128), | ||
F128::from(0u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::LastVars, | ||
); | ||
projection(&mut builder, projection_data, "test_2"); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
4usize, | ||
15usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::LastVars, | ||
); | ||
projection(&mut builder, projection_data, "test_3"); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
6usize, | ||
60usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::LastVars, | ||
); | ||
/* | ||
With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input, | ||
the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4. | ||
the index of the input byte to use as projection is computed as follows (according to | ||
a LastVars projection variant regularity): | ||
idx + decimal, e.g.: | ||
0 + 60 | ||
1 + 60 | ||
2 + 60 | ||
3 + 60 | ||
where idx is [0..4]. | ||
Memory layout: | ||
input: [a5, a2, b1, 60, 91, ed, 5e, fb, ae, 1c, b2, 14, 92, 73, 92, c8, 56, 6d, fa, de, a8, 46, 77, 48, e1, cc, 90, 75, 78, d5, 19, be, 0c, 86, 39, 28, 0c, cc, e9, 4e, 46, d9, 84, 65, 4a, a2, b4, 64, eb, 59, 7b, fd, 3f, 0e, 2d, ea, 06, 42, a9, ea, (19), (8f), (19), (52)], len: 64 | ||
output: [ | ||
BinaryField128b(0x00000000000000000000000000000019), | ||
BinaryField128b(0x0000000000000000000000000000008f), | ||
BinaryField128b(0x00000000000000000000000000000019), | ||
BinaryField128b(0x00000000000000000000000000000052) | ||
] | ||
*/ | ||
projection(&mut builder, projection_data, "test_4"); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
4usize, | ||
15usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::FirstVars, | ||
); | ||
projection(&mut builder, projection_data, "test_5"); | ||
|
||
let projection_data = U8U128ProjectionInfo::new( | ||
6usize, | ||
13usize, | ||
vec![ | ||
F128::from(1u128), | ||
F128::from(0u128), | ||
F128::from(1u128), | ||
F128::from(1u128), | ||
], | ||
ProjectionVariant::FirstVars, | ||
); | ||
/* | ||
With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input, | ||
the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4. | ||
the index of the input byte to use as projection is computed as follows: | ||
idx * 2usize.pow(binary.len()) + decimal, e.g.: | ||
0 * 2.pow(4) + 13 = 13, so input[13] | ||
1 * 2.pow(4) + 13 = 29, so input[29] | ||
2 * 2.pow(4) + 13 = 45, so input[45] | ||
3 * 2.pow(4) + 13 = 61, so input[61] | ||
where idx is [0..4] according to a FirstVars projection variant regularity. | ||
Memory layout: | ||
input: [18, d8, 58, d3, 24, f1, 8b, ec, 74, 1c, ab, 78, 13, (3e), 57, d7, 36, 15, 54, 50, 9a, cb, 98, 90, 58, cb, 79, 05, 83, (72), ea, 4d, f6, 3d, f3, 2f, af, e3, 32, 11, c9, 97, fb, ba, 24, (36), e9, 38, 7e, c7, a9, 68, bf, 31, 51, cf, 7b, 12, 20, 53, d8, (df), d7, cc], len: 64 | ||
output: BinaryField128b(0x0000000000000000000000000000003e) | ||
output: BinaryField128b(0x00000000000000000000000000000072) | ||
output: BinaryField128b(0x00000000000000000000000000000036) | ||
output: BinaryField128b(0x000000000000000000000000000000df) | ||
*/ | ||
projection(&mut builder, projection_data, "test_6"); | ||
|
||
let witness = builder.take_witness().unwrap(); | ||
let cs = builder.build().unwrap(); | ||
|
||
validate_witness(&cs, &[], &witness).unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use binius_circuits::{builder::ConstraintSystemBuilder, unconstrained::unconstrained}; | ||
use binius_core::constraint_system::validate::validate_witness; | ||
use binius_field::{ | ||
arch::OptimalUnderlier, packed::set_packed_slice, BinaryField128b, BinaryField1b, | ||
BinaryField8b, PackedBinaryField128x1b, | ||
}; | ||
|
||
type U = OptimalUnderlier; | ||
type F128 = BinaryField128b; | ||
type F8 = BinaryField8b; | ||
type F1 = BinaryField1b; | ||
|
||
// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production | ||
|
||
const LOG_SIZE: usize = 8; | ||
|
||
// The idea of 'Repeated' column is that one can just copy data from initial column multiple times, | ||
// so new column is X times bigger than original one. The following gadget operates over bytes, e.g. | ||
// it creates column with some input bytes written and then creates one more 'Repeated' column | ||
// where the same bytes are copied multiple times. | ||
fn bytes_repeat_gadget(builder: &mut ConstraintSystemBuilder<U, F128>) { | ||
builder.push_namespace("bytes_repeat_gadget"); | ||
|
||
let bytes = unconstrained::<U, F128, F8>(builder, "input", LOG_SIZE).unwrap(); | ||
|
||
let repeat_times_log = 4usize; | ||
let repeating = builder | ||
.add_repeating("repeating", bytes, repeat_times_log) | ||
.unwrap(); | ||
|
||
if let Some(witness) = builder.witness() { | ||
let input_values = witness.get::<F8>(bytes).unwrap().as_slice::<u8>(); | ||
|
||
let mut repeating_witness = witness.new_column::<F8>(repeating); | ||
let repeating_values = repeating_witness.as_mut_slice::<u8>(); | ||
|
||
let repeat_times = 2usize.pow(repeat_times_log as u32); | ||
assert_eq!(2usize.pow(LOG_SIZE as u32), input_values.len()); | ||
assert_eq!(input_values.len() * repeat_times, repeating_values.len()); | ||
|
||
for idx in 0..repeat_times { | ||
let start = idx * input_values.len(); | ||
let end = start + input_values.len(); | ||
repeating_values[start..end].copy_from_slice(input_values); | ||
} | ||
} | ||
|
||
builder.pop_namespace(); | ||
} | ||
|
||
// Bit-oriented repeating is more elaborated due to a specifics of memory layout in Binius. | ||
// In the following example, we use LOG_SIZE=8, which gives 2.pow(8) = 32 bytes written in the memory | ||
// layout. This gives 32 * 8 = 256 bits of input information. Having that Repeated' column | ||
// is instantiated with 'repeat_times_log = 2', this means that we have to repeat our bytes | ||
// 2.pow(repeat_times_log) = 4 times ultimately. For setting bit values we use PackedBinaryField128x1b, | ||
// so for 32 bytes (256 bits) of input data we use 2 PackedBinaryField128x1b elements. Considering 4 | ||
// repetitions Binius creates column with 8 PackedBinaryField128x1b elements totally. | ||
// Proper writing bits requires separate iterating over PackedBinaryField128x1b elements and input bytes | ||
// with extracting particular bit values from the input and setting appropriate bit in a given PackedBinaryField128x1b. | ||
fn bits_repeat_gadget(builder: &mut ConstraintSystemBuilder<U, F128>) { | ||
builder.push_namespace("bits_repeat_gadget"); | ||
|
||
let bits = unconstrained::<U, F128, F1>(builder, "input", LOG_SIZE).unwrap(); | ||
let repeat_times_log = 2usize; | ||
|
||
// Binius will create column with appropriate height for us | ||
let repeating = builder | ||
.add_repeating("repeating", bits, repeat_times_log) | ||
.unwrap(); | ||
|
||
if let Some(witness) = builder.witness() { | ||
let input_values = witness.get::<F1>(bits).unwrap().as_slice::<u8>(); | ||
let mut repeating_witness = witness.new_column::<F1>(repeating); | ||
let output_values = repeating_witness.packed(); | ||
|
||
// this performs writing input bits exactly 1 time. Depending on number of repetitions we | ||
// need to call this multiple times, providing offset for output values (PackedBinaryField128x1b elements) | ||
fn write_input( | ||
input_values: &[u8], | ||
output_values: &mut [PackedBinaryField128x1b], | ||
output_packed_offset: usize, | ||
) { | ||
let mut output_index = output_packed_offset; | ||
for (input_index, _) in (0..input_values.len()).enumerate() { | ||
let byte = input_values[input_index]; | ||
|
||
set_packed_slice(output_values, output_index, F1::from(byte)); | ||
set_packed_slice(output_values, output_index + 1, F1::from((byte >> 1) & 0x01)); | ||
set_packed_slice(output_values, output_index + 2, F1::from((byte >> 2) & 0x01)); | ||
set_packed_slice(output_values, output_index + 3, F1::from((byte >> 3) & 0x01)); | ||
set_packed_slice(output_values, output_index + 4, F1::from((byte >> 4) & 0x01)); | ||
set_packed_slice(output_values, output_index + 5, F1::from((byte >> 5) & 0x01)); | ||
set_packed_slice(output_values, output_index + 6, F1::from((byte >> 6) & 0x01)); | ||
set_packed_slice(output_values, output_index + 7, F1::from((byte >> 7) & 0x01)); | ||
|
||
output_index += 8; | ||
} | ||
} | ||
|
||
let repeat_times = 2u32.pow(repeat_times_log as u32); | ||
|
||
let mut offset = 0; | ||
for _ in 0..repeat_times { | ||
write_input(input_values, output_values, offset); | ||
offset += input_values.len() * 8; | ||
} | ||
} | ||
|
||
builder.pop_namespace(); | ||
} | ||
|
||
fn main() { | ||
let allocator = bumpalo::Bump::new(); | ||
|
||
let mut builder = ConstraintSystemBuilder::<U, F128>::new_with_witness(&allocator); | ||
|
||
bytes_repeat_gadget(&mut builder); | ||
bits_repeat_gadget(&mut builder); | ||
|
||
let witness = builder.take_witness().unwrap(); | ||
let cs = builder.build().unwrap(); | ||
|
||
validate_witness(&cs, &[], &witness).unwrap(); | ||
} |
Oops, something went wrong.