Rust FFI Bindings for Univariate Polynomial
Please refer to the Polynomials overview page for a deep overview. This section is a brief description of the Rust FFI bindings.
This documentation is designed to provide developers with a clear understanding of how to utilize the Rust bindings for polynomial operations efficiently and effectively, leveraging the robust capabilities of both Rust and C++ in their applications.
Introduction
The Rust FFI bindings for the Univariate Polynomial serve as a "shallow wrapper" around the underlying C++ implementation. These bindings provide a straightforward Rust interface that directly calls functions from a C++ library, effectively bridging Rust and C++ operations. The Rust layer handles simple interface translations without delving into complex logic or data structures, which are managed on the C++ side. This design ensures efficient data handling, memory management, and execution of polynomial operations directly via C++. Currently, these bindings are tailored specifically for polynomials where the coefficients, domain, and images are represented as scalar fields.
Initialization Requirements
Before utilizing any functions from the polynomial API, it is mandatory to initialize the appropriate polynomial backend (e.g., CUDA). Additionally, the NTT (Number Theoretic Transform) domain must also be initialized, as the CUDA backend relies on this for certain operations. Failing to properly initialize these components can result in errors.
Field-Specific Initialization Requirement
The ICICLE library is structured such that each field or curve has its dedicated library implementation. As a result, initialization must be performed individually for each field or curve to ensure the correct setup and functionality of the library.
Core Trait: UnivariatePolynomial
The UnivariatePolynomial
trait encapsulates the essential functionalities required for managing univariate polynomials in the Rust ecosystem. This trait standardizes the operations that can be performed on polynomials, regardless of the underlying implementation details. It allows for a unified approach to polynomial manipulation, providing a suite of methods that are fundamental to polynomial arithmetic.
Trait Definition
pub trait UnivariatePolynomial
where
Self::Field: FieldImpl,
Self::FieldConfig: FieldConfig,
{
type Field: FieldImpl;
type FieldConfig: FieldConfig;
// Methods to create polynomials from coefficients or roots-of-unity evaluations.
fn from_coeffs<S: HostOrDeviceSlice<Self::Field> + ?Sized>(coeffs: &S, size: usize) -> Self;
fn from_rou_evals<S: HostOrDeviceSlice<Self::Field> + ?Sized>(evals: &S, size: usize) -> Self;
// Method to divide this polynomial by another, returning quotient and remainder.
fn divide(&self, denominator: &Self) -> (Self, Self) where Self: Sized;
// Method to divide this polynomial by the vanishing polynomial 'X^N-1'.
fn div_by_vanishing(&self, degree: u64) -> Self;
// Methods to add or subtract a monomial in-place.
fn add_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64);
fn sub_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64);
// Method to slice the polynomial, creating a sub-polynomial.
fn slice(&self, offset: u64, stride: u64, size: u64) -> Self;
// Methods to return new polynomials containing only the even or odd terms.
fn even(&self) -> Self;
fn odd(&self) -> Self;
// Method to evaluate the polynomial at a given domain point.
fn eval(&self, x: &Self::Field) -> Self::Field;
// Method to evaluate the polynomial over a domain and store the results.
fn eval_on_domain<D: HostOrDeviceSlice<Self::Field> + ?Sized, E: HostOrDeviceSlice<Self::Field> + ?Sized>(
&self,
domain: &D,
evals: &mut E,
);
// Method to evaluate the polynomial over the roots-of-unity domain for power-of-two sized domain
fn eval_on_rou_domain<E: HostOrDeviceSlice<Self::Field> + ?Sized>(&self, domain_log_size: u64, evals: &mut E);
// Method to retrieve a coefficient at a specific index.
fn get_coeff(&self, idx: u64) -> Self::Field;
// Method to copy coefficients into a provided slice.
fn copy_coeffs<S: HostOrDeviceSlice<Self::Field> + ?Sized>(&self, start_idx: u64, coeffs: &mut S);
// Method to get the degree of the polynomial.
fn degree(&self) -> i64;
}
DensePolynomial
Struct
The DensePolynomial struct represents a dense univariate polynomial in Rust, leveraging a handle to manage its underlying memory within the CUDA device context. This struct acts as a high-level abstraction over complex C++ memory management practices, facilitating the integration of high-performance polynomial operations through Rust's Foreign Function Interface (FFI) bindings.
pub struct DensePolynomial {
handle: PolynomialHandle,
}
Traits implementation and methods
Drop
Ensures proper resource management by releasing the CUDA memory when a DensePolynomial instance goes out of scope. This prevents memory leaks and ensures that resources are cleaned up correctly, adhering to Rust's RAII (Resource Acquisition Is Initialization) principles.
Clone
Provides a way to create a new instance of a DensePolynomial with its own unique handle, thus duplicating the polynomial data in the CUDA context. Cloning is essential since the DensePolynomial manages external resources, which cannot be safely shared across instances without explicit duplication.
Operator Overloading: Add
, Sub
, Mul
, Rem
, Div
These traits are implemented for references to DensePolynomial (i.e., &DensePolynomial), enabling natural mathematical operations such as addition (+), subtraction (-), multiplication (*), division (/), and remainder (%). This syntactic convenience allows users to compose complex polynomial expressions in a way that is both readable and expressive.
Key Methods
In addition to the traits, the following methods are implemented:
impl DensePolynomial {
pub fn init_cuda_backend() -> bool {...}
// Returns a mutable slice of the polynomial coefficients on the device
pub fn coeffs_mut_slice(&mut self) -> &mut DeviceSlice<F> {...}
}
Flexible Memory Handling With HostOrDeviceSlice
The DensePolynomial API is designed to accommodate a wide range of computational environments by supporting both host and device memory through the HostOrDeviceSlice
trait. This approach ensures that polynomial operations can be seamlessly executed regardless of where the data resides, making the API highly adaptable and efficient for various hardware configurations.
Overview of HostOrDeviceSlice
The HostOrDeviceSlice is a Rust trait that abstracts over slices of memory that can either be on the host (CPU) or the device (GPU), as managed by CUDA. This abstraction is crucial for high-performance computing scenarios where data might need to be moved between different memory spaces depending on the operations being performed and the specific hardware capabilities available.
Usage in API Functions
Functions within the DensePolynomial API that deal with polynomial coefficients or evaluations use the HostOrDeviceSlice trait to accept inputs. This design allows the functions to be agnostic of the actual memory location of the data, whether it's in standard system RAM accessible by the CPU or in GPU memory accessible by CUDA cores.
// Assume `coeffs` could either be in host memory or CUDA device memory
let coeffs: DeviceSlice<F> = DeviceVec::<F>::cuda_malloc(coeffs_len).unwrap();
let p_from_coeffs = PolynomialBabyBear::from_coeffs(&coeffs, coeffs.len());
// Similarly for evaluations from roots of unity
let evals: HostSlice<F> = HostSlice::from_slice(&host_memory_evals);
let p_from_evals = PolynomialBabyBear::from_rou_evals(&evals, evals.len());
// Same applies for any API that accepts HostOrDeviceSlice
Usage
This section outlines practical examples demonstrating how to utilize the DensePolynomial
Rust API. The API is flexible, supporting multiple scalar fields. Below are examples showing how to use polynomials defined over different fields and perform a variety of operations.
Initialization and Basic Operations
First, choose the appropriate field implementation for your polynomial operations, initializing the CUDA backend if necessary
use icicle_babybear::polynomials::DensePolynomial as PolynomialBabyBear;
// Initialize the CUDA backend for polynomial operations
PolynomialBabyBear::init_cuda_backend();
let f = PolynomialBabyBear::from_coeffs(...);
// now use f by calling the implemented traits
// For operations over another field, such as BN254
use icicle_bn254::polynomials::DensePolynomial as PolynomialBn254;
// Use PolynomialBn254 similarly
Creation
Polynomials can be created from coefficients or evaluations:
let coeffs = ...;
let p_from_coeffs = PolynomialBabyBear::from_coeffs(HostSlice::from_slice(&coeffs), size);
let evals = ...;
let p_from_evals = PolynomialBabyBear::from_rou_evals(HostSlice::from_slice(&evals), size);