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