openvm_circuit_primitives/range_tuple/
mod.rs1use std::{
8 borrow::Borrow,
9 sync::{atomic::AtomicU32, Arc},
10};
11
12use openvm_cpu_backend::CpuBackend;
13use openvm_stark_backend::{
14 interaction::InteractionBuilder,
15 p3_air::{Air, AirBuilder, BaseAir, PairBuilder},
16 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
17 p3_matrix::{dense::RowMajorMatrix, Matrix},
18 prover::AirProvingContext,
19 BaseAirWithPublicValues, PartitionedBaseAir, StarkProtocolConfig, Val,
20};
21
22use crate::{Chip, ColumnsAir};
23
24mod bus;
25pub use bus::*;
26
27#[cfg(feature = "cuda")]
28mod cuda;
29#[cfg(feature = "cuda")]
30pub use cuda::*;
31
32#[cfg(test)]
33pub mod tests;
34
35#[derive(Copy, Clone)]
36pub struct RangeTupleColsRef<'a, T> {
37 pub tuple: &'a [T],
39 pub mult: &'a T,
41}
42
43impl<'a, T> RangeTupleColsRef<'a, T> {
44 fn from_slice<const N: usize>(slice: &'a [T]) -> Self {
45 let (tuple, rest) = slice.split_at(N);
46 Self {
47 tuple,
48 mult: &rest[0],
49 }
50 }
51}
52
53pub struct RangeTupleColsRefMut<'a, T> {
54 pub tuple: &'a mut [T],
56 pub mult: &'a mut T,
58}
59
60impl<'a, T> RangeTupleColsRefMut<'a, T> {
61 fn from_slice_mut<const N: usize>(slice: &'a mut [T]) -> Self {
62 let (tuple, rest) = slice.split_at_mut(N);
63 Self {
64 tuple,
65 mult: &mut rest[0],
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug)]
71pub struct RangeTupleCheckerAir<const N: usize> {
72 pub bus: RangeTupleCheckerBus<N>,
73}
74
75impl<const N: usize> RangeTupleCheckerAir<N> {
76 pub fn height(&self) -> u32 {
77 self.bus.sizes.iter().product()
78 }
79}
80impl<F: Field, const N: usize> BaseAirWithPublicValues<F> for RangeTupleCheckerAir<N> {}
81impl<F: Field, const N: usize> PartitionedBaseAir<F> for RangeTupleCheckerAir<N> {}
82impl<const N: usize> ColumnsAir for RangeTupleCheckerAir<N> {
83 fn columns(&self) -> Option<Vec<String>> {
85 Some(
86 (0..N)
87 .map(|i| format!("tuple[{}]", i))
88 .chain(std::iter::once("mult".to_string()))
89 .collect(),
90 )
91 }
92}
93
94impl<F: Field, const N: usize> BaseAir<F> for RangeTupleCheckerAir<N> {
95 fn width(&self) -> usize {
96 N + 1
97 }
98}
99
100impl<AB: InteractionBuilder + PairBuilder, const N: usize> Air<AB> for RangeTupleCheckerAir<N> {
102 fn eval(&self, builder: &mut AB) {
103 let main = builder.main();
104 let (local, next) = (
105 main.row_slice(0).expect("window should have two elements"),
106 main.row_slice(1).expect("window should have two elements"),
107 );
108 let local = RangeTupleColsRef::from_slice::<N>((*local).borrow());
109 let next = RangeTupleColsRef::from_slice::<N>((*next).borrow());
110
111 for i in 0..N {
114 builder.when_first_row().assert_zero(local.tuple[i]);
115 builder
116 .when_last_row()
117 .assert_eq(local.tuple[i], AB::F::from_u32(self.bus.sizes[i] - 1));
118 }
119
120 builder
122 .when_transition()
123 .assert_bool(next.tuple[0] - local.tuple[0]);
124 for i in 1..N - 1 {
127 builder
128 .when_ne(next.tuple[i] - local.tuple[i], AB::Expr::ZERO)
129 .when_ne(next.tuple[i] - local.tuple[i], AB::Expr::ONE)
130 .assert_eq(local.tuple[i], AB::F::from_u32(self.bus.sizes[i] - 1));
131 builder
132 .when_ne(next.tuple[i] - local.tuple[i], AB::Expr::ZERO)
133 .when_ne(next.tuple[i] - local.tuple[i], AB::Expr::ONE)
134 .assert_eq(next.tuple[i], AB::Expr::ZERO);
135 }
136 builder
138 .when_ne(next.tuple[N - 1] - local.tuple[N - 1], AB::Expr::ONE)
139 .assert_eq(
140 local.tuple[N - 1],
141 AB::F::from_u32(self.bus.sizes[N - 1] - 1),
142 );
143 builder
144 .when_ne(next.tuple[N - 1] - local.tuple[N - 1], AB::Expr::ONE)
145 .assert_eq(next.tuple[N - 1], AB::Expr::ZERO);
146
147 for i in 0..N - 1 {
150 let x = next.tuple[i] - local.tuple[i];
151 let y = next.tuple[i + 1] - local.tuple[i + 1];
152 let a = -AB::F::from_u32(self.bus.sizes[i] - 1);
153 let b = -AB::F::from_u32(self.bus.sizes[i + 1] - 1);
154 builder.assert_zero(
156 y.clone() * (y.clone() - AB::Expr::ONE) * (-x.clone() * (a + AB::F::ONE) + a)
157 + x.clone() * x.clone() * (y.clone() * (b + b - AB::F::ONE) - b * b),
158 );
159 }
160
161 self.bus
162 .receive(local.tuple.to_vec())
163 .eval(builder, *local.mult);
164 }
165}
166
167#[derive(Debug)]
168pub struct RangeTupleCheckerChip<const N: usize> {
169 pub air: RangeTupleCheckerAir<N>,
170 pub count: Vec<Arc<AtomicU32>>,
171}
172
173pub type SharedRangeTupleCheckerChip<const N: usize> = Arc<RangeTupleCheckerChip<N>>;
174
175impl<const N: usize> RangeTupleCheckerChip<N> {
176 pub fn new(bus: RangeTupleCheckerBus<N>) -> Self {
177 assert!(N > 1, "RangeTupleChecker requires at least 2 dimensions");
178 assert!(
180 bus.sizes.iter().all(|&s| s > 1),
181 "RangeTupleChecker requires all sizes to be > 1 (size=1 dimensions break \
182 the carry coupling constraint)"
183 );
184 let range_max = bus.sizes.iter().product();
185 assert!(
186 range_max > 0 && (range_max & (range_max - 1)) == 0,
187 "RangeTupleChecker requires range_max ({}) to be a power of 2",
188 range_max
189 );
190 let count = (0..range_max)
191 .map(|_| Arc::new(AtomicU32::new(0)))
192 .collect();
193
194 Self {
195 air: RangeTupleCheckerAir { bus },
196 count,
197 }
198 }
199
200 pub fn bus(&self) -> &RangeTupleCheckerBus<N> {
201 &self.air.bus
202 }
203
204 pub fn sizes(&self) -> &[u32; N] {
205 &self.air.bus.sizes
206 }
207
208 pub fn add_count(&self, ids: &[u32]) {
209 let index = ids
210 .iter()
211 .zip(self.air.bus.sizes.iter())
212 .fold(0, |acc, (id, sz)| acc * sz + id) as usize;
213 assert!(
214 index < self.count.len(),
215 "range exceeded: {} >= {}",
216 index,
217 self.count.len()
218 );
219 let val_atomic = &self.count[index];
220 val_atomic.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
221 }
222
223 pub fn clear(&self) {
224 for val in &self.count {
225 val.store(0, std::sync::atomic::Ordering::Relaxed);
226 }
227 }
228
229 pub fn generate_trace<F: Field + PrimeField32>(&self) -> RowMajorMatrix<F> {
230 let mut rows = F::zero_vec(self.count.len() * (N + 1));
231
232 for (i, row) in rows.chunks_exact_mut(N + 1).enumerate() {
233 let cols = RangeTupleColsRefMut::from_slice_mut::<N>(row);
234 let mut tmp_idx = i as u32;
235 for j in (0..N).rev() {
236 cols.tuple[j] = F::from_u32(tmp_idx % self.air.bus.sizes[j]);
237 tmp_idx /= self.air.bus.sizes[j];
238 }
239 *cols.mult = F::from_u32(self.count[i].swap(0, std::sync::atomic::Ordering::Relaxed));
240 }
241
242 RowMajorMatrix::new(rows, N + 1)
243 }
244}
245
246impl<R, SC: StarkProtocolConfig, const N: usize> Chip<R, CpuBackend<SC>>
247 for RangeTupleCheckerChip<N>
248where
249 Val<SC>: PrimeField32,
250{
251 fn generate_proving_ctx(&self, _: R) -> AirProvingContext<CpuBackend<SC>> {
252 let trace_row_maj = self.generate_trace::<Val<SC>>();
253 AirProvingContext::simple_no_pis(trace_row_maj)
254 }
255
256 fn constant_trace_height(&self) -> Option<usize> {
257 Some(self.air.height() as usize)
258 }
259}