use derive_new::new;
use openvm_circuit_primitives_derive::AlignedBorrow;
use openvm_stark_backend::{
interaction::InteractionBuilder,
p3_air::AirBuilder,
p3_field::{Field, PrimeCharacteristicRing},
};
use crate::{
var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip},
StructReflection, StructReflectionHelper, SubAir, TraceSubRowGenerator,
};
#[cfg(test)]
pub mod tests;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct AssertLessThanIo<T> {
pub x: T,
pub y: T,
pub count: T,
}
impl<T> AssertLessThanIo<T> {
pub fn new(x: impl Into<T>, y: impl Into<T>, count: impl Into<T>) -> Self {
Self {
x: x.into(),
y: y.into(),
count: count.into(),
}
}
}
#[repr(C)]
#[derive(AlignedBorrow, StructReflection, Clone, Copy, Debug, new)]
pub struct LessThanAuxCols<T, const AUX_LEN: usize> {
pub lower_decomp: [T; AUX_LEN],
}
#[derive(Copy, Clone, Debug)]
pub struct AssertLtSubAir {
pub bus: VariableRangeCheckerBus,
pub max_bits: usize,
pub decomp_limbs: usize,
}
impl AssertLtSubAir {
pub fn new(bus: VariableRangeCheckerBus, max_bits: usize) -> Self {
assert!(max_bits <= 29); let decomp_limbs = max_bits.div_ceil(bus.range_max_bits);
Self {
bus,
max_bits,
decomp_limbs,
}
}
pub fn range_max_bits(&self) -> usize {
self.bus.range_max_bits
}
#[inline(always)]
fn eval_without_range_checks<AB: AirBuilder<Var: Copy>>(
&self,
builder: &mut AB,
io: AssertLessThanIo<AB::Expr>,
lower_decomp: &[AB::Var],
) {
assert_eq!(lower_decomp.len(), self.decomp_limbs);
let intermed_val = io.y - io.x - AB::Expr::ONE;
let lower = lower_decomp
.iter()
.enumerate()
.fold(AB::Expr::ZERO, |acc, (i, &val)| {
acc + val * AB::Expr::from_usize(1 << (i * self.range_max_bits()))
});
builder.when(io.count).assert_eq(intermed_val, lower);
}
#[inline(always)]
fn eval_range_checks<AB: InteractionBuilder>(
&self,
builder: &mut AB,
lower_decomp: &[AB::Var],
count: impl Into<AB::Expr>,
) {
let count = count.into();
let mut bits_remaining = self.max_bits;
for limb in lower_decomp {
let range_bits = bits_remaining.min(self.range_max_bits());
self.bus
.range_check(*limb, range_bits)
.eval(builder, count.clone());
bits_remaining = bits_remaining.saturating_sub(self.range_max_bits());
}
}
}
impl<AB: InteractionBuilder> SubAir<AB> for AssertLtSubAir {
type AirContext<'a>
= (AssertLessThanIo<AB::Expr>, &'a [AB::Var])
where
AB::Expr: 'a,
AB::Var: 'a,
AB: 'a;
fn eval<'a>(
&'a self,
builder: &'a mut AB,
(io, lower_decomp): (AssertLessThanIo<AB::Expr>, &'a [AB::Var]),
) where
AB::Var: 'a,
AB::Expr: 'a,
{
self.eval_range_checks(builder, lower_decomp, io.count.clone());
self.eval_without_range_checks(builder, io, lower_decomp);
}
}
impl<F: Field> TraceSubRowGenerator<F> for AssertLtSubAir {
type TraceContext<'a> = (&'a VariableRangeCheckerChip, u32, u32);
type ColsMut<'a> = &'a mut [F];
#[inline(always)]
fn generate_subrow<'a>(
&'a self,
(range_checker, x, y): (&'a VariableRangeCheckerChip, u32, u32),
lower_decomp: &'a mut [F],
) {
debug_assert!(x < y, "assert {x} < {y} failed");
debug_assert_eq!(lower_decomp.len(), self.decomp_limbs);
debug_assert!(
x < (1 << self.max_bits),
"{x} has more than {} bits",
self.max_bits
);
debug_assert!(
y < (1 << self.max_bits),
"{y} has more than {} bits",
self.max_bits
);
range_checker.decompose(y - x - 1, self.max_bits, lower_decomp);
}
}