@@ -16,7 +16,7 @@ use super::{
1616} ;
1717use crate :: {
1818 big_num:: { DelayedReduction , SmallValue , SmallValueEngine } ,
19- polys:: { eq:: EqPolynomial , eq:: compute_suffix_eq_pyramid, multilinear:: MultilinearPolynomial } ,
19+ polys:: { eq:: build_eq_pyramid , eq:: compute_suffix_eq_pyramid, multilinear:: MultilinearPolynomial } ,
2020} ;
2121use ff:: PrimeField ;
2222use num_traits:: Zero ;
@@ -28,12 +28,14 @@ use super::index::compute_idx4;
2828/// For Spartan's cubic relation (A·B - C), D=2 yields quadratic t_i.
2929pub const SPARTAN_T_DEGREE : usize = 2 ;
3030
31- /// Precomputed eq polynomial tables with balanced split.
31+ /// Precomputed eq polynomial pyramids with balanced split.
3232struct EqSplitTables < F : PrimeField > {
33- /// eq(τ[l0..l0+in_vars], ·) evaluations, size 2^in_vars
34- e_in : Vec < F > ,
35- /// eq(τ[l0+in_vars..], ·) evaluations, size 2^xout_vars
36- e_xout : Vec < F > ,
33+ /// Full pyramid for inner variables: eq(τ[l0..l0+in_vars], ·)
34+ /// Layer k has size 2^k, layer in_vars is the full table (size 2^in_vars)
35+ e_in_pyramid : Vec < Vec < F > > ,
36+ /// Full pyramid for outer variables: eq(τ[l0+in_vars..], ·)
37+ /// Layer k has size 2^k, layer xout_vars is the full table (size 2^xout_vars)
38+ e_xout_pyramid : Vec < Vec < F > > ,
3739 /// Suffix eq pyramid for prefix variables, Vec per round
3840 e_y : Vec < Vec < F > > ,
3941}
@@ -44,25 +46,37 @@ pub(crate) struct BetaPrefixCache {
4446 num_betas : usize ,
4547}
4648
47- /// Precompute eq polynomial tables with balanced split for e_in and e_xout.
49+ /// Precompute eq polynomial pyramids with balanced split for e_in and e_xout.
4850///
4951/// Returns (tables, in_vars, xout_vars) where:
5052/// - in_vars = ceil((l - l0) / 2) - variables for inner loop (e_in)
5153/// - xout_vars = floor((l - l0) / 2) - variables for outer loop (e_xout)
5254///
5355/// The balanced split reduces precomputation cost by ~33% compared to the
5456/// asymmetric l/2 split, and enables odd number of rounds.
57+ ///
58+ /// Both e_in and e_xout are returned as full pyramids (not just top layers),
59+ /// enabling reuse by EqSumCheckInstance for the remaining sumcheck rounds.
5560fn precompute_eq_tables < F : PrimeField > ( taus : & [ F ] , l0 : usize ) -> ( EqSplitTables < F > , usize , usize ) {
5661 let l = taus. len ( ) ;
5762 let suffix_vars = l - l0;
5863 let in_vars = suffix_vars. div_ceil ( 2 ) ; // ceiling: e_in larger (inner loop, sequential access)
5964 let xout_vars = suffix_vars - in_vars; // floor: e_xout smaller (outer loop, reused)
6065
61- let e_in = EqPolynomial :: evals_from_points ( & taus[ l0..l0 + in_vars] ) ; // 2^in_vars entries
62- let e_xout = EqPolynomial :: evals_from_points ( & taus[ l0 + in_vars..] ) ; // 2^xout_vars entries
66+ // Build full pyramids (not just top layers) for reuse by EqSumCheckInstance
67+ let e_in_pyramid = build_eq_pyramid ( & taus[ l0..l0 + in_vars] ) ; // in_vars+1 layers
68+ let e_xout_pyramid = build_eq_pyramid ( & taus[ l0 + in_vars..] ) ; // xout_vars+1 layers
6369 let e_y = compute_suffix_eq_pyramid ( & taus[ ..l0] , l0) ; // Vec per round, total 2^l0 - 1
6470
65- ( EqSplitTables { e_in, e_xout, e_y } , in_vars, xout_vars)
71+ (
72+ EqSplitTables {
73+ e_in_pyramid,
74+ e_xout_pyramid,
75+ e_y,
76+ } ,
77+ in_vars,
78+ xout_vars,
79+ )
6680}
6781
6882/// Build beta → prefix index cache for O(1) scatter access.
@@ -90,6 +104,16 @@ pub(crate) fn build_beta_cache<const D: usize>(l0: usize) -> BetaPrefixCache {
90104/// - `F`: Field type with small-value and delayed reduction support
91105/// - `SV`: Witness value type (i32 or i64)
92106///
107+ /// # Returns
108+ ///
109+ /// A tuple of (accumulators, e_in_pyramid, e_xout_pyramid) where:
110+ /// - accumulators: The computed Lagrange accumulators for small-value rounds
111+ /// - e_in_pyramid: Full eq pyramid for inner variables τ[l0..l0+in_vars], has in_vars+1 layers
112+ /// - e_xout_pyramid: Full eq pyramid for outer variables τ[l0+in_vars..ℓ], has xout_vars+1 layers
113+ ///
114+ /// The pyramids can be reused by EqSumCheckInstance for the remaining sumcheck rounds,
115+ /// avoiding redundant eq polynomial computation.
116+ ///
93117/// # Parallelism strategy
94118///
95119/// - Outer parallel loop over x_out values (using Rayon fold-reduce)
@@ -105,7 +129,7 @@ pub fn build_accumulators_spartan<F, SV>(
105129 bz : & MultilinearPolynomial < SV > ,
106130 taus : & [ F ] ,
107131 l0 : usize ,
108- ) -> LagrangeAccumulators < F , 2 >
132+ ) -> ( LagrangeAccumulators < F , 2 > , Vec < Vec < F > > , Vec < Vec < F > > )
109133where
110134 F : SmallValueEngine < SV > ,
111135 SV : SmallValue ,
@@ -120,10 +144,16 @@ where
120144 let suffix_vars = l - l0;
121145 let prefix_size = 1usize << l0;
122146
123- // Precompute eq tables with balanced split
124- let ( eq_tables, _in_vars , xout_vars) = precompute_eq_tables ( taus, l0) ;
147+ // Precompute eq pyramids with balanced split
148+ let ( eq_tables, in_vars , xout_vars) = precompute_eq_tables ( taus, l0) ;
125149 let num_x_out = 1usize << xout_vars;
126150
151+ // Get top layers (full eq tables) for accumulator computation
152+ let e_in = eq_tables. e_in_pyramid . last ( ) . expect ( "e_in_pyramid non-empty" ) ;
153+ let e_xout = eq_tables. e_xout_pyramid . last ( ) . expect ( "e_xout_pyramid non-empty" ) ;
154+ debug_assert_eq ! ( e_in. len( ) , 1 << in_vars) ;
155+ debug_assert_eq ! ( e_xout. len( ) , 1 << xout_vars) ;
156+
127157 // Build beta → prefix index cache
128158 let BetaPrefixCache {
129159 cache : beta_prefix_cache,
@@ -145,8 +175,7 @@ where
145175 . e_y
146176 . iter ( )
147177 . map ( |round_ey| {
148- eq_tables
149- . e_xout
178+ e_xout
150179 . iter ( )
151180 . flat_map ( |ex| round_ey. iter ( ) . map ( |ey| * ex * * ey) )
152181 . collect ( )
@@ -156,9 +185,6 @@ where
156185 // Precompute num_y per round for transposed access
157186 let num_y_per_round: Vec < usize > = eq_tables. e_y . iter ( ) . map ( |ey| ey. len ( ) ) . collect ( ) ;
158187
159- // Borrow e_in directly (no clone needed)
160- let e_in = & eq_tables. e_in ;
161-
162188 // Parallel over x_out with thread-local state (zero per-iteration allocations)
163189 type State < F2 , SV2 > = SpartanThreadState < F2 , SV2 , 2 > ;
164190
@@ -249,9 +275,16 @@ where
249275 . expect ( "num_x_out > 0 guarantees non-empty fold results" ) ;
250276
251277 // Finalize: reduce each bucket from wide 9-limb to field element
252- merged
278+ let accumulators = merged
253279 . acc
254- . map ( |acc| <F as DelayedReduction < F > >:: reduce ( acc) )
280+ . map ( |acc| <F as DelayedReduction < F > >:: reduce ( acc) ) ;
281+
282+ // Return accumulators along with full pyramids for EqSumCheckInstance reuse
283+ (
284+ accumulators,
285+ eq_tables. e_in_pyramid ,
286+ eq_tables. e_xout_pyramid ,
287+ )
255288}
256289
257290#[ cfg( test) ]
@@ -283,7 +316,7 @@ mod tests {
283316
284317 let taus: Vec < Scalar > = vec ! [ Scalar :: from( 3u64 ) , Scalar :: from( 5u64 ) ] ;
285318
286- let acc = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
319+ let ( acc, _ , _ ) = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
287320
288321 // Only round 0 exists (v is empty). β ranges over U_d with binary {0,1} and non-binary {∞}.
289322 // Buckets for u = 0 should be zero (binary β), bucket for u = ∞ should be non-zero.
@@ -331,8 +364,8 @@ mod tests {
331364 ] ;
332365
333366 // Build accumulators twice
334- let acc1 = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
335- let acc2 = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
367+ let ( acc1, _ , _ ) = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
368+ let ( acc2, _ , _ ) = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
336369
337370 // Compare all buckets
338371 for round in 0 ..l0 {
@@ -369,8 +402,8 @@ mod tests {
369402 let taus: Vec < Scalar > = ( 0 ..l) . map ( |i| Scalar :: from ( ( i * 7 + 3 ) as u64 ) ) . collect ( ) ;
370403
371404 // Build accumulators twice to verify consistency
372- let acc1 = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
373- let acc2 = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
405+ let ( acc1, _ , _ ) = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
406+ let ( acc2, _ , _ ) = build_accumulators_spartan ( & az, & bz, & taus, l0) ;
374407
375408 for round in 0 ..l0 {
376409 let num_v = ( D + 1 ) . pow ( round as u32 ) ;
0 commit comments