forked from IrreducibleOSS/binius
-
Notifications
You must be signed in to change notification settings - Fork 0
example: Projected / Repeated columns usage #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These gadgets are missing the push/pop of a namespace
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, thanks! This is important, when multiple gadgets are combined into the circuit