use p3_field::Algebra;
pub trait Composition<F> {
fn arity(&self) -> usize;
fn degree(&self) -> usize;
fn eval<A: Algebra<F> + Copy>(&self, values: &[A]) -> A;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Conjunction;
impl<F> Composition<F> for Conjunction {
fn arity(&self) -> usize {
3
}
fn degree(&self) -> usize {
2
}
fn eval<A: Algebra<F> + Copy>(&self, values: &[A]) -> A {
assert_eq!(values.len(), 3, "the conjunction reads three operands");
values[0] * values[1] - values[2]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SquareProduct;
impl<F> Composition<F> for SquareProduct {
fn arity(&self) -> usize {
3
}
fn degree(&self) -> usize {
3
}
fn eval<A: Algebra<F> + Copy>(&self, values: &[A]) -> A {
assert_eq!(values.len(), 3, "the cubic constraint reads three operands");
values[0] * values[0] * values[1] - values[2]
}
}
#[cfg(test)]
mod tests {
use p3_binary_field::{BinaryField8, BinaryField128};
use p3_field::PrimeCharacteristicRing;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
type F = BinaryField8;
fn in_alphabet(values: &[F]) -> F {
Composition::<F>::eval(&Conjunction, values)
}
fn in_extension(values: &[BinaryField128]) -> BinaryField128 {
Composition::<F>::eval(&Conjunction, values)
}
#[test]
fn the_conjunction_vanishes_exactly_on_satisfying_bits() {
for a in [0u8, 1] {
for b in [0u8, 1] {
for c in [0u8, 1] {
let bit = |v: u8| if v == 1 { F::ONE } else { F::ZERO };
let value = in_alphabet(&[bit(a), bit(b), bit(c)]);
let satisfied = (a & b) == c;
assert_eq!(value == F::ZERO, satisfied, "{a}{b}{c}");
}
}
}
}
#[test]
fn one_constraint_reads_in_both_carriers() {
let mut rng = SmallRng::seed_from_u64(0xCA2);
for _ in 0..32 {
let small: [F; 3] = core::array::from_fn(|_| rng.random());
let large = small.map(BinaryField128::from);
assert_eq!(
BinaryField128::from(in_alphabet(&small)),
in_extension(&large)
);
assert_eq!(
BinaryField128::from(Composition::<F>::eval(&SquareProduct, &small)),
Composition::<F>::eval(&SquareProduct, &large)
);
}
}
#[test]
fn the_cubic_constraint_vanishes_exactly_where_it_should() {
let mut rng = SmallRng::seed_from_u64(0xCB1C);
for _ in 0..32 {
let (a, b) = (rng.random::<F>(), rng.random::<F>());
let c = a * a * b;
assert_eq!(Composition::<F>::eval(&SquareProduct, &[a, b, c]), F::ZERO);
assert_ne!(
Composition::<F>::eval(&SquareProduct, &[a, b, c + F::ONE]),
F::ZERO
);
}
}
#[test]
fn the_declared_shape_matches_what_the_constraint_reads() {
assert_eq!(Composition::<F>::arity(&Conjunction), 3);
assert_eq!(Composition::<F>::degree(&Conjunction), 2);
assert_eq!(Composition::<F>::arity(&SquareProduct), 3);
assert_eq!(Composition::<F>::degree(&SquareProduct), 3);
}
}