1use std::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 utils::{and, assert_array_eq, not},
5 ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
6};
7use openvm_recursion_circuit_derive::AlignedBorrow;
8use openvm_stark_backend::{
9 interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
10};
11use openvm_stark_sdk::config::baby_bear_poseidon2::D_EF;
12use p3_air::{Air, AirBuilder, BaseAir};
13use p3_field::{
14 extension::BinomiallyExtendable, PrimeCharacteristicRing, PrimeField32, TwoAdicField,
15};
16use p3_matrix::Matrix;
17
18use crate::{
19 batch_constraint::bus::{EqNegInternalBus, EqNegInternalMessage},
20 bus::{
21 EqNegBaseRandBus, EqNegBaseRandMessage, EqNegResultBus, EqNegResultMessage, SelUniBus,
22 SelUniBusMessage,
23 },
24 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
25 utils::{
26 base_to_ext, ext_field_add, ext_field_add_scalar, ext_field_multiply,
27 ext_field_multiply_scalar, ext_field_subtract,
28 },
29};
30
31#[repr(C)]
32#[derive(AlignedBorrow, Debug, StructReflection)]
33pub struct EqNegCols<F> {
34 pub proof_idx: F,
36 pub is_valid: F,
37 pub is_first: F,
38 pub is_last: F,
39
40 pub neg_hypercube: F,
42 pub neg_hypercube_nz_inv: F,
43 pub row_index: F,
44 pub is_first_hypercube: F,
45 pub is_last_hypercube: F,
46
47 pub u_pow: [F; D_EF],
50 pub r_pow: [F; D_EF],
51 pub r_omega_pow: [F; D_EF],
52
53 pub prod_u_r: [F; D_EF],
55 pub prod_u_r_omega: [F; D_EF],
56 pub prod_1_r: [F; D_EF],
57 pub prod_1_r_omega: [F; D_EF],
58 pub sel_first_count: F,
59 pub sel_last_trans_count: F,
60 pub one_half_pow: F,
61}
62
63#[derive(ColumnsAir)]
64#[columns_via(EqNegCols<u8>)]
65pub struct EqNegAir {
66 pub result_bus: EqNegResultBus,
67 pub base_rand_bus: EqNegBaseRandBus,
68 pub internal_bus: EqNegInternalBus,
69 pub sel_uni_bus: SelUniBus,
70 pub l_skip: usize,
71}
72
73impl<F> BaseAirWithPublicValues<F> for EqNegAir {}
74impl<F> PartitionedBaseAir<F> for EqNegAir {}
75impl<F> BaseAir<F> for EqNegAir {
76 fn width(&self) -> usize {
77 EqNegCols::<F>::width()
78 }
79}
80
81impl<AB: AirBuilder + InteractionBuilder> Air<AB> for EqNegAir
82where
83 AB::F: PrimeField32 + TwoAdicField,
84 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
85{
86 fn eval(&self, builder: &mut AB) {
87 let main = builder.main();
88 let (local, next) = (
89 main.row_slice(0).expect("window should have two elements"),
90 main.row_slice(1).expect("window should have two elements"),
91 );
92
93 let local: &EqNegCols<AB::Var> = (*local).borrow();
94 let next: &EqNegCols<AB::Var> = (*next).borrow();
95
96 NestedForLoopSubAir::<3> {}.eval(
97 builder,
98 (
99 NestedForLoopIoCols {
100 is_enabled: local.is_valid.into(),
101 counter: [
102 local.proof_idx.into(),
103 local.neg_hypercube.into(),
104 local.row_index.into(),
105 ],
106 is_first: [
107 local.is_first.into(),
108 local.is_first_hypercube.into(),
109 local.is_valid.into(),
110 ],
111 },
112 NestedForLoopIoCols {
113 is_enabled: next.is_valid.into(),
114 counter: [
115 next.proof_idx.into(),
116 next.neg_hypercube.into(),
117 next.row_index.into(),
118 ],
119 is_first: [
120 next.is_first.into(),
121 next.is_first_hypercube.into(),
122 next.is_valid.into(),
123 ],
124 },
125 ),
126 );
127
128 type LoopSubAir = NestedForLoopSubAir<3>;
132 builder.assert_eq(
133 local.is_last,
134 LoopSubAir::local_is_last(local.is_valid, next.is_valid, next.is_first),
135 );
136 builder.assert_eq(
137 local.is_last_hypercube,
138 LoopSubAir::local_is_last(local.is_valid, next.is_valid, next.is_first_hypercube),
139 );
140 builder.when(local.is_last).assert_one(local.is_valid);
141 builder
142 .when(local.is_last_hypercube)
143 .assert_one(local.is_valid);
144
145 builder
151 .when(local.is_first)
152 .assert_zero(local.neg_hypercube);
153 builder
154 .when(local.is_last)
155 .assert_eq(local.neg_hypercube, AB::F::from_usize(self.l_skip - 1));
156 builder
157 .when(local.is_first_hypercube)
158 .assert_zero(local.row_index);
159
160 builder.when(local.is_last_hypercube).assert_eq(
161 local.row_index,
162 AB::Expr::from_usize(self.l_skip) - local.neg_hypercube,
163 );
164
165 builder
166 .when(local.is_valid - local.is_last_hypercube)
167 .assert_one(next.row_index - local.row_index);
168 builder
169 .when(local.is_valid - local.is_last_hypercube)
170 .assert_eq(next.neg_hypercube, local.neg_hypercube);
171
172 let initial_omega = AB::F::two_adic_generator(self.l_skip);
178 assert_array_eq(
179 &mut builder.when(local.is_first),
180 local.r_omega_pow,
181 ext_field_multiply_scalar(local.r_pow, initial_omega),
182 );
183
184 self.base_rand_bus.receive(
185 builder,
186 local.proof_idx,
187 EqNegBaseRandMessage {
188 u: local.u_pow,
189 r: local.r_pow,
190 },
191 local.is_first,
192 );
193
194 self.internal_bus.send(
195 builder,
196 local.proof_idx,
197 EqNegInternalMessage {
198 neg_n: local.neg_hypercube + AB::F::ONE,
199 u: local.u_pow.map(Into::into),
200 r: ext_field_multiply(local.r_pow, local.r_pow),
201 r_omega: ext_field_multiply(local.r_omega_pow, local.r_omega_pow),
202 },
203 and(local.is_first_hypercube, not(next.is_last)),
204 );
205
206 self.internal_bus.receive(
207 builder,
208 local.proof_idx,
209 EqNegInternalMessage {
210 neg_n: local.neg_hypercube,
211 u: local.u_pow,
212 r: local.r_pow,
213 r_omega: local.r_omega_pow,
214 },
215 and(local.is_first_hypercube, not(local.is_first)),
216 );
217
218 builder
220 .when(local.is_first_hypercube)
221 .assert_one(local.one_half_pow * AB::Expr::TWO);
222 builder
223 .when(local.is_valid - local.is_last_hypercube)
224 .assert_eq(next.one_half_pow * AB::Expr::TWO, local.one_half_pow);
225
226 assert_array_eq(
230 &mut builder.when(local.is_first_hypercube),
231 local.prod_u_r,
232 ext_field_multiply(local.u_pow, ext_field_add(local.u_pow, local.r_pow)),
233 );
234
235 assert_array_eq(
236 &mut builder.when(local.is_first_hypercube),
237 local.prod_1_r,
238 ext_field_add(local.r_pow, base_to_ext::<AB::Expr>(AB::F::ONE)),
239 );
240
241 assert_array_eq(
242 &mut builder.when(local.is_valid - local.is_last_hypercube),
243 ext_field_multiply(local.u_pow, local.u_pow),
244 next.u_pow,
245 );
246
247 assert_array_eq(
248 &mut builder.when(local.is_valid - local.is_last_hypercube),
249 ext_field_multiply(local.r_pow, local.r_pow),
250 next.r_pow,
251 );
252
253 assert_array_eq(
254 &mut builder.when(local.is_valid - local.is_last_hypercube),
255 ext_field_multiply(local.prod_u_r, ext_field_add(next.u_pow, next.r_pow)),
256 next.prod_u_r,
257 );
258
259 assert_array_eq(
260 &mut builder.when(local.is_valid - local.is_last_hypercube),
261 ext_field_multiply(
262 local.prod_1_r,
263 ext_field_add(next.r_pow, base_to_ext::<AB::Expr>(AB::F::ONE)),
264 ),
265 next.prod_1_r,
266 );
267
268 assert_array_eq(
272 &mut builder.when(local.is_first_hypercube),
273 local.prod_u_r_omega,
274 ext_field_multiply(local.u_pow, ext_field_add(local.u_pow, local.r_omega_pow)),
275 );
276 assert_array_eq(
277 &mut builder.when(local.is_first_hypercube),
278 local.prod_1_r_omega,
279 ext_field_add_scalar(local.r_omega_pow, AB::Expr::ONE),
280 );
281
282 assert_array_eq(
283 &mut builder.when(local.is_valid - local.is_last_hypercube),
284 ext_field_multiply(local.r_omega_pow, local.r_omega_pow),
285 next.r_omega_pow,
286 );
287
288 assert_array_eq(
289 &mut builder.when(local.is_valid - local.is_last_hypercube),
290 ext_field_multiply(
291 local.prod_u_r_omega,
292 ext_field_add(next.u_pow, next.r_omega_pow),
293 ),
294 next.prod_u_r_omega,
295 );
296 assert_array_eq(
297 &mut builder.when(local.is_valid - local.is_last_hypercube),
298 ext_field_multiply(
299 local.prod_1_r_omega,
300 ext_field_add_scalar(next.r_omega_pow, AB::Expr::ONE),
301 ),
302 next.prod_1_r_omega,
303 );
304
305 self.sel_uni_bus.add_key_with_lookups(
306 builder,
307 local.proof_idx,
308 SelUniBusMessage {
309 n: -local.neg_hypercube.into(),
310 is_first: AB::Expr::ONE,
311 value: local.prod_1_r.map(|x| x * local.one_half_pow),
312 },
313 next.is_last_hypercube * next.sel_first_count,
314 );
315 self.sel_uni_bus.add_key_with_lookups(
316 builder,
317 local.proof_idx,
318 SelUniBusMessage {
319 n: -local.neg_hypercube.into(),
320 is_first: AB::Expr::ZERO,
321 value: local.prod_1_r_omega.map(|x| x * local.one_half_pow),
322 },
323 next.is_last_hypercube * next.sel_last_trans_count,
324 );
325
326 self.sel_uni_bus.add_key_with_lookups(
329 builder,
330 local.proof_idx,
331 SelUniBusMessage {
332 n: -AB::Expr::from_usize(self.l_skip),
333 is_first: AB::Expr::ONE,
334 value: [
335 AB::Expr::ONE,
336 AB::Expr::ZERO,
337 AB::Expr::ZERO,
338 AB::Expr::ZERO,
339 ],
340 },
341 local.is_first * local.sel_first_count,
342 );
343 self.sel_uni_bus.add_key_with_lookups(
344 builder,
345 local.proof_idx,
346 SelUniBusMessage {
347 n: -AB::Expr::from_usize(self.l_skip),
348 is_first: AB::Expr::ZERO,
349 value: [
350 AB::Expr::ONE,
351 AB::Expr::ZERO,
352 AB::Expr::ZERO,
353 AB::Expr::ZERO,
354 ],
355 },
356 local.is_first * local.sel_last_trans_count,
357 );
358
359 let eq = ext_field_add_scalar::<AB::Expr>(
364 ext_field_subtract(local.prod_u_r, next.u_pow),
365 AB::F::ONE,
366 );
367 let k_rot = ext_field_add_scalar::<AB::Expr>(
368 ext_field_subtract(local.prod_u_r_omega, next.u_pow),
369 AB::F::ONE,
370 );
371
372 let is_neg = local.neg_hypercube * local.neg_hypercube_nz_inv;
373 builder.when(local.neg_hypercube).assert_one(is_neg.clone());
374 self.result_bus.send(
375 builder,
376 local.proof_idx,
377 EqNegResultMessage {
378 n: AB::Expr::ZERO - local.neg_hypercube,
379 eq,
380 k_rot,
381 },
382 is_neg * next.is_last_hypercube,
383 );
384 }
385}