mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-03 06:13:07 +00:00
No functional changes here. The biggest change was moving certain files into new directories like `plonk` and `iop` (for things like `Challenger` that could be used in STARKs or other IOPs). I also split a few files, renames, etc, but again nothing functional, so I don't think a careful review is necessary (just a sanity check).
267 lines
9.6 KiB
Rust
267 lines
9.6 KiB
Rust
use crate::field::fft::{fft, ifft};
|
|
use crate::field::field_types::Field;
|
|
use crate::polynomial::polynomial::PolynomialCoeffs;
|
|
use crate::util::{log2_ceil, log2_strict};
|
|
|
|
impl<F: Field> PolynomialCoeffs<F> {
|
|
/// Polynomial division.
|
|
/// Returns `(q, r)`, the quotient and remainder of the polynomial division of `a` by `b`.
|
|
pub(crate) fn div_rem(&self, b: &Self) -> (Self, Self) {
|
|
let (a_degree_plug_1, b_degree_plus_1) = (self.degree_plus_one(), b.degree_plus_one());
|
|
if a_degree_plug_1 == 0 {
|
|
(Self::zero(1), Self::empty())
|
|
} else if b_degree_plus_1 == 0 {
|
|
panic!("Division by zero polynomial");
|
|
} else if a_degree_plug_1 < b_degree_plus_1 {
|
|
(Self::zero(1), self.clone())
|
|
} else if b_degree_plus_1 == 1 {
|
|
(self * b.coeffs[0].inverse(), Self::empty())
|
|
} else {
|
|
let rev_b = b.rev();
|
|
let rev_b_inv = rev_b.inv_mod_xn(a_degree_plug_1 - b_degree_plus_1 + 1);
|
|
let rhs: Self = self.rev().coeffs[..=a_degree_plug_1 - b_degree_plus_1]
|
|
.to_vec()
|
|
.into();
|
|
let rev_q: Self = (&rev_b_inv * &rhs).coeffs[..=a_degree_plug_1 - b_degree_plus_1]
|
|
.to_vec()
|
|
.into();
|
|
let mut q = rev_q.rev();
|
|
let qb = &q * b;
|
|
let mut r = self - &qb;
|
|
q.trim();
|
|
r.trim();
|
|
(q, r)
|
|
}
|
|
}
|
|
|
|
/// Polynomial long division.
|
|
/// Returns `(q, r)`, the quotient and remainder of the polynomial division of `a` by `b`.
|
|
/// Generally slower that the equivalent function `Polynomial::polynomial_division`.
|
|
pub fn div_rem_long_division(&self, b: &Self) -> (Self, Self) {
|
|
let b = b.trimmed();
|
|
|
|
let (a_degree_plus_1, b_degree_plus_1) = (self.degree_plus_one(), b.degree_plus_one());
|
|
if a_degree_plus_1 == 0 {
|
|
(Self::zero(1), Self::empty())
|
|
} else if b_degree_plus_1 == 0 {
|
|
panic!("Division by zero polynomial");
|
|
} else if a_degree_plus_1 < b_degree_plus_1 {
|
|
(Self::zero(1), self.clone())
|
|
} else {
|
|
// Now we know that self.degree() >= divisor.degree();
|
|
let mut quotient = Self::zero(a_degree_plus_1 - b_degree_plus_1 + 1);
|
|
let mut remainder = self.clone();
|
|
// Can unwrap here because we know self is not zero.
|
|
let divisor_leading_inv = b.lead().inverse();
|
|
while !remainder.is_zero() && remainder.degree_plus_one() >= b_degree_plus_1 {
|
|
let cur_q_coeff = remainder.lead() * divisor_leading_inv;
|
|
let cur_q_degree = remainder.degree_plus_one() - b_degree_plus_1;
|
|
quotient.coeffs[cur_q_degree] = cur_q_coeff;
|
|
|
|
for (i, &div_coeff) in b.coeffs.iter().enumerate() {
|
|
remainder.coeffs[cur_q_degree + i] -= cur_q_coeff * div_coeff;
|
|
}
|
|
remainder.trim();
|
|
}
|
|
(quotient, remainder)
|
|
}
|
|
}
|
|
|
|
/// Takes a polynomial `a` in coefficient form, and divides it by `Z_H = X^n - 1`.
|
|
///
|
|
/// This assumes `Z_H | a`, otherwise result is meaningless.
|
|
pub(crate) fn divide_by_z_h(&self, n: usize) -> PolynomialCoeffs<F> {
|
|
let mut a = self.clone();
|
|
|
|
// TODO: Is this special case needed?
|
|
if a.coeffs.iter().all(|p| *p == F::ZERO) {
|
|
return a;
|
|
}
|
|
|
|
let g = F::MULTIPLICATIVE_GROUP_GENERATOR;
|
|
let mut g_pow = F::ONE;
|
|
// Multiply the i-th coefficient of `a` by `g^i`. Then `new_a(w^j) = old_a(g.w^j)`.
|
|
a.coeffs.iter_mut().for_each(|x| {
|
|
*x *= g_pow;
|
|
g_pow *= g;
|
|
});
|
|
|
|
let root = F::primitive_root_of_unity(log2_strict(a.len()));
|
|
// Equals to the evaluation of `a` on `{g.w^i}`.
|
|
let mut a_eval = fft(&a);
|
|
// Compute the denominators `1/(g^n.w^(n*i) - 1)` using batch inversion.
|
|
let denominator_g = g.exp(n as u64);
|
|
let root_n = root.exp(n as u64);
|
|
let mut root_pow = F::ONE;
|
|
let denominators = (0..a_eval.len())
|
|
.map(|i| {
|
|
if i != 0 {
|
|
root_pow *= root_n;
|
|
}
|
|
denominator_g * root_pow - F::ONE
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let denominators_inv = F::batch_multiplicative_inverse(&denominators);
|
|
// Divide every element of `a_eval` by the corresponding denominator.
|
|
// Then, `a_eval` is the evaluation of `a/Z_H` on `{g.w^i}`.
|
|
a_eval
|
|
.values
|
|
.iter_mut()
|
|
.zip(denominators_inv.iter())
|
|
.for_each(|(x, &d)| {
|
|
*x *= d;
|
|
});
|
|
// `p` is the interpolating polynomial of `a_eval` on `{w^i}`.
|
|
let mut p = ifft(&a_eval);
|
|
// We need to scale it by `g^(-i)` to get the interpolating polynomial of `a_eval` on `{g.w^i}`,
|
|
// a.k.a `a/Z_H`.
|
|
let g_inv = g.inverse();
|
|
let mut g_inv_pow = F::ONE;
|
|
p.coeffs.iter_mut().for_each(|x| {
|
|
*x *= g_inv_pow;
|
|
g_inv_pow *= g_inv;
|
|
});
|
|
p
|
|
}
|
|
|
|
/// Let `self=p(X)`, this returns `(p(X)-p(z))/(X-z)` and `p(z)`.
|
|
/// See https://en.wikipedia.org/wiki/Horner%27s_method
|
|
pub(crate) fn divide_by_linear(&self, z: F) -> (PolynomialCoeffs<F>, F) {
|
|
let mut bs = self
|
|
.coeffs
|
|
.iter()
|
|
.rev()
|
|
.scan(F::ZERO, |acc, &c| {
|
|
*acc = *acc * z + c;
|
|
Some(*acc)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let ev = bs.pop().unwrap_or(F::ZERO);
|
|
bs.reverse();
|
|
(Self { coeffs: bs }, ev)
|
|
}
|
|
|
|
/// Computes the inverse of `self` modulo `x^n`.
|
|
pub fn inv_mod_xn(&self, n: usize) -> Self {
|
|
assert!(self.coeffs[0].is_nonzero(), "Inverse doesn't exist.");
|
|
|
|
let h = if self.len() < n {
|
|
self.padded(n)
|
|
} else {
|
|
self.clone()
|
|
};
|
|
|
|
let mut a = Self::empty();
|
|
a.coeffs.push(h.coeffs[0].inverse());
|
|
for i in 0..log2_ceil(n) {
|
|
let l = 1 << i;
|
|
let h0 = h.coeffs[..l].to_vec().into();
|
|
let mut h1: Self = h.coeffs[l..].to_vec().into();
|
|
let mut c = &a * &h0;
|
|
if l == c.len() {
|
|
c = Self::zero(1);
|
|
} else {
|
|
c.coeffs.drain(0..l);
|
|
}
|
|
h1.trim();
|
|
let mut tmp = &a * &h1;
|
|
tmp = &tmp + &c;
|
|
tmp.coeffs.iter_mut().for_each(|x| *x = -(*x));
|
|
tmp.trim();
|
|
let mut b = &a * &tmp;
|
|
b.trim();
|
|
if b.len() > l {
|
|
b.coeffs.drain(l..);
|
|
}
|
|
a.coeffs.extend_from_slice(&b.coeffs);
|
|
}
|
|
a.coeffs.drain(n..);
|
|
a
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::time::Instant;
|
|
|
|
use crate::field::crandall_field::CrandallField;
|
|
use crate::field::extension_field::quartic::QuarticCrandallField;
|
|
use crate::field::field_types::Field;
|
|
use crate::polynomial::polynomial::PolynomialCoeffs;
|
|
|
|
#[test]
|
|
fn zero_div_z_h() {
|
|
type F = CrandallField;
|
|
let zero = PolynomialCoeffs::<F>::zero(16);
|
|
let quotient = zero.divide_by_z_h(4);
|
|
assert_eq!(quotient, zero);
|
|
}
|
|
|
|
#[test]
|
|
fn division_by_z_h() {
|
|
type F = CrandallField;
|
|
let zero = F::ZERO;
|
|
let one = F::ONE;
|
|
let two = F::TWO;
|
|
let three = F::from_canonical_u64(3);
|
|
let four = F::from_canonical_u64(4);
|
|
let five = F::from_canonical_u64(5);
|
|
let six = F::from_canonical_u64(6);
|
|
|
|
// a(x) = Z_4(x) q(x), where
|
|
// a(x) = 3 x^7 + 4 x^6 + 5 x^5 + 6 x^4 - 3 x^3 - 4 x^2 - 5 x - 6
|
|
// Z_4(x) = x^4 - 1
|
|
// q(x) = 3 x^3 + 4 x^2 + 5 x + 6
|
|
let a = PolynomialCoeffs::new(vec![-six, -five, -four, -three, six, five, four, three]);
|
|
let q = PolynomialCoeffs::new(vec![six, five, four, three, zero, zero, zero, zero]);
|
|
|
|
let computed_q = a.divide_by_z_h(4);
|
|
assert_eq!(computed_q, q);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_division_by_linear() {
|
|
type F = QuarticCrandallField;
|
|
let n = 1_000_000;
|
|
let poly = PolynomialCoeffs::new(F::rand_vec(n));
|
|
let z = F::rand();
|
|
let ev = poly.eval(z);
|
|
|
|
let timer = Instant::now();
|
|
let (quotient, ev2) = poly.div_rem(&PolynomialCoeffs::new(vec![-z, F::ONE]));
|
|
println!("{:.3}s for usual", timer.elapsed().as_secs_f32());
|
|
assert_eq!(ev2.trimmed().coeffs, vec![ev]);
|
|
|
|
let timer = Instant::now();
|
|
let (quotient, ev3) = poly.div_rem_long_division(&PolynomialCoeffs::new(vec![-z, F::ONE]));
|
|
println!("{:.3}s for long division", timer.elapsed().as_secs_f32());
|
|
assert_eq!(ev3.trimmed().coeffs, vec![ev]);
|
|
|
|
let timer = Instant::now();
|
|
let horn = poly.divide_by_linear(z);
|
|
println!("{:.3}s for Horner", timer.elapsed().as_secs_f32());
|
|
assert_eq!((quotient, ev), horn);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_division_by_quadratic() {
|
|
type F = QuarticCrandallField;
|
|
let n = 1_000_000;
|
|
let poly = PolynomialCoeffs::new(F::rand_vec(n));
|
|
let quad = PolynomialCoeffs::new(F::rand_vec(2));
|
|
|
|
let timer = Instant::now();
|
|
let (quotient0, rem0) = poly.div_rem(&quad);
|
|
println!("{:.3}s for usual", timer.elapsed().as_secs_f32());
|
|
|
|
let timer = Instant::now();
|
|
let (quotient1, rem1) = poly.div_rem_long_division(&quad);
|
|
println!("{:.3}s for long division", timer.elapsed().as_secs_f32());
|
|
|
|
assert_eq!(quotient0.trimmed(), quotient1.trimmed());
|
|
assert_eq!(rem0.trimmed(), rem1.trimmed());
|
|
}
|
|
}
|