1use crypto_primitives::SemiringConfig;
2use thiserror::Error;
3use zinc_utils::{add, mul, sub};
4
5use crate::mle::{DenseMultilinearExtension, dense::CollectDenseMleWithZero};
6
7#[derive(Debug, Clone, Error)]
9pub enum ArithErrors {
10 #[error("Invalid parameters: {0}")]
11 InvalidParameters(String),
12}
13
14pub fn build_eq_x_r<C: SemiringConfig>(
21 cfg: &C,
22 r: &[C::Element],
23) -> Result<DenseMultilinearExtension<C::Element>, ArithErrors> {
24 let evals = build_eq_x_r_vec(cfg, r)?;
25 let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), evals, cfg.zero());
26
27 Ok(mle)
28}
29
30pub fn build_eq_x_r_vec<C: SemiringConfig>(
38 cfg: &C,
39 r: &[C::Element],
40) -> Result<Vec<C::Element>, ArithErrors> {
41 if r.is_empty() {
53 return Err(ArithErrors::InvalidParameters("r length is 0".to_owned()));
54 }
55
56 let one = cfg.one();
57 let mut eval = vec![one; 1 << r.len()];
58 let mut s = 1;
59 for ri in r {
60 for j in (0..s).rev() {
61 let prev = eval[j].clone();
62 let hi = cfg.mul(&prev, ri);
63 eval[j] = cfg.sub(&prev, &hi);
64 eval[add!(j, s)] = hi;
65 }
66 s = mul!(s, 2);
67 }
68
69 Ok(eval)
70}
71
72pub fn build_next_c_r_mle<C: SemiringConfig>(
82 cfg: &C,
83 r: &[C::Element],
84 c: usize,
85) -> Result<DenseMultilinearExtension<C::Element>, ArithErrors> {
86 let num_vars = r.len();
87 let n = 1 << num_vars;
88 assert!(c < n, "shift c={c} must be < domain size {n}");
89
90 let eq_r = build_eq_x_r(cfg, r)?;
91 if c == 0 {
92 return Ok(eq_r);
93 }
94
95 let mut evaluations = Vec::with_capacity(n);
98 evaluations.resize(c, cfg.zero());
99 evaluations.extend_from_slice(&eq_r.evaluations[..sub!(n, c)]);
100
101 Ok(DenseMultilinearExtension {
102 num_vars,
103 evaluations,
104 })
105}
106
107pub fn eq_eval<C: SemiringConfig>(
109 cfg: &C,
110 x: &[C::Element],
111 y: &[C::Element],
112) -> Result<C::Element, ArithErrors> {
113 if x.len() != y.len() {
114 return Err(ArithErrors::InvalidParameters(
115 "x and y have different length".to_string(),
116 ));
117 }
118
119 let one = cfg.one();
120 let mut res = one.clone();
121 for (xi, yi) in x.iter().zip(y.iter()) {
122 let xi_yi = cfg.mul(xi, yi);
124 let mut term = cfg.add(&xi_yi, &xi_yi);
125 cfg.sub_assign(&mut term, xi);
126 cfg.sub_assign(&mut term, yi);
127 cfg.add_assign(&mut term, &one);
128 cfg.mul_assign(&mut res, &term);
129 }
130
131 Ok(res)
132}
133
134pub fn mle_eval_with_eq_table<C: SemiringConfig>(
144 cfg: &C,
145 evaluations: &[C::Element],
146 eq_table: &[C::Element],
147) -> C::Element {
148 let mut acc = cfg.zero();
149 assert_eq!(
150 evaluations.len(),
151 eq_table.len(),
152 "evaluations and eq_table must have the same length"
153 );
154 for (eval, eq_val) in evaluations.iter().zip(eq_table.iter()) {
155 let term = cfg.mul(eq_val, eval);
156 cfg.add_assign(&mut acc, &term);
157 }
158 acc
159}
160
161#[allow(clippy::arithmetic_side_effects)]
164pub fn next_mle<E: Clone>(
165 num_vars: u32,
166 zero: E,
167 one: E,
168) -> Result<DenseMultilinearExtension<E>, ArithErrors> {
169 if !num_vars.is_multiple_of(2) {
170 return Err(ArithErrors::InvalidParameters(
171 "num_vars must be even".to_string(),
172 ));
173 }
174
175 let mut mle = (0..1 << num_vars)
176 .map(|_| zero.clone())
177 .collect_dense_mle_with_zero(&zero);
178
179 let half_vars = num_vars / 2;
180
181 for i in 0usize..(1 << half_vars) - 1 {
182 let next = i + 1;
183
184 let i_concat_next = (next << half_vars) | i;
185
186 mle[i_concat_next] = one.clone();
187 }
188
189 Ok(mle)
190}
191
192#[allow(clippy::arithmetic_side_effects)]
213pub fn next_mle_eval<C: SemiringConfig>(cfg: &C, u: &[C::Element], v: &[C::Element]) -> C::Element {
214 let n = u.len();
215 assert_eq!(n, v.len(), "u and v must have the same length");
216 if n == 0 {
217 return cfg.zero();
218 }
219
220 let one = cfg.one();
221
222 let mut suffix_eq = vec![one.clone(); n + 1];
224 for i in (0..n).rev() {
225 let uv = cfg.mul(&u[i], &v[i]);
227 let eq_i = cfg.add(&uv, &cfg.mul(&cfg.sub(&one, &u[i]), &cfg.sub(&one, &v[i])));
228 suffix_eq[i] = cfg.mul(&suffix_eq[i + 1], &eq_i);
229 }
230
231 let mut prefix_carry = one.clone();
233 let mut result = cfg.zero();
234 for j in 0..n {
235 let mut term = cfg.mul(&prefix_carry, &cfg.sub(&one, &u[j]));
237 cfg.mul_assign(&mut term, &v[j]);
238 cfg.mul_assign(&mut term, &suffix_eq[j + 1]);
239 cfg.add_assign(&mut result, &term);
240
241 let carry = cfg.mul(&u[j], &cfg.sub(&one, &v[j]));
242 cfg.mul_assign(&mut prefix_carry, &carry);
243 }
244 result
245}
246
247#[cfg(test)]
248#[allow(
249 clippy::arithmetic_side_effects,
250 clippy::cast_possible_truncation,
251 clippy::needless_range_loop
252)]
253mod tests {
254 use crypto_bigint::{U128, const_monty_params};
255 use crypto_primitives::{FixedConfig, crypto_bigint_const_monty::ConstMontyField};
256 use num_traits::{One, Zero};
257 use proptest::{prelude::*, proptest};
258
259 use super::*;
260
261 const_monty_params!(Params, U128, "00000000b933426489189cb5b47d567f");
262
263 type F = ConstMontyField<Params, { U128::LIMBS }>;
264
265 const NUM_VARS: u32 = 8;
266
267 fn cfg() -> FixedConfig<F> {
268 FixedConfig::default()
269 }
270
271 #[test]
272 fn build_eq_x_r_vec_matches_product_formula() {
273 let r: Vec<F> = (0..NUM_VARS).map(|i| F::from(i + 11)).collect();
276 let eq_vec = build_eq_x_r_vec(&cfg(), &r).unwrap();
277
278 let n = 1usize << NUM_VARS;
279 assert_eq!(eq_vec.len(), n);
280
281 let one = F::one();
282 for b in 0..n {
283 let mut expected = one;
284 for (i, ri) in r.iter().enumerate() {
285 let bit = (b >> i) & 1;
286 expected *= if bit == 1 { *ri } else { one - ri };
287 }
288 assert_eq!(eq_vec[b], expected, "mismatch at b={b}");
289 }
290 }
291
292 #[test]
293 fn build_eq_x_r_vec_basic() {
294 let r: [F; _] = [F::from(3_u64)];
295 let evals = build_eq_x_r_vec(&cfg(), &r).unwrap();
296 assert_eq!(evals, vec![F::one() - r[0], r[0]]);
297 }
298
299 #[test]
300 fn build_eq_x_r_vec_two_vars() {
301 let r: [F; _] = [F::from(2_u64), F::from(5_u64)];
302 let evals = build_eq_x_r_vec(&cfg(), &r).unwrap();
303 let e00 = (F::one() - r[0]) * (F::one() - r[1]);
304 let e01 = r[0] * (F::one() - r[1]);
305 let e10 = (F::one() - r[0]) * r[1];
306 let e11 = r[0] * r[1];
307 assert_eq!(evals, vec![e00, e01, e10, e11]);
308 }
309
310 #[test]
311 fn build_eq_x_r_error_on_empty() {
312 let r: [F; 0] = [];
313 let err = build_eq_x_r_vec(&cfg(), &r).unwrap_err();
314 let msg = format!("{err}");
315 assert!(msg.contains("Invalid parameters"));
316 }
317
318 #[test]
319 fn build_eq_x_r_mle_properties() {
320 let r: [F; _] = [F::from(7_u64), F::from(11_u64), F::from(13_u64)];
321 let mle = build_eq_x_r(&cfg(), &r).unwrap();
322 assert_eq!(mle.num_vars, r.len());
323 let evals = mle.evaluations;
324 let direct = build_eq_x_r_vec(&cfg(), &r).unwrap();
325 assert_eq!(evals, direct);
326 }
327
328 #[test]
329 fn next_mle_is_one_on_successors() {
330 let next_mle = next_mle(NUM_VARS, F::zero(), F::one()).unwrap();
331
332 for i in 0..(1 << ((NUM_VARS / 2) - 1)) {
333 let mut point: Vec<F> = (0..(NUM_VARS / 2))
334 .map(|j| {
335 if i & (1 << j) == 0 {
336 F::zero()
337 } else {
338 F::one()
339 }
340 })
341 .collect();
342
343 point.extend((0..(NUM_VARS / 2)).map(|j| {
344 if (i + 1) & (1 << j) == 0 {
345 F::zero()
346 } else {
347 F::one()
348 }
349 }));
350
351 assert_eq!(next_mle.clone().evaluate(&cfg(), &point), Ok(F::one()));
352 }
353 }
354
355 #[test]
356 fn next_mle_is_one_only_on_successors() {
357 let next_mle = next_mle(NUM_VARS, F::zero(), F::one()).unwrap();
358
359 assert_eq!(
363 next_mle.evaluations.iter().filter(|x| !x.is_zero()).count(),
364 (1 << (NUM_VARS / 2)) - 1
365 );
366 }
367
368 fn any_f() -> impl Strategy<Value = F> + 'static {
369 any::<u128>().prop_map(F::from)
370 }
371
372 fn point_n(n: usize) -> impl Strategy<Value = Vec<F>> {
373 prop::collection::vec(any_f(), n)
374 }
375
376 #[test]
377 fn next_mle_eval_coincides_with_next_mle_evaluated_at_successors() {
378 let next_mle = next_mle(NUM_VARS, F::zero(), F::one()).unwrap();
379
380 for i in 0..(1 << ((NUM_VARS / 2) - 1)) {
381 let mut point: Vec<F> = (0..(NUM_VARS / 2))
382 .map(|j| {
383 if i & (1 << j) == 0 {
384 F::zero()
385 } else {
386 F::one()
387 }
388 })
389 .collect();
390
391 point.extend((0..(NUM_VARS / 2)).map(|j| {
392 if (i + 1) & (1 << j) == 0 {
393 F::zero()
394 } else {
395 F::one()
396 }
397 }));
398
399 let (u, v) = point.split_at(NUM_VARS as usize / 2);
400 assert_eq!(
401 next_mle.clone().evaluate(&cfg(), &point),
402 Ok(next_mle_eval(&cfg(), u, v))
403 );
404 }
405 }
406
407 proptest! {
408 #[test]
409 #[cfg_attr(miri, ignore)] fn prop_next_mle_eval_coincides_with_next_mle_evaluate_at_point(r in point_n(NUM_VARS as usize)) {
411 let next_mle = next_mle(NUM_VARS, F::zero(), F::one()).unwrap();
412
413 let (u, v) = r.split_at(NUM_VARS as usize / 2);
414 prop_assert_eq!(
415 next_mle.evaluate(&cfg(), &r),
416 Ok(next_mle_eval(&cfg(), u, v))
417 );
418 }
419 }
420
421 #[test]
422 #[cfg_attr(miri, ignore)] fn next_c_r_mle_c1_matches_shift_by_1() {
424 let num_vars: usize = 4;
426 let r: Vec<F> = (0..num_vars).map(|i| F::from((i + 3) as u32)).collect();
427
428 let next_1 = build_next_c_r_mle(&cfg(), &r, 1).unwrap();
429
430 let eq_r = build_eq_x_r(&cfg(), &r).unwrap();
432 let n = 1 << num_vars;
433 let mut expected = vec![F::zero(); 1];
434 expected.extend_from_slice(&eq_r.evaluations[..n - 1]);
435
436 assert_eq!(next_1.evaluations, expected);
437 }
438
439 #[test]
440 #[cfg_attr(miri, ignore)] fn next_c_r_mle_c0_is_eq() {
442 let num_vars: usize = 4;
444 let r: Vec<F> = (0..num_vars).map(|i| F::from((i + 7) as u32)).collect();
445
446 let next_0 = build_next_c_r_mle(&cfg(), &r, 0).unwrap();
447 let eq_r = build_eq_x_r(&cfg(), &r).unwrap();
448
449 assert_eq!(next_0.evaluations, eq_r.evaluations);
450 }
451
452 #[test]
453 #[cfg_attr(miri, ignore)] fn next_c_r_mle_has_correct_structure() {
455 let num_vars: usize = 4;
459 let n = 1 << num_vars;
460 let r: Vec<F> = (0..num_vars).map(|i| F::from((i + 5) as u32)).collect();
461
462 for c in [2, 3, 5, 7] {
463 let next_c = build_next_c_r_mle(&cfg(), &r, c).unwrap();
464 let eq_r = build_eq_x_r(&cfg(), &r).unwrap();
465
466 for b in 0..c {
468 assert!(
469 next_c.evaluations[b].is_zero(),
470 "c={c}, b={b}: expected zero"
471 );
472 }
473 for b in c..n {
475 assert_eq!(
476 next_c.evaluations[b],
477 eq_r.evaluations[b - c],
478 "c={c}, b={b}: mismatch"
479 );
480 }
481 }
482 }
483
484 proptest! {
485 #[test]
486 #[cfg_attr(miri, ignore)] fn prop_next_c_r_mle_evaluates_correctly(r in point_n(4), c in 1..15usize) {
488 let next_c = build_next_c_r_mle(&cfg(), &r, c).unwrap();
491 let eq_r = build_eq_x_r(&cfg(), &r).unwrap();
492
493 let n = 1 << r.len();
495 for b in 0..c.min(n) {
496 prop_assert!(next_c.evaluations[b].is_zero());
497 }
498 for b in c..n {
499 prop_assert_eq!(&next_c.evaluations[b], &eq_r.evaluations[b - c]);
500 }
501 }
502 }
503}