1use std::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 utils::{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::{extension::BinomiallyExtendable, PrimeCharacteristicRing};
14use p3_matrix::Matrix;
15
16use crate::{
17 batch_constraint::bus::{
18 BatchConstraintConductorBus, BatchConstraintConductorMessage,
19 BatchConstraintInnerMessageType, EqNOuterBus, EqNOuterMessage, ExpressionClaimBus,
20 ExpressionClaimMessage, SumcheckClaimBus, SumcheckClaimMessage,
21 },
22 bus::{ExpressionClaimNMaxBus, ExpressionClaimNMaxMessage, HyperdimBus, HyperdimBusMessage},
23 primitives::bus::{PowerCheckerBus, PowerCheckerBusMessage},
24 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
25 utils::{base_to_ext, ext_field_add, ext_field_multiply, ext_field_multiply_scalar},
26};
27
28#[derive(AlignedBorrow, Copy, Clone, Debug, StructReflection)]
54#[repr(C)]
55pub struct ExpressionClaimCols<T> {
56 pub is_valid: T,
58 pub is_first: T,
60 pub proof_idx: T,
61 pub group_idx: T,
63 pub is_first_in_group: T,
65
66 pub idx: T,
70 pub idx_parity: T,
73 pub trace_idx: T,
75 pub value: [T; D_EF],
79 pub eq_sharp_ns: [T; D_EF],
81
82 pub cur_sum: [T; D_EF],
84 pub mu: [T; D_EF],
85 pub multiplier: [T; D_EF],
86
87 pub n_abs: T,
89 pub n_abs_pow: T,
90 pub n_sign: T,
91 pub num_multilinear_sumcheck_rounds: T,
93}
94
95#[derive(ColumnsAir)]
96#[columns_via(ExpressionClaimCols<u8>)]
97pub struct ExpressionClaimAir {
98 pub expression_claim_n_max_bus: ExpressionClaimNMaxBus,
99 pub expr_claim_bus: ExpressionClaimBus,
100 pub mu_bus: BatchConstraintConductorBus,
101 pub sumcheck_claim_bus: SumcheckClaimBus,
102 pub eq_n_outer_bus: EqNOuterBus,
103 pub pow_checker_bus: PowerCheckerBus,
104 pub hyperdim_bus: HyperdimBus,
105}
106
107impl<F> BaseAirWithPublicValues<F> for ExpressionClaimAir {}
108impl<F> PartitionedBaseAir<F> for ExpressionClaimAir {}
109
110impl<F> BaseAir<F> for ExpressionClaimAir {
111 fn width(&self) -> usize {
112 ExpressionClaimCols::<F>::width()
113 }
114}
115
116impl<AB: AirBuilder + InteractionBuilder> Air<AB> for ExpressionClaimAir
117where
118 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
119{
120 fn eval(&self, builder: &mut AB) {
121 let main = builder.main();
122 let (local, next) = (
123 main.row_slice(0).expect("window should have two elements"),
124 main.row_slice(1).expect("window should have two elements"),
125 );
126
127 let local: &ExpressionClaimCols<AB::Var> = (*local).borrow();
128 let next: &ExpressionClaimCols<AB::Var> = (*next).borrow();
129
130 type LoopSubAir = NestedForLoopSubAir<4>;
136 let local_is_trace_start = local.is_valid - local.idx_parity;
137 let next_is_trace_start = next.is_valid - next.idx_parity;
138 LoopSubAir {}.eval(
139 builder,
140 (
141 NestedForLoopIoCols {
142 is_enabled: local.is_valid.into(),
143 counter: [
144 local.proof_idx.into(),
145 local.group_idx.into(),
146 local.trace_idx.into(),
147 local.idx_parity.into(),
148 ],
149 is_first: [
150 local.is_first.into(),
151 local.is_first_in_group.into(),
152 local_is_trace_start,
153 local.is_valid.into(),
154 ],
155 },
156 NestedForLoopIoCols {
157 is_enabled: next.is_valid.into(),
158 counter: [
159 next.proof_idx.into(),
160 next.group_idx.into(),
161 next.trace_idx.into(),
162 next.idx_parity.into(),
163 ],
164 is_first: [
165 next.is_first.into(),
166 next.is_first_in_group.into(),
167 next_is_trace_start,
168 next.is_valid.into(),
169 ],
170 },
171 ),
172 );
173
174 let is_interaction: AB::Expr = AB::Expr::ONE - local.group_idx.into();
177 let is_same_proof: AB::Expr = LoopSubAir::local_is_transition(next.is_valid, next.is_first);
179 let is_last_in_proof: AB::Expr =
181 LoopSubAir::local_is_last(local.is_valid, next.is_valid, next.is_first);
182
183 builder
186 .when(is_last_in_proof.clone())
187 .assert_one(local.group_idx);
188
189 builder.assert_bool(local.n_sign);
191 builder
193 .when(local.is_valid)
194 .when(is_interaction.clone())
195 .assert_eq(local.idx_parity + next.idx_parity, AB::Expr::ONE);
196 builder.when(local.idx_parity).assert_zero(local.group_idx);
198
199 builder.when(is_interaction.clone()).assert_eq(
202 local.idx,
203 local.trace_idx * AB::Expr::TWO + local.idx_parity,
204 );
205 builder
207 .when(local.group_idx)
208 .assert_eq(local.idx, local.trace_idx);
209
210 assert_array_eq(
212 &mut builder.when(is_same_proof.clone()),
213 next.mu,
214 local.mu.map(Into::into),
215 );
216
217 builder
222 .when(local.is_valid)
223 .when(is_interaction.clone())
224 .when(not(local.idx_parity))
225 .assert_eq(next.n_abs, local.n_abs);
226 builder
227 .when(local.is_valid)
228 .when(is_interaction.clone())
229 .when(not(local.idx_parity))
230 .assert_eq(next.n_sign, local.n_sign);
231
232 assert_array_eq(
235 &mut builder.when(is_same_proof),
236 local.cur_sum,
237 ext_field_add::<AB::Expr>(
238 ext_field_multiply::<AB::Expr>(local.value, local.multiplier),
239 ext_field_multiply::<AB::Expr>(next.cur_sum, local.mu),
240 ),
241 );
242 assert_array_eq(
244 &mut builder.when(is_last_in_proof),
245 local.cur_sum,
246 ext_field_multiply::<AB::Expr>(local.value, local.multiplier),
247 );
248
249 assert_array_eq(
251 &mut builder.when(local.group_idx).when(local.is_valid),
252 local.multiplier,
253 base_to_ext::<AB::Expr>(AB::Expr::ONE),
254 );
255
256 assert_array_eq(
258 &mut builder.when(local.n_sign * (is_interaction.clone() - local.idx_parity)),
259 ext_field_multiply_scalar::<AB::Expr>(local.multiplier, local.n_abs_pow),
260 local.eq_sharp_ns,
261 );
262 assert_array_eq(
264 &mut builder.when(is_interaction.clone() * (AB::Expr::ONE - local.n_sign)),
265 local.multiplier,
266 local.eq_sharp_ns,
267 );
268 assert_array_eq(
270 &mut builder.when(local.idx_parity),
271 local.multiplier,
272 local.eq_sharp_ns,
273 );
274
275 self.expr_claim_bus.receive(
277 builder,
278 local.proof_idx,
279 ExpressionClaimMessage {
280 is_interaction: is_interaction.clone(),
281 idx: local.idx.into(),
282 value: local.value.map(Into::into),
283 },
284 local.is_valid,
285 );
286
287 self.mu_bus.lookup_key(
288 builder,
289 local.proof_idx,
290 BatchConstraintConductorMessage {
291 msg_type: BatchConstraintInnerMessageType::Mu.to_field(),
292 idx: AB::Expr::ZERO,
293 value: local.mu.map(Into::into),
294 },
295 local.is_first * local.is_valid,
296 );
297
298 self.expression_claim_n_max_bus.receive(
300 builder,
301 local.proof_idx,
302 ExpressionClaimNMaxMessage {
303 n_max: local.num_multilinear_sumcheck_rounds,
304 },
305 local.is_first * local.is_valid,
306 );
307
308 self.sumcheck_claim_bus.receive(
309 builder,
310 local.proof_idx,
311 SumcheckClaimMessage::<AB::Expr> {
312 round: local.num_multilinear_sumcheck_rounds.into(),
313 value: local.cur_sum.map(Into::into),
314 },
315 local.is_first * local.is_valid,
316 );
317
318 self.hyperdim_bus.lookup_key(
319 builder,
320 local.proof_idx,
321 HyperdimBusMessage {
322 sort_idx: local.trace_idx.into(),
323 n_abs: local.n_abs.into(),
324 n_sign_bit: local.n_sign.into(),
325 },
326 local.is_valid * (is_interaction.clone() - local.idx_parity),
327 );
328
329 self.eq_n_outer_bus.lookup_key(
330 builder,
331 local.proof_idx,
332 EqNOuterMessage {
333 is_sharp: AB::Expr::ONE,
334 n: local.n_abs * (AB::Expr::ONE - local.n_sign),
335 value: local.eq_sharp_ns.map(Into::into),
336 },
337 local.is_valid * is_interaction.clone(),
338 );
339
340 self.pow_checker_bus.lookup_key(
341 builder,
342 PowerCheckerBusMessage {
343 log: local.n_abs.into(),
344 exp: local.n_abs_pow.into(),
345 },
346 local.is_valid * is_interaction,
347 );
348 }
349}