binius_math/multilinear/
evaluate.rs1use std::ops::{Deref, DerefMut};
7
8use binius_compute::BufferData;
9use binius_field::{Field, PackedField, field::FieldOps};
10use binius_utils::rayon::prelude::*;
11
12use crate::{
13 FieldBuffer,
14 inner_product::inner_product_buffers,
15 multilinear::{eq::eq_ind_partial_eval, fold::fold_highest_var_inplace},
16};
17
18pub fn evaluate<F, P, Data>(evals: &FieldBuffer<P, Data>, point: &[F]) -> F
28where
29 F: Field,
30 P: PackedField<Scalar = F>,
31 Data: Deref<Target = [P]>,
32{
33 assert_eq!(
34 point.len(),
35 evals.log_len(),
36 "precondition: point length must equal evals log length"
37 );
38
39 let first_half_len = (point.len() / 2).max(P::LOG_WIDTH).min(point.len());
42 let (first_coords, remaining_coords) = point.split_at(first_half_len);
43 let eq_tensor = eq_ind_partial_eval::<P>(first_coords);
44
45 if remaining_coords.is_empty() {
47 return inner_product_buffers(evals, &eq_tensor);
48 }
49
50 let scalars = evals
53 .par_chunks(first_half_len)
54 .map(|chunk| inner_product_buffers(&chunk, &eq_tensor))
55 .collect::<Vec<_>>();
56
57 evaluate_inplace(FieldBuffer::<P>::from_values(&scalars), remaining_coords)
58}
59
60pub fn evaluate_inplace<F, P, Data>(mut evals: FieldBuffer<P, Data>, coords: &[F]) -> F
69where
70 F: Field,
71 P: PackedField<Scalar = F>,
72 Data: BufferData<P>,
73{
74 assert_eq!(
75 coords.len(),
76 evals.log_len(),
77 "precondition: coords length must equal evals log length"
78 );
79
80 for &coord in coords.iter().rev() {
83 fold_highest_var_inplace(&mut evals, coord);
84 }
85
86 assert_eq!(evals.len(), 1);
87 evals.get(0)
88}
89
90pub fn evaluate_inplace_scalars<F: FieldOps>(
106 mut evals: impl DerefMut<Target = [F]>,
107 point: &[F],
108) -> F {
109 assert_eq!(evals.len(), 1 << point.len(), "precondition: evals length must be 2^point.len()");
110
111 for (log_half_len, point_i) in point.iter().enumerate().rev() {
112 let half_len = 1 << log_half_len;
113 for j in 0..half_len {
114 let delta = evals[j + half_len].clone() - evals[j].clone();
115 evals[j] += point_i.clone() * delta;
116 }
117 }
118 evals[0].clone()
119}
120
121#[cfg(test)]
122mod tests {
123 use proptest::prelude::*;
124 use rand::prelude::*;
125
126 use super::*;
127 use crate::{
128 inner_product::inner_product_par,
129 test_utils::{
130 B128, Packed128b, index_to_hypercube_point, random_field_buffer, random_scalars,
131 },
132 };
133
134 type P = Packed128b;
135 type F = B128;
136
137 const MAX_VARS: usize = 8;
139
140 #[test]
141 fn test_evaluate_inplace_scalars_consistency() {
142 let mut rng = StdRng::seed_from_u64(0);
143
144 for log_n in [0, P::LOG_WIDTH - 1, P::LOG_WIDTH, 10] {
145 let buffer = random_field_buffer::<P>(&mut rng, log_n);
146 let point = random_scalars::<F>(&mut rng, log_n);
147
148 let result_inplace = evaluate_inplace(buffer.clone(), &point);
149
150 let scalar_evals = buffer.iter_scalars().collect::<Vec<_>>();
151 let result_scalar = evaluate_inplace_scalars(scalar_evals, &point);
152
153 assert_eq!(result_inplace, result_scalar, "mismatch at log_n={log_n}");
154 }
155 }
156
157 #[test]
158 fn evaluate_at_a_hypercube_vertex_reads_that_coefficient() {
159 let mut rng = StdRng::seed_from_u64(0);
160
161 let n_vars = 8;
163 let buffer = random_field_buffer::<F>(&mut rng, n_vars);
164
165 for index in 0..1 << n_vars {
166 let point = index_to_hypercube_point::<F>(n_vars, index);
167
168 assert_eq!(evaluate(&buffer, &point), buffer.get(index), "mismatch at vertex {index}");
169 }
170 }
171
172 #[test]
173 fn evaluating_a_borrowed_view_matches_evaluating_its_owner() {
174 let mut rng = StdRng::seed_from_u64(0);
175
176 let buffer = random_field_buffer::<P>(&mut rng, 5);
181 let point = random_scalars::<F>(&mut rng, 5);
182 let view = buffer.as_view();
183
184 assert_eq!(view.log_len(), 5);
185 assert_eq!(evaluate(&view, &point), evaluate(&buffer, &point));
186 }
187
188 #[test]
189 fn evaluate_is_linear_in_every_coordinate() {
190 let mut rng = StdRng::seed_from_u64(0);
191
192 let n_vars = 8;
193 let buffer = random_field_buffer::<F>(&mut rng, n_vars);
194 let mut point = random_scalars::<F>(&mut rng, n_vars);
195
196 for coord_idx in 0..n_vars {
197 let coord_vals = random_scalars::<F>(&mut rng, 3);
199 let evals = coord_vals
200 .iter()
201 .map(|&coord_val| {
202 point[coord_idx] = coord_val;
203 evaluate(&buffer, &point)
204 })
205 .collect::<Vec<_>>();
206
207 let [x0, x1, x2] = [coord_vals[0], coord_vals[1], coord_vals[2]];
209 let [y0, y1, y2] = [evals[0], evals[1], evals[2]];
210 assert_eq!((y2 - y0) * (x1 - x0), (y1 - y0) * (x2 - x0));
211 }
212 }
213
214 proptest! {
215 #[test]
216 fn the_two_evaluations_agree_with_the_definition(
217 n_vars in 0..=MAX_VARS,
218 seed: u64,
219 ) {
220 let mut rng = StdRng::seed_from_u64(seed);
221 let buffer = random_field_buffer::<P>(&mut rng, n_vars);
222 let point = random_scalars::<F>(&mut rng, n_vars);
223
224 let reference = inner_product_par(&buffer, &eq_ind_partial_eval::<P>(&point));
226
227 prop_assert_eq!(evaluate(&buffer, &point), reference);
228
229 prop_assert_eq!(evaluate_inplace(buffer, &point), reference);
231 }
232 }
233}