openvm_recursion_circuit/subairs/nested_for_loop/
air.rs1use openvm_circuit_primitives::{StructReflection, StructReflectionHelper, SubAir};
2use openvm_recursion_circuit_derive::AlignedBorrow;
3use p3_air::AirBuilder;
4use p3_field::PrimeCharacteristicRing;
5
6#[derive(Default)]
14pub struct NestedForLoopSubAir<const DEPTH_MINUS_ONE: usize>;
15
16#[repr(C)]
17#[derive(AlignedBorrow, StructReflection, Copy, Clone, Debug)]
18pub struct NestedForLoopIoCols<T, const DEPTH_MINUS_ONE: usize> {
19 pub is_enabled: T,
21 pub counter: [T; DEPTH_MINUS_ONE],
25 pub is_first: [T; DEPTH_MINUS_ONE],
29}
30
31impl<T, const DEPTH_MINUS_ONE: usize> NestedForLoopIoCols<T, DEPTH_MINUS_ONE> {
32 pub fn map_into<S>(self) -> NestedForLoopIoCols<S, DEPTH_MINUS_ONE>
33 where
34 T: Into<S>,
35 {
36 NestedForLoopIoCols {
37 is_enabled: self.is_enabled.into(),
38 counter: self.counter.map(Into::into),
39 is_first: self.is_first.map(Into::into),
40 }
41 }
42}
43
44impl<AB: AirBuilder, const DEPTH_MINUS_ONE: usize> SubAir<AB>
45 for NestedForLoopSubAir<DEPTH_MINUS_ONE>
46{
47 type AirContext<'a>
48 = (
49 NestedForLoopIoCols<AB::Expr, DEPTH_MINUS_ONE>,
50 NestedForLoopIoCols<AB::Expr, DEPTH_MINUS_ONE>,
51 )
52 where
53 AB: 'a,
54 AB::Var: 'a,
55 AB::Expr: 'a;
56
57 fn eval<'a>(&'a self, builder: &'a mut AB, ctx: Self::AirContext<'a>)
58 where
59 AB::Var: 'a,
60 AB::Expr: 'a,
61 {
62 let (local_io, next_io) = ctx;
63
64 builder.assert_bool(local_io.is_enabled.clone());
66 builder
67 .when_transition()
68 .when_ne(local_io.is_enabled.clone(), AB::Expr::ONE)
69 .assert_zero(next_io.is_enabled.clone());
70
71 for level in 0..DEPTH_MINUS_ONE {
72 let counter_diff = next_io.counter[level].clone() - local_io.counter[level].clone();
73 let local_is_first = local_io.is_first[level].clone();
74 let next_is_first = next_io.is_first[level].clone();
75
76 builder.assert_bool(local_is_first.clone());
77 builder.assert_bool(next_is_first.clone());
78 builder
79 .when(local_io.is_first[level].clone())
80 .assert_one(local_io.is_enabled.clone());
81
82 let mut builder_when_first_row = if level == 0 {
84 builder.when_first_row()
85 } else {
86 let parent_level = level - 1;
87 let parent_is_first = local_io.is_first[parent_level].clone();
88 builder.when(parent_is_first)
89 };
90 self.eval_first_row(&mut builder_when_first_row, &local_io, local_is_first);
91 builder_when_first_row.assert_zero(local_io.counter[level].clone());
92
93 let mut builder_when_transition = if level == 0 {
95 builder.when_transition()
96 } else {
97 let parent_level = level - 1;
98 let parent_next_is_first = next_io.is_first[parent_level].clone();
99 let parent_is_transition: AB::Expr =
100 Self::local_is_transition(next_io.is_enabled.clone(), parent_next_is_first);
101
102 builder.when(parent_is_transition)
103 };
104
105 self.eval_transition(
106 &mut builder_when_transition,
107 &local_io,
108 &next_io,
109 next_is_first.clone(),
110 counter_diff.clone(),
111 );
112
113 builder
115 .when(next_io.is_enabled.clone())
116 .when(counter_diff)
117 .assert_one(next_is_first.clone());
118 }
119 }
120}
121
122impl<const DEPTH_MINUS_ONE: usize> NestedForLoopSubAir<DEPTH_MINUS_ONE> {
123 fn eval_first_row<AB: AirBuilder>(
127 &self,
128 builder_first_row: &mut AB,
129 local_io: &NestedForLoopIoCols<AB::Expr, DEPTH_MINUS_ONE>,
130 local_is_first: AB::Expr,
131 ) {
132 builder_first_row
133 .when(local_io.is_enabled.clone())
134 .assert_one(local_is_first);
135 }
136
137 fn eval_transition<AB: AirBuilder>(
143 &self,
144 builder_transition: &mut AB,
145 local_io: &NestedForLoopIoCols<AB::Expr, DEPTH_MINUS_ONE>,
146 next_io: &NestedForLoopIoCols<AB::Expr, DEPTH_MINUS_ONE>,
147 next_is_first: AB::Expr,
148 counter_diff: AB::Expr,
149 ) {
150 builder_transition
152 .when(next_io.is_enabled.clone())
153 .assert_bool(counter_diff.clone());
154
155 builder_transition
157 .when(local_io.is_enabled.clone())
158 .when_ne(counter_diff, AB::Expr::ONE)
159 .assert_zero(next_is_first);
160 }
161
162 pub fn local_is_transition<FA>(
168 next_is_enabled: impl Into<FA>,
169 next_is_first: impl Into<FA>,
170 ) -> FA
171 where
172 FA: PrimeCharacteristicRing,
173 {
174 next_is_enabled.into() - next_is_first.into()
175 }
176
177 pub fn local_is_last<FA>(
184 local_is_enabled: impl Into<FA>,
185 next_is_enabled: impl Into<FA>,
186 next_is_first: impl Into<FA>,
187 ) -> FA
188 where
189 FA: PrimeCharacteristicRing,
190 {
191 local_is_enabled.into() - next_is_enabled.into() + next_is_first.into()
192 }
193}