p3_commit/domain.rs
1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3
4use itertools::Itertools;
5use p3_field::coset::TwoAdicMultiplicativeCoset;
6use p3_field::{ExtensionField, Field, TwoAdicField, batch_multiplicative_inverse};
7use p3_matrix::Matrix;
8use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
9use p3_matrix::interpolation::Interpolate;
10use p3_util::{log2_ceil_usize, log2_strict_usize};
11
12use crate::PeriodicColumns;
13
14/// Given a `PolynomialSpace`, `S`, and a subset `R`, a Lagrange selector `P_R` is
15/// a polynomial which is not equal to `0` for every element in `R` but is equal
16/// to `0` for every element of `S` not in `R`.
17///
18/// This struct contains evaluations of several Lagrange selectors for a fixed
19/// `PolynomialSpace` over some collection of points disjoint from that
20/// `PolynomialSpace`.
21///
22/// The Lagrange selector is normalized if it is equal to `1` for every element in `R`.
23/// The LagrangeSelectors given here are not normalized.
24#[derive(Debug)]
25pub struct LagrangeSelectors<T> {
26 /// A Lagrange selector corresponding to the first point in the space.
27 pub is_first_row: T,
28 /// A Lagrange selector corresponding to the last point in the space.
29 pub is_last_row: T,
30 /// A Lagrange selector corresponding the subset of all but the last point.
31 pub is_transition: T,
32 /// The inverse of the vanishing polynomial which is a Lagrange selector corresponding to the empty set
33 pub inv_vanishing: T,
34}
35
36/// Fixing a field, `F`, `PolynomialSpace<Val = F>` denotes an indexed subset of `F^n`
37/// with some additional algebraic structure.
38///
39/// We do not expect `PolynomialSpace` to store this subset, instead it usually contains
40/// some associated data which allows it to generate the subset or pieces of it.
41///
42/// Each `PolynomialSpace` should be part of a family of similar spaces for some
43/// collection of sizes (usually powers of two). Any space other than at the smallest size
44/// should be decomposable into a disjoint collection of smaller spaces. Additionally, the
45/// set of all `PolynomialSpace` of a given size should form a disjoint partition of some
46/// subset of `F^n` which supports a group structure.
47///
48/// The canonical example of a `PolynomialSpace` is a coset `gH` of
49/// a two-adic subgroup `H` of the multiplicative group `F*`. This satisfies the properties
50/// above as cosets partition the group and decompose as `gH = g(H^2) u gh(H^2)` for `h` any
51/// generator of `H`.
52///
53/// The other example in this code base is twin cosets which are sets of the form `gH u g^{-1}H`.
54/// The decomposition above extends easily to this case as `h` is a generator if and only if `h^{-1}`
55/// is and so `gH u g^{-1}H = (g(H^2) u g^{-1}(H^2)) u (gh(H^2) u (gh)^{-1}(H^2))`.
56pub trait PolynomialSpace: Copy {
57 /// The base field `F`.
58 type Val: Field;
59
60 /// The number of elements of the space.
61 fn size(&self) -> usize;
62
63 /// Degree multiple of the transition selector in units of a trace column.
64 ///
65 /// The default is for two-adic domains, whose linear selector has degree
66 /// independent of the trace length. Domains using a full trace-space
67 /// selector, such as Circle, return one instead.
68 fn transition_degree_multiple(&self) -> usize {
69 0
70 }
71
72 /// The first point in the space.
73 fn first_point(&self) -> Self::Val;
74
75 /// An algebraic function which takes the i'th element of the space and returns
76 /// the (i+1)'th evaluated on the given point.
77 ///
78 /// When `PolynomialSpace` corresponds to a coset, `gH` this
79 /// function is multiplication by `h` for a chosen generator `h` of `H`.
80 ///
81 /// This function may not exist for other classes of `PolynomialSpace` in which
82 /// case this will return `None`.
83 fn next_point<Ext: ExtensionField<Self::Val>>(&self, x: Ext) -> Option<Ext>;
84
85 /// Return another `PolynomialSpace` with size at least `min_size` disjoint from this space.
86 ///
87 /// When working with spaces of power of two size, this will return a space of size `2^ceil(log_2(min_size))`.
88 /// This will fail if `min_size` is too large. In particular, `log_2(min_size)` should be
89 /// smaller than the `2`-adicity of the field.
90 ///
91 /// This fixes a canonical choice for prover/verifier determinism and LDE caching.
92 ///
93 /// # Panics
94 ///
95 /// Panics if `min_size` is too large for a disjoint domain to be constructed. Verifier-side
96 /// code processing untrusted input should prefer [`Self::try_create_disjoint_domain`], which
97 /// reports this condition as `None` instead of panicking.
98 fn create_disjoint_domain(&self, min_size: usize) -> Self {
99 self.try_create_disjoint_domain(min_size)
100 .unwrap_or_else(|| {
101 panic!("cannot construct a domain of size at least {min_size} disjoint from `self`")
102 })
103 }
104
105 /// The non-panicking counterpart to [`Self::create_disjoint_domain`].
106 ///
107 /// Returns `None` instead of panicking when `min_size` is too large for a disjoint domain
108 /// to be constructed (for two-adic domains, this happens when `log_2(min_size)` is not
109 /// smaller than the field's `2`-adicity). Intended for verifier-side code, which must
110 /// reject malformed or adversarial input rather than panic on it.
111 fn try_create_disjoint_domain(&self, min_size: usize) -> Option<Self>;
112
113 /// Split the `PolynomialSpace` into `num_chunks` smaller `PolynomialSpaces` of equal size.
114 ///
115 /// `num_chunks` must divide `self.size()` (which usually forces it to be a power of 2.) or
116 /// this function will panic.
117 fn split_domains(&self, num_chunks: usize) -> Vec<Self>;
118
119 /// Split a set of polynomial evaluations over this `PolynomialSpace` into a vector
120 /// of polynomial evaluations over each `PolynomialSpace` generated from `split_domains`.
121 ///
122 /// `evals.height()` must equal `self.size()` and `num_chunks` must divide `self.size()`.
123 /// `evals` are assumed to be in standard (not bit-reversed) order.
124 fn split_evals(
125 &self,
126 num_chunks: usize,
127 evals: RowMajorMatrix<Self::Val>,
128 ) -> Vec<RowMajorMatrix<Self::Val>>;
129
130 /// Compute the vanishing polynomial of the space, evaluated at the given point.
131 ///
132 /// This is a polynomial which evaluates to `0` on every point of the
133 /// space `self` and has degree equal to `self.size()`. In other words it is
134 /// a choice of element of the defining ideal of the given set with this extra
135 /// degree property.
136 ///
137 /// In the univariate case, it is equal, up to a linear factor, to the product over
138 /// all elements `x`, of `(X - x)`. In particular this implies it will not evaluate
139 /// to `0` at any point not in `self`.
140 fn vanishing_poly_at_point<Ext: ExtensionField<Self::Val>>(&self, point: Ext) -> Ext;
141
142 /// Compute several Lagrange selectors at a given point.
143 /// - The Lagrange selector of the first point.
144 /// - The Lagrange selector of the last point.
145 /// - The Lagrange selector of everything but the last point.
146 /// - The inverse of the vanishing polynomial.
147 ///
148 /// Note that these may not be normalized.
149 fn selectors_at_point<Ext: ExtensionField<Self::Val>>(
150 &self,
151 point: Ext,
152 ) -> LagrangeSelectors<Ext>;
153
154 /// Compute several Lagrange selectors at all points of the given disjoint `PolynomialSpace`.
155 /// - The Lagrange selector of the first point.
156 /// - The Lagrange selector of the last point.
157 /// - The Lagrange selector of everything but the last point.
158 /// - The inverse of the vanishing polynomial.
159 ///
160 /// Note that these may not be normalized.
161 fn selectors_on_coset(&self, coset: Self) -> LagrangeSelectors<Vec<Self::Val>>;
162
163 /// Evaluate the polynomial defined by `evals` (evaluations over `self`) at `point`.
164 fn evaluate_polynomial_at<Ext: ExtensionField<Self::Val>>(
165 &self,
166 evals: &[Self::Val],
167 point: Ext,
168 ) -> Ext;
169
170 /// Evaluate one periodic column polynomial at `point`.
171 ///
172 /// The column lists one period of values, and row `i` of the trace reads position `i mod p`.
173 ///
174 /// This is the per-column primitive behind the batched entry point.
175 /// It assumes the length is a power of two that divides the domain size.
176 /// Callers reach it through the batched entry point, which is where that is established.
177 ///
178 /// # Performance
179 ///
180 /// The default expands the column to the full domain size, so it costs `O(n)` time and space.
181 /// A domain with algebraic structure can fold onto a sub-coset instead and pay `O(p)`.
182 /// On a large trace with a small period that gap is many orders of magnitude of verifier time.
183 /// Every new implementor should override this rather than rely on the default.
184 fn evaluate_periodic_column_at<Ext: ExtensionField<Self::Val>>(
185 &self,
186 col: &[Self::Val],
187 point: Ext,
188 ) -> Ext {
189 let n = self.size();
190 let period = col.len();
191 let evals: Vec<Self::Val> = (0..n).map(|i| col[i % period]).collect();
192 self.evaluate_polynomial_at(&evals, point)
193 }
194
195 /// Evaluate several periodic column polynomials at `point`.
196 ///
197 /// Taking screened columns is what makes the shape rule unskippable.
198 /// Every caller has to establish it before it can name this method at all.
199 ///
200 /// The default evaluates one column at a time.
201 /// Domains with algebraic structure can override to batch columns that share a period.
202 /// That pays for one interpolation per period instead of one per column.
203 ///
204 /// # Panics
205 ///
206 /// Debug builds panic when the columns were screened against a different number of rows.
207 /// The rule is a relation between the lengths and the height, so the wrong height voids it.
208 fn evaluate_periodic_columns_at<Ext: ExtensionField<Self::Val>>(
209 &self,
210 periodic_columns: PeriodicColumns<'_, Self::Val>,
211 point: Ext,
212 ) -> Vec<Ext> {
213 debug_assert_eq!(periodic_columns.height(), self.size());
214
215 periodic_columns
216 .as_slice()
217 .iter()
218 .map(|col| self.evaluate_periodic_column_at(col, point))
219 .collect()
220 }
221}
222
223impl<Val: TwoAdicField> PolynomialSpace for TwoAdicMultiplicativeCoset<Val> {
224 type Val = Val;
225
226 fn size(&self) -> usize {
227 self.size()
228 }
229
230 fn first_point(&self) -> Self::Val {
231 self.shift()
232 }
233
234 /// Getting the next point corresponds to multiplication by the generator.
235 fn next_point<Ext: ExtensionField<Val>>(&self, x: Ext) -> Option<Ext> {
236 Some(x * self.subgroup_generator())
237 }
238
239 /// Given the coset `gH`, return the disjoint coset `gfK` where `f`
240 /// is a fixed generator of `F^*` and `K` is the unique two-adic subgroup
241 /// of with size `2^(ceil(log_2(min_size)))`.
242 ///
243 /// Returns `None` if `min_size` > `1 << Val::TWO_ADICITY`.
244 fn try_create_disjoint_domain(&self, min_size: usize) -> Option<Self> {
245 // We provide a short proof that these cosets are always disjoint:
246 //
247 // Assume without loss of generality that `|H| <= min_size <= |K|`.
248 // Then we know that `gH` is entirely contained in `gK`. As cosets are
249 // either equal or disjoint, this means that `gH` is disjoint from `g'K`
250 // for every `g'` not contained in `gK`. As `f` is a generator of `F^*`
251 // it does not lie in `K` and so `gf` cannot lie in `gK`.
252 //
253 // Thus `gH` and `gfK` are disjoint.
254
255 // This is `None` if (and only if) `min_size` > `1 << Val::TWO_ADICITY`.
256 Self::new(self.shift() * Val::GENERATOR, log2_ceil_usize(min_size))
257 }
258
259 /// Given the coset `gH` and generator `h` of `H`, let `K = H^{num_chunks}`
260 /// be the unique group of order `|H|/num_chunks`.
261 ///
262 /// Then we decompose `gH` into `gK, ghK, gh^2K, ..., gh^{num_chunks}K`.
263 fn split_domains(&self, num_chunks: usize) -> Vec<Self> {
264 let log_chunks = log2_strict_usize(num_chunks);
265 debug_assert!(log_chunks <= self.log_size());
266 (0..num_chunks)
267 .map(|i| {
268 Self::new(
269 self.shift() * self.subgroup_generator().exp_u64(i as u64),
270 self.log_size() - log_chunks,
271 )
272 .unwrap() // This won't panic as `self.log_size() - log_chunks < self.log_size() < Val::TWO_ADICITY`
273 })
274 .collect()
275 }
276
277 fn split_evals(
278 &self,
279 num_chunks: usize,
280 evals: RowMajorMatrix<Self::Val>,
281 ) -> Vec<RowMajorMatrix<Self::Val>> {
282 debug_assert_eq!(evals.height(), self.size());
283 debug_assert!(log2_strict_usize(num_chunks) <= self.log_size());
284 let height = evals.height();
285 let width = evals.width();
286 let rows_per_chunk = height / num_chunks;
287
288 // Preallocate zeroed buffers per chunk; often faster for field elements.
289 let mut values: Vec<Vec<Self::Val>> = (0..num_chunks)
290 .map(|_| Self::Val::zero_vec(rows_per_chunk * width))
291 .collect();
292
293 // Distribute rows without using modulo: iterate blocks of size num_chunks.
294 for i in 0..rows_per_chunk {
295 let base_row = i * num_chunks;
296 let dst_start = i * width;
297 let dst_end = dst_start + width;
298 for (chunk, dst_vec) in values.iter_mut().enumerate().take(num_chunks) {
299 let r = base_row + chunk;
300 // Safety: r < height == rows_per_chunk * num_chunks
301 let row = unsafe { evals.row_slice_unchecked(r) };
302 dst_vec[dst_start..dst_end].copy_from_slice(&row);
303 }
304 }
305
306 values
307 .into_iter()
308 .map(|v| RowMajorMatrix::new(v, width))
309 .collect()
310 }
311
312 /// Compute the vanishing polynomial at the given point:
313 ///
314 /// `Z_{gH}(X) = g^{-|H|}\prod_{h \in H} (X - gh) = (g^{-1}X)^|H| - 1`
315 fn vanishing_poly_at_point<Ext: ExtensionField<Val>>(&self, point: Ext) -> Ext {
316 (point * self.shift_inverse()).exp_power_of_2(self.log_size()) - Ext::ONE
317 }
318
319 /// Compute several Lagrange selectors at the given point:
320 ///
321 /// Defining the vanishing polynomial by `Z_{gH}(X) = g^{-|H|}\prod_{h \in H} (X - gh) = (g^{-1}X)^|H| - 1` return:
322 /// - `Z_{gH}(X)/(g^{-1}X - 1)`: The Lagrange selector of the point `g`.
323 /// - `Z_{gH}(X)/(g^{-1}X - h^{-1})`: The Lagrange selector of the point `gh^{-1}` where `h` is the generator of `H`.
324 /// - `(g^{-1}X - h^{-1})`: The Lagrange selector of the subset consisting of everything but the point `gh^{-1}`.
325 /// - `1/Z_{gH}(X)`: The inverse of the vanishing polynomial.
326 fn selectors_at_point<Ext: ExtensionField<Val>>(&self, point: Ext) -> LagrangeSelectors<Ext> {
327 let unshifted_point = point * self.shift_inverse();
328 let z_h = unshifted_point.exp_power_of_2(self.log_size()) - Ext::ONE;
329 LagrangeSelectors {
330 is_first_row: z_h / (unshifted_point - Ext::ONE),
331 is_last_row: z_h / (unshifted_point - self.subgroup_generator().inverse()),
332 is_transition: unshifted_point - self.subgroup_generator().inverse(),
333 inv_vanishing: z_h.inverse(),
334 }
335 }
336
337 /// Compute the Lagrange selectors of our space at every point in the coset.
338 ///
339 /// This will error if our space is not the group `H` and if the given
340 /// coset is not disjoint from `H`.
341 fn selectors_on_coset(&self, coset: Self) -> LagrangeSelectors<Vec<Val>> {
342 assert_eq!(self.shift(), Val::ONE);
343 assert_ne!(coset.shift(), Val::ONE);
344 assert!(coset.log_size() >= self.log_size());
345 let rate_bits = coset.log_size() - self.log_size();
346
347 let s_pow_n = coset.shift().exp_power_of_2(self.log_size());
348 // evals of Z_H(X) = X^n - 1
349 let evals = Val::two_adic_generator(rate_bits)
350 .powers()
351 .take(1 << rate_bits)
352 .map(|x| s_pow_n * x - Val::ONE)
353 .collect_vec();
354
355 let xs = coset.iter().collect();
356
357 let single_point_selector = |i: u64| {
358 let coset_i = self.subgroup_generator().exp_u64(i);
359 let denoms = xs.iter().map(|&x| x - coset_i).collect_vec();
360 let invs = batch_multiplicative_inverse(&denoms);
361 evals
362 .iter()
363 .cycle()
364 .zip(invs)
365 .map(|(&z_h, inv)| z_h * inv)
366 .collect_vec()
367 };
368
369 let subgroup_last = self.subgroup_generator().inverse();
370
371 LagrangeSelectors {
372 is_first_row: single_point_selector(0),
373 is_last_row: single_point_selector(self.size() as u64 - 1),
374 is_transition: xs.into_iter().map(|x| x - subgroup_last).collect(),
375 inv_vanishing: batch_multiplicative_inverse(&evals)
376 .into_iter()
377 .cycle()
378 .take(coset.size())
379 .collect(),
380 }
381 }
382
383 fn evaluate_polynomial_at<Ext: ExtensionField<Val>>(&self, evals: &[Val], point: Ext) -> Ext {
384 let evals_mat = RowMajorMatrixView::new(evals, 1);
385 evals_mat.interpolate_coset(self.shift(), point)[0]
386 }
387
388 fn evaluate_periodic_column_at<Ext: ExtensionField<Val>>(
389 &self,
390 col: &[Val],
391 point: Ext,
392 ) -> Ext {
393 let log_period = log2_strict_usize(col.len());
394 let folds = self.log_size() - log_period;
395 let sub_coset = Self::new(self.shift().exp_power_of_2(folds), log_period).unwrap();
396 sub_coset.evaluate_polynomial_at(col, point.exp_power_of_2(folds))
397 }
398
399 /// Evaluate several periodic column polynomials at `point`.
400 ///
401 /// Columns sharing a period share one coset materialization and one batch inversion.
402 fn evaluate_periodic_columns_at<Ext: ExtensionField<Val>>(
403 &self,
404 periodic_columns: PeriodicColumns<'_, Val>,
405 point: Ext,
406 ) -> Vec<Ext> {
407 debug_assert_eq!(periodic_columns.height(), self.size());
408
409 let periodic_columns = periodic_columns.as_slice();
410
411 let mut cols_by_period: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
412 for (i, col) in periodic_columns.iter().enumerate() {
413 cols_by_period.entry(col.len()).or_default().push(i);
414 }
415
416 let mut result = Ext::zero_vec(periodic_columns.len());
417 for (period, indices) in cols_by_period {
418 let log_period = log2_strict_usize(period);
419 let folds = self.log_size() - log_period;
420 let sub_shift = self.shift().exp_power_of_2(folds);
421 let sub_point = point.exp_power_of_2(folds);
422
423 // Interleave the columns sharing this period into one row-major matrix
424 // so `interpolate_coset` can evaluate all of them with a single batch
425 // inversion.
426 let k = indices.len();
427 let mut values = Val::zero_vec(period * k);
428 for (col_pos, &orig_idx) in indices.iter().enumerate() {
429 for (row, &v) in periodic_columns[orig_idx].iter().enumerate() {
430 values[row * k + col_pos] = v;
431 }
432 }
433
434 let evals = RowMajorMatrix::new(values, k).interpolate_coset(sub_shift, sub_point);
435 for (col_pos, &orig_idx) in indices.iter().enumerate() {
436 result[orig_idx] = evals[col_pos];
437 }
438 }
439
440 result
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use alloc::vec;
447 use alloc::vec::Vec;
448
449 use p3_baby_bear::BabyBear;
450 use p3_field::PrimeCharacteristicRing;
451
452 use super::*;
453
454 type F = BabyBear;
455
456 #[test]
457 fn evaluate_periodic_columns_at_matches_per_column_eval() {
458 let domain = TwoAdicMultiplicativeCoset::<F>::new(F::GENERATOR, 4).unwrap();
459 let point = F::from_u32(12345);
460
461 // Two columns of period 4 (sharing a period class with >1 member) plus one
462 // of period 2, to exercise both the grouping and the interleaving.
463 let columns: Vec<Vec<F>> = vec![
464 (0..4).map(F::from_u32).collect(),
465 (0..2).map(|x| F::from_u32(x + 10)).collect(),
466 (0..4).map(|x| F::from_u32(x + 100)).collect(),
467 ];
468
469 let expected: Vec<F> = columns
470 .iter()
471 .map(|col| domain.evaluate_periodic_column_at(col, point))
472 .collect();
473 let screened = PeriodicColumns::new(&columns, domain.size()).unwrap();
474 let actual = domain.evaluate_periodic_columns_at(screened, point);
475
476 assert_eq!(actual, expected);
477 }
478
479 #[test]
480 fn evaluate_periodic_columns_at_empty() {
481 let domain = TwoAdicMultiplicativeCoset::<F>::new(F::GENERATOR, 4).unwrap();
482 let point = F::from_u32(7);
483 let columns: Vec<Vec<F>> = vec![];
484 let screened = PeriodicColumns::new(&columns, domain.size()).unwrap();
485
486 assert_eq!(
487 domain.evaluate_periodic_columns_at(screened, point),
488 Vec::<F>::new()
489 );
490 }
491}