1use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use alloc::{format, vec};
10
11use hashbrown::HashMap;
12use p3_air::{AirBuilder, PermutationAirBuilder, RowWindow};
13use p3_field::Field;
14use p3_matrix::Matrix;
15use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
16use p3_matrix::stack::VerticalPair;
17
18use crate::lookup_traits::{Kind, Lookup, symbolic_to_expr};
19
20pub struct LookupDebugInstance<'a, F: Field> {
22 pub main_trace: &'a RowMajorMatrix<F>,
24 pub preprocessed_trace: &'a Option<RowMajorMatrix<F>>,
26 pub public_values: &'a [F],
28 pub lookups: &'a [Lookup<F>],
30 pub permutation_challenges: &'a [F],
32}
33
34#[allow(unused)] #[derive(Clone, Debug)]
37struct Location {
38 instance: usize,
39 lookup: usize,
40 row: usize,
41}
42
43#[derive(Default)]
45struct MultiSet<F: Field> {
46 entries: HashMap<Vec<F>, (F, Vec<Location>)>,
48}
49
50impl<F: Field> MultiSet<F> {
51 fn add(&mut self, key: Vec<F>, multiplicity: F, location: Location) {
54 if multiplicity.is_zero() {
55 return;
56 }
57
58 self.entries
59 .entry(key)
60 .and_modify(|(total, locations)| {
61 *total += multiplicity;
62 locations.push(location.clone());
63 })
64 .or_insert_with(|| (multiplicity, vec![location]));
65 }
66
67 fn assert_empty(&self, label: &str) {
73 let mut entries: Vec<_> = self.entries.iter().collect();
74 entries.sort_by(|(a, _), (b, _)| {
75 let a_str: Vec<String> = a.iter().map(|v| v.to_string()).collect();
76 let b_str: Vec<String> = b.iter().map(|v| v.to_string()).collect();
77 a_str.cmp(&b_str)
78 });
79 for (key, (total, locations)) in entries {
80 if !total.is_zero() {
81 let rendered_key: Vec<String> = key.iter().map(|v| v.to_string()).collect();
82 panic!(
83 "Lookup mismatch ({label}): tuple {:?} has net multiplicity {:?}. Locations: {:?}",
84 rendered_key, total, locations
85 );
86 }
87 }
88 }
89}
90
91pub fn check_lookups<F: Field>(instances: &[LookupDebugInstance<'_, F>]) {
98 for (instance_idx, instance) in instances.iter().enumerate() {
100 for (lookup_idx, lookup) in instance.lookups.iter().enumerate() {
101 if matches!(lookup.kind, Kind::Local) {
102 let mut multiset = MultiSet::default();
103 accumulate_lookup(instance_idx, lookup_idx, instance, lookup, &mut multiset);
104 multiset.assert_empty(&format!(
105 "instance {instance_idx} local lookup {lookup_idx}"
106 ));
107 }
108 }
109 }
110
111 let mut global_sets: Vec<(String, MultiSet<F>)> = Vec::new();
115 let mut global_index: HashMap<String, usize> = HashMap::new();
116
117 for (instance_idx, instance) in instances.iter().enumerate() {
118 for (lookup_idx, lookup) in instance.lookups.iter().enumerate() {
119 if let Kind::Global(name) = &lookup.kind {
120 let idx = *global_index.entry(name.clone()).or_insert_with(|| {
121 global_sets.push((name.clone(), MultiSet::default()));
122 global_sets.len() - 1
123 });
124
125 accumulate_lookup(
126 instance_idx,
127 lookup_idx,
128 instance,
129 lookup,
130 &mut global_sets[idx].1,
131 );
132 }
133 }
134 }
135
136 for (name, multiset) in global_sets {
137 multiset.assert_empty(&format!("global lookup '{name}'"));
138 }
139}
140
141fn accumulate_lookup<F: Field>(
142 instance_idx: usize,
143 lookup_idx: usize,
144 instance: &LookupDebugInstance<'_, F>,
145 lookup: &Lookup<F>,
146 multiset: &mut MultiSet<F>,
147) {
148 let height = instance.main_trace.height();
149
150 for row in 0..height {
151 let local_main = instance.main_trace.row_slice(row).unwrap();
152 let next_main = instance.main_trace.row_slice((row + 1) % height).unwrap();
153 let main_rows = VerticalPair::new(
154 RowMajorMatrixView::new_row(&*local_main),
155 RowMajorMatrixView::new_row(&*next_main),
156 );
157
158 let preprocessed_rows_data = instance.preprocessed_trace.as_ref().map(|prep| {
159 (
160 prep.row_slice(row).unwrap(),
161 prep.row_slice((row + 1) % height).unwrap(),
162 )
163 });
164 let preprocessed_rows = match preprocessed_rows_data.as_ref() {
165 Some((prep_local, prep_next)) => VerticalPair::new(
166 RowMajorMatrixView::new_row(&**prep_local),
167 RowMajorMatrixView::new_row(&**prep_next),
168 ),
169 None => VerticalPair::new(
170 RowMajorMatrixView::new(&[], 0),
171 RowMajorMatrixView::new(&[], 0),
172 ),
173 };
174
175 let builder = MiniLookupBuilder {
176 main: main_rows,
177 preprocessed: RowWindow::from_two_rows(
178 preprocessed_rows.top.values,
179 preprocessed_rows.bottom.values,
180 ),
181 public_values: instance.public_values,
182 permutation_challenges: instance.permutation_challenges,
183 row,
184 height,
185 };
186
187 for (tuple_idx, elements) in lookup.element_exprs.iter().enumerate() {
188 let key = elements
189 .iter()
190 .map(|expr| symbolic_to_expr(&builder, expr))
191 .collect::<Vec<_>>();
192
193 let multiplicity = symbolic_to_expr(&builder, &lookup.multiplicities_exprs[tuple_idx]);
194
195 multiset.add(
196 key,
197 multiplicity,
198 Location {
199 instance: instance_idx,
200 lookup: lookup_idx,
201 row,
202 },
203 );
204 }
205 }
206}
207
208struct MiniLookupBuilder<'a, F: Field> {
209 main: VerticalPair<RowMajorMatrixView<'a, F>, RowMajorMatrixView<'a, F>>,
210 preprocessed: RowWindow<'a, F>,
211 public_values: &'a [F],
212 permutation_challenges: &'a [F],
213 row: usize,
214 height: usize,
215}
216
217impl<'a, F: Field> AirBuilder for MiniLookupBuilder<'a, F> {
218 type F = F;
219 type Expr = F;
220 type Var = F;
221 type PreprocessedWindow = RowWindow<'a, F>;
222 type MainWindow = RowWindow<'a, F>;
223 type PublicVar = F;
224
225 fn main(&self) -> Self::MainWindow {
226 RowWindow::from_two_rows(self.main.top.values, self.main.bottom.values)
227 }
228
229 fn preprocessed(&self) -> &Self::PreprocessedWindow {
230 &self.preprocessed
231 }
232
233 fn is_first_row(&self) -> Self::Expr {
234 F::from_bool(self.row == 0)
235 }
236
237 fn is_last_row(&self) -> Self::Expr {
238 F::from_bool(self.row + 1 == self.height)
239 }
240
241 fn is_transition_window(&self, size: usize) -> Self::Expr {
242 assert!(size <= 2, "only two-row windows are supported, got {size}");
243 F::from_bool(self.row + 1 < self.height)
244 }
245
246 fn assert_zero<I: Into<Self::Expr>>(&mut self, _x: I) {}
247
248 fn public_values(&self) -> &[Self::PublicVar] {
249 self.public_values
250 }
251}
252
253impl<'a, F: Field> p3_air::ExtensionBuilder for MiniLookupBuilder<'a, F> {
254 type EF = F;
255 type ExprEF = F;
256 type VarEF = F;
257
258 fn assert_zero_ext<I: Into<Self::ExprEF>>(&mut self, _x: I) {}
259}
260
261impl<'a, F: Field> PermutationAirBuilder for MiniLookupBuilder<'a, F> {
262 type MP = RowWindow<'a, F>;
263 type RandomVar = F;
264
265 type PermutationVar = F;
266
267 fn permutation(&self) -> Self::MP {
268 RowWindow::from_two_rows(&[], &[])
270 }
271
272 fn permutation_randomness(&self) -> &[Self::RandomVar] {
273 self.permutation_challenges
274 }
275
276 fn permutation_values(&self) -> &[Self::PermutationVar] {
277 &[]
278 }
279}