Skip to content

Commit 1e335fb

Browse files
committed
feat(fft): fast division
1 parent 0f56fc9 commit 1e335fb

File tree

3 files changed

+111
-16
lines changed

3 files changed

+111
-16
lines changed

crates/math/benches/polynomials/polynomial.rs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ pub fn polynomial_benchmarks(c: &mut Criterion) {
4444
let y_poly = rand_poly(order);
4545
bench.iter(|| black_box(&x_poly) * black_box(&y_poly));
4646
});
47-
group.bench_function("fast mul", |bench| {
48-
let x_poly = rand_complex_mersenne_poly(order as u32);
49-
let y_poly = rand_complex_mersenne_poly(order as u32);
50-
bench.iter(|| black_box(&x_poly) * black_box(&y_poly));
51-
});
5247

5348
let big_order = 9;
5449
let x_poly = rand_complex_mersenne_poly(big_order);
@@ -64,18 +59,17 @@ pub fn polynomial_benchmarks(c: &mut Criterion) {
6459
bench.iter(|| black_box(&x_poly) * black_box(&y_poly));
6560
});
6661

67-
let big_order = 9;
68-
let x_poly = rand_complex_mersenne_poly(big_order);
69-
let y_poly = rand_complex_mersenne_poly(big_order);
70-
group.bench_function("fast_mul big poly", |bench| {
71-
bench.iter(|| {
72-
black_box(&x_poly)
73-
.fast_fft_multiplication::<Degree2ExtensionField>(black_box(&y_poly))
74-
.unwrap()
75-
});
62+
let y_poly = rand_complex_mersenne_poly(big_order - 2);
63+
64+
group.bench_function("fast div big poly", |bench| {
65+
let x_poly = rand_complex_mersenne_poly(order as u32);
66+
let y_poly = rand_complex_mersenne_poly(order as u32);
67+
bench
68+
.iter(|| black_box(&x_poly).fast_division::<Degree2ExtensionField>(black_box(&y_poly)));
7669
});
77-
group.bench_function("slow mul big poly", |bench| {
78-
bench.iter(|| black_box(&x_poly) * black_box(&y_poly));
70+
71+
group.bench_function("slow div big poly", |bench| {
72+
bench.iter(|| black_box(x_poly.clone()).long_division_with_remainder(black_box(&y_poly)));
7973
});
8074

8175
group.bench_function("div", |bench| {

crates/math/src/fft/polynomial.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,63 @@ impl<E: IsField> Polynomial<FieldElement<E>> {
115115

116116
Polynomial::interpolate_fft::<F>(&r)
117117
}
118+
119+
/// Divides two polynomials with remainder.
120+
/// This is faster than the naive division if the degree of the divisor
121+
/// is greater than the degree of the dividend and both degrees are large enough.
122+
pub fn fast_division<F: IsSubFieldOf<E> + IsFFTField>(
123+
&self,
124+
divisor: &Self,
125+
) -> Result<(Self, Self), FFTError> {
126+
let n = self.degree();
127+
let m = divisor.degree();
128+
if divisor.coefficients.is_empty() {
129+
panic!("Division by zero polynomial");
130+
}
131+
if n < m {
132+
return Ok((Self::zero(), self.clone()));
133+
}
134+
let d = n - m; // Degree of the quotient
135+
let a_rev = self.reverse(n);
136+
let b_rev = divisor.reverse(m);
137+
let inv_b_rev = Self::invert_polynomial::<F>(&b_rev, d + 1)?;
138+
let q = a_rev
139+
.fast_multiplication::<F>(&inv_b_rev)?
140+
.truncate(d + 1)
141+
.reverse(d);
142+
143+
let r = self - q.fast_multiplication::<F>(divisor)?;
144+
Ok((q, r))
145+
}
146+
147+
/// Computes the inverse of polynomial P modulo x^k using Newton iteration.
148+
/// P must have an invertible constant term.
149+
pub fn invert_polynomial<F: IsSubFieldOf<E> + IsFFTField>(
150+
p: &Self,
151+
k: usize,
152+
) -> Result<Self, FFTError> {
153+
if p.coefficients.is_empty() || p.coefficients.iter().all(|c| c == &FieldElement::zero()) {
154+
panic!("Cannot invert polynomial with zero constant term");
155+
}
156+
let mut q = Self::new(&[p.coefficients[0].inv().unwrap()]);
157+
let mut current_precision = 1;
158+
159+
while current_precision < k {
160+
let temp = p
161+
.fast_multiplication::<F>(&q)?
162+
.truncate(2 * current_precision);
163+
let two = Self::new(&[FieldElement::<F>::one() + FieldElement::one()]);
164+
let correction = two - temp;
165+
q = q
166+
.fast_multiplication::<F>(&correction)?
167+
.truncate(2 * current_precision);
168+
169+
current_precision *= 2;
170+
}
171+
172+
// Final truncation to desired degree k
173+
Ok(q.truncate(k))
174+
}
118175
}
119176

120177
pub fn compose_fft<F, E>(
@@ -273,6 +330,11 @@ mod tests {
273330
vec
274331
}
275332
}
333+
prop_compose! {
334+
fn non_empty_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 1 << max_exp)) -> Vec<FE> {
335+
vec
336+
}
337+
}
276338
prop_compose! {
277339
fn non_power_of_two_sized_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 2..1<<max_exp).prop_filter("Avoid polynomials of size power of two", |vec| !vec.len().is_power_of_two())) -> Vec<FE> {
278340
vec
@@ -283,6 +345,11 @@ mod tests {
283345
Polynomial::new(&coeffs)
284346
}
285347
}
348+
prop_compose! {
349+
fn non_zero_poly(max_exp: u8)(coeffs in non_empty_field_vec(max_exp)) -> Polynomial<FE> {
350+
Polynomial::new(&coeffs)
351+
}
352+
}
286353
prop_compose! {
287354
fn poly_with_non_power_of_two_coeffs(max_exp: u8)(coeffs in non_power_of_two_sized_field_vec(max_exp)) -> Polynomial<FE> {
288355
Polynomial::new(&coeffs)
@@ -336,6 +403,11 @@ mod tests {
336403
fn test_fft_multiplication_works(poly in poly(7), other in poly(7)) {
337404
prop_assert_eq!(poly.fast_fft_multiplication::<F>(&other).unwrap(), poly * other);
338405
}
406+
407+
#[test]
408+
fn test_fft_division_works(poly in non_zero_poly(7), other in non_zero_poly(7)) {
409+
prop_assert_eq!(poly.fast_division::<F>(&other).unwrap(), poly.long_division_with_remainder(&other));
410+
}
339411
}
340412

341413
#[test]
@@ -371,6 +443,11 @@ mod tests {
371443
vec
372444
}
373445
}
446+
prop_compose! {
447+
fn non_empty_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 1 << max_exp)) -> Vec<FE> {
448+
vec
449+
}
450+
}
374451
prop_compose! {
375452
fn non_power_of_two_sized_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 2..1<<max_exp).prop_filter("Avoid polynomials of size power of two", |vec| !vec.len().is_power_of_two())) -> Vec<FE> {
376453
vec
@@ -381,6 +458,11 @@ mod tests {
381458
Polynomial::new(&coeffs)
382459
}
383460
}
461+
prop_compose! {
462+
fn non_zero_poly(max_exp: u8)(coeffs in non_empty_field_vec(max_exp)) -> Polynomial<FE> {
463+
Polynomial::new(&coeffs)
464+
}
465+
}
384466
prop_compose! {
385467
fn poly_with_non_power_of_two_coeffs(max_exp: u8)(coeffs in non_power_of_two_sized_field_vec(max_exp)) -> Polynomial<FE> {
386468
Polynomial::new(&coeffs)
@@ -436,6 +518,11 @@ mod tests {
436518
fn test_fft_multiplication_works(poly in poly(7), other in poly(7)) {
437519
prop_assert_eq!(poly.fast_fft_multiplication::<F>(&other).unwrap(), poly * other);
438520
}
521+
522+
#[test]
523+
fn test_fft_division_works(poly in poly(7), other in non_zero_poly(7)) {
524+
prop_assert_eq!(poly.fast_division::<F>(&other).unwrap(), poly.long_division_with_remainder(&other));
525+
}
439526
}
440527
}
441528

crates/math/src/polynomial/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,20 @@ impl<F: IsField> Polynomial<FieldElement<F>> {
317317
.collect(),
318318
}
319319
}
320+
321+
pub fn truncate(&self, k: usize) -> Self {
322+
if k == 0 {
323+
Self::zero()
324+
} else {
325+
Self::new(&self.coefficients[0..k.min(self.coefficients.len())])
326+
}
327+
}
328+
pub fn reverse(&self, d: usize) -> Self {
329+
let mut coeffs = self.coefficients.clone();
330+
coeffs.resize(d + 1, FieldElement::zero());
331+
coeffs.reverse();
332+
Self::new(&coeffs)
333+
}
320334
}
321335

322336
impl<F: IsPrimeField> Polynomial<FieldElement<F>> {

0 commit comments

Comments
 (0)