openvm_circuit_primitives/range_gate/
mod.rs1use std::{
7 borrow::Borrow,
8 mem::{size_of, transmute},
9 sync::atomic::AtomicU32,
10};
11
12use openvm_circuit_primitives_derive::AlignedBorrow;
13use openvm_stark_backend::{
14 interaction::{BusIndex, InteractionBuilder},
15 p3_air::{Air, AirBuilder, BaseAir},
16 p3_field::{Field, PrimeCharacteristicRing},
17 p3_matrix::{dense::RowMajorMatrix, Matrix},
18 p3_util::indices_arr,
19 BaseAirWithPublicValues, PartitionedBaseAir,
20};
21
22pub use crate::range::RangeCheckBus;
23use crate::{ColumnsAir, StructReflection, StructReflectionHelper};
24
25#[cfg(test)]
26mod tests;
27
28#[repr(C)]
29#[derive(Copy, Clone, Default, AlignedBorrow, StructReflection)]
30pub struct RangeGateCols<T> {
31 pub counter: T,
33 pub mult: T,
35}
36
37impl<T: Clone> RangeGateCols<T> {
38 pub fn from_slice(slice: &[T]) -> Self {
39 let counter = slice[0].clone();
40 let mult = slice[1].clone();
41
42 Self { counter, mult }
43 }
44}
45
46pub const NUM_RANGE_GATE_COLS: usize = size_of::<RangeGateCols<u8>>();
47pub const RANGE_GATE_COL_MAP: RangeGateCols<usize> = make_col_map();
48
49#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
50#[columns_via(RangeGateCols<u8>)]
51pub struct RangeCheckerGateAir {
52 pub bus: RangeCheckBus,
53}
54
55impl<F: Field> BaseAirWithPublicValues<F> for RangeCheckerGateAir {}
56impl<F: Field> PartitionedBaseAir<F> for RangeCheckerGateAir {}
57impl<F: Field> BaseAir<F> for RangeCheckerGateAir {
58 fn width(&self) -> usize {
59 NUM_RANGE_GATE_COLS
60 }
61}
62
63impl<AB: InteractionBuilder> Air<AB> for RangeCheckerGateAir {
64 fn eval(&self, builder: &mut AB) {
65 let main = builder.main();
66
67 let (local, next) = (
68 main.row_slice(0).expect("window should have two elements"),
69 main.row_slice(1).expect("window should have two elements"),
70 );
71 let local: &RangeGateCols<AB::Var> = (*local).borrow();
72 let next: &RangeGateCols<AB::Var> = (*next).borrow();
73
74 builder
76 .when_first_row()
77 .assert_eq(local.counter, AB::Expr::ZERO);
78 builder
80 .when_transition()
81 .assert_eq(local.counter + AB::Expr::ONE, next.counter);
82 builder
85 .when_last_row()
86 .assert_eq(local.counter, AB::F::from_u32(self.bus.range_max - 1));
87 self.bus.receive(local.counter).eval(builder, local.mult);
89 }
90}
91
92pub struct RangeCheckerGateChip {
97 pub air: RangeCheckerGateAir,
98 pub count: Vec<AtomicU32>,
99}
100
101impl RangeCheckerGateChip {
102 pub fn new(bus: RangeCheckBus) -> Self {
103 let count = (0..bus.range_max).map(|_| AtomicU32::new(0)).collect();
104
105 Self {
106 air: RangeCheckerGateAir::new(bus),
107 count,
108 }
109 }
110
111 pub fn bus(&self) -> RangeCheckBus {
112 self.air.bus
113 }
114
115 pub fn bus_index(&self) -> BusIndex {
116 self.air.bus.inner.index
117 }
118
119 pub fn range_max(&self) -> u32 {
120 self.air.bus.range_max
121 }
122
123 pub fn air_width(&self) -> usize {
124 2
125 }
126
127 pub fn add_count(&self, val: u32) {
128 assert!(
129 val < self.range_max(),
130 "range exceeded: {} >= {}",
131 val,
132 self.range_max()
133 );
134 let val_atomic = &self.count[val as usize];
135 val_atomic.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
136 }
137
138 pub fn clear(&self) {
139 for i in 0..self.count.len() {
140 self.count[i].store(0, std::sync::atomic::Ordering::Relaxed);
141 }
142 }
143
144 pub fn generate_trace<F: Field>(&self) -> RowMajorMatrix<F> {
145 let rows = self
146 .count
147 .iter()
148 .enumerate()
149 .flat_map(|(i, count)| {
150 let c = count.swap(0, std::sync::atomic::Ordering::Relaxed);
151 vec![F::from_usize(i), F::from_u32(c)]
152 })
153 .collect();
154 RowMajorMatrix::new(rows, NUM_RANGE_GATE_COLS)
155 }
156}
157
158const fn make_col_map() -> RangeGateCols<usize> {
159 let indices_arr = indices_arr::<NUM_RANGE_GATE_COLS>();
160 unsafe { transmute::<[usize; NUM_RANGE_GATE_COLS], RangeGateCols<usize>>(indices_arr) }
163}