Skip to main content

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