Skip to content

Commit 08c585f

Browse files
committed
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
1 parent 0e50660 commit 08c585f

4 files changed

Lines changed: 429 additions & 0 deletions

File tree

examples/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ path = "acc-shifted.rs"
106106
name = "acc-packed"
107107
path = "acc-packed.rs"
108108

109+
[[example]]
110+
name = "acc-projected"
111+
path = "acc-projected.rs"
112+
113+
[[example]]
114+
name = "acc-repeated"
115+
path = "acc-repeated.rs"
116+
117+
[[example]]
118+
name = "acc-zeropadded"
119+
path = "acc-zeropadded.rs"
120+
109121
[lints.clippy]
110122
needless_range_loop = "allow"
111123

examples/acc-projected.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use binius_circuits::{builder::ConstraintSystemBuilder, unconstrained::unconstrained};
2+
use binius_core::{constraint_system::validate::validate_witness, oracle::ProjectionVariant};
3+
use binius_field::{arch::OptimalUnderlier, BinaryField128b, BinaryField8b};
4+
5+
type U = OptimalUnderlier;
6+
type F128 = BinaryField128b;
7+
type F8 = BinaryField8b;
8+
9+
#[derive(Clone)]
10+
struct U8U128ProjectionInfo {
11+
log_size: usize,
12+
decimal: usize,
13+
binary: Vec<F128>,
14+
variant: ProjectionVariant,
15+
}
16+
17+
// The idea behind projection is that data from a column of some given field (F8)
18+
// can be interpreted as a data of some or greater field (F128) and written to another column with equal or smaller length,
19+
// which depends on LOG_SIZE and values of projection. Also two possible variants of projections are available, which
20+
// has significant impact on input data processing.
21+
// In the following example we have input column with bytes (u8) projected to the output column with u128 values.
22+
fn projection(
23+
builder: &mut ConstraintSystemBuilder<U, F128>,
24+
projection_info: U8U128ProjectionInfo,
25+
namespace: &str,
26+
) {
27+
builder.push_namespace(format!("projection {}", namespace));
28+
29+
let input =
30+
unconstrained::<U, F128, F8>(builder, "in", projection_info.clone().log_size).unwrap();
31+
32+
let projected = builder
33+
.add_projected(
34+
"projected",
35+
input,
36+
projection_info.clone().binary,
37+
projection_info.clone().variant,
38+
)
39+
.unwrap();
40+
41+
if let Some(witness) = builder.witness() {
42+
let input_values = witness.get::<F8>(input).unwrap().as_slice::<u8>();
43+
let mut projected_witness = witness.new_column::<F128>(projected);
44+
let projected_values = projected_witness.as_mut_slice::<F128>();
45+
46+
assert_eq!(projected_values.len(), projection_info.expected_projection_len());
47+
48+
match projection_info.variant {
49+
ProjectionVariant::FirstVars => {
50+
// Quite elaborated regularity, on my opinion
51+
for idx in 0..projected_values.len() {
52+
projected_values[idx] = F128::new(
53+
input_values[(idx
54+
* 2usize.pow(projection_info.clone().binary.len() as u32))
55+
+ projection_info.clone().decimal] as u128,
56+
);
57+
}
58+
}
59+
ProjectionVariant::LastVars => {
60+
// decimal representation of the binary values is used as a simple offset
61+
for idx in 0..projected_values.len() {
62+
projected_values[idx] =
63+
F128::new(input_values[projection_info.clone().decimal + idx] as u128);
64+
}
65+
}
66+
};
67+
}
68+
builder.pop_namespace();
69+
}
70+
71+
impl U8U128ProjectionInfo {
72+
fn new(
73+
log_size: usize,
74+
decimal: usize,
75+
binary: Vec<F128>,
76+
variant: ProjectionVariant,
77+
) -> U8U128ProjectionInfo {
78+
assert!(log_size >= binary.len());
79+
80+
if variant == ProjectionVariant::LastVars {
81+
// Pad with zeroes to LOG_SIZE len iterator.
82+
// In this case we interpret binary values in a reverse order, meaning that the very first
83+
// element is elder byte, so zeroes must be explicitly appended
84+
let mut binary_clone = binary.clone();
85+
let mut zeroes = vec![F128::new(0u128); log_size - binary.len()];
86+
binary_clone.append(&mut zeroes);
87+
88+
let coefficients = (0..binary_clone.len())
89+
.map(|degree| F128::new(2usize.pow(degree as u32) as u128))
90+
.collect::<Vec<F128>>();
91+
92+
let value = binary_clone
93+
.iter()
94+
.zip(coefficients.iter().rev())
95+
.fold(F128::new(0u128), |acc, (byte, coefficient)| acc + (*byte) * (*coefficient));
96+
97+
assert_eq!(decimal as u128, value.val());
98+
}
99+
100+
U8U128ProjectionInfo {
101+
log_size,
102+
decimal,
103+
binary,
104+
variant,
105+
}
106+
}
107+
108+
fn expected_projection_len(&self) -> usize {
109+
2usize.pow((self.log_size - self.binary.len()) as u32)
110+
}
111+
}
112+
113+
fn main() {
114+
let allocator = bumpalo::Bump::new();
115+
let mut builder = ConstraintSystemBuilder::<U, F128>::new_with_witness(&allocator);
116+
117+
let projection_data = U8U128ProjectionInfo::new(
118+
4usize,
119+
9usize,
120+
vec![
121+
F128::from(1u128),
122+
F128::from(0u128),
123+
F128::from(0u128),
124+
F128::from(1u128),
125+
],
126+
ProjectionVariant::FirstVars,
127+
);
128+
projection(&mut builder, projection_data, "test_1");
129+
130+
let projection_data = U8U128ProjectionInfo::new(
131+
16usize,
132+
34816usize,
133+
vec![
134+
F128::from(1u128),
135+
F128::from(0u128),
136+
F128::from(0u128),
137+
F128::from(0u128),
138+
F128::from(1u128),
139+
],
140+
ProjectionVariant::LastVars,
141+
);
142+
projection(&mut builder, projection_data, "test_2");
143+
144+
let projection_data = U8U128ProjectionInfo::new(
145+
4usize,
146+
15usize,
147+
vec![
148+
F128::from(1u128),
149+
F128::from(1u128),
150+
F128::from(1u128),
151+
F128::from(1u128),
152+
],
153+
ProjectionVariant::LastVars,
154+
);
155+
projection(&mut builder, projection_data, "test_3");
156+
157+
let projection_data = U8U128ProjectionInfo::new(
158+
6usize,
159+
60usize,
160+
vec![
161+
F128::from(1u128),
162+
F128::from(1u128),
163+
F128::from(1u128),
164+
F128::from(1u128),
165+
],
166+
ProjectionVariant::LastVars,
167+
);
168+
/*
169+
With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input,
170+
the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4.
171+
the index of the input byte to use as projection is computed as follows (according to
172+
a LastVars projection variant regularity):
173+
174+
idx + decimal, e.g.:
175+
176+
0 + 60
177+
1 + 60
178+
2 + 60
179+
3 + 60
180+
181+
where idx is [0..4].
182+
183+
Memory layout:
184+
185+
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
186+
output: [
187+
BinaryField128b(0x00000000000000000000000000000019),
188+
BinaryField128b(0x0000000000000000000000000000008f),
189+
BinaryField128b(0x00000000000000000000000000000019),
190+
BinaryField128b(0x00000000000000000000000000000052)
191+
]
192+
*/
193+
projection(&mut builder, projection_data, "test_4");
194+
195+
let projection_data = U8U128ProjectionInfo::new(
196+
4usize,
197+
15usize,
198+
vec![
199+
F128::from(1u128),
200+
F128::from(1u128),
201+
F128::from(1u128),
202+
F128::from(1u128),
203+
],
204+
ProjectionVariant::FirstVars,
205+
);
206+
projection(&mut builder, projection_data, "test_5");
207+
208+
let projection_data = U8U128ProjectionInfo::new(
209+
6usize,
210+
13usize,
211+
vec![
212+
F128::from(1u128),
213+
F128::from(0u128),
214+
F128::from(1u128),
215+
F128::from(1u128),
216+
],
217+
ProjectionVariant::FirstVars,
218+
);
219+
/*
220+
With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input,
221+
the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4.
222+
the index of the input byte to use as projection is computed as follows:
223+
224+
idx * 2usize.pow(binary.len()) + decimal, e.g.:
225+
226+
0 * 2.pow(4) + 13 = 13, so input[13]
227+
1 * 2.pow(4) + 13 = 29, so input[29]
228+
2 * 2.pow(4) + 13 = 45, so input[45]
229+
3 * 2.pow(4) + 13 = 61, so input[61]
230+
231+
where idx is [0..4] according to a FirstVars projection variant regularity.
232+
233+
Memory layout:
234+
235+
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
236+
237+
output: BinaryField128b(0x0000000000000000000000000000003e)
238+
output: BinaryField128b(0x00000000000000000000000000000072)
239+
output: BinaryField128b(0x00000000000000000000000000000036)
240+
output: BinaryField128b(0x000000000000000000000000000000df)
241+
*/
242+
projection(&mut builder, projection_data, "test_6");
243+
244+
let witness = builder.take_witness().unwrap();
245+
let cs = builder.build().unwrap();
246+
247+
validate_witness(&cs, &[], &witness).unwrap();
248+
}

