Polynomial API Overview
Read our paper on the Polynomials API in ICICLE v2 by clicking here.
Introduction
The Polynomial API offers a robust framework for polynomial operations within a computational environment. It's designed for flexibility and efficiency, supporting a broad range of operations like arithmetic, evaluation, and manipulation, all while abstracting from the computation and storage specifics. This enables adaptability to various backend technologies, employing modern C++ practices.
Key Features
Backend Agnostic Architecture
Our API is structured to be independent of any specific computational backend. While a CUDA backend is currently implemented, the architecture facilitates easy integration of additional backends. This capability allows users to perform polynomial operations without the need to tailor their code to specific hardware, enhancing code portability and scalability.
Templating in the Polynomial API
The Polynomial API is designed with a templated structure to accommodate different data types for coefficients, the domain, and images. This flexibility allows the API to be adapted for various computational needs and types of data.
template <typename Coeff, typename Domain = Coeff, typename Image = Coeff>
class Polynomial {
// Polynomial class definition
}
In this template:
Coeff
: Represents the type of the coefficients of the polynomial.Domain
: Specifies the type for the input values over which the polynomial is evaluated. By default, it is the same as the type of the coefficients but can be specified separately to accommodate different computational contexts.Image
: Defines the type of the output values of the polynomial. This is typically the same as the coefficients.
Default instantiation
extern template class Polynomial<scalar_t>;
Extended use cases
The templated nature of the Polynomial API also supports more complex scenarios. For example, coefficients and images could be points on an elliptic curve (EC points), which are useful in cryptographic applications and advanced algebraic structures. This approach allows the API to be extended easily to support new algebraic constructions without modifying the core implementation.
Supported Operations
The Polynomial class encapsulates a polynomial, providing a variety of operations:
- Construction: Create polynomials from coefficients or evaluations on roots-of-unity domains.
- Arithmetic Operations: Perform addition, subtraction, multiplication, and division.
- Evaluation: Directly evaluate polynomials at specific points or across a domain.
- Manipulation: Features like slicing polynomials, adding or subtracting monomials inplace, and computing polynomial degrees.
- Memory Access: Access internal states or obtain device-memory views of polynomials.
Usage
This section outlines how to use the Polynomial API in C++. Bindings for Rust and Go are detailed under the Bindings sections.
Make sure to set an ICICLE device prior to using the polynomial API.
Construction
Polynomials can be constructed from coefficients, from evaluations on roots-of-unity domains, or by cloning existing polynomials.
// Construction
static Polynomial from_coefficients(const Coeff* coefficients, uint64_t nof_coefficients);
static Polynomial from_rou_evaluations(const Image* evaluations, uint64_t nof_evaluations);
// Clone the polynomial
Polynomial clone() const;
Example:
auto p_from_coeffs = Polynomial_t::from_coefficients(coeff /* :scalar_t* */, nof_coeffs);
auto p_from_rou_evals = Polynomial_t::from_rou_evaluations(rou_evals /* :scalar_t* */, nof_evals);
auto p_cloned = p.clone(); // p_cloned and p do not share memory
The coefficients or evaluations may be allocated either on host or device memory. In both cases the memory is copied to the backend device.
Arithmetic
Constructed polynomials can be used for various arithmetic operations:
// Addition
Polynomial operator+(const Polynomial& rhs) const;
Polynomial& operator+=(const Polynomial& rhs); // inplace addition
// Subtraction
Polynomial operator-(const Polynomial& rhs) const;
// Multiplication
Polynomial operator*(const Polynomial& rhs) const;
Polynomial operator*(const Domain& scalar) const; // scalar multiplication
// Division A(x) = B(x)Q(x) + R(x)
std::pair<Polynomial, Polynomial> divide(const Polynomial& rhs) const; // returns (Q(x), R(x))
Polynomial operator/(const Polynomial& rhs) const; // returns quotient Q(x)
Polynomial operator%(const Polynomial& rhs) const; // returns remainder R(x)
Polynomial divide_by_vanishing_polynomial(uint64_t degree) const; // sdivision by the vanishing polynomial V(x)=X^N-1
Example
Given polynomials A(x),B(x),C(x) and V(x) the vanishing polynomial.
auto H = (A*B-C).divide_by_vanishing_polynomial(N);
Evaluation
Evaluate polynomials at arbitrary domain points, across a domain or on a roots-of-unity domain.
Image operator()(const Domain& x) const; // evaluate f(x)
void evaluate(const Domain* x, Image* evals /*OUT*/) const;
void evaluate_on_domain(Domain* domain, uint64_t size, Image* evals /*OUT*/) const; // caller allocates memory
void evaluate_on_rou_domain(uint64_t domain_log_size, Image* evals /*OUT*/) const; // caller allocate memory
Example:
Coeff x = rand();
Image f_x = f(x); // evaluate f at x
// evaluate f(x) on a domain
uint64_t domain_size = ...;
auto domain = /*build domain*/; // host or device memory
auto evaluations = std::make_unique<scalar_t[]>(domain_size); // can be device memory too
f.evaluate_on_domain(domain, domain_size, evaluations);
// evaluate f(x) on roots of unity domain
uint64_t domain_log_size = ...;
auto evaluations_rou_domain = std::make_unique<scalar_t[]>(1 << domain_log_size); // can be device memory too
f.evaluate_on_rou_domain(domain_log_size, evaluations_rou_domain);
Manipulations
Beyond arithmetic, the API supports efficient polynomial manipulations: