Contains all the relevant tools for polynomials. Supports:
- Dense univariate polynomials
- Sparse univariate polynomials - memory-efficient for polynomials with few non-zero terms
- Dense multilinear polynomials and Sparse multilinear polynomials
lambdaworks's polynomials work over Finite Fields.
Univariate polynomials are expressions of the form
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Polynomial<FE> {
pub coefficients: Vec<FE>,
}it contains the coefficients in increasing order (we start with the independent term,
let my_poly = Polynomial::new(&[FE::new(1), FE::new(2), FE::new(3)])This creates the polynomial new method will remove those unnecessary zeros. For example,
let my_poly = Polynomial::new(&[FE::new(1), FE::new(2), FE::new(3), FE::ZERO])generates the same polynomial as before. We can also create a monomial, such as
let my_monomial = Polynomial::new_monomial(FE::new(27),6)generates the monomial
Univariate polynomials have a ring structure: we can add, subtract, multiply and divide as we did with integers. For example, to add two polynomials,
let p_1 = Polynomial::new(&[FE::new(3), FE::new(4), FE::new(5)])
let p_2 = Polynomial::new(&[FE::new(4), FE::new(6), FE::new(8)])
let p_a = p_1 + p_2Polynomial multiplication,
let p1 = Polynomial::new(&[FE::new(3), FE::new(3), FE::new(2)]);
let p2 = Polynomial::new(&[FE::new(4), FE::new(1)]);
assert_eq!(
p2 * p1,
Polynomial::new(&[FE::new(12), FE::new(15), FE::new(11), FE::new(2)])
);Division,
let p1 = Polynomial::new(&[FE::new(1), FE::new(3)]);
let p2 = Polynomial::new(&[FE::new(1), FE::new(3)]);
let p3 = p1.mul_with_ref(&p2);
assert_eq!(p3 / p2, p1);Note that, in the case of polynomial division, it may have a remainder. If you want to divide a polynomial ruffini_division or ruffini_division_inplace.
Polynomials can also be evaluated at a point evaluate. This provides the evaluation
let p = Polynomial::new(&[FE::new(3), -FE::new(2), FE::new(4)]);
assert_eq!(p.evaluate(&FE::new(2)), FE::new(15));evaluates the polynomial evaluate_slice.
Alternatively, polynomials of degree interpolate, which takes to vectors, of equal length: the first contains the
let p = Polynomial::interpolate(&[FE::new(0), FE::new(1)], &[FE::new(2), FE::new(1)]).unwrap();Many polynomial operations can go faster by using the Fast Fourier Transform.
When a polynomial has few non-zero coefficients relative to its degree (e.g., X^1000 - 1), using a sparse representation is more memory-efficient. Sparse polynomials store only non-zero terms as (degree, coefficient) pairs.
use lambdaworks_math::polynomial::sparse::SparsePolynomial;
// Create polynomial 3*X^100 + 2*X^50 + 1
let poly = SparsePolynomial::from_coefficients(vec![
(0, FE::from(1)),
(50, FE::from(2)),
(100, FE::from(3)),
]);
assert_eq!(poly.degree(), 100);
assert_eq!(poly.num_terms(), 3);Sparse polynomials support arithmetic operations (add, subtract, multiply) and can be converted to/from dense representation when needed. They are useful for:
- Vanishing polynomials like
X^n - 1 - R1CS constraint systems
- Custom gates in PLONK-like systems
Multilinear polynomials are useful to define multilinear extensions of functions, which then play an important role in proof systems involving the sumcheck protocol. There are two ways to define multilinear polynomials:
- Dense
- Sparse
Sparse is more convenient whenever the number of non-zero coefficients in the polynomial is small (compared to the length of the polynomial), avoiding the storage of unnecessary zeros. For dense multilinear polynomials we have the following structure, working over some field
pub struct DenseMultilinearPolynomial<F: IsField>
where
<F as IsField>::BaseType: Send + Sync,
{
evals: Vec<FieldElement<F>>,
n_vars: usize,
len: usize,
}The polynomial is assumed to be given in evaluation form over the binary strings of length
An advantage of Lagrange basis polynomials is that we can evaluate all
To create a new polynomial, provide a list of evaluations of
pub fn new(mut evals: Vec<FieldElement<F>>) -> Self {
while !evals.len().is_power_of_two() {
evals.push(FieldElement::zero());
}
let len = evals.len();
DenseMultilinearPolynomial {
n_vars: log_2(len),
evals,
len,
}
}Dense multilinear polynomials allow you to access the fields n_vars, len and evals with the methods pub fn num_vars(&self) -> usize, pub fn len(&self) -> usize and pub fn evals(&self) -> &Vec<FieldElement<F>>.
If you want to evaluate outside pub fn evaluate(&self, r: Vec<FieldElement<F>>) and pub fn evaluate_with(evals: &[FieldElement<F>], r: &[FieldElement<F>]), providing the point
// Example: Z = [1, 2, 1, 4]
let z = vec![FE::one(), FE::from(2u64), FE::one(), FE::from(4u64)];
// r = [4, 3]
let r = vec![FE::from(4u64), FE::from(3u64)];
let eval_with_lr = evaluate_with_lr(&z, &r);
let poly = DenseMultilinearPolynomial::new(z);
let eval = poly.evaluate(r).unwrap();
assert_eq!(eval, FE::from(28u64));An important functionality is pub fn to_univariate(&self) -> Polynomial<FieldElement<F>>, which converts a multilinear polynomial into a univariate polynomial, by summing over all variables over
let univar0 = prover.poly.to_univariate();is used in the sumcheck protocol.
Multilinear polynomials can be added and multiplied by scalars.