p3_commit/
domain.rs

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