1use std::{cmp::Reverse, marker::PhantomData, ops::Range};
4
5use binius_field::{util::eq, ExtensionField, Field, PackedExtension, PackedField, TowerField};
6use binius_hal::{
7 make_portable_backend, ComputationBackend, Error as HalError, SumcheckEvaluator,
8 SumcheckMultilinear,
9};
10use binius_math::{
11 CompositionPoly, EvaluationDomainFactory, EvaluationOrder, InterpolationDomain,
12 MLEDirectAdapter, MultilinearPoly, RowsBatchRef,
13};
14use binius_maybe_rayon::prelude::*;
15use binius_utils::bail;
16use getset::Getters;
17use itertools::izip;
18use stackalloc::stackalloc_with_default;
19use tracing::instrument;
20
21use crate::{
22 polynomial::{ArithCircuitPoly, Error as PolynomialError, MultivariatePoly},
23 protocols::sumcheck::{
24 common::{
25 equal_n_vars_check, get_nontrivial_evaluation_points,
26 interpolation_domains_for_composition_degrees, RoundCoeffs,
27 },
28 prove::{common::fold_partial_eq_ind, ProverState, SumcheckInterpolator, SumcheckProver},
29 CompositeSumClaim, Error,
30 },
31 transparent::{eq_ind::EqIndPartialEval, step_up::StepUp},
32};
33
34pub fn validate_witness<F, P, M, Composition>(
35 n_vars: usize,
36 multilinears: &[SumcheckMultilinear<P, M>],
37 eq_ind_challenges: &[F],
38 eq_ind_sum_claims: impl IntoIterator<Item = CompositeSumClaim<F, Composition>>,
39) -> Result<(), Error>
40where
41 F: Field,
42 P: PackedField<Scalar = F>,
43 M: MultilinearPoly<P> + Send + Sync,
44 Composition: CompositionPoly<P>,
45{
46 if eq_ind_challenges.len() != n_vars {
47 bail!(Error::IncorrectEqIndChallengesLength);
48 }
49
50 for multilinear in multilinears {
51 match *multilinear {
52 SumcheckMultilinear::Transparent {
53 ref multilinear,
54 const_suffix: (suffix_eval, suffix_len),
55 ..
56 } => {
57 if multilinear.n_vars() != n_vars {
58 bail!(Error::NumberOfVariablesMismatch);
59 }
60
61 let first_const = (1usize << n_vars)
62 .checked_sub(suffix_len)
63 .ok_or(Error::IncorrectConstSuffixes)?;
64
65 for i in first_const..1 << n_vars {
66 if multilinear.evaluate_on_hypercube(i)? != suffix_eval {
67 bail!(Error::IncorrectConstSuffixes);
68 }
69 }
70 }
71
72 SumcheckMultilinear::Folded {
73 large_field_folded_evals: ref evals,
74 ..
75 } => {
76 if evals.len() > 1 << n_vars.saturating_sub(P::LOG_WIDTH) {
77 bail!(Error::IncorrectConstSuffixes)
78 }
79 }
80 }
81 }
82
83 let backend = make_portable_backend();
84 let eq_ind =
85 EqIndPartialEval::new(eq_ind_challenges).multilinear_extension::<P, _>(&backend)?;
86
87 for (i, claim) in eq_ind_sum_claims.into_iter().enumerate() {
88 let CompositeSumClaim {
89 composition,
90 sum: expected_sum,
91 } = claim;
92 let sum = (0..(1 << n_vars))
93 .into_par_iter()
94 .try_fold(
95 || (vec![P::zero(); multilinears.len()], F::ZERO),
96 |(mut multilinear_evals, mut running_sum), j| -> Result<_, Error> {
97 for (eval, multilinear) in izip!(&mut multilinear_evals, multilinears) {
98 *eval = P::broadcast(match multilinear {
99 SumcheckMultilinear::Transparent { multilinear, .. } => {
100 multilinear.evaluate_on_hypercube(j)?
101 }
102 SumcheckMultilinear::Folded {
103 large_field_folded_evals,
104 suffix_eval,
105 } => binius_field::packed::get_packed_slice_checked(
106 large_field_folded_evals,
107 j,
108 )
109 .unwrap_or(*suffix_eval),
110 });
111 }
112
113 running_sum += eq_ind.evaluate_on_hypercube(j)?
114 * composition.evaluate(&multilinear_evals)?.get(0);
115 Ok((multilinear_evals, running_sum))
116 },
117 )
118 .map(|fold_state| -> Result<_, Error> { Ok(fold_state?.1) })
119 .try_reduce(|| F::ZERO, |a, b| Ok(a + b))?;
120
121 if sum != expected_sum {
122 bail!(Error::SumcheckNaiveValidationFailure {
123 composition_index: i,
124 });
125 }
126 }
127 Ok(())
128}
129
130pub struct EqIndSumcheckProverBuilder<'a, P, M, Backend>
146where
147 P: PackedField,
148 M: MultilinearPoly<P>,
149 Backend: ComputationBackend,
150{
151 n_vars: usize,
152 eq_ind_partial_evals: Option<Backend::Vec<P>>,
153 first_round_eval_1s: Option<Vec<P::Scalar>>,
154 multilinears: Vec<SumcheckMultilinear<P, M>>,
155 backend: &'a Backend,
156}
157
158impl<'a, F, P, Backend> EqIndSumcheckProverBuilder<'a, P, MLEDirectAdapter<P, Vec<P>>, Backend>
159where
160 F: TowerField,
161 P: PackedField<Scalar = F>,
162 Backend: ComputationBackend,
163{
164 pub fn without_switchover(
165 n_vars: usize,
166 multilinears: Vec<Vec<P>>,
167 backend: &'a Backend,
168 ) -> Self {
169 let multilinears = multilinears
170 .into_iter()
171 .map(SumcheckMultilinear::folded)
172 .collect();
173
174 Self {
175 n_vars,
176 eq_ind_partial_evals: None,
177 first_round_eval_1s: None,
178 multilinears,
179 backend,
180 }
181 }
182}
183
184impl<'a, F, P, M, Backend> EqIndSumcheckProverBuilder<'a, P, M, Backend>
185where
186 F: TowerField,
187 P: PackedField<Scalar = F>,
188 M: MultilinearPoly<P> + Send + Sync,
189 Backend: ComputationBackend,
190{
191 pub fn with_switchover(
192 multilinears: Vec<M>,
193 switchover_fn: impl Fn(usize) -> usize,
194 backend: &'a Backend,
195 ) -> Result<Self, Error> {
196 let n_vars = equal_n_vars_check(&multilinears)?;
197 let multilinears = multilinears
198 .into_iter()
199 .map(|multilinear| SumcheckMultilinear::transparent(multilinear, &switchover_fn))
200 .collect();
201
202 Ok(Self {
203 n_vars,
204 eq_ind_partial_evals: None,
205 first_round_eval_1s: None,
206 multilinears,
207 backend,
208 })
209 }
210
211 pub fn with_eq_ind_partial_evals(mut self, eq_ind_partial_evals: Backend::Vec<P>) -> Self {
213 self.eq_ind_partial_evals = Some(eq_ind_partial_evals);
214 self
215 }
216
217 pub fn with_first_round_eval_1s(mut self, first_round_eval_1s: &[F]) -> Self {
223 self.first_round_eval_1s = Some(first_round_eval_1s.to_vec());
224 self
225 }
226
227 pub fn with_const_suffixes(mut self, const_suffixes: &[(F, usize)]) -> Result<Self, Error> {
232 if const_suffixes.len() != self.multilinears.len() {
233 bail!(Error::IncorrectConstSuffixes);
234 }
235
236 for (multilinear, &const_suffix) in izip!(&mut self.multilinears, const_suffixes) {
237 let (_, suffix_len) = const_suffix;
238
239 if suffix_len > 1 << self.n_vars {
240 bail!(Error::IncorrectConstSuffixes);
241 }
242
243 multilinear.update_const_suffix(self.n_vars, const_suffix);
244 }
245
246 Ok(self)
247 }
248
249 #[instrument(skip_all, level = "debug", name = "EqIndSumcheckProverBuilder::build")]
250 pub fn build<FDomain, Composition>(
251 self,
252 evaluation_order: EvaluationOrder,
253 eq_ind_challenges: &[F],
254 composite_claims: impl IntoIterator<Item = CompositeSumClaim<F, Composition>>,
255 domain_factory: impl EvaluationDomainFactory<FDomain>,
256 ) -> Result<EqIndSumcheckProver<'a, FDomain, P, Composition, M, Backend>, Error>
257 where
258 F: ExtensionField<FDomain>,
259 P: PackedExtension<FDomain>,
260 FDomain: Field,
261 Composition: CompositionPoly<P>,
262 {
263 let Self {
264 n_vars,
265 backend,
266 multilinears,
267 ..
268 } = self;
269 let composite_claims = composite_claims.into_iter().collect::<Vec<_>>();
270
271 #[cfg(feature = "debug_validate_sumcheck")]
272 {
273 let composite_claims = composite_claims
274 .iter()
275 .map(|composite_claim| CompositeSumClaim {
276 composition: &composite_claim.composition,
277 sum: composite_claim.sum,
278 })
279 .collect::<Vec<_>>();
280 validate_witness(n_vars, &multilinears, eq_ind_challenges, composite_claims.clone())?;
281 }
282
283 if eq_ind_challenges.len() != n_vars {
284 bail!(Error::IncorrectEqIndChallengesLength);
285 }
286
287 let eq_ind_partial_evals = if let Some(eq_ind_partial_evals) = self.eq_ind_partial_evals {
290 if eq_ind_partial_evals.len() != 1 << n_vars.saturating_sub(P::LOG_WIDTH + 1) {
291 bail!(Error::IncorrectEqIndPartialEvalsSize);
292 }
293
294 eq_ind_partial_evals
295 } else {
296 eq_ind_expand(evaluation_order, eq_ind_challenges, backend)?
297 };
298
299 if let Some(ref first_round_eval_1s) = self.first_round_eval_1s {
300 if first_round_eval_1s.len() != composite_claims.len() {
301 bail!(Error::IncorrectFirstRoundEvalOnesLength);
302 }
303 }
304
305 for claim in &composite_claims {
306 if claim.composition.n_vars() != multilinears.len() {
307 bail!(Error::InvalidComposition {
308 expected: multilinears.len(),
309 actual: claim.composition.n_vars(),
310 });
311 }
312 }
313
314 let (compositions, claimed_sums) = determine_const_eval_suffixes(
315 composite_claims,
316 multilinears
317 .iter()
318 .map(|multilinear| multilinear.const_suffix(n_vars)),
319 );
320
321 let domains = interpolation_domains_for_composition_degrees(
322 domain_factory,
323 compositions
324 .iter()
325 .map(|(composition, _)| composition.degree()),
326 )?;
327
328 let nontrivial_evaluation_points = get_nontrivial_evaluation_points(&domains)?;
329
330 let state = ProverState::new(
331 evaluation_order,
332 n_vars,
333 multilinears,
334 claimed_sums,
335 nontrivial_evaluation_points,
336 backend,
337 )?;
338
339 let eq_ind_prefix_eval = F::ONE;
340 let eq_ind_challenges = eq_ind_challenges.to_vec();
341 let first_round_eval_1s = self.first_round_eval_1s;
342
343 Ok(EqIndSumcheckProver {
344 n_vars,
345 state,
346 eq_ind_prefix_eval,
347 eq_ind_partial_evals,
348 eq_ind_challenges,
349 compositions,
350 domains,
351 first_round_eval_1s,
352 backend: PhantomData,
353 })
354 }
355}
356
357#[derive(Default, PartialEq, Eq, Debug)]
358pub struct ConstEvalSuffix<F: Field> {
359 pub suffix: usize,
360 pub value: F,
361 pub value_at_inf: F,
362}
363
364impl<F: Field> ConstEvalSuffix<F> {
365 fn update(&mut self, evaluation_order: EvaluationOrder, n_vars: usize) {
366 let eval_prefix = (1 << n_vars) - self.suffix;
367 let updated_eval_prefix = match evaluation_order {
368 EvaluationOrder::LowToHigh => eval_prefix.div_ceil(2),
369 EvaluationOrder::HighToLow => eval_prefix.min(1 << (n_vars - 1)),
370 };
371 self.suffix = (1 << (n_vars - 1)) - updated_eval_prefix;
372 }
373}
374
375#[derive(Debug, Getters)]
376pub struct EqIndSumcheckProver<'a, FDomain, P, Composition, M, Backend>
377where
378 FDomain: Field,
379 P: PackedField,
380 M: MultilinearPoly<P> + Send + Sync,
381 Backend: ComputationBackend,
382{
383 n_vars: usize,
384 state: ProverState<'a, FDomain, P, M, Backend>,
385 eq_ind_prefix_eval: P::Scalar,
386 eq_ind_partial_evals: Backend::Vec<P>,
387 eq_ind_challenges: Vec<P::Scalar>,
388 #[getset(get = "pub")]
389 compositions: Vec<(Composition, ConstEvalSuffix<P::Scalar>)>,
390 domains: Vec<InterpolationDomain<FDomain>>,
391 first_round_eval_1s: Option<Vec<P::Scalar>>,
392 backend: PhantomData<Backend>,
393}
394
395impl<F, FDomain, P, Composition, M, Backend>
396 EqIndSumcheckProver<'_, FDomain, P, Composition, M, Backend>
397where
398 F: TowerField + ExtensionField<FDomain>,
399 FDomain: Field,
400 P: PackedExtension<FDomain, Scalar = F>,
401 Composition: CompositionPoly<P>,
402 M: MultilinearPoly<P> + Send + Sync,
403 Backend: ComputationBackend,
404{
405 fn round(&self) -> usize {
406 self.n_vars - self.n_rounds_remaining()
407 }
408
409 fn n_rounds_remaining(&self) -> usize {
410 self.state.n_vars()
411 }
412
413 fn eq_ind_round_challenge(&self) -> F {
414 match self.state.evaluation_order() {
415 EvaluationOrder::LowToHigh => self.eq_ind_challenges[self.round()],
416 EvaluationOrder::HighToLow => {
417 self.eq_ind_challenges[self.eq_ind_challenges.len() - 1 - self.round()]
418 }
419 }
420 }
421
422 fn update_eq_ind_prefix_eval(&mut self, challenge: F) {
423 self.eq_ind_prefix_eval *= eq(self.eq_ind_round_challenge(), challenge);
425 }
426}
427
428pub fn eq_ind_expand<P, Backend>(
429 evaluation_order: EvaluationOrder,
430 eq_ind_challenges: &[P::Scalar],
431 backend: &Backend,
432) -> Result<Backend::Vec<P>, HalError>
433where
434 P: PackedField,
435 Backend: ComputationBackend,
436{
437 let n_vars = eq_ind_challenges.len();
438 backend.tensor_product_full_query(match evaluation_order {
439 EvaluationOrder::LowToHigh => &eq_ind_challenges[n_vars.min(1)..],
440 EvaluationOrder::HighToLow => &eq_ind_challenges[..n_vars.saturating_sub(1)],
441 })
442}
443
444type CompositionsAndSums<F, Composition> = (Vec<(Composition, ConstEvalSuffix<F>)>, Vec<F>);
445
446fn determine_const_eval_suffixes<F, P, Composition>(
453 composite_claims: Vec<CompositeSumClaim<F, Composition>>,
454 const_suffixes: impl IntoIterator<Item = (F, usize)>,
455) -> CompositionsAndSums<F, Composition>
456where
457 F: Field,
458 P: PackedField<Scalar = F>,
459 Composition: CompositionPoly<P>,
460{
461 let mut const_suffixes = const_suffixes.into_iter().enumerate().collect::<Vec<_>>();
462
463 const_suffixes.sort_by_key(|&(_var, (_suffix_eval, suffix_len))| Reverse(suffix_len));
464
465 composite_claims
466 .into_iter()
467 .map(|claim| {
468 let CompositeSumClaim { composition, sum } = claim;
469 assert_eq!(const_suffixes.len(), composition.n_vars());
470
471 let mut const_eval_suffix = Default::default();
472
473 let mut expr = composition.expression();
474 let mut expr_at_inf = composition.expression().leading_term();
475
476 for &(var_index, (suffix_eval, suffix)) in &const_suffixes {
477 expr = expr.const_subst(var_index, suffix_eval).optimize();
478 expr_at_inf = expr_at_inf
480 .const_subst(var_index, suffix_eval + suffix_eval)
481 .optimize();
482
483 if let Some((value, value_at_inf)) =
484 expr.get_constant().zip(expr_at_inf.get_constant())
485 {
486 const_eval_suffix = ConstEvalSuffix {
487 suffix,
488 value,
489 value_at_inf,
490 };
491 break;
492 }
493 }
494
495 ((composition, const_eval_suffix), sum)
496 })
497 .unzip::<_, _, Vec<_>, Vec<_>>()
498}
499
500impl<F, FDomain, P, Composition, M, Backend> SumcheckProver<F>
501 for EqIndSumcheckProver<'_, FDomain, P, Composition, M, Backend>
502where
503 F: TowerField + ExtensionField<FDomain>,
504 FDomain: Field,
505 P: PackedExtension<FDomain, Scalar = F>,
506 Composition: CompositionPoly<P>,
507 M: MultilinearPoly<P> + Send + Sync,
508 Backend: ComputationBackend,
509{
510 fn n_vars(&self) -> usize {
511 self.n_vars
512 }
513
514 fn evaluation_order(&self) -> EvaluationOrder {
515 self.state.evaluation_order()
516 }
517
518 fn execute(&mut self, batch_coeff: F) -> Result<RoundCoeffs<F>, Error> {
519 let round = self.round();
520 let n_rounds_remaining = self.n_rounds_remaining();
521
522 let alpha = self.eq_ind_round_challenge();
523 let eq_ind_partial_evals = &self.eq_ind_partial_evals;
524
525 let first_round_eval_1s = self.first_round_eval_1s.take();
526 let have_first_round_eval_1s = first_round_eval_1s.is_some();
527
528 let eq_ind_challenges = match self.state.evaluation_order() {
529 EvaluationOrder::LowToHigh => &self.eq_ind_challenges[self.n_vars.min(round + 1)..],
530 EvaluationOrder::HighToLow => {
531 &self.eq_ind_challenges[..self.n_vars.saturating_sub(round + 1)]
532 }
533 };
534
535 let evaluators = self
536 .compositions
537 .iter_mut()
538 .map(|(composition, const_eval_suffix)| {
539 let composition_at_infinity =
540 ArithCircuitPoly::new(composition.expression().leading_term());
541
542 const_eval_suffix.update(self.state.evaluation_order(), n_rounds_remaining);
543
544 Evaluator {
545 n_rounds_remaining,
546 composition,
547 composition_at_infinity,
548 have_first_round_eval_1s,
549 eq_ind_challenges,
550 eq_ind_partial_evals,
551 const_eval_suffix,
552 }
553 })
554 .collect::<Vec<_>>();
555
556 let interpolators = self
557 .domains
558 .iter()
559 .enumerate()
560 .map(|(index, interpolation_domain)| Interpolator {
561 interpolation_domain,
562 alpha,
563 first_round_eval_1: first_round_eval_1s
564 .as_ref()
565 .map(|first_round_eval_1s| first_round_eval_1s[index]),
566 })
567 .collect::<Vec<_>>();
568
569 let round_evals = self.state.calculate_round_evals(&evaluators)?;
570
571 let prime_coeffs = self.state.calculate_round_coeffs_from_evals(
572 &interpolators,
573 batch_coeff,
574 round_evals,
575 )?;
576
577 let (prime_coeffs_scaled_by_constant_term, mut prime_coeffs_scaled_by_linear_term) =
582 if F::CHARACTERISTIC == 2 {
583 (prime_coeffs.clone() * (F::ONE + alpha), prime_coeffs)
584 } else {
585 (prime_coeffs.clone() * (F::ONE - alpha), prime_coeffs * (alpha.double() - F::ONE))
586 };
587
588 prime_coeffs_scaled_by_linear_term.0.insert(0, F::ZERO); let coeffs = (prime_coeffs_scaled_by_constant_term + &prime_coeffs_scaled_by_linear_term)
591 * self.eq_ind_prefix_eval;
592
593 Ok(coeffs)
594 }
595
596 #[instrument(skip_all, name = "EqIndSumcheckProver::fold", level = "debug")]
597 fn fold(&mut self, challenge: F) -> Result<(), Error> {
598 self.update_eq_ind_prefix_eval(challenge);
599
600 let evaluation_order = self.state.evaluation_order();
601 let n_rounds_remaining = self.n_rounds_remaining();
602
603 let Self {
604 state,
605 eq_ind_partial_evals,
606 ..
607 } = self;
608
609 binius_maybe_rayon::join(
610 || state.fold(challenge),
611 || {
612 fold_partial_eq_ind::<P, Backend>(
613 evaluation_order,
614 n_rounds_remaining - 1,
615 eq_ind_partial_evals,
616 );
617 },
618 )
619 .0?;
620 Ok(())
621 }
622
623 fn finish(self: Box<Self>) -> Result<Vec<F>, Error> {
624 let mut evals = self.state.finish()?;
625 evals.push(self.eq_ind_prefix_eval);
626 Ok(evals)
627 }
628}
629
630struct Evaluator<'a, P, Composition>
631where
632 P: PackedField,
633{
634 n_rounds_remaining: usize,
635 composition: &'a Composition,
636 composition_at_infinity: ArithCircuitPoly<P::Scalar>,
637 have_first_round_eval_1s: bool,
638 eq_ind_challenges: &'a [P::Scalar],
639 eq_ind_partial_evals: &'a [P],
640 const_eval_suffix: &'a ConstEvalSuffix<P::Scalar>,
641}
642
643impl<P, Composition> SumcheckEvaluator<P, Composition> for Evaluator<'_, P, Composition>
644where
645 P: PackedField<Scalar: TowerField>,
646 Composition: CompositionPoly<P>,
647{
648 fn eval_point_indices(&self) -> Range<usize> {
649 let start_index = if self.have_first_round_eval_1s { 2 } else { 1 };
651 start_index..self.composition.degree() + 1
652 }
653
654 fn process_subcube_at_eval_point(
655 &self,
656 subcube_vars: usize,
657 subcube_index: usize,
658 is_infinity_point: bool,
659 batch_query: &RowsBatchRef<P>,
660 ) -> P {
661 let row_len = batch_query.row_len();
662
663 stackalloc_with_default(row_len, |evals| {
664 if is_infinity_point {
665 self.composition_at_infinity
666 .batch_evaluate(batch_query, evals)
667 .expect("correct by query construction invariant");
668 } else {
669 self.composition
670 .batch_evaluate(batch_query, evals)
671 .expect("correct by query construction invariant");
672 };
673
674 let subcube_start = subcube_index << subcube_vars.saturating_sub(P::LOG_WIDTH);
675 for (i, eval) in evals.iter_mut().enumerate() {
676 *eval *= self.eq_ind_partial_evals[subcube_start + i];
680 }
681 evals.iter().copied().sum::<P>()
682 })
683 }
684
685 fn process_constant_eval_suffix(
686 &self,
687 const_eval_suffix: usize,
688 is_infinity_point: bool,
689 ) -> P::Scalar {
690 let eval_prefix = (1 << self.n_rounds_remaining) - const_eval_suffix;
691 let eq_ind_suffix_sum = StepUp::new(self.eq_ind_challenges.len(), eval_prefix)
692 .expect("eval_prefix does not exceed the equality indicator size")
693 .evaluate(self.eq_ind_challenges)
694 .expect("StepUp is initialized with eq_ind_challenges.len()");
695
696 eq_ind_suffix_sum
697 * if is_infinity_point {
698 self.const_eval_suffix.value_at_inf
699 } else {
700 self.const_eval_suffix.value
701 }
702 }
703
704 fn composition(&self) -> &Composition {
705 self.composition
706 }
707
708 fn eq_ind_partial_eval(&self) -> Option<&[P]> {
709 Some(self.eq_ind_partial_evals)
710 }
711
712 fn const_eval_suffix(&self) -> usize {
713 self.const_eval_suffix.suffix
714 }
715}
716
717struct Interpolator<'a, F, FDomain>
718where
719 F: Field,
720 FDomain: Field,
721{
722 interpolation_domain: &'a InterpolationDomain<FDomain>,
723 alpha: F,
724 first_round_eval_1: Option<F>,
725}
726
727impl<F, FDomain> SumcheckInterpolator<F> for Interpolator<'_, F, FDomain>
728where
729 F: ExtensionField<FDomain>,
730 FDomain: Field,
731{
732 #[instrument(
733 skip_all,
734 name = "eq_ind::Interpolator::round_evals_to_coeffs",
735 level = "debug"
736 )]
737 fn round_evals_to_coeffs(
738 &self,
739 last_round_sum: F,
740 mut round_evals: Vec<F>,
741 ) -> Result<Vec<F>, PolynomialError> {
742 if let Some(first_round_eval_1) = self.first_round_eval_1 {
743 round_evals.insert(0, first_round_eval_1);
744 }
745
746 let one_evaluation = round_evals[0];
747 let zero_evaluation_numerator = last_round_sum - one_evaluation * self.alpha;
748 let zero_evaluation_denominator_inv = (F::ONE - self.alpha).invert_or_zero();
749 let zero_evaluation = zero_evaluation_numerator * zero_evaluation_denominator_inv;
750 round_evals.insert(0, zero_evaluation);
751
752 if round_evals.len() > 3 {
753 let infinity_round_eval = round_evals.remove(2);
758 round_evals.push(infinity_round_eval);
759 }
760
761 Ok(self.interpolation_domain.interpolate(&round_evals)?)
762 }
763}