remainder/layer/gate/
gate_helpers.rs

1use ark_std::{cfg_into_iter, cfg_iter};
2use itertools::Itertools;
3
4use std::fmt::Debug;
5
6use crate::{
7    mle::{betavalues::BetaValues, Mle},
8    sumcheck::*,
9    utils::mle::{
10        compute_flipped_bit_idx_and_values_lexicographic,
11        compute_inverses_vec_and_one_minus_inverted_vec, compute_next_beta_value_from_current,
12        compute_next_beta_values_vec_from_current,
13    },
14};
15use shared_types::Field;
16
17use crate::mle::{dense::DenseMle, MleIndex};
18use thiserror::Error;
19
20use super::BinaryOperation;
21#[cfg(feature = "parallel")]
22use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
23
24use anyhow::{anyhow, Ok, Result};
25
26/// Error handling for gate mle construction.
27#[derive(Error, Debug, Clone)]
28pub enum GateError {
29    #[error("phase 1 not initialized")]
30    /// Error when initializing the first phase, which is when we bind the "x"
31    /// bits.
32    Phase1InitError,
33    #[error("phase 2 not initialized")]
34    /// Error when initializing the second phase, which is when we bind the "y"
35    /// bits.
36    Phase2InitError,
37    #[error("mle not fully bound")]
38    /// We are on the last round of sumcheck and want to grab claims, but the
39    /// MLE is not fully bound which should not be the case for the last round
40    /// of sumcheck.
41    MleNotFullyBoundError,
42    #[error("empty list for lhs or rhs")]
43    /// We are initializing a gate on something that does not have either a left
44    /// or right side of the expression.
45    EmptyMleList,
46    #[error("bound indices fail to match challenge")]
47    /// When checking the last round of sumcheck, the challenges don't match
48    /// what is bound to the MLE.
49    EvaluateBoundIndicesDontMatch,
50}
51
52/// Given (possibly half-fixed) bookkeeping tables of the MLEs which are
53/// multiplied, e.g. V_i(u_1, ..., u_{k - 1}, x, b_{k + 1}, ..., b_n) * V_{i +
54/// 1}(u_1, ..., u_{k - 1}, x, b_{k + 1}, ..., b_n) computes g_k(x) = \sum_{b_{k
55/// + 1}, ..., b_n} V_i(u_1, ..., u_{k - 1}, x, b_{k + 1}, ..., b_n) * V_{i +
56///   1}(u_1, ..., u_{k - 1}, x, b_{k + 1}, ..., b_n) at `degree + 1` points.
57///
58/// ## Arguments
59/// * `mles` - MLEs pointing to the actual bookkeeping tables for the above
60/// * `independent_variable` - whether the `x` from above resides within at
61///   least one of the `mles`
62/// * `degree` - degree of `g_k(x)`, i.e. number of evaluations to send (minus
63///   one!)
64pub(crate) fn evaluate_mle_product_no_beta_table<F: Field>(
65    mles: &[&impl Mle<F>],
66    independent_variable: bool,
67    degree: usize,
68) -> Result<SumcheckEvals<F>> {
69    // Gets the total number of free variables across all MLEs within this
70    // product
71    let max_num_vars = mles
72        .iter()
73        .map(|mle| mle.num_free_vars())
74        .max()
75        .ok_or(MleError::EmptyMleList)?;
76
77    if independent_variable {
78        // There is an independent variable, and we must extract `degree`
79        // evaluations of it, over `0..degree`
80        let eval_count = degree + 1;
81        let mle_num_coefficients_mid = 1 << (max_num_vars - 1);
82
83        // iterate across all pairs of evaluations
84        let evals = cfg_into_iter!((0..mle_num_coefficients_mid)).fold(
85            #[cfg(feature = "parallel")]
86            || vec![F::ZERO; eval_count],
87            #[cfg(not(feature = "parallel"))]
88            vec![F::ZERO; eval_count],
89            |mut acc, index| {
90                // get the product of all evaluations over 0/1/..degree
91                let evals = mles
92                    .iter()
93                    .map(|mle| {
94                        let index = if mle.num_free_vars() < max_num_vars {
95                            let max = 1 << mle.num_free_vars();
96                            let multiple = (1 << max_num_vars) / max;
97                            index / multiple
98                        } else {
99                            index
100                        };
101                        let first = mle.get(index).unwrap_or(F::ZERO);
102                        let second = if mle.num_free_vars() != 0 {
103                            mle.get(index + mle_num_coefficients_mid).unwrap_or(F::ZERO)
104                        } else {
105                            first
106                        };
107
108                        let step = second - first;
109
110                        let successors =
111                            std::iter::successors(Some(second), move |item| Some(*item + step));
112                        // iterator that represents all evaluations of the MLE
113                        // extended to arbitrarily many linear extrapolations on
114                        // the line of 0/1
115                        std::iter::once(first).chain(successors)
116                    })
117                    .map(|item| -> Box<dyn Iterator<Item = F>> { Box::new(item) })
118                    .reduce(|acc, evals| Box::new(acc.zip(evals).map(|(acc, eval)| acc * eval)))
119                    .unwrap();
120
121                acc.iter_mut()
122                    .zip(evals)
123                    .for_each(|(acc, eval)| *acc += eval);
124                acc
125            },
126        );
127
128        #[cfg(feature = "parallel")]
129        let evals = evals.reduce(
130            || vec![F::ZERO; eval_count],
131            |mut acc, partial| {
132                acc.iter_mut()
133                    .zip(partial)
134                    .for_each(|(acc, partial)| *acc += partial);
135                acc
136            },
137        );
138
139        Ok(SumcheckEvals(evals))
140    } else {
141        // There is no independent variable and we can sum over everything
142        let sum = cfg_into_iter!((0..(1 << max_num_vars))).fold(
143            #[cfg(feature = "parallel")]
144            || F::ZERO,
145            #[cfg(not(feature = "parallel"))]
146            F::ZERO,
147            |acc, index| {
148                // Go through each MLE within the product
149                let product = mles
150                    .iter()
151                    // Result of this `map()`: A list of evaluations of the MLEs
152                    // at `index`
153                    .map(|mle| {
154                        let index = if mle.num_free_vars() < max_num_vars {
155                            let max = 1 << mle.num_free_vars();
156                            let multiple = (1 << max_num_vars) / max;
157                            index / multiple
158                        } else {
159                            index
160                        };
161                        // Access the MLE at that index. Pad with zeros
162                        mle.get(index).unwrap_or(F::ZERO)
163                    })
164                    .reduce(|acc, eval| acc * eval)
165                    .unwrap();
166
167                // Combine them into the accumulator.
168                //
169                // Note that the accumulator stores g(0), g(1), ..., g(d - 1)
170                acc + product
171            },
172        );
173
174        #[cfg(feature = "parallel")]
175        let sum = sum.reduce(|| F::ZERO, |acc, partial| acc + partial);
176
177        Ok(SumcheckEvals(vec![sum; degree]))
178    }
179}
180
181/// Index mle indices for an array of mles.
182pub fn index_mle_indices_gate<F: Field>(mles: &mut [impl Mle<F>], index: usize) {
183    mles.iter_mut().for_each(|mle| {
184        mle.index_mle_indices(index);
185    })
186}
187
188/// Compute the fully bound identity gate function, which is f_1(g, u) where the
189/// output gate label variables are fully bound to the claim on the layer and
190/// the input gate label variables are fully bound to the round challenges from
191/// sumcheck.
192///
193/// Similar to [fold_wiring_into_beta_mle_identity_gate], this function utilizes
194/// an inspiration of Rothblum's memory-efficient sumcheck trick in order to
195/// compute the relevant beta values over the sparse wiring function by just
196/// multiplying by the relevant inverses.
197///
198/// The function is also usable if parallelism is turned on, by having the
199/// folding accumulator keep track of the previous state.
200pub(crate) fn compute_fully_bound_identity_gate_function<F: Field>(
201    nondataparallel_round_challenges: &[F],
202    nondataparallel_claim_challenges_vec: &[&[F]],
203    wiring: &[(u32, u32)],
204    random_coefficients: &[F],
205) -> F {
206    let (inverses_round_challenges, one_minus_elem_inverted_round_challenges): (Vec<F>, Vec<F>) =
207        nondataparallel_round_challenges
208            .iter()
209            .map(|elem| {
210                let inverse = elem.invert().unwrap();
211                let one_minus_elem_inverse = (F::ONE - elem).invert().unwrap();
212                (inverse, one_minus_elem_inverse)
213            })
214            .unzip();
215
216    let (inverses_vec_claim_challenges, one_minus_elem_inverted_vec_claim_challenges) =
217        compute_inverses_vec_and_one_minus_inverted_vec(nondataparallel_claim_challenges_vec);
218
219    let prev_aux_and_gate_function = cfg_iter!(wiring).fold(
220        #[cfg(feature = "parallel")]
221        || {
222            (
223                ((None::<&u32>, None::<&u32>), (None::<Vec<F>>, None::<F>)),
224                F::ZERO,
225            )
226        },
227        #[cfg(not(feature = "parallel"))]
228        (
229            ((None::<&u32>, None::<&u32>), (None::<Vec<F>>, None::<F>)),
230            F::ZERO,
231        ),
232        |(maybe_previous_aux, current_accumulation_of_gate_value),
233         (next_nonzero_output_gate_label, next_nonzero_input_gate_label)| {
234            // If there is Some(value) for the previous auxiliary information, then we know
235            // that we are past the first initialization of the iterator, and
236            // use these previous values to accumulate.
237            if let (
238                (Some(current_nonzero_output_gate_label), Some(current_nonzero_input_gate_label)),
239                (
240                    Some(current_beta_values_claim_challenges),
241                    Some(current_beta_value_round_challenges),
242                ),
243            ) = maybe_previous_aux
244            {
245                // Between the previous input label and the next one, compute the
246                // flipped bits and their values in order to decide which inverses
247                // to multiply by.
248                let flipped_bit_idx_and_values_round =
249                    compute_flipped_bit_idx_and_values_lexicographic(
250                        *current_nonzero_input_gate_label,
251                        *next_nonzero_input_gate_label,
252                    );
253
254                // We do the same thing for the previous output gate label and
255                // the next one.
256                let flipped_bit_idx_and_values_claim =
257                    compute_flipped_bit_idx_and_values_lexicographic(
258                        *current_nonzero_output_gate_label,
259                        *next_nonzero_output_gate_label,
260                    );
261
262                // Compute the next beta value by multiplying the current ones
263                // by the appropriate inverses and elements -- this is
264                // specifically for the round challenges over the input labels
265                // as indices. We know there is only one, so we directly index
266                // into our function.
267                let next_beta_value_round = compute_next_beta_value_from_current(
268                    &current_beta_value_round_challenges,
269                    &inverses_round_challenges,
270                    &one_minus_elem_inverted_round_challenges,
271                    nondataparallel_round_challenges,
272                    &flipped_bit_idx_and_values_round,
273                );
274
275                // Compute the next beta values by multiplying the current ones
276                // by the appropriate inverses and elements -- this is
277                // specifically for the claim challenges over the output labels
278                // as indices.
279                let next_beta_values_claim = compute_next_beta_values_vec_from_current(
280                    &current_beta_values_claim_challenges,
281                    &inverses_vec_claim_challenges,
282                    &one_minus_elem_inverted_vec_claim_challenges,
283                    nondataparallel_claim_challenges_vec,
284                    &flipped_bit_idx_and_values_claim,
285                );
286
287                let rlc_over_claim_beta_values = next_beta_values_claim
288                    .iter()
289                    .zip(random_coefficients)
290                    .fold(F::ZERO, |acc, (elem, random_coeff)| {
291                        acc + (*elem * random_coeff)
292                    });
293
294                // We accumulate by adding the previous value of \beta(g, z)
295                // * \beta(u, x) to the current one.
296                let accumulation_of_gate_value_vec = current_accumulation_of_gate_value
297                    + next_beta_value_round * rlc_over_claim_beta_values;
298
299                (
300                    (
301                        (
302                            Some(next_nonzero_output_gate_label),
303                            Some(next_nonzero_input_gate_label),
304                        ),
305                        (Some(next_beta_values_claim), Some(next_beta_value_round)),
306                    ),
307                    accumulation_of_gate_value_vec,
308                )
309            } else {
310                // If the previous auxiliary information is None, we are on our
311                // first iteration of the iterator and therefore simply
312                // initialize the beta function over the point. We send this
313                // information into the accumulator for future rounds.
314                let next_beta_value_round = BetaValues::compute_beta_over_challenge_and_index(
315                    nondataparallel_round_challenges,
316                    *next_nonzero_input_gate_label as usize,
317                );
318
319                let next_beta_values_claim = nondataparallel_claim_challenges_vec
320                    .iter()
321                    .map(|claim_point| {
322                        BetaValues::compute_beta_over_challenge_and_index(
323                            claim_point,
324                            *next_nonzero_output_gate_label as usize,
325                        )
326                    })
327                    .collect_vec();
328
329                let rlc_over_claim_beta_values = next_beta_values_claim
330                    .iter()
331                    .zip(random_coefficients)
332                    .fold(F::ZERO, |acc, (elem, random_coeff)| {
333                        acc + (*elem * random_coeff)
334                    });
335
336                (
337                    (
338                        (
339                            Some(next_nonzero_output_gate_label),
340                            Some(next_nonzero_input_gate_label),
341                        ),
342                        (Some(next_beta_values_claim), Some(next_beta_value_round)),
343                    ),
344                    next_beta_value_round * rlc_over_claim_beta_values,
345                )
346            }
347        },
348    );
349
350    // For RLC Claim aggregation, we have multiple claims and therefore multiple beta tables.
351    #[cfg(feature = "parallel")]
352    {
353        prev_aux_and_gate_function
354            .map(|(_, gate_function)| gate_function)
355            .sum::<F>()
356    }
357    #[cfg(not(feature = "parallel"))]
358    {
359        let (_, gate_function_over_claims) = prev_aux_and_gate_function;
360        gate_function_over_claims
361    }
362}
363
364/// Similar to [compute_fully_bound_identity_gate_function], this
365/// function uses the "Rothblum" trick in order to evaluate the
366/// fully bound gate function in a streaming fashion.
367pub(crate) fn compute_fully_bound_binary_gate_function<F: Field>(
368    nondataparallel_round_u_challenges: &[F],
369    nondataparallel_round_v_challenges: &[F],
370    nondataparallel_claim_challenges_vec: &[&[F]],
371    wiring: &[(u32, u32, u32)],
372    random_coefficients: &[F],
373) -> F {
374    let (inverses_round_u_challenges, one_minus_elem_inverted_round_u_challenges): (
375        Vec<F>,
376        Vec<F>,
377    ) = nondataparallel_round_u_challenges
378        .iter()
379        .map(|elem| {
380            let inverse = elem.invert().unwrap();
381            let one_minus_elem_inverse = (F::ONE - elem).invert().unwrap();
382            (inverse, one_minus_elem_inverse)
383        })
384        .unzip();
385
386    let (inverses_round_v_challenges, one_minus_elem_inverted_round_v_challenges): (
387        Vec<F>,
388        Vec<F>,
389    ) = nondataparallel_round_v_challenges
390        .iter()
391        .map(|elem| {
392            let inverse = elem.invert().unwrap();
393            let one_minus_elem_inverse = (F::ONE - elem).invert().unwrap();
394            (inverse, one_minus_elem_inverse)
395        })
396        .unzip();
397
398    let (inverses_vec_claim_challenges, one_minus_elem_inverted_vec_claim_challenges) =
399        compute_inverses_vec_and_one_minus_inverted_vec(nondataparallel_claim_challenges_vec);
400
401    let prev_aux_and_gate_function = cfg_iter!(wiring).fold(
402        #[cfg(feature = "parallel")]
403        || {
404            (
405                (None::<(&u32, &u32, &u32)>, None::<(Vec<F>, F, F)>),
406                F::ZERO,
407            )
408        },
409        #[cfg(not(feature = "parallel"))]
410        (
411            (None::<(&u32, &u32, &u32)>, None::<(Vec<F>, F, F)>),
412            F::ZERO,
413        ),
414        |(maybe_previous_aux, current_accumulation_of_gate_value), (next_z, next_x, next_y)| {
415            // If there is Some(value) for the previous auxiliary information, then
416            // we know that we are past the first initialization of the iterator, and
417            // use these previous values to accumulate.
418            if let (
419                Some((current_z, current_x, current_y)),
420                Some((current_beta_gz_vec, current_beta_u, current_beta_v)),
421            ) = maybe_previous_aux
422            {
423                // Between the previous input label and the next one, compute the
424                // flipped bits and their values in order to decide which inverses
425                // to multiply by.
426                let flipped_bit_idx_and_values_u =
427                    compute_flipped_bit_idx_and_values_lexicographic(*current_x, *next_x);
428                let flipped_bit_idx_and_values_v =
429                    compute_flipped_bit_idx_and_values_lexicographic(*current_y, *next_y);
430
431                // We do the same thing for the previous output gate label and
432                // the next one.
433                let flipped_bit_idx_and_values_z =
434                    compute_flipped_bit_idx_and_values_lexicographic(*current_z, *next_z);
435
436                // Compute the next beta value by multiplying the current ones
437                // by the appropriate inverses and elements -- this is
438                // specifically for the round challenges over the input labels
439                // as indices. We know there is only one, so we directly index
440                // into our function.
441                let next_beta_value_u = compute_next_beta_value_from_current(
442                    &current_beta_u,
443                    &inverses_round_u_challenges,
444                    &one_minus_elem_inverted_round_u_challenges,
445                    nondataparallel_round_u_challenges,
446                    &flipped_bit_idx_and_values_u,
447                );
448                let next_beta_value_v = compute_next_beta_value_from_current(
449                    &current_beta_v,
450                    &inverses_round_v_challenges,
451                    &one_minus_elem_inverted_round_v_challenges,
452                    nondataparallel_round_v_challenges,
453                    &flipped_bit_idx_and_values_v,
454                );
455
456                // Compute the next beta values by multiplying the current ones
457                // by the appropriate inverses and elements -- this is
458                // specifically for the claim challenges over the output labels
459                // as indices.
460                let next_beta_values_gz = compute_next_beta_values_vec_from_current(
461                    current_beta_gz_vec.as_slice(),
462                    &inverses_vec_claim_challenges,
463                    &one_minus_elem_inverted_vec_claim_challenges,
464                    nondataparallel_claim_challenges_vec,
465                    &flipped_bit_idx_and_values_z,
466                );
467
468                let rlc_over_claim_beta_values = next_beta_values_gz
469                    .iter()
470                    .zip(random_coefficients)
471                    .fold(F::ZERO, |acc, (elem, random_coeff)| {
472                        acc + (*elem * random_coeff)
473                    });
474
475                // We accumulate by adding the previous value of \beta(g, z)
476                // * \beta(u, x) to the current one.
477                let accumulation_of_gate_value_vec = current_accumulation_of_gate_value
478                    + next_beta_value_u * next_beta_value_v * rlc_over_claim_beta_values;
479
480                (
481                    (
482                        Some((next_z, next_x, next_y)),
483                        Some((next_beta_values_gz, next_beta_value_u, next_beta_value_v)),
484                    ),
485                    accumulation_of_gate_value_vec,
486                )
487            } else {
488                // If the previous auxiliary information is None, we are on our
489                // first iteration of the iterator and therefore simply
490                // initialize the beta function over the point. We send this
491                // information into the accumulator for future rounds.
492                let next_beta_value_round_u = BetaValues::compute_beta_over_challenge_and_index(
493                    nondataparallel_round_u_challenges,
494                    *next_x as usize,
495                );
496                let next_beta_value_round_v = BetaValues::compute_beta_over_challenge_and_index(
497                    nondataparallel_round_v_challenges,
498                    *next_y as usize,
499                );
500
501                let next_beta_values_claim = nondataparallel_claim_challenges_vec
502                    .iter()
503                    .map(|claim_point| {
504                        BetaValues::compute_beta_over_challenge_and_index(
505                            claim_point,
506                            *next_z as usize,
507                        )
508                    })
509                    .collect_vec();
510
511                let rlc_over_claim_beta_values = next_beta_values_claim
512                    .iter()
513                    .zip(random_coefficients)
514                    .fold(F::ZERO, |acc, (elem, random_coeff)| {
515                        acc + (*elem * random_coeff)
516                    });
517
518                (
519                    (
520                        Some((next_z, next_x, next_y)),
521                        Some((
522                            next_beta_values_claim,
523                            next_beta_value_round_u,
524                            next_beta_value_round_v,
525                        )),
526                    ),
527                    next_beta_value_round_u * next_beta_value_round_v * rlc_over_claim_beta_values,
528                )
529            }
530        },
531    );
532
533    // For RLC Claim aggregation, we have multiple claims and therefore multiple beta tables.
534    #[cfg(feature = "parallel")]
535    {
536        prev_aux_and_gate_function
537            .map(|(_, gate_function)| gate_function)
538            .sum::<F>()
539    }
540    #[cfg(not(feature = "parallel"))]
541    {
542        let (_, gate_function_over_claims) = prev_aux_and_gate_function;
543        gate_function_over_claims
544    }
545}
546
547/// The folding occurs by first iterating through the nonzero gates, and
548/// computing the value \beta(g, z) for each output label z and the challenge
549/// point g. Instead of computing the \beta value from scratch, we take
550/// inspiration from the Rothblum sumcheck trick to just multiply by the
551/// inverses and points necessary to go from one beta value to the next. This
552/// conserves memory by not having to store the entire table (which is done to
553/// achieve linear computation) while continuing to achieve amortized linear
554/// computation for each progressive beta value as opposed to the O(log(n))
555/// method used to compute it from scratch.
556///
557/// We have multiple `claim_points` when using RLC claim agg, and in this case,
558/// we compute the random linear combination of the `random_coefficients` and
559/// the `g` variables.
560pub(crate) fn fold_wiring_into_beta_mle_identity_gate<F: Field>(
561    wiring: &[(u32, u32)],
562    claim_points: &[&[F]],
563    num_vars_folded_vec: usize,
564    random_coefficients: &[F],
565) -> Vec<F> {
566    // Precompute all the inverses necessary for each of the claim points.
567    let (inverses_vec, one_minus_inverses_vec) =
568        compute_inverses_vec_and_one_minus_inverted_vec(claim_points);
569
570    // Initialize the folded vector of coefficients.
571    let mut folded_vec = vec![F::ZERO; 1 << num_vars_folded_vec];
572
573    // We start at the first nonzero gate and first beta value for each claim
574    // challenge.
575    let (mut current_nonzero_output_gate_label, mut current_nonzero_input_gate_label) = wiring[0];
576    let mut current_beta_values = claim_points
577        .iter()
578        .map(|claim_point| {
579            BetaValues::compute_beta_over_challenge_and_index(
580                claim_point,
581                current_nonzero_output_gate_label as usize,
582            )
583        })
584        .collect_vec();
585    let first_nonzero_gate_beta_rlc = current_beta_values
586        .iter()
587        .zip(random_coefficients)
588        .fold(F::ZERO, |acc, (elem, random_coeff)| {
589            acc + (*elem * random_coeff)
590        });
591
592    // We update the folded vector with the base case.
593    folded_vec[current_nonzero_input_gate_label as usize] = first_nonzero_gate_beta_rlc;
594
595    wiring.iter().skip(1).for_each(
596        |(next_nonzero_output_gate_label, next_nonzero_input_gate_label)| {
597            // Between the previous output label and the next one, compute the
598            // flipped bits and their values in order to decide which inverses
599            // to multiply by.
600            let flipped_bit_idx_and_values = compute_flipped_bit_idx_and_values_lexicographic(
601                current_nonzero_output_gate_label,
602                *next_nonzero_output_gate_label,
603            );
604            let next_beta_values = compute_next_beta_values_vec_from_current(
605                &current_beta_values,
606                &inverses_vec,
607                &one_minus_inverses_vec,
608                claim_points,
609                &flipped_bit_idx_and_values,
610            );
611
612            let beta_values_rlc = next_beta_values
613                .iter()
614                .zip(random_coefficients)
615                .fold(F::ZERO, |acc, (elem, random_coeff)| {
616                    acc + (*elem * random_coeff)
617                });
618
619            // Update the folded vector with the appropriate RLC'ed value of the
620            // wiring into its equality MLE.
621            folded_vec[*next_nonzero_input_gate_label as usize] += beta_values_rlc;
622            current_nonzero_input_gate_label = *next_nonzero_input_gate_label;
623            current_nonzero_output_gate_label = *next_nonzero_output_gate_label;
624            current_beta_values = next_beta_values;
625        },
626    );
627
628    folded_vec
629}
630
631/// This function uses the "Rothblum"-inspired sumcheck trick
632/// in order to evaluate the necessary MLEs as defined in
633/// <https://eprint.iacr.org/2019/317.pdf> for phase 1 of
634/// binary gate sumcheck messages.
635///
636/// # Arguments:
637/// * `wiring`: The gate wiring in the form (z, x, y) such that
638///   the gate_operation(value of the LHS MLE at x, value of the
639///   RHS MLE at y) = value of the output MLE at z.
640/// * `claim_points`: The claims made on the output MLE of this
641///   layer.
642/// * `f2_x_mle`: The MLE representing the LHS of the input MLE
643///   into this binary gate.
644/// * `f3_y_mle`: The MLE representing the RHS of the input MLE
645///   into this binary gate.
646/// * `random_coefficients`: The random coefficients used to
647///   aggregate the claims made on this layer.
648/// * `gate_operation`: The binary operation used to combine the
649///   input MLEs, which is either [BinaryOperation::Add] or
650///   [BinaryOperation::Mul].
651pub(crate) fn fold_binary_gate_wiring_into_mles_phase_1<F: Field>(
652    wiring: &[(u32, u32, u32)],
653    claim_points: &[&[F]],
654    f2_x_mle: &DenseMle<F>,
655    f3_y_mle: &DenseMle<F>,
656    random_coefficients: &[F],
657    gate_operation: BinaryOperation,
658) -> (Vec<F>, Vec<F>) {
659    let num_vars = f2_x_mle.num_free_vars();
660    let (inverses, one_minus_elem_inverted) =
661        compute_inverses_vec_and_one_minus_inverted_vec(claim_points);
662    let folded_tables = cfg_into_iter!(wiring).fold(
663        #[cfg(feature = "parallel")]
664        || {
665            (
666                None::<(Vec<F>, u32)>,
667                vec![F::ZERO; 1 << num_vars],
668                vec![F::ZERO; 1 << num_vars],
669            )
670        },
671        #[cfg(not(feature = "parallel"))]
672        (
673            None::<(Vec<F>, u32)>,
674            vec![F::ZERO; 1 << num_vars],
675            vec![F::ZERO; 1 << num_vars],
676        ),
677        |(maybe_current_beta_aux, mut acc_of_a_hg_lhs, mut acc_of_a_hg_rhs),
678         (next_z, next_x, next_y)| {
679            let next_beta_vec = {
680                if let Some((current_beta_values, current_z_idx)) = maybe_current_beta_aux {
681                    let flipped_bits_and_idx =
682                        compute_flipped_bit_idx_and_values_lexicographic(current_z_idx, *next_z);
683                    compute_next_beta_values_vec_from_current(
684                        &current_beta_values,
685                        &inverses,
686                        &one_minus_elem_inverted,
687                        claim_points,
688                        &flipped_bits_and_idx,
689                    )
690                } else {
691                    claim_points
692                        .iter()
693                        .map(|claim_point| {
694                            BetaValues::compute_beta_over_challenge_and_index(
695                                claim_point,
696                                *next_z as usize,
697                            )
698                        })
699                        .collect_vec()
700                }
701            };
702            let beta_vec_rlc = next_beta_vec
703                .iter()
704                .zip(random_coefficients)
705                .fold(F::ZERO, |acc, (random_coeff, beta)| {
706                    acc + *random_coeff * beta
707                });
708            let f_3_at_y = f3_y_mle.get(*next_y as usize).unwrap_or(F::ZERO);
709            acc_of_a_hg_rhs[*next_x as usize] += beta_vec_rlc * f_3_at_y;
710            if gate_operation == BinaryOperation::Add {
711                acc_of_a_hg_lhs[*next_x as usize] += beta_vec_rlc;
712            }
713            (
714                Some((next_beta_vec, *next_z)),
715                acc_of_a_hg_lhs,
716                acc_of_a_hg_rhs,
717            )
718        },
719    );
720    #[cfg(feature = "parallel")]
721    {
722        let (_, a_hg_lhs, a_hg_rhs) = folded_tables.reduce(
723            || {
724                (
725                    None,
726                    vec![F::ZERO; 1 << num_vars],
727                    vec![F::ZERO; 1 << num_vars],
728                )
729            },
730            |(_, mut a_hg_lhs_acc, mut a_hg_rhs_acc), (_, a_hg_lhs_partial, a_hg_rhs_partial)| {
731                a_hg_lhs_acc
732                    .iter_mut()
733                    .zip(a_hg_lhs_partial.into_iter())
734                    .for_each(|(acc, partial)| *acc += partial);
735
736                a_hg_rhs_acc
737                    .iter_mut()
738                    .zip(a_hg_rhs_partial.into_iter())
739                    .for_each(|(acc, partial)| *acc += partial);
740
741                (None, a_hg_lhs_acc, a_hg_rhs_acc)
742            },
743        );
744
745        (a_hg_lhs, a_hg_rhs)
746    }
747    #[cfg(not(feature = "parallel"))]
748    {
749        let (_, a_hg_lhs, a_hg_rhs) = folded_tables;
750        (a_hg_lhs, a_hg_rhs)
751    }
752}
753
754/// This function uses the "Rothblum"-inspired sumcheck trick
755/// in order to evaluate the necessary MLEs as defined in
756/// <https://eprint.iacr.org/2019/317.pdf> for phase 2 of binary
757/// gate sumcheck messages.
758///
759/// # Arguments:
760/// * `wiring`: The gate wiring in the form (z, x, y) such that
761///   the gate_operation(value of the LHS MLE at x, value of the
762///   RHS MLE at y) = value of the output MLE at z.
763/// * `f2_at_u`: The fully bound value of the LHS MLE at the point
764///   `u_claim`.
765/// * `u_claim:` The challenges bound to the `x` variables (i.e.,
766///   the variables that make up the MLE that represents the LHS
767///   input to the binary gate).
768/// * `g1_claim_points`: The nondataparallel claims made on the
769///   output MLE of this layer.
770/// * `random_coefficients`: The random coefficients used to
771///   aggregate the claims made on this layer.
772/// * `num_vars`: The number of variables in each of the folded
773///   tables in the output.
774/// * `gate_operation`: The binary operation used to combine the
775///   input MLEs, which is either [BinaryOperation::Add] or
776///   [BinaryOperation::Mul].
777pub(crate) fn fold_binary_gate_wiring_into_mles_phase_2<F: Field>(
778    wiring: &[(u32, u32, u32)],
779    f2_at_u: F,
780    u_claim: &[F],
781    g1_claim_points: &[&[F]],
782    random_coefficients: &[F],
783    num_vars: usize,
784    gate_operation: BinaryOperation,
785) -> (Vec<F>, Vec<F>) {
786    let (inverses_g1, one_minus_elem_inverted_g1) =
787        compute_inverses_vec_and_one_minus_inverted_vec(g1_claim_points);
788    let (inverses_u, one_minus_elem_inverted_u): (Vec<F>, Vec<F>) = u_claim
789        .iter()
790        .map(|point| (point.invert().unwrap(), (F::ONE - point).invert().unwrap()))
791        .unzip();
792    let folded_tables = cfg_into_iter!(wiring).fold(
793        #[cfg(feature = "parallel")]
794        || {
795            (
796                (None::<(Vec<F>, u32)>, None::<(F, u32)>),
797                vec![F::ZERO; 1 << num_vars],
798                vec![F::ZERO; 1 << num_vars],
799            )
800        },
801        #[cfg(not(feature = "parallel"))]
802        (
803            (None::<(Vec<F>, u32)>, None::<(F, u32)>),
804            vec![F::ZERO; 1 << num_vars],
805            vec![F::ZERO; 1 << num_vars],
806        ),
807        |(maybe_current_beta_aux, mut acc_of_a_f1_lhs, mut acc_of_a_f1_rhs),
808         (next_z, next_x, next_y)| {
809            let (next_beta_vec_g1, next_beta_u) = {
810                if let (
811                    Some((current_beta_values_g1, current_z_idx)),
812                    Some((current_beta_value_u, current_x_idx)),
813                ) = maybe_current_beta_aux
814                {
815                    let flipped_bits_and_idx_g1 =
816                        compute_flipped_bit_idx_and_values_lexicographic(current_z_idx, *next_z);
817                    let flipped_bits_and_idx_u =
818                        compute_flipped_bit_idx_and_values_lexicographic(current_x_idx, *next_x);
819                    let next_beta_vec_g1 = compute_next_beta_values_vec_from_current(
820                        &current_beta_values_g1,
821                        &inverses_g1,
822                        &one_minus_elem_inverted_g1,
823                        g1_claim_points,
824                        &flipped_bits_and_idx_g1,
825                    );
826                    let next_beta_u = compute_next_beta_value_from_current(
827                        &current_beta_value_u,
828                        &inverses_u,
829                        &one_minus_elem_inverted_u,
830                        u_claim,
831                        &flipped_bits_and_idx_u,
832                    );
833                    (next_beta_vec_g1, next_beta_u)
834                } else {
835                    let next_beta_vec_g1 = g1_claim_points
836                        .iter()
837                        .map(|claim_point| {
838                            BetaValues::compute_beta_over_challenge_and_index(
839                                claim_point,
840                                *next_z as usize,
841                            )
842                        })
843                        .collect_vec();
844                    let next_beta_u = BetaValues::compute_beta_over_challenge_and_index(
845                        u_claim,
846                        *next_x as usize,
847                    );
848                    (next_beta_vec_g1, next_beta_u)
849                }
850            };
851            let beta_vec_rlc_g1 = next_beta_vec_g1
852                .iter()
853                .zip(random_coefficients)
854                .fold(F::ZERO, |acc, (random_coeff, beta)| {
855                    acc + *random_coeff * beta
856                });
857            let adder = beta_vec_rlc_g1 * next_beta_u;
858            acc_of_a_f1_lhs[*next_y as usize] += adder * f2_at_u;
859            if gate_operation == BinaryOperation::Add {
860                acc_of_a_f1_rhs[*next_y as usize] += adder;
861            }
862            (
863                (
864                    Some((next_beta_vec_g1, *next_z)),
865                    Some((next_beta_u, *next_x)),
866                ),
867                acc_of_a_f1_lhs,
868                acc_of_a_f1_rhs,
869            )
870        },
871    );
872
873    #[cfg(feature = "parallel")]
874    {
875        let (_, a_f1_lhs, a_f1_rhs) = folded_tables.reduce(
876            || {
877                (
878                    (None, None),
879                    vec![F::ZERO; 1 << num_vars],
880                    vec![F::ZERO; 1 << num_vars],
881                )
882            },
883            |(_, mut a_f1_lhs_acc, mut a_f1_rhs_acc), (_, a_f1_lhs_partial, a_f1_rhs_partial)| {
884                a_f1_lhs_acc
885                    .iter_mut()
886                    .zip(a_f1_lhs_partial.into_iter())
887                    .for_each(|(acc, partial)| *acc += partial);
888
889                a_f1_rhs_acc
890                    .iter_mut()
891                    .zip(a_f1_rhs_partial.into_iter())
892                    .for_each(|(acc, partial)| *acc += partial);
893
894                ((None, None), a_f1_lhs_acc, a_f1_rhs_acc)
895            },
896        );
897
898        (a_f1_lhs, a_f1_rhs)
899    }
900    #[cfg(not(feature = "parallel"))]
901    {
902        let (_, a_f1_lhs, a_f1_rhs) = folded_tables;
903        (a_f1_lhs, a_f1_rhs)
904    }
905}
906
907/// Compute sumcheck message without a beta table.
908pub fn compute_sumcheck_message_no_beta_table<F: Field>(
909    mles: &[&impl Mle<F>],
910    round_index: usize,
911    degree: usize,
912) -> Result<Vec<F>> {
913    // Go through all of the MLEs being multiplied together on the LHS and
914    // see if any of them contain an IV
915    let independent_variable = mles
916        .iter()
917        .map(|mle| mle.mle_indices().contains(&MleIndex::Indexed(round_index)))
918        .reduce(|acc, item| acc | item)
919        .ok_or(anyhow!(GateError::EmptyMleList))?;
920
921    let eval = evaluate_mle_product_no_beta_table(mles, independent_variable, degree).unwrap();
922
923    let SumcheckEvals(evaluations) = eval;
924
925    Ok(evaluations)
926}
927
928/// Get the evals for a binary gate specified by the BinaryOperation. Note that
929/// this specifically refers to computing the prover message while binding the
930/// dataparallel bits of a `IdentityGate` expression.
931pub(crate) fn compute_sumcheck_message_data_parallel_identity_gate<F: Field>(
932    source_mle: &DenseMle<F>,
933    wiring: &[(u32, u32)],
934    num_dataparallel_vars: usize,
935    challenges_vec: &[&[F]],
936    random_coefficients: &[F],
937) -> Result<Vec<F>> {
938    let (inverses_vec, one_minus_elem_inverted_vec) =
939        compute_inverses_vec_and_one_minus_inverted_vec(challenges_vec);
940
941    // The degree is 2 because we have the independent variable as degree 2,
942    // appearing once in the wiring folded into the beta MLE, and once in
943    // the source MLE.
944    let degree = 2;
945
946    // There is an independent variable, and we must extract `degree`
947    // evaluations of it, over `0..degree`.
948    let eval_count = degree + 1;
949
950    let num_dataparallel_copies_mid = 1 << (num_dataparallel_vars - 1);
951
952    let num_nondataparallel_coeffs_source =
953        1 << (source_mle.num_free_vars() - num_dataparallel_vars);
954    let num_z_coeffs = 1 << (challenges_vec[0].len() - num_dataparallel_vars);
955    let scaled_wirings: Vec<(u32, u32)> = cfg_into_iter!((0..num_dataparallel_copies_mid))
956        .flat_map(|p2_idx| {
957            wiring
958                .iter()
959                .map(|(z, x)| {
960                    (
961                        num_z_coeffs * p2_idx + z,
962                        num_nondataparallel_coeffs_source * p2_idx + x,
963                    )
964                })
965                .collect_vec()
966        })
967        .collect();
968
969    let evals = cfg_into_iter!(scaled_wirings).fold(
970        #[cfg(feature = "parallel")]
971        || (vec![F::ZERO; eval_count], None::<(Vec<F>, u32)>),
972        #[cfg(not(feature = "parallel"))]
973        (vec![F::ZERO; eval_count], None::<(Vec<F>, u32)>),
974        |(mut acc, maybe_current_beta_aux), (next_scaled_z, next_scaled_x)| {
975            let next_beta_values_at_0 =
976                if let Some((current_beta_values, current_scaled_z)) = maybe_current_beta_aux {
977                    let flipped_bits_and_idx = compute_flipped_bit_idx_and_values_lexicographic(
978                        current_scaled_z,
979                        next_scaled_z,
980                    );
981                    compute_next_beta_values_vec_from_current(
982                        &current_beta_values,
983                        &inverses_vec,
984                        &one_minus_elem_inverted_vec,
985                        challenges_vec,
986                        &flipped_bits_and_idx,
987                    )
988                } else {
989                    challenges_vec
990                        .iter()
991                        .map(|challenge| {
992                            BetaValues::compute_beta_over_challenge_and_index(
993                                challenge,
994                                next_scaled_z as usize,
995                            )
996                        })
997                        .collect_vec()
998                };
999            let next_beta_values_at_1 = next_beta_values_at_0
1000                .iter()
1001                .zip(
1002                    challenges_vec
1003                        .iter()
1004                        .zip(one_minus_elem_inverted_vec.iter()),
1005                )
1006                .map(|(beta_at_0, (challenges, one_minus_inverses))| {
1007                    *beta_at_0 * one_minus_inverses[0] * challenges[0]
1008                })
1009                .collect_vec();
1010
1011            let rlc_beta_values_at_0 = next_beta_values_at_0
1012                .iter()
1013                .zip(random_coefficients)
1014                .fold(F::ZERO, |acc, (beta_val, random_coeff)| {
1015                    acc + *beta_val * random_coeff
1016                });
1017            let rlc_beta_values_at_1 = next_beta_values_at_1
1018                .iter()
1019                .zip(random_coefficients)
1020                .fold(F::ZERO, |acc, (beta_val, random_coeff)| {
1021                    acc + *beta_val * random_coeff
1022                });
1023            let linear_diff_betas = rlc_beta_values_at_1 - rlc_beta_values_at_0;
1024            let beta_evals_p2_z =
1025                std::iter::successors(Some(rlc_beta_values_at_1), move |rlc_beta_values_prev| {
1026                    Some(*rlc_beta_values_prev + linear_diff_betas)
1027                });
1028            let all_beta_evals_p2_z = std::iter::once(rlc_beta_values_at_0).chain(beta_evals_p2_z);
1029
1030            // Compute f_2((A, p_2), x) Note that the bookkeeping table
1031            // is big-endian, so we shift by idx * (number of non
1032            // dataparallel vars) to index into the correct copy.
1033            let source_0_p2_x = source_mle.get(next_scaled_x as usize).unwrap();
1034            let source_1_p2_x = if source_mle.num_free_vars() != 0 {
1035                source_mle
1036                    .get(
1037                        (next_scaled_x
1038                            + (num_nondataparallel_coeffs_source * num_dataparallel_copies_mid))
1039                            as usize,
1040                    )
1041                    .unwrap()
1042            } else {
1043                source_0_p2_x
1044            };
1045            let linear_diff_f2 = source_1_p2_x - source_0_p2_x;
1046
1047            let f2_evals_p2_x = std::iter::successors(Some(source_1_p2_x), move |f2_prev_p2_x| {
1048                Some(*f2_prev_p2_x + linear_diff_f2)
1049            });
1050            let all_f2_evals_p2_x = std::iter::once(source_0_p2_x).chain(f2_evals_p2_x);
1051
1052            // The evals we want are simply the element-wise product
1053            // of the accessed evals
1054            let g1_z_times_source_p2_x = all_beta_evals_p2_z
1055                .zip(all_f2_evals_p2_x)
1056                .map(|(g1_z_eval, source_eval)| g1_z_eval * source_eval);
1057
1058            let evals_iter: Box<dyn Iterator<Item = F>> = Box::new(g1_z_times_source_p2_x);
1059
1060            acc.iter_mut()
1061                .zip(evals_iter)
1062                .for_each(|(acc, eval)| *acc += eval);
1063            (acc, Some((next_beta_values_at_0, next_scaled_z)))
1064        },
1065    );
1066
1067    #[cfg(feature = "parallel")]
1068    {
1069        let evals = evals.reduce(
1070            || (vec![F::ZERO; eval_count], None),
1071            |(mut acc, _), (partial, _)| {
1072                acc.iter_mut()
1073                    .zip(partial)
1074                    .for_each(|(acc, partial)| *acc += partial);
1075                (acc, None)
1076            },
1077        );
1078        Ok(evals.0)
1079    }
1080    #[cfg(not(feature = "parallel"))]
1081    {
1082        Ok(evals.0)
1083    }
1084}
1085
1086/// Compute the sumcheck message for a binary gate specified by the BinaryOperation.
1087///
1088/// We use a "Rothblum"-inspired sumcheck trick in order to stream the beta MLE
1089/// while folding it into the wiring. We use the random coefficients to compute
1090/// the RLC of all the beta values.
1091pub fn compute_sumcheck_message_data_parallel_gate<F: Field>(
1092    f2_p2_x: &DenseMle<F>,
1093    f3_p2_y: &DenseMle<F>,
1094    operation: BinaryOperation,
1095    wiring: &[(u32, u32, u32)],
1096    num_dataparallel_vars: usize,
1097    challenges_vec: &[&[F]],
1098    random_coefficients: &[F],
1099) -> Result<Vec<F>> {
1100    let (inverses_vec, one_minus_elem_inverted_vec) =
1101        compute_inverses_vec_and_one_minus_inverted_vec(challenges_vec);
1102
1103    // When we have an add gate, we can distribute the beta table over the
1104    // dataparallel challenges so we only multiply to the function with the x
1105    // variables or y variables one at a time.
1106    //
1107    // When we have a mul gate, we have to multiply the beta table over the
1108    // dataparallel challenges with the function on the x variables and the
1109    // function on the y variables.
1110    let degree = match operation {
1111        BinaryOperation::Add => 2,
1112        BinaryOperation::Mul => 3,
1113    };
1114
1115    // There is an independent variable, and we must extract `degree`
1116    // evaluations of it, over `0..degree`.
1117    let eval_count = degree + 1;
1118
1119    let num_dataparallel_copies_mid = 1 << (num_dataparallel_vars - 1);
1120
1121    let num_nondataparallel_coeffs_f2_x = 1 << (f2_p2_x.num_free_vars() - num_dataparallel_vars);
1122    let num_nondataparallel_coeffs_f3_y = 1 << (f3_p2_y.num_free_vars() - num_dataparallel_vars);
1123    let num_z_coeffs = 1 << (challenges_vec[0].len() - num_dataparallel_vars);
1124    let scaled_wirings: Vec<(u32, u32, u32)> = cfg_into_iter!((0..num_dataparallel_copies_mid))
1125        .flat_map(|p2_idx| {
1126            wiring
1127                .iter()
1128                .map(|(z, x, y)| {
1129                    (
1130                        num_z_coeffs * p2_idx + z,
1131                        num_nondataparallel_coeffs_f2_x * p2_idx + x,
1132                        num_nondataparallel_coeffs_f3_y * p2_idx + y,
1133                    )
1134                })
1135                .collect_vec()
1136        })
1137        .collect();
1138
1139    let evals = cfg_into_iter!(scaled_wirings).fold(
1140        #[cfg(feature = "parallel")]
1141        || (vec![F::ZERO; eval_count], None::<(Vec<F>, u32)>),
1142        #[cfg(not(feature = "parallel"))]
1143        (vec![F::ZERO; eval_count], None::<(Vec<F>, u32)>),
1144        |(mut acc, maybe_current_beta_aux), (scaled_z, scaled_x, scaled_y)| {
1145            let next_beta_values_at_0 = if let Some((current_beta_values, current_scaled_z)) =
1146                maybe_current_beta_aux
1147            {
1148                let flipped_bits_and_idx =
1149                    compute_flipped_bit_idx_and_values_lexicographic(current_scaled_z, scaled_z);
1150                compute_next_beta_values_vec_from_current(
1151                    &current_beta_values,
1152                    &inverses_vec,
1153                    &one_minus_elem_inverted_vec,
1154                    challenges_vec,
1155                    &flipped_bits_and_idx,
1156                )
1157            } else {
1158                challenges_vec
1159                    .iter()
1160                    .map(|challenge| {
1161                        BetaValues::compute_beta_over_challenge_and_index(
1162                            challenge,
1163                            scaled_z as usize,
1164                        )
1165                    })
1166                    .collect_vec()
1167            };
1168            let next_beta_values_at_1 = next_beta_values_at_0
1169                .iter()
1170                .zip(
1171                    challenges_vec
1172                        .iter()
1173                        .zip(one_minus_elem_inverted_vec.iter()),
1174                )
1175                .map(|(beta_at_0, (challenges, one_minus_inverses))| {
1176                    *beta_at_0 * one_minus_inverses[0] * challenges[0]
1177                })
1178                .collect_vec();
1179
1180            let rlc_beta_values_at_0 = next_beta_values_at_0
1181                .iter()
1182                .zip(random_coefficients)
1183                .fold(F::ZERO, |acc, (beta_val, random_coeff)| {
1184                    acc + *beta_val * random_coeff
1185                });
1186            let rlc_beta_values_at_1 = next_beta_values_at_1
1187                .iter()
1188                .zip(random_coefficients)
1189                .fold(F::ZERO, |acc, (beta_val, random_coeff)| {
1190                    acc + *beta_val * random_coeff
1191                });
1192            let linear_diff_betas = rlc_beta_values_at_1 - rlc_beta_values_at_0;
1193            let beta_evals_p2_z =
1194                std::iter::successors(Some(rlc_beta_values_at_1), move |rlc_beta_values_prev| {
1195                    Some(*rlc_beta_values_prev + linear_diff_betas)
1196                });
1197            let all_beta_evals_p2_z = std::iter::once(rlc_beta_values_at_0).chain(beta_evals_p2_z);
1198
1199            // Compute f_2((A, p_2), x) Note that the bookkeeping table
1200            // is big-endian, so we shift by idx * (number of non
1201            // dataparallel vars) to index into the correct copy.
1202            let f2_0_p2_x = f2_p2_x.get(scaled_x as usize).unwrap();
1203            let f2_1_p2_x = if f2_p2_x.num_free_vars() != 0 {
1204                f2_p2_x
1205                    .get(
1206                        (scaled_x + (num_nondataparallel_coeffs_f2_x * num_dataparallel_copies_mid))
1207                            as usize,
1208                    )
1209                    .unwrap()
1210            } else {
1211                f2_0_p2_x
1212            };
1213            let linear_diff_f2 = f2_1_p2_x - f2_0_p2_x;
1214
1215            let f2_evals_p2_x = std::iter::successors(Some(f2_1_p2_x), move |f2_prev_p2_x| {
1216                Some(*f2_prev_p2_x + linear_diff_f2)
1217            });
1218            let all_f2_evals_p2_x = std::iter::once(f2_0_p2_x).chain(f2_evals_p2_x);
1219
1220            // Compute f_3((A, p_2), y). Note that the bookkeeping table is
1221            // big-endian, so we shift by `idx * (number of non dataparallel
1222            // vars)` to index into the correct copy.
1223            let f3_0_p2_y = f3_p2_y.get(scaled_y as usize).unwrap();
1224            let f3_1_p2_y = if f3_p2_y.num_free_vars() != 0 {
1225                f3_p2_y
1226                    .get(
1227                        (scaled_y + (num_nondataparallel_coeffs_f3_y * num_dataparallel_copies_mid))
1228                            as usize,
1229                    )
1230                    .unwrap()
1231            } else {
1232                f3_0_p2_y
1233            };
1234            let linear_diff_f3 = f3_1_p2_y - f3_0_p2_y;
1235
1236            let f3_evals_p2_y = std::iter::successors(Some(f3_1_p2_y), move |f3_prev_p2_y| {
1237                Some(*f3_prev_p2_y + linear_diff_f3)
1238            });
1239            let all_f3_evals_p2_y = std::iter::once(f3_0_p2_y).chain(f3_evals_p2_y);
1240
1241            // The evals we want are simply the element-wise product
1242            // of the accessed evals
1243            let g1_z_times_f2_evals_p2_x_times_f3_evals_p2_y = all_beta_evals_p2_z
1244                .zip(all_f2_evals_p2_x.zip(all_f3_evals_p2_y))
1245                .map(|(g1_z_eval, (f2_eval, f3_eval))| {
1246                    g1_z_eval * operation.perform_operation(f2_eval, f3_eval)
1247                });
1248
1249            let evals_iter: Box<dyn Iterator<Item = F>> =
1250                Box::new(g1_z_times_f2_evals_p2_x_times_f3_evals_p2_y);
1251
1252            acc.iter_mut()
1253                .zip(evals_iter)
1254                .for_each(|(acc, eval)| *acc += eval);
1255            (acc, Some((next_beta_values_at_0, scaled_z)))
1256        },
1257    );
1258
1259    #[cfg(feature = "parallel")]
1260    {
1261        let evals = evals.reduce(
1262            || (vec![F::ZERO; eval_count], None),
1263            |(mut acc, _), (partial, _)| {
1264                acc.iter_mut()
1265                    .zip(partial)
1266                    .for_each(|(acc, partial)| *acc += partial);
1267                (acc, None)
1268            },
1269        );
1270        Ok(evals.0)
1271    }
1272    #[cfg(not(feature = "parallel"))]
1273    {
1274        Ok(evals.0)
1275    }
1276}