1use std::iter;
34
35use binius_compute::{Allocator, CollectIntoAllocVec};
36use binius_field::{ExtensionField, Field, PackedField, field::FieldOps};
37use binius_utils::rayon::{iter::repeat_n, prelude::*, task_size::min_len_for_bytes};
38
39use super::eq::{eq_ind_partial_eval_in, eq_ind_partial_eval_scalars};
40use crate::{FieldBuffer, FieldVec, line::extrapolate_line, tensor_algebra::TensorAlgebra};
41
42pub fn eq_ind_prefix_eval<F: FieldOps>(m: usize, x: &[F], y: &[F]) -> F {
49 let n = check_prefix_args(m, x.len(), y.len());
50
51 let mut prefix = F::zero();
52 let mut eq = F::one();
53 for (k, (x_k, y_k)) in iter::zip(x, y).enumerate() {
54 let eq_hi = eq.clone() * y_k;
55 let eq_lo = eq - &eq_hi;
56 let prefix_hi = prefix.clone() * y_k;
57 prefix = if (m >> k) & 1 == 1 {
58 extrapolate_line(eq_lo.clone(), prefix_hi, x_k.clone())
59 } else {
60 extrapolate_line(prefix - prefix_hi, F::zero(), x_k.clone())
61 };
62 eq = extrapolate_line(eq_lo, eq_hi, x_k.clone());
63 }
64 if m >> n == 1 { eq } else { prefix }
65}
66
67pub fn eq_ind_prefix_eval_tensor_algebra<F, FE>(
77 m: usize,
78 x: &[FE],
79 y: &[FE],
80) -> TensorAlgebra<F, FE>
81where
82 F: Field,
83 FE: FieldOps<Scalar: ExtensionField<F>>,
84{
85 let n = check_prefix_args(m, x.len(), y.len());
86
87 let lerp =
89 |t: &FE, a: TensorAlgebra<F, FE>, b: TensorAlgebra<F, FE>| (b - &a).scale_vertical(t) + &a;
90
91 let mut prefix = TensorAlgebra::default();
92 let mut eq = TensorAlgebra::one();
93 for (k, (x_k, y_k)) in iter::zip(x, y).enumerate() {
94 let eq_hi = eq.clone().scale_horizontal(y_k);
95 let eq_lo = eq - &eq_hi;
96 let prefix_hi = prefix.clone().scale_horizontal(y_k);
97 prefix = if (m >> k) & 1 == 1 {
98 lerp(x_k, eq_lo.clone(), prefix_hi)
99 } else {
100 lerp(x_k, prefix - &prefix_hi, TensorAlgebra::default())
101 };
102 eq = lerp(x_k, eq_lo, eq_hi);
103 }
104 if m >> n == 1 { eq } else { prefix }
105}
106
107pub fn eq_ind_prefix_partial_eval_in<A: Allocator, P: PackedField>(
114 alloc: &A,
115 m: usize,
116 point: &[P::Scalar],
117) -> FieldVec<P, A> {
118 let n = point.len();
119 assert!(n < usize::BITS as usize && m <= 1 << n, "precondition: m must be at most 2^n");
120
121 let low_len = (n / 2).max(P::LOG_WIDTH).min(n);
129 let (low_coords, high_coords) = point.split_at(low_len);
130 let low = eq_ind_partial_eval_in::<A, P>(alloc, low_coords);
131 let low_words = low.as_ref();
132 let high = eq_ind_partial_eval_scalars(high_coords);
133
134 let log_block_words = low_len.saturating_sub(P::LOG_WIDTH);
136 let block_mask = (1 << log_block_words) - 1;
137 let kept_words = m.div_ceil(P::WIDTH);
138 let total_words = 1 << n.saturating_sub(P::LOG_WIDTH);
139 let words = (0..kept_words)
140 .into_par_iter()
141 .map_init(
143 || (usize::MAX, P::zero()),
144 |(block, coeff), word| {
145 if word >> log_block_words != *block {
146 *block = word >> log_block_words;
147 *coeff = P::broadcast(high[*block]);
148 }
149 low_words[word & block_mask] * *coeff
150 },
151 )
152 .chain(repeat_n(P::zero(), total_words - kept_words))
153 .with_min_len(min_len_for_bytes::<P>())
154 .collect_into_alloc_vec(alloc);
155 let mut values = FieldBuffer::new(n, words);
156
157 for i in m..m.next_multiple_of(P::WIDTH).min(values.len()) {
159 values.set(i, P::Scalar::ZERO);
160 }
161 values
162}
163
164fn check_prefix_args(m: usize, x_len: usize, y_len: usize) -> usize {
165 assert_eq!(x_len, y_len, "precondition: x and y must be the same length");
166 assert!(x_len < usize::BITS as usize && m <= 1 << x_len, "precondition: m must be at most 2^n");
167 x_len
168}
169
170#[cfg(test)]
171mod tests {
172 use binius_compute::GlobalAllocator;
173 use binius_field::{BinaryField1b as B1, Field};
174 use rand::prelude::*;
175
176 use super::*;
177 use crate::{
178 inner_product::inner_product_buffers,
179 multilinear::eq::eq_ind,
180 test_utils::{
181 B128, Packed128b, index_to_hypercube_point, random_field_buffer, random_scalars,
182 },
183 };
184
185 type P = Packed128b;
186 type F = B128;
187
188 #[test]
189 fn scalar_eval_matches_naive_sum() {
190 let mut rng = StdRng::seed_from_u64(0);
191 for n in 0..=4 {
192 let x = random_scalars::<F>(&mut rng, n);
193 let y = random_scalars::<F>(&mut rng, n);
194 for m in 0..=1 << n {
195 let expected = (0..m)
196 .map(|v| {
197 let vertex = index_to_hypercube_point::<F>(n, v);
198 eq_ind(&x, &vertex) * eq_ind(&vertex, &y)
199 })
200 .sum::<F>();
201 assert_eq!(eq_ind_prefix_eval(m, &x, &y), expected, "n={n}, m={m}");
202 }
203 }
204 }
205
206 #[test]
207 fn scalar_eval_endpoints() {
208 let mut rng = StdRng::seed_from_u64(0);
209 let n = 5;
210 let x = random_scalars::<F>(&mut rng, n);
211 let y = random_scalars::<F>(&mut rng, n);
212 assert_eq!(eq_ind_prefix_eval(1 << n, &x, &y), eq_ind(&x, &y));
213 assert_eq!(eq_ind_prefix_eval(0, &x, &y), F::ZERO);
214 }
215
216 #[test]
217 fn tensor_algebra_eval_matches_naive_sum() {
218 let mut rng = StdRng::seed_from_u64(0);
219 for n in 0..=3 {
220 let x = random_scalars::<F>(&mut rng, n);
221 let y = random_scalars::<F>(&mut rng, n);
222 for m in 0..=1 << n {
223 let expected = (0..m)
224 .map(|v| {
225 let vertex = index_to_hypercube_point::<F>(n, v);
226 TensorAlgebra::<B1, F>::tensor(eq_ind(&x, &vertex), eq_ind(&vertex, &y))
227 })
228 .fold(TensorAlgebra::default(), |sum, term| sum + &term);
229 assert_eq!(eq_ind_prefix_eval_tensor_algebra(m, &x, &y), expected, "n={n}, m={m}");
230 }
231 }
232 }
233
234 #[test]
235 fn tensor_algebra_eval_with_subfield_horizontal_matches_scalar() {
236 let mut rng = StdRng::seed_from_u64(0);
239 let n = 4;
240 let x = random_scalars::<F>(&mut rng, n);
241 for y_index in 0..1 << n {
242 let y = index_to_hypercube_point::<F>(n, y_index);
243 for m in 0..=1 << n {
244 let elem = eq_ind_prefix_eval_tensor_algebra::<B1, F>(m, &x, &y);
245 assert_eq!(elem.try_extract_vertical(), Some(eq_ind_prefix_eval(m, &x, &y)));
246 }
247 }
248 }
249
250 #[test]
251 fn partial_eval_inner_product_matches_scalar_eval() {
252 let mut rng = StdRng::seed_from_u64(0);
253 for n in 0..=7 {
254 let r = random_scalars::<F>(&mut rng, n);
255 let table = random_field_buffer::<P>(&mut rng, n);
256 for m in 0..=1 << n {
257 let tensor = eq_ind_prefix_partial_eval_in::<_, P>(&GlobalAllocator, m, &r);
258 let expected = (0..1 << n)
260 .map(|v| {
261 let vertex = index_to_hypercube_point::<F>(n, v);
262 eq_ind_prefix_eval(m, &r, &vertex) * table.get(v)
263 })
264 .sum::<F>();
265 assert_eq!(inner_product_buffers(&tensor, &table), expected, "n={n}, m={m}");
266 }
267 }
268 }
269}