binius_core/composition/
index.rsuse std::fmt::Debug;
use binius_field::{Field, PackedField};
use binius_math::{ArithExpr, CompositionPolyOS};
use binius_utils::bail;
use crate::polynomial::Error;
#[derive(Clone, Debug)]
pub struct IndexComposition<C, const N: usize> {
n_vars: usize,
indices: [usize; N],
composition: C,
}
impl<C, const N: usize> IndexComposition<C, N> {
pub fn new(n_vars: usize, indices: [usize; N], composition: C) -> Result<Self, Error> {
if indices.iter().any(|&index| index >= n_vars) {
bail!(Error::IndexCompositionIndicesOutOfBounds);
}
Ok(Self {
n_vars,
indices,
composition,
})
}
}
impl<P: PackedField, C: CompositionPolyOS<P>, const N: usize> CompositionPolyOS<P>
for IndexComposition<C, N>
{
fn n_vars(&self) -> usize {
self.n_vars
}
fn degree(&self) -> usize {
self.composition.degree()
}
fn expression(&self) -> ArithExpr<<P as PackedField>::Scalar> {
fn map_variables<F: Field, const M: usize>(
index_map: &[usize; M],
expr: &ArithExpr<F>,
) -> ArithExpr<F> {
match expr {
ArithExpr::Var(i) => ArithExpr::Var(index_map[*i]),
ArithExpr::Const(c) => ArithExpr::Const(*c),
ArithExpr::Add(a, b) => ArithExpr::Add(
Box::new(map_variables(index_map, a)),
Box::new(map_variables(index_map, b)),
),
ArithExpr::Mul(a, b) => ArithExpr::Mul(
Box::new(map_variables(index_map, a)),
Box::new(map_variables(index_map, b)),
),
ArithExpr::Pow(a, n) => ArithExpr::Pow(Box::new(map_variables(index_map, a)), *n),
}
}
map_variables(&self.indices, &self.composition.expression())
}
fn evaluate(&self, query: &[P]) -> Result<P, binius_math::Error> {
if query.len() != self.n_vars {
bail!(binius_math::Error::IncorrectQuerySize {
expected: self.n_vars,
});
}
let subquery = self.indices.map(|index| query[index]);
self.composition.evaluate(&subquery)
}
fn binary_tower_level(&self) -> usize {
self.composition.binary_tower_level()
}
fn batch_evaluate(
&self,
batch_query: &[&[P]],
evals: &mut [P],
) -> Result<(), binius_math::Error> {
let batch_subquery = self.indices.map(|index| batch_query[index]);
self.composition
.batch_evaluate(batch_subquery.as_slice(), evals)
}
}
pub fn index_composition<E, C, const N: usize>(
superset: &[E],
subset: [E; N],
composition: C,
) -> Result<IndexComposition<C, N>, Error>
where
E: PartialEq,
{
let n_vars = superset.len();
let proper_subset = subset.iter().all(|subset_item| {
superset
.iter()
.any(|superset_item| superset_item == subset_item)
});
if !proper_subset {
bail!(Error::MixedMultilinearNotFound);
}
let indices = subset.map(|subset_item| {
superset
.iter()
.position(|superset_item| superset_item == &subset_item)
.expect("Superset condition checked above.")
});
Ok(IndexComposition {
n_vars,
indices,
composition,
})
}
#[cfg(test)]
mod tests {
use binius_field::BinaryField1b;
use super::*;
use crate::polynomial::ArithCircuitPoly;
#[test]
fn tests_expr() {
let expr = ArithExpr::Add(
Box::new(ArithExpr::Var(0)),
Box::new(ArithExpr::Mul(
Box::new(ArithExpr::Var(1)),
Box::new(ArithExpr::Const(BinaryField1b::ONE)),
)),
);
let circuit = ArithCircuitPoly::new(expr);
let composition = IndexComposition {
n_vars: 3,
indices: [1, 2],
composition: circuit,
};
assert_eq!(
(&composition as &dyn CompositionPolyOS<BinaryField1b>).expression(),
ArithExpr::Add(
Box::new(ArithExpr::Var(1)),
Box::new(ArithExpr::Mul(
Box::new(ArithExpr::Var(2)),
Box::new(ArithExpr::Const(BinaryField1b::ONE)),
)),
)
);
}
}