Skip to content

Commit a0cd1b6

Browse files
committed
feat(fft): fast division
1 parent 87c9976 commit a0cd1b6

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_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
@@ -111,6 +111,63 @@ impl<E: IsField> Polynomial<FieldElement<E>> {
111111

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

116173
pub fn compose_fft<F, E>(
@@ -269,6 +326,11 @@ mod tests {
269326
vec
270327
}
271328
}
329+
prop_compose! {
330+
fn non_empty_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 1 << max_exp)) -> Vec<FE> {
331+
vec
332+
}
333+
}
272334
prop_compose! {
273335
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> {
274336
vec
@@ -279,6 +341,11 @@ mod tests {
279341
Polynomial::new(&coeffs)
280342
}
281343
}
344+
prop_compose! {
345+
fn non_zero_poly(max_exp: u8)(coeffs in non_empty_field_vec(max_exp)) -> Polynomial<FE> {
346+
Polynomial::new(&coeffs)
347+
}
348+
}
282349
prop_compose! {
283350
fn poly_with_non_power_of_two_coeffs(max_exp: u8)(coeffs in non_power_of_two_sized_field_vec(max_exp)) -> Polynomial<FE> {
284351
Polynomial::new(&coeffs)
@@ -332,6 +399,11 @@ mod tests {
332399
fn test_fft_multiplication_works(poly in poly(7), other in poly(7)) {
333400
prop_assert_eq!(poly.fast_multiplication::<F>(&other).unwrap(), poly * other);
334401
}
402+
403+
#[test]
404+
fn test_fft_division_works(poly in non_zero_poly(7), other in non_zero_poly(7)) {
405+
prop_assert_eq!(poly.fast_division::<F>(&other).unwrap(), poly.long_division_with_remainder(&other));
406+
}
335407
}
336408

337409
#[test]
@@ -367,6 +439,11 @@ mod tests {
367439
vec
368440
}
369441
}
442+
prop_compose! {
443+
fn non_empty_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 1 << max_exp)) -> Vec<FE> {
444+
vec
445+
}
446+
}
370447
prop_compose! {
371448
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> {
372449
vec
@@ -377,6 +454,11 @@ mod tests {
377454
Polynomial::new(&coeffs)
378455
}
379456
}
457+
prop_compose! {
458+
fn non_zero_poly(max_exp: u8)(coeffs in non_empty_field_vec(max_exp)) -> Polynomial<FE> {
459+
Polynomial::new(&coeffs)
460+
}
461+
}
380462
prop_compose! {
381463
fn poly_with_non_power_of_two_coeffs(max_exp: u8)(coeffs in non_power_of_two_sized_field_vec(max_exp)) -> Polynomial<FE> {
382464
Polynomial::new(&coeffs)
@@ -432,6 +514,11 @@ mod tests {
432514
fn test_fft_multiplication_works(poly in poly(7), other in poly(7)) {
433515
prop_assert_eq!(poly.fast_multiplication::<F>(&other).unwrap(), poly * other);
434516
}
517+
518+
#[test]
519+
fn test_fft_division_works(poly in poly(7), other in non_zero_poly(7)) {
520+
prop_assert_eq!(poly.fast_division::<F>(&other).unwrap(), poly.long_division_with_remainder(&other));
521+
}
435522
}
436523
}
437524

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)