p3_commit/domain.rs
1use alloc::vec::Vec;
2
3use itertools::Itertools;
4use p3_field::coset::TwoAdicMultiplicativeCoset;
5use p3_field::{ExtensionField, Field, TwoAdicField, batch_multiplicative_inverse};
6use p3_matrix::Matrix;
7use p3_matrix::dense::RowMajorMatrix;
8use p3_matrix::interpolation::Interpolate;
9use p3_util::{log2_ceil_usize, log2_strict_usize};
10
11/// Given a `PolynomialSpace`, `S`, and a subset `R`, a Lagrange selector `P_R` is
12/// a polynomial which is not equal to `0` for every element in `R` but is equal
13/// to `0` for every element of `S` not in `R`.
14///
15/// This struct contains evaluations of several Lagrange selectors for a fixed
16/// `PolynomialSpace` over some collection of points disjoint from that
17/// `PolynomialSpace`.
18///
19/// The Lagrange selector is normalized if it is equal to `1` for every element in `R`.
20/// The LagrangeSelectors given here are not normalized.
21#[derive(Debug)]
22pub struct LagrangeSelectors<T> {
23 /// A Lagrange selector corresponding to the first point in the space.
24 pub is_first_row: T,
25 /// A Lagrange selector corresponding to the last point in the space.
26 pub is_last_row: T,
27 /// A Lagrange selector corresponding the subset of all but the last point.
28 pub is_transition: T,
29 /// The inverse of the vanishing polynomial which is a Lagrange selector corresponding to the empty set
30 pub inv_vanishing: T,
31}
32
33/// Fixing a field, `F`, `PolynomialSpace<Val = F>` denotes an indexed subset of `F^n`
34/// with some additional algebraic structure.
35///
36/// We do not expect `PolynomialSpace` to store this subset, instead it usually contains
37/// some associated data which allows it to generate the subset or pieces of it.
38///
39/// Each `PolynomialSpace` should be part of a family of similar spaces for some
40/// collection of sizes (usually powers of two). Any space other than at the smallest size
41/// should be decomposable into a disjoint collection of smaller spaces. Additionally, the
42/// set of all `PolynomialSpace` of a given size should form a disjoint partition of some
43/// subset of `F^n` which supports a group structure.
44///
45/// The canonical example of a `PolynomialSpace` is a coset `gH` of
46/// a two-adic subgroup `H` of the multiplicative group `F*`. This satisfies the properties
47/// above as cosets partition the group and decompose as `gH = g(H^2) u gh(H^2)` for `h` any
48/// generator of `H`.
49///
50/// The other example in this code base is twin cosets which are sets of the form `gH u g^{-1}H`.
51/// The decomposition above extends easily to this case as `h` is a generator if and only if `h^{-1}`
52/// 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))`.
53pub trait PolynomialSpace: Copy {
54 /// The base field `F`.
55 type Val: Field;
56
57 /// The number of elements of the space.
58 fn size(&self) -> usize;
59
60 /// The first point in the space.
61 fn first_point(&self) -> Self::Val;
62
63 /// An algebraic function which takes the i'th element of the space and returns
64 /// the (i+1)'th evaluated on the given point.
65 ///
66 /// When `PolynomialSpace` corresponds to a coset, `gH` this
67 /// function is multiplication by `h` for a chosen generator `h` of `H`.
68 ///
69 /// This function may not exist for other classes of `PolynomialSpace` in which
70 /// case this will return `None`.
71 fn next_point<Ext: ExtensionField<Self::Val>>(&self, x: Ext) -> Option<Ext>;
72
73 /// Return another `PolynomialSpace` with size at least `min_size` disjoint from this space.
74 ///
75 /// When working with spaces of power of two size, this will return a space of size `2^ceil(log_2(min_size))`.
76 /// This will fail if `min_size` is too large. In particular, `log_2(min_size)` should be
77 /// smaller than the `2`-adicity of the field.
78 ///
79 /// This fixes a canonical choice for prover/verifier determinism and LDE caching.
80 fn create_disjoint_domain(&self, min_size: usize) -> Self;
81
82 /// Split the `PolynomialSpace` into `num_chunks` smaller `PolynomialSpaces` of equal size.
83 ///
84 /// `num_chunks` must divide `self.size()` (which usually forces it to be a power of 2.) or
85 /// this function will panic.
86 fn split_domains(&self, num_chunks: usize) -> Vec<Self>;
87
88 /// Split a set of polynomial evaluations over this `PolynomialSpace` into a vector
89 /// of polynomial evaluations over each `PolynomialSpace` generated from `split_domains`.
90 ///
91 /// `evals.height()` must equal `self.size()` and `num_chunks` must divide `self.size()`.
92 /// `evals` are assumed to be in standard (not bit-reversed) order.
93 fn split_evals(
94 &self,
95 num_chunks: usize,
96 evals: RowMajorMatrix<Self::Val>,
97 ) -> Vec<RowMajorMatrix<Self::Val>>;
98
99 /// Compute the vanishing polynomial of the space, evaluated at the given point.
100 ///
101 /// This is a polynomial which evaluates to `0` on every point of the
102 /// space `self` and has degree equal to `self.size()`. In other words it is
103 /// a choice of element of the defining ideal of the given set with this extra
104 /// degree property.
105 ///
106 /// In the univariate case, it is equal, up to a linear factor, to the product over
107 /// all elements `x`, of `(X - x)`. In particular this implies it will not evaluate
108 /// to `0` at any point not in `self`.
109 fn vanishing_poly_at_point<Ext: ExtensionField<Self::Val>>(&self, point: Ext) -> Ext;
110
111 /// Compute several Lagrange selectors at a given point.
112 /// - The Lagrange selector of the first point.
113 /// - The Lagrange selector of the last point.
114 /// - The Lagrange selector of everything but the last point.
115 /// - The inverse of the vanishing polynomial.
116 ///
117 /// Note that these may not be normalized.
118 fn selectors_at_point<Ext: ExtensionField<Self::Val>>(
119 &self,
120 point: Ext,
121 ) -> LagrangeSelectors<Ext>;
122
123 /// Compute several Lagrange selectors at all points of the given disjoint `PolynomialSpace`.
124 /// - The Lagrange selector of the first point.
125 /// - The Lagrange selector of the last point.
126 /// - The Lagrange selector of everything but the last point.
127 /// - The inverse of the vanishing polynomial.
128 ///
129 /// Note that these may not be normalized.
130 fn selectors_on_coset(&self, coset: Self) -> LagrangeSelectors<Vec<Self::Val>>;
131
132 /// Evaluate the polynomial defined by `evals` (evaluations over `self`) at `point`.
133 fn evaluate_polynomial_at<Ext: ExtensionField<Self::Val>>(
134 &self,
135 evals: &[Self::Val],
136 point: Ext,
137 ) -> Ext;
138
139 /// Evaluate a periodic column polynomial at `point`.
140 ///
141 /// `col` contains the period-length evaluations: row `i` of the full trace
142 /// gets value `col[i % col.len()]`. The default expands to trace size and
143 /// delegates to [`Self::evaluate_polynomial_at`]; domains with algebraic
144 /// structure (e.g. two-adic cosets) can override for O(period) work.
145 fn evaluate_periodic_column_at<Ext: ExtensionField<Self::Val>>(
146 &self,
147 col: &[Self::Val],
148 point: Ext,
149 ) -> Ext {
150 let n = self.size();
151 let period = col.len();
152 let evals: Vec<Self::Val> = (0..n).map(|i| col[i % period]).collect();
153 self.evaluate_polynomial_at(&evals, point)
154 }
155}
156
157impl<Val: TwoAdicField> PolynomialSpace for TwoAdicMultiplicativeCoset<Val> {
158 type Val = Val;
159
160 fn size(&self) -> usize {
161 self.size()
162 }
163
164 fn first_point(&self) -> Self::Val {
165 self.shift()
166 }
167
168 /// Getting the next point corresponds to multiplication by the generator.
169 fn next_point<Ext: ExtensionField<Val>>(&self, x: Ext) -> Option<Ext> {
170 Some(x * self.subgroup_generator())
171 }
172
173 /// Given the coset `gH`, return the disjoint coset `gfK` where `f`
174 /// is a fixed generator of `F^*` and `K` is the unique two-adic subgroup
175 /// of with size `2^(ceil(log_2(min_size)))`.
176 ///
177 /// # Panics
178 ///
179 /// This will panic if `min_size` > `1 << Val::TWO_ADICITY`.
180 fn create_disjoint_domain(&self, min_size: usize) -> Self {
181 // We provide a short proof that these cosets are always disjoint:
182 //
183 // Assume without loss of generality that `|H| <= min_size <= |K|`.
184 // Then we know that `gH` is entirely contained in `gK`. As cosets are
185 // either equal or disjoint, this means that `gH` is disjoint from `g'K`
186 // for every `g'` not contained in `gK`. As `f` is a generator of `F^*`
187 // it does not lie in `K` and so `gf` cannot lie in `gK`.
188 //
189 // Thus `gH` and `gfK` are disjoint.
190
191 // This panics if (and only if) `min_size` > `1 << Val::TWO_ADICITY`.
192 Self::new(self.shift() * Val::GENERATOR, log2_ceil_usize(min_size)).unwrap()
193 }
194
195 /// Given the coset `gH` and generator `h` of `H`, let `K = H^{num_chunks}`
196 /// be the unique group of order `|H|/num_chunks`.
197 ///
198 /// Then we decompose `gH` into `gK, ghK, gh^2K, ..., gh^{num_chunks}K`.
199 fn split_domains(&self, num_chunks: usize) -> Vec<Self> {
200 let log_chunks = log2_strict_usize(num_chunks);
201 debug_assert!(log_chunks <= self.log_size());
202 (0..num_chunks)
203 .map(|i| {
204 Self::new(
205 self.shift() * self.subgroup_generator().exp_u64(i as u64),
206 self.log_size() - log_chunks,
207 )
208 .unwrap() // This won't panic as `self.log_size() - log_chunks < self.log_size() < Val::TWO_ADICITY`
209 })
210 .collect()
211 }
212
213 fn split_evals(
214 &self,
215 num_chunks: usize,
216 evals: RowMajorMatrix<Self::Val>,
217 ) -> Vec<RowMajorMatrix<Self::Val>> {
218 debug_assert_eq!(evals.height(), self.size());
219 debug_assert!(log2_strict_usize(num_chunks) <= self.log_size());
220 let height = evals.height();
221 let width = evals.width();
222 let rows_per_chunk = height / num_chunks;
223
224 // Preallocate zeroed buffers per chunk; often faster for field elements.
225 let mut values: Vec<Vec<Self::Val>> = (0..num_chunks)
226 .map(|_| Self::Val::zero_vec(rows_per_chunk * width))
227 .collect();
228
229 // Distribute rows without using modulo: iterate blocks of size num_chunks.
230 for i in 0..rows_per_chunk {
231 let base_row = i * num_chunks;
232 let dst_start = i * width;
233 let dst_end = dst_start + width;
234 for (chunk, dst_vec) in values.iter_mut().enumerate().take(num_chunks) {
235 let r = base_row + chunk;
236 // Safety: r < height == rows_per_chunk * num_chunks
237 let row = unsafe { evals.row_slice_unchecked(r) };
238 dst_vec[dst_start..dst_end].copy_from_slice(&row);
239 }
240 }
241
242 values
243 .into_iter()
244 .map(|v| RowMajorMatrix::new(v, width))
245 .collect()
246 }
247
248 /// Compute the vanishing polynomial at the given point:
249 ///
250 /// `Z_{gH}(X) = g^{-|H|}\prod_{h \in H} (X - gh) = (g^{-1}X)^|H| - 1`
251 fn vanishing_poly_at_point<Ext: ExtensionField<Val>>(&self, point: Ext) -> Ext {
252 (point * self.shift_inverse()).exp_power_of_2(self.log_size()) - Ext::ONE
253 }
254
255 /// Compute several Lagrange selectors at the given point:
256 ///
257 /// Defining the vanishing polynomial by `Z_{gH}(X) = g^{-|H|}\prod_{h \in H} (X - gh) = (g^{-1}X)^|H| - 1` return:
258 /// - `Z_{gH}(X)/(g^{-1}X - 1)`: The Lagrange selector of the point `g`.
259 /// - `Z_{gH}(X)/(g^{-1}X - h^{-1})`: The Lagrange selector of the point `gh^{-1}` where `h` is the generator of `H`.
260 /// - `(g^{-1}X - h^{-1})`: The Lagrange selector of the subset consisting of everything but the point `gh^{-1}`.
261 /// - `1/Z_{gH}(X)`: The inverse of the vanishing polynomial.
262 fn selectors_at_point<Ext: ExtensionField<Val>>(&self, point: Ext) -> LagrangeSelectors<Ext> {
263 let unshifted_point = point * self.shift_inverse();
264 let z_h = unshifted_point.exp_power_of_2(self.log_size()) - Ext::ONE;
265 LagrangeSelectors {
266 is_first_row: z_h / (unshifted_point - Ext::ONE),
267 is_last_row: z_h / (unshifted_point - self.subgroup_generator().inverse()),
268 is_transition: unshifted_point - self.subgroup_generator().inverse(),
269 inv_vanishing: z_h.inverse(),
270 }
271 }
272
273 /// Compute the Lagrange selectors of our space at every point in the coset.
274 ///
275 /// This will error if our space is not the group `H` and if the given
276 /// coset is not disjoint from `H`.
277 fn selectors_on_coset(&self, coset: Self) -> LagrangeSelectors<Vec<Val>> {
278 assert_eq!(self.shift(), Val::ONE);
279 assert_ne!(coset.shift(), Val::ONE);
280 assert!(coset.log_size() >= self.log_size());
281 let rate_bits = coset.log_size() - self.log_size();
282
283 let s_pow_n = coset.shift().exp_power_of_2(self.log_size());
284 // evals of Z_H(X) = X^n - 1
285 let evals = Val::two_adic_generator(rate_bits)
286 .powers()
287 .take(1 << rate_bits)
288 .map(|x| s_pow_n * x - Val::ONE)
289 .collect_vec();
290
291 let xs = coset.iter().collect();
292
293 let single_point_selector = |i: u64| {
294 let coset_i = self.subgroup_generator().exp_u64(i);
295 let denoms = xs.iter().map(|&x| x - coset_i).collect_vec();
296 let invs = batch_multiplicative_inverse(&denoms);
297 evals
298 .iter()
299 .cycle()
300 .zip(invs)
301 .map(|(&z_h, inv)| z_h * inv)
302 .collect_vec()
303 };
304
305 let subgroup_last = self.subgroup_generator().inverse();
306
307 LagrangeSelectors {
308 is_first_row: single_point_selector(0),
309 is_last_row: single_point_selector(self.size() as u64 - 1),
310 is_transition: xs.into_iter().map(|x| x - subgroup_last).collect(),
311 inv_vanishing: batch_multiplicative_inverse(&evals)
312 .into_iter()
313 .cycle()
314 .take(coset.size())
315 .collect(),
316 }
317 }
318
319 fn evaluate_polynomial_at<Ext: ExtensionField<Val>>(&self, evals: &[Val], point: Ext) -> Ext {
320 let evals_mat = RowMajorMatrix::new(evals.to_vec(), 1);
321 evals_mat.interpolate_coset(self.shift(), point)[0]
322 }
323
324 fn evaluate_periodic_column_at<Ext: ExtensionField<Val>>(
325 &self,
326 col: &[Val],
327 point: Ext,
328 ) -> Ext {
329 let log_period = log2_strict_usize(col.len());
330 let folds = self.log_size() - log_period;
331 let sub_coset = Self::new(self.shift().exp_power_of_2(folds), log_period).unwrap();
332 sub_coset.evaluate_polynomial_at(col, point.exp_power_of_2(folds))
333 }
334}