1use crypto_primitives::{BaseFieldConfig, ProjectElementWithConfig, boolean::Boolean};
2use itertools::Itertools;
3use num_traits::Zero;
4use zinc_poly::{
5 mle::DenseMultilinearExtension,
6 univariate::{binary::BinaryPoly, binary_ref::BinaryRefPoly, binary_u64::BinaryU64Poly},
7};
8use zinc_utils::{add, mul};
9
10pub trait FoldTrace<From, To> {
15 const FOLDING_FACTOR: usize;
17
18 fn fold_trace_mle(mle: &DenseMultilinearExtension<From>) -> DenseMultilinearExtension<To>;
19
20 fn fold_eval_claim<C, A>(
37 bar_u_coeffs: &[C::Element],
38 alphas: &[A],
39 folding_challenges: &[C::Element],
40 field_cfg: &C,
41 ) -> C::Element
42 where
43 C: BaseFieldConfig + ProjectElementWithConfig<A>,
44 {
45 debug_assert_eq!(
58 1usize << folding_challenges.len(),
59 Self::FOLDING_FACTOR,
60 "fold_eval_claim: 1 << folding_challenges.len() must equal FOLDING_FACTOR",
61 );
62 debug_assert!(
63 bar_u_coeffs.len() <= mul!(alphas.len(), Self::FOLDING_FACTOR),
64 "fold_eval_claim: bar_u_coeffs.len() must not exceed alphas.len() * FOLDING_FACTOR",
65 );
66
67 let chunk_size = alphas.len();
68 let alphas = alphas.iter().map(|a| field_cfg.project(a)).collect_vec();
69
70 let chunk_evals: Vec<C::Element> = (0..Self::FOLDING_FACTOR)
74 .map(|i| {
75 let start = mul!(i, chunk_size);
76 let mut acc = field_cfg.zero();
77 for (j, alpha) in alphas.iter().enumerate() {
78 if let Some(coeff) = bar_u_coeffs.get(add!(start, j)) {
79 field_cfg.add_assign(&mut acc, &field_cfg.mul(alpha, coeff));
80 }
81 }
82 acc
83 })
84 .collect();
85
86 mle_eval_msb_first(field_cfg, chunk_evals, folding_challenges)
89 }
90}
91
92pub struct NoopFoldTrace;
97
98impl<T: Clone> FoldTrace<T, T> for NoopFoldTrace {
99 const FOLDING_FACTOR: usize = 1;
100
101 fn fold_trace_mle(mle: &DenseMultilinearExtension<T>) -> DenseMultilinearExtension<T> {
102 mle.clone()
103 }
104}
105
106pub struct FoldBinaryTrace2x<const D: usize, const HALF_D: usize>;
111
112impl<const D: usize, const HALF_D: usize> FoldTrace<BinaryPoly<D>, BinaryPoly<HALF_D>>
113 for FoldBinaryTrace2x<D, HALF_D>
114{
115 const FOLDING_FACTOR: usize = 2;
116
117 fn fold_trace_mle(
118 mle: &DenseMultilinearExtension<BinaryPoly<D>>,
119 ) -> DenseMultilinearExtension<BinaryPoly<HALF_D>> {
120 split_binary_poly_mle(mle)
121 }
122}
123
124pub struct FoldBinaryTrace4x<const D: usize, const HALF_D: usize, const QUARTER_D: usize>;
125
126impl<const D: usize, const HALF_D: usize, const QUARTER_D: usize>
127 FoldTrace<BinaryPoly<D>, BinaryPoly<QUARTER_D>> for FoldBinaryTrace4x<D, HALF_D, QUARTER_D>
128{
129 const FOLDING_FACTOR: usize = 4;
130
131 fn fold_trace_mle(
132 mle: &DenseMultilinearExtension<BinaryPoly<D>>,
133 ) -> DenseMultilinearExtension<BinaryPoly<QUARTER_D>> {
134 let mle = split_binary_poly_mle::<D, HALF_D>(mle);
135 split_binary_poly_mle::<HALF_D, QUARTER_D>(&mle)
136 }
137}
138
139fn split_binary_poly_mle<const D: usize, const HALF_D: usize>(
161 mle: &DenseMultilinearExtension<BinaryPoly<D>>,
162) -> DenseMultilinearExtension<BinaryPoly<HALF_D>> {
163 const {
164 assert!(D == 2 * HALF_D, "split_column: D must equal 2 * HALF_D");
165 }
166
167 #[cfg(not(feature = "simd"))]
168 let res = split_binary_poly_mle_ref(mle);
169
170 #[cfg(feature = "simd")]
171 let res = split_binary_poly_mle_u64(mle);
172
173 res
174}
175
176#[allow(dead_code)]
177fn split_binary_poly_mle_ref<const D: usize, const HALF_D: usize>(
178 mle: &DenseMultilinearExtension<BinaryRefPoly<D>>,
179) -> DenseMultilinearExtension<BinaryRefPoly<HALF_D>> {
180 let n = mle.evaluations.len();
181 let mut lo_evals = Vec::with_capacity(n);
182 let mut hi_evals = Vec::with_capacity(n);
183
184 for entry in &mle.evaluations {
185 let lo_arr: [Boolean; HALF_D] = std::array::from_fn(|i| entry[i]);
186 let hi_arr: [Boolean; HALF_D] = std::array::from_fn(|i| entry[add!(HALF_D, i)]);
187 lo_evals.push(BinaryRefPoly::<HALF_D>::new(lo_arr));
188 hi_evals.push(BinaryRefPoly::<HALF_D>::new(hi_arr));
189 }
190
191 lo_evals.extend(hi_evals);
193
194 DenseMultilinearExtension::from_evaluations_vec(add!(mle.num_vars, 1), lo_evals, Zero::zero())
195}
196
197#[allow(dead_code)]
198fn split_binary_poly_mle_u64<const D: usize, const HALF_D: usize>(
199 mle: &DenseMultilinearExtension<BinaryU64Poly<D>>,
200) -> DenseMultilinearExtension<BinaryU64Poly<HALF_D>> {
201 let n = mle.evaluations.len();
202 let mut lo_evals: Vec<BinaryU64Poly<HALF_D>> = Vec::with_capacity(n);
203 let mut hi_evals: Vec<BinaryU64Poly<HALF_D>> = Vec::with_capacity(n);
204
205 for entry in &mle.evaluations {
206 let bits: u64 = *entry.inner();
207 lo_evals.push(BinaryU64Poly::<HALF_D>::from(bits));
210 hi_evals.push(BinaryU64Poly::<HALF_D>::from(bits >> HALF_D));
211 }
212
213 lo_evals.extend(hi_evals);
216
217 DenseMultilinearExtension::from_evaluations_vec(add!(mle.num_vars, 1), lo_evals, Zero::zero())
218}
219
220fn mle_eval_msb_first<C: BaseFieldConfig>(
225 cfg: &C,
226 values: Vec<C::Element>,
227 gammas: &[C::Element],
228) -> C::Element {
229 if gammas.is_empty() {
230 debug_assert_eq!(values.len(), 1);
231 return values.into_iter().next().expect("non-empty values");
232 }
233 debug_assert_eq!(values.len(), 1usize << gammas.len());
234
235 let half = values.len() >> 1;
236 let g = &gammas[0];
237 let one_minus_g = cfg.sub(&cfg.one(), g);
238
239 let mut next: Vec<C::Element> = Vec::with_capacity(half);
240 for i in 0..half {
241 let mut lo = cfg.mul(&one_minus_g, &values[i]);
242 cfg.add_assign(&mut lo, &cfg.mul(g, &values[add!(i, half)]));
243 next.push(lo);
244 }
245 mle_eval_msb_first(cfg, next, &gammas[1..])
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crypto_primitives::Wrapper;
252 use rand::prelude::*;
253 use zinc_transcript::traits::GenTranscribable;
254
255 fn build_matched_mles<const D: usize>(
258 bits_list: &[u64],
259 ) -> (
260 DenseMultilinearExtension<BinaryRefPoly<D>>,
261 DenseMultilinearExtension<BinaryU64Poly<D>>,
262 ) {
263 let n = bits_list.len();
264 assert!(n.is_power_of_two(), "n must be a power of two");
265 let num_vars = n.trailing_zeros() as usize;
266
267 let ref_entries: Vec<BinaryRefPoly<D>> = bits_list
268 .iter()
269 .map(|&bits| BinaryRefPoly::read_transcription_bytes_exact(&bits.to_le_bytes()))
270 .collect();
271 let u64_entries: Vec<BinaryU64Poly<D>> = bits_list
272 .iter()
273 .map(|&bits| BinaryU64Poly::from(bits))
274 .collect();
275
276 let ref_mle =
277 DenseMultilinearExtension::from_evaluations_vec(num_vars, ref_entries, Zero::zero());
278 let u64_mle =
279 DenseMultilinearExtension::from_evaluations_vec(num_vars, u64_entries, Zero::zero());
280
281 (ref_mle, u64_mle)
282 }
283
284 fn assert_split_matches<const D: usize, const HALF_D: usize>(bits_list: Vec<u64>) {
287 let (ref_mle, u64_mle) = build_matched_mles::<D>(&bits_list);
288
289 let split_ref = split_binary_poly_mle_ref::<D, HALF_D>(&ref_mle);
290 let split_u64 = split_binary_poly_mle_u64::<D, HALF_D>(&u64_mle);
291
292 assert_eq!(split_ref.num_vars, split_u64.num_vars);
293 assert_eq!(split_ref.evaluations.len(), split_u64.evaluations.len());
294 for (idx, (r, u)) in split_ref
295 .evaluations
296 .iter()
297 .zip(split_u64.evaluations.iter())
298 .enumerate()
299 {
300 for i in 0..HALF_D {
303 let r_bit = *r[i].inner();
304 let u_bit = ((*u.inner()) >> i) & 1 != 0;
305 assert_eq!(
306 r_bit, u_bit,
307 "mismatch at output entry {idx}, coefficient bit {i}",
308 );
309 }
310
311 for (i, pair) in r.iter().zip_longest(u.iter()).enumerate() {
312 match pair {
313 itertools::EitherOrBoth::Both(r_bit, u_bit) => {
314 assert_eq!(
315 *r_bit.inner(),
316 *u_bit,
317 "mismatch at output entry {idx}, coefficient bit {i}",
318 );
319 }
320 itertools::EitherOrBoth::Left(_) | itertools::EitherOrBoth::Right(_) => {
321 panic!("mismatch in number of coefficients at output entry {idx}");
322 }
323 }
324 }
325 }
326 }
327
328 #[test]
329 fn split_ref_and_u64_match_d4_exhaustive() {
330 for bits in 0u64..16 {
332 assert_split_matches::<4, 2>(vec![bits]);
333 }
334
335 let mut rng = rand::rng();
337 for _ in 0..32 {
338 let bits_list: Vec<u64> = (0..4).map(|_| rng.random::<u64>() & 0xF).collect();
339 assert_split_matches::<4, 2>(bits_list);
340 }
341 }
342
343 #[test]
344 fn split_ref_and_u64_match_random() {
345 let mut rng = rand::rng();
346
347 for n_log in 0..=3 {
348 let n = 1usize << n_log;
349 let bits_list: Vec<u64> = (0..n).map(|_| rng.random::<u64>() & 0xF).collect();
350 assert_split_matches::<4, 2>(bits_list);
351 }
352
353 for n_log in 0..=4 {
354 let n = 1usize << n_log;
355 let bits_list: Vec<u64> = (0..n).map(|_| rng.random::<u64>() & 0xFF).collect();
356 assert_split_matches::<8, 4>(bits_list);
357 }
358
359 for n_log in 0..=5 {
360 let n = 1usize << n_log;
361 let bits_list: Vec<u64> = (0..n).map(|_| rng.random::<u64>() & 0xFFFF_FFFF).collect();
362 assert_split_matches::<32, 16>(bits_list);
363 }
364
365 for n_log in 0..=6 {
366 let n = 1usize << n_log;
367 let bits_list: Vec<u64> = (0..n).map(|_| rng.random::<u64>()).collect();
368 assert_split_matches::<64, 32>(bits_list);
369 }
370 }
371
372 #[test]
373 fn split_u64_pins_all_zero_entries() {
374 let bits_list = vec![0u64; 8];
376 assert_split_matches::<8, 4>(bits_list.clone());
377 assert_split_matches::<32, 16>(bits_list.clone());
378 assert_split_matches::<64, 32>(bits_list);
379 }
380
381 #[test]
382 fn split_u64_handles_all_ones_d64() {
383 let bits_list = vec![u64::MAX; 4];
387 assert_split_matches::<64, 32>(bits_list);
388 }
389}