1#[cfg(feature = "parallel")]
2use rayon::prelude::*;
3
4use crypto_primitives::{FromWithConfig, PrimeField, Semiring};
5use std::{collections::HashMap, iter};
6use zinc_poly::{
7 EvaluationError,
8 mle::DenseMultilinearExtension,
9 univariate::dynamic::over_field::{DynamicPolyFInnerProduct, DynamicPolynomialF},
10};
11use zinc_uair::{BitOp, BitOpSpec, Uair, UairTrace, collect_scalars::collect_scalars};
12use zinc_utils::{
13 UNCHECKED, cfg_extend, cfg_into_iter, cfg_iter, cfg_iter_mut, inner_product::InnerProduct,
14 powers,
15};
16
17pub type RowMajorTrace<F> = Vec<Vec<DynamicPolynomialF<F>>>;
21
22pub type ColumnMajorTrace<F> = Vec<DenseMultilinearExtension<DynamicPolynomialF<F>>>;
26
27#[derive(Clone, Debug)]
30pub enum ProjectedTrace<F: PrimeField> {
31 RowMajor(RowMajorTrace<F>),
32 ColumnMajor(ColumnMajorTrace<F>),
33}
34
35#[allow(clippy::arithmetic_side_effects)]
41pub fn project_trace_coeffs_row_major<F, PolyCoeff, Int, const DB: usize, const DA: usize>(
42 trace: &UairTrace<PolyCoeff, Int, DB, DA>,
43 field_cfg: &F::Config,
44) -> RowMajorTrace<F>
45where
46 F: for<'a> FromWithConfig<&'a PolyCoeff> + for<'a> FromWithConfig<&'a Int> + Send + Sync,
47 PolyCoeff: Clone + Send + Sync,
48 Int: Clone + Send + Sync,
49{
50 let zero = F::zero_with_cfg(field_cfg);
51 let one = F::one_with_cfg(field_cfg);
52
53 let binary_len = trace.binary_poly.len();
54 let arbitrary_len = trace.arbitrary_poly.len();
55 let num_cols = trace.binary_poly.len() + trace.arbitrary_poly.len() + trace.int.len();
56
57 let num_rows = trace
59 .binary_poly
60 .first()
61 .map(|c| c.len())
62 .or_else(|| trace.arbitrary_poly.first().map(|c| c.len()))
63 .or_else(|| trace.int.first().map(|c| c.len()))
64 .unwrap_or(0);
65
66 let mut result: RowMajorTrace<F> = iter::repeat_with(|| Vec::with_capacity(num_cols))
69 .take(num_rows)
70 .collect();
71
72 cfg_iter_mut!(result)
74 .enumerate()
75 .for_each(|(row_idx, row)| {
76 let spare = row.spare_capacity_mut();
77
78 cfg_iter_mut!(spare[..binary_len])
80 .zip(cfg_iter!(trace.binary_poly))
81 .for_each(|(slot, col)| {
82 let binary_poly = &col.evaluations[row_idx];
83 slot.write(
84 binary_poly
85 .iter()
86 .map(|coeff| {
87 if coeff.into_inner() {
88 one.clone()
89 } else {
90 zero.clone()
91 }
92 })
93 .collect(),
94 );
95 });
96
97 cfg_iter_mut!(spare[binary_len..binary_len + arbitrary_len])
99 .zip(cfg_iter!(trace.arbitrary_poly))
100 .for_each(|(slot, col)| {
101 let arbitrary_poly = &col.evaluations[row_idx];
102 slot.write(
103 arbitrary_poly
104 .iter()
105 .map(|coeff| F::from_with_cfg(coeff, field_cfg))
106 .collect(),
107 );
108 });
109
110 cfg_iter_mut!(spare[binary_len + arbitrary_len..])
112 .zip(cfg_iter!(trace.int))
113 .for_each(|(slot, col)| {
114 let int_val = &col.evaluations[row_idx];
115 slot.write(DynamicPolynomialF {
116 coeffs: vec![F::from_with_cfg(int_val, field_cfg)],
117 });
118 });
119
120 unsafe { row.set_len(num_cols) };
122 });
123 result
124}
125
126#[allow(clippy::arithmetic_side_effects)]
132pub fn project_trace_coeffs_column_major<F, PolyCoeff, Int, const DB: usize, const DA: usize>(
133 trace: &UairTrace<PolyCoeff, Int, DB, DA>,
134 field_cfg: &F::Config,
135) -> ColumnMajorTrace<F>
136where
137 F: for<'a> FromWithConfig<&'a PolyCoeff> + for<'a> FromWithConfig<&'a Int> + Send + Sync,
138 PolyCoeff: Clone + Send + Sync,
139 Int: Clone + Send + Sync,
140{
141 let zero = F::zero_with_cfg(field_cfg);
142 let one = F::one_with_cfg(field_cfg);
143
144 let num_vars = [
145 trace.binary_poly.first().map(|c| c.num_vars),
146 trace.arbitrary_poly.first().map(|c| c.num_vars),
147 trace.int.first().map(|c| c.num_vars),
148 ]
149 .into_iter()
150 .flatten()
151 .max()
152 .unwrap_or(0);
153
154 let mut result =
155 Vec::with_capacity(trace.binary_poly.len() + trace.arbitrary_poly.len() + trace.int.len());
156
157 cfg_extend!(
159 result,
160 cfg_iter!(trace.binary_poly).map(|column| {
161 let evaluations: Vec<DynamicPolynomialF<F>> = column
162 .iter()
163 .map(|binary_poly| {
164 binary_poly
165 .iter()
166 .map(|coeff| {
167 if coeff.into_inner() {
168 one.clone()
169 } else {
170 zero.clone()
171 }
172 })
173 .collect()
174 })
175 .collect();
176 DenseMultilinearExtension {
177 evaluations,
178 num_vars,
179 }
180 })
181 );
182
183 cfg_extend!(
185 result,
186 cfg_iter!(trace.arbitrary_poly).map(|column| {
187 let evaluations: Vec<DynamicPolynomialF<F>> = column
188 .iter()
189 .map(|arbitrary_poly| {
190 arbitrary_poly
191 .iter()
192 .map(|coeff| F::from_with_cfg(coeff, field_cfg))
193 .collect()
194 })
195 .collect();
196 DenseMultilinearExtension {
197 evaluations,
198 num_vars,
199 }
200 })
201 );
202
203 cfg_extend!(
205 result,
206 cfg_iter!(trace.int).map(|column| {
207 let evaluations: Vec<DynamicPolynomialF<F>> = column
208 .iter()
209 .map(|int| DynamicPolynomialF {
210 coeffs: vec![F::from_with_cfg(int, field_cfg)],
211 })
212 .collect();
213 DenseMultilinearExtension {
214 evaluations,
215 num_vars,
216 }
217 })
218 );
219
220 result
221}
222
223pub fn column_major_to_row_major<F: PrimeField>(trace: &ColumnMajorTrace<F>) -> RowMajorTrace<F> {
229 let num_rows = trace.first().map(|c| c.evaluations.len()).unwrap_or(0);
230
231 cfg_into_iter!(0..num_rows)
232 .map(|row_idx| {
233 trace
234 .iter()
235 .map(|col| col.evaluations[row_idx].clone())
236 .collect()
237 })
238 .collect()
239}
240
241#[allow(clippy::arithmetic_side_effects)]
245pub fn evaluate_trace_to_column_mles<F: PrimeField + 'static>(
246 trace: &ProjectedTrace<F>,
247 projecting_element: &F,
248) -> Vec<DenseMultilinearExtension<F::Inner>> {
249 let zero = F::zero_with_cfg(projecting_element.cfg());
250 let one = F::one_with_cfg(projecting_element.cfg());
251
252 let max_coeffs_len = {
253 macro_rules! common_code {
255 ($v:expr) => {
256 $v.iter()
257 .flat_map(|row| row.iter())
258 .map(|poly| poly.degree().map_or(0, |d| d + 1))
259 .max()
260 .unwrap_or(0)
261 .max(1)
262 };
263 }
264 match trace {
265 ProjectedTrace::RowMajor(t) => common_code!(t),
266 ProjectedTrace::ColumnMajor(t) => common_code!(t),
267 }
268 };
269
270 let projection_powers: Vec<F> = powers(projecting_element.clone(), one, max_coeffs_len);
271
272 let evaluate_poly = |poly: &DynamicPolynomialF<F>| -> F::Inner {
273 let deg = poly.degree().map_or(0, |d| d + 1);
274 DynamicPolyFInnerProduct::inner_product::<UNCHECKED>(
275 &poly.coeffs[..deg],
276 &projection_powers[..deg],
277 zero.clone(),
278 )
279 .expect("inner product cannot fail here")
280 .into_inner()
281 };
282
283 match trace {
284 ProjectedTrace::RowMajor(t) => {
285 let num_rows = t.len();
286 let num_cols = t.first().map(|r| r.len()).unwrap_or(0);
287 let num_vars = num_rows.next_power_of_two().trailing_zeros() as usize;
288
289 cfg_into_iter!(0..num_cols)
290 .map(|col_idx| {
291 let evaluations: Vec<F::Inner> = (0..num_rows)
292 .map(|row_idx| evaluate_poly(&t[row_idx][col_idx]))
293 .collect();
294 DenseMultilinearExtension::from_evaluations_vec(
295 num_vars,
296 evaluations,
297 zero.inner().clone(),
298 )
299 })
300 .collect()
301 }
302 ProjectedTrace::ColumnMajor(t) => cfg_iter!(t)
303 .map(|col_mle| {
304 let evaluations: Vec<F::Inner> = cfg_iter!(col_mle).map(evaluate_poly).collect();
305 DenseMultilinearExtension::from_evaluations_vec(
306 col_mle.num_vars,
307 evaluations,
308 zero.inner().clone(),
309 )
310 })
311 .collect(),
312 }
313}
314
315pub fn build_bit_op_virtual_mle<F: PrimeField + 'static, const D: usize>(
320 trace: &ProjectedTrace<F>,
321 spec: &BitOpSpec,
322 projecting_element: &F,
323 field_cfg: &F::Config,
324) -> DenseMultilinearExtension<F::Inner> {
325 let zero = F::zero_with_cfg(field_cfg);
326 let one = F::one_with_cfg(field_cfg);
327 let projection_powers: Vec<F> = powers(projecting_element.clone(), one, D);
328
329 let c = spec.op().count();
330 assert!(
331 c > 0 && c < D,
332 "BitOp count {c} out of range for cell width D = {D}",
333 );
334
335 let evaluate_with_bit_op = |cell: &DynamicPolynomialF<F>| -> F::Inner {
336 let transformed = match spec.op() {
337 BitOp::Rot(c) => cell.rotate_right::<D>(c, field_cfg),
338 BitOp::ShR(c) => cell.shr::<D>(c, field_cfg),
339 };
340 DynamicPolyFInnerProduct::inner_product::<UNCHECKED>(
341 &transformed.coeffs,
342 &projection_powers,
343 zero.clone(),
344 )
345 .expect("inner product cannot fail here")
346 .into_inner()
347 };
348
349 match trace {
350 ProjectedTrace::RowMajor(t) => {
351 let num_rows = t.len();
352 let num_vars = num_rows.next_power_of_two().trailing_zeros() as usize;
353 let evaluations: Vec<F::Inner> = (0..num_rows)
354 .map(|row_idx| evaluate_with_bit_op(&t[row_idx][spec.source_col()]))
355 .collect();
356 DenseMultilinearExtension::from_evaluations_vec(
357 num_vars,
358 evaluations,
359 zero.inner().clone(),
360 )
361 }
362 ProjectedTrace::ColumnMajor(t) => {
363 let col_mle = &t[spec.source_col()];
364 let evaluations: Vec<F::Inner> = col_mle.iter().map(evaluate_with_bit_op).collect();
365 DenseMultilinearExtension::from_evaluations_vec(
366 col_mle.num_vars,
367 evaluations,
368 zero.inner().clone(),
369 )
370 }
371 }
372}
373
374pub fn project_scalars<F: PrimeField, U: Uair>(
376 project: impl Fn(&U::Scalar) -> DynamicPolynomialF<F>,
377) -> HashMap<U::Scalar, DynamicPolynomialF<F>> {
378 let uair_scalars = collect_scalars::<U>();
379
380 uair_scalars
383 .into_iter()
384 .map(|scalar| {
385 let mut dynamic_poly = project(&scalar);
386 dynamic_poly.trim();
387 (scalar, dynamic_poly)
388 })
389 .collect()
390}
391
392#[allow(clippy::arithmetic_side_effects)]
394pub fn project_scalars_to_field<R: Semiring + 'static, F: PrimeField>(
395 scalars: HashMap<R, DynamicPolynomialF<F>>,
396 projecting_element: &F,
397) -> Result<HashMap<R, F>, (R, F, EvaluationError)> {
398 let one = F::one_with_cfg(projecting_element.cfg());
402 let zero = F::zero_with_cfg(projecting_element.cfg());
403
404 let max_coeffs_len = scalars
405 .values()
406 .map(|poly| poly.degree().map_or(0, |d| d + 1))
407 .max()
408 .unwrap_or(0)
409 .max(1);
410
411 let projection_powers: Vec<F> = powers(projecting_element.clone(), one, max_coeffs_len);
412
413 Ok(scalars
414 .into_iter()
415 .map(|(scalar, value)| {
416 let deg = value.degree().map_or(0, |d| d + 1);
417 (
418 scalar,
419 DynamicPolyFInnerProduct::inner_product::<UNCHECKED>(
420 &value.coeffs[..deg],
421 &projection_powers[..deg],
422 zero.clone(),
423 )
424 .expect("inner product cannot fail here"),
425 )
426 })
427 .collect())
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use crate::test_utils::{LIMBS, test_config};
434 use crypto_primitives::{Field, FromWithConfig, crypto_bigint_monty::MontyField};
435
436 type F = MontyField<LIMBS>;
437
438 fn f(value: u32, cfg: &<F as PrimeField>::Config) -> F {
439 F::from_with_cfg(value, cfg)
440 }
441
442 fn poly(coeffs: Vec<F>) -> DynamicPolynomialF<F> {
443 DynamicPolynomialF { coeffs }
444 }
445
446 fn expected_inner(
447 coeffs: &[F],
448 projecting_element: &F,
449 cfg: &<F as PrimeField>::Config,
450 ) -> <F as Field>::Inner {
451 let zero = F::zero_with_cfg(cfg);
452 let one = F::one_with_cfg(cfg);
453 let powers = powers(projecting_element.clone(), one, coeffs.len());
454 DynamicPolyFInnerProduct::inner_product::<UNCHECKED>(coeffs, &powers, zero)
455 .expect("inner product cannot fail here")
456 .into_inner()
457 }
458
459 #[test]
460 fn builds_bit_op_virtual_mle_from_row_major_trace() {
461 let cfg = test_config();
462 let alpha = f(2, &cfg);
463 let zero = F::zero_with_cfg(&cfg);
464 let row0 = [f(1, &cfg), f(2, &cfg), f(3, &cfg), f(4, &cfg)];
465 let row1 = [f(5, &cfg), f(6, &cfg), f(7, &cfg), f(8, &cfg)];
466 let trace =
467 ProjectedTrace::RowMajor(vec![vec![poly(row0.to_vec())], vec![poly(row1.to_vec())]]);
468
469 let mle = build_bit_op_virtual_mle::<F, 4>(
470 &trace,
471 &BitOpSpec::new(0, BitOp::ShR(2)),
472 &alpha,
473 &cfg,
474 );
475
476 assert_eq!(mle.num_vars, 1);
477 assert_eq!(
478 mle.evaluations,
479 vec![
480 expected_inner(
481 &[row0[2].clone(), row0[3].clone(), zero.clone(), zero.clone()],
482 &alpha,
483 &cfg
484 ),
485 expected_inner(
486 &[row1[2].clone(), row1[3].clone(), zero.clone(), zero],
487 &alpha,
488 &cfg
489 ),
490 ],
491 );
492 }
493
494 #[test]
495 fn builds_bit_op_virtual_mle_from_column_major_trace() {
496 let cfg = test_config();
497 let alpha = f(3, &cfg);
498 let row0 = [f(1, &cfg), f(2, &cfg), f(3, &cfg), f(4, &cfg)];
499 let row1 = [f(5, &cfg), f(6, &cfg), f(7, &cfg), f(8, &cfg)];
500 let trace = ProjectedTrace::ColumnMajor(vec![DenseMultilinearExtension {
501 evaluations: vec![poly(row0.to_vec()), poly(row1.to_vec())],
502 num_vars: 1,
503 }]);
504
505 let mle = build_bit_op_virtual_mle::<F, 4>(
506 &trace,
507 &BitOpSpec::new(0, BitOp::Rot(1)),
508 &alpha,
509 &cfg,
510 );
511
512 assert_eq!(mle.num_vars, 1);
513 assert_eq!(
514 mle.evaluations,
515 vec![
516 expected_inner(
517 &[
518 row0[1].clone(),
519 row0[2].clone(),
520 row0[3].clone(),
521 row0[0].clone()
522 ],
523 &alpha,
524 &cfg,
525 ),
526 expected_inner(
527 &[
528 row1[1].clone(),
529 row1[2].clone(),
530 row1[3].clone(),
531 row1[0].clone()
532 ],
533 &alpha,
534 &cfg,
535 ),
536 ],
537 );
538 }
539
540 #[test]
541 #[should_panic(expected = "out of range")]
542 fn rejects_bit_op_count_at_cell_width() {
543 let cfg = test_config();
544 let alpha = f(2, &cfg);
545 let trace = ProjectedTrace::RowMajor(vec![vec![poly(vec![f(1, &cfg), f(2, &cfg)])]]);
546
547 let _ = build_bit_op_virtual_mle::<F, 2>(
548 &trace,
549 &BitOpSpec::new(0, BitOp::Rot(2)),
550 &alpha,
551 &cfg,
552 );
553 }
554}