examples/acc-repeated.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use binius_circuits::{builder::ConstraintSystemBuilder, unconstrained::unconstrained};
2+
use binius_core::constraint_system::validate::validate_witness;
3+
use binius_field::{
4+
arch::OptimalUnderlier, packed::set_packed_slice, BinaryField128b, BinaryField1b,
5+
BinaryField8b, PackedBinaryField128x1b,
6+
};
7+
8+
type U = OptimalUnderlier;
9+
type F128 = BinaryField128b;
10+
type F8 = BinaryField8b;
11+
type F1 = BinaryField1b;
12+
13+
// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production
14+
15+
const LOG_SIZE: usize = 8;
16+
17+
// The idea of 'Repeated' column is that one can just copy data from initial column multiple times,
18+
// so new column is X times bigger than original one. The following gadget operates over bytes, e.g.
19+
// it creates column with some input bytes written and then creates one more 'Repeated' column
20+
// where the same bytes are copied multiple times.
21+
fn bytes_repeat_gadget(builder: &mut ConstraintSystemBuilder<U, F128>) {
22+
builder.push_namespace("bytes_repeat_gadget");
23+
24+
let bytes = unconstrained::<U, F128, F8>(builder, "input", LOG_SIZE).unwrap();
25+
26+
let repeat_times_log = 4usize;
27+
let repeating = builder
28+
.add_repeating("repeating", bytes, repeat_times_log)
29+
.unwrap();
30+
31+
if let Some(witness) = builder.witness() {
32+
let input_values = witness.get::<F8>(bytes).unwrap().as_slice::<u8>();
33+
34+
let mut repeating_witness = witness.new_column::<F8>(repeating);
35+
let repeating_values = repeating_witness.as_mut_slice::<u8>();
36+
37+
let repeat_times = 2usize.pow(repeat_times_log as u32);
38+
assert_eq!(2usize.pow(LOG_SIZE as u32), input_values.len());
39+
assert_eq!(input_values.len() * repeat_times, repeating_values.len());
40+
41+
for idx in 0..repeat_times {
42+
let start = idx * input_values.len();
43+
let end = start + input_values.len();
44+
repeating_values[start..end].copy_from_slice(input_values);
45+
}
46+
}
47+
48+
builder.pop_namespace();
49+
}
50+
51+
// Bit-oriented repeating is more elaborated due to a specifics of memory layout in Binius.
52+
// In the following example, we use LOG_SIZE=8, which gives 2.pow(8) = 32 bytes written in the memory
53+
// layout. This gives 32 * 8 = 256 bits of input information. Having that Repeated' column
54+
// is instantiated with 'repeat_times_log = 2', this means that we have to repeat our bytes
55+
// 2.pow(repeat_times_log) = 4 times ultimately. For setting bit values we use PackedBinaryField128x1b,
56+
// so for 32 bytes (256 bits) of input data we use 2 PackedBinaryField128x1b elements. Considering 4
57+
// repetitions Binius creates column with 8 PackedBinaryField128x1b elements totally.
58+
// Proper writing bits requires separate iterating over PackedBinaryField128x1b elements and input bytes
59+
// with extracting particular bit values from the input and setting appropriate bit in a given PackedBinaryField128x1b.
60+
fn bits_repeat_gadget(builder: &mut ConstraintSystemBuilder<U, F128>) {
61+
builder.push_namespace("bits_repeat_gadget");
62+
63+
let bits = unconstrained::<U, F128, F1>(builder, "input", LOG_SIZE).unwrap();
64+
let repeat_times_log = 2usize;
65+
66+
// Binius will create column with appropriate height for us
67+
let repeating = builder
68+
.add_repeating("repeating", bits, repeat_times_log)
69+
.unwrap();
70+
71+
if let Some(witness) = builder.witness() {
72+
let input_values = witness.get::<F1>(bits).unwrap().as_slice::<u8>();
73+
let mut repeating_witness = witness.new_column::<F1>(repeating);
74+
let output_values = repeating_witness.packed();
75+
76+
// this performs writing input bits exactly 1 time. Depending on number of repetitions we
77+
// need to call this multiple times, providing offset for output values (PackedBinaryField128x1b elements)
78+
fn write_input(
79+
input_values: &[u8],
80+
output_values: &mut [PackedBinaryField128x1b],
81+
output_packed_offset: usize,
82+
) {
83+
let mut output_index = output_packed_offset;
84+
for (input_index, _) in (0..input_values.len()).enumerate() {
85+
let byte = input_values[input_index];
86+
87+
set_packed_slice(output_values, output_index, F1::from(byte));
88+
set_packed_slice(output_values, output_index + 1, F1::from((byte >> 1) & 0x01));
89+
set_packed_slice(output_values, output_index + 2, F1::from((byte >> 2) & 0x01));
90+
set_packed_slice(output_values, output_index + 3, F1::from((byte >> 3) & 0x01));
91+
set_packed_slice(output_values, output_index + 4, F1::from((byte >> 4) & 0x01));
92+
set_packed_slice(output_values, output_index + 5, F1::from((byte >> 5) & 0x01));
93+
set_packed_slice(output_values, output_index + 6, F1::from((byte >> 6) & 0x01));
94+
set_packed_slice(output_values, output_index + 7, F1::from((byte >> 7) & 0x01));
95+
96+
output_index += 8;
97+
}
98+
}
99+
100+
let repeat_times = 2u32.pow(repeat_times_log as u32);
101+
102+
let mut offset = 0;
103+
for _ in 0..repeat_times {
104+
write_input(input_values, output_values, offset);
105+
offset += input_values.len() * 8;
106+
}
107+
}
108+
109+
builder.pop_namespace();
110+
}
111+
112+
fn main() {
113+
let allocator = bumpalo::Bump::new();
114+
115+
let mut builder = ConstraintSystemBuilder::<U, F128>::new_with_witness(&allocator);
116+
117+
bytes_repeat_gadget(&mut builder);
118+
bits_repeat_gadget(&mut builder);
119+
120+
let witness = builder.take_witness().unwrap();
121+
let cs = builder.build().unwrap();
122+
123+
validate_witness(&cs, &[], &witness).unwrap();
124+
}

0 commit comments

Comments
 (0)