1use crate::{
2 circuits::{
3 domains::EvaluationDomains,
4 gate::{CircuitGate, CurrOrNext, GateType},
5 lookup::{
6 index::LookupSelectors,
7 tables::{
8 combine_table_entry, get_table, GateLookupTable, LookupTable, RANGE_CHECK_TABLE_ID,
9 XOR_TABLE_ID,
10 },
11 },
12 },
13 collections::BTreeSet,
14};
15use alloc::{vec, vec::Vec};
16use ark_ff::{Field, One, PrimeField, Zero};
17use ark_poly::{EvaluationDomain, Evaluations as E, Radix2EvaluationDomain as D};
18use core::ops::{Mul, Neg};
19use o1_utils::field_helpers::i32_to_field;
20use serde::{Deserialize, Serialize};
21use strum_macros::EnumIter;
22
23type Evaluations<Field> = E<Field, D<Field>>;
24
25fn max_lookups_per_row(kinds: LookupPatterns) -> usize {
39 kinds
40 .into_iter()
41 .fold(0, |acc, x| core::cmp::max(x.max_lookups_per_row(), acc))
42}
43
44#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
52#[cfg_attr(
53 feature = "ocaml_types",
54 derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)
55)]
56#[cfg_attr(feature = "wasm_types", wasm_bindgen::prelude::wasm_bindgen)]
57pub struct LookupPatterns {
58 pub xor: bool,
60 pub lookup: bool,
62 pub range_check: bool,
64 pub foreign_field_mul: bool,
66}
67
68impl IntoIterator for LookupPatterns {
69 type Item = LookupPattern;
70 type IntoIter = alloc::vec::IntoIter<Self::Item>;
71
72 fn into_iter(self) -> Self::IntoIter {
73 let LookupPatterns {
75 xor,
76 lookup,
77 range_check,
78 foreign_field_mul,
79 } = self;
80
81 let mut patterns = Vec::with_capacity(5);
82
83 if xor {
84 patterns.push(LookupPattern::Xor)
85 }
86 if lookup {
87 patterns.push(LookupPattern::Lookup)
88 }
89 if range_check {
90 patterns.push(LookupPattern::RangeCheck)
91 }
92 if foreign_field_mul {
93 patterns.push(LookupPattern::ForeignFieldMul)
94 }
95 patterns.into_iter()
96 }
97}
98
99impl core::ops::Index<LookupPattern> for LookupPatterns {
100 type Output = bool;
101
102 fn index(&self, index: LookupPattern) -> &Self::Output {
103 match index {
104 LookupPattern::Xor => &self.xor,
105 LookupPattern::Lookup => &self.lookup,
106 LookupPattern::RangeCheck => &self.range_check,
107 LookupPattern::ForeignFieldMul => &self.foreign_field_mul,
108 }
109 }
110}
111
112impl core::ops::IndexMut<LookupPattern> for LookupPatterns {
113 fn index_mut(&mut self, index: LookupPattern) -> &mut Self::Output {
114 match index {
115 LookupPattern::Xor => &mut self.xor,
116 LookupPattern::Lookup => &mut self.lookup,
117 LookupPattern::RangeCheck => &mut self.range_check,
118 LookupPattern::ForeignFieldMul => &mut self.foreign_field_mul,
119 }
120 }
121}
122
123impl LookupPatterns {
124 pub fn from_gates<F: PrimeField>(gates: &[CircuitGate<F>]) -> LookupPatterns {
130 let mut kinds = LookupPatterns::default();
131 for g in gates.iter() {
132 for r in &[CurrOrNext::Curr, CurrOrNext::Next] {
133 if let Some(lookup_pattern) = LookupPattern::from_gate(g.typ, *r) {
134 kinds[lookup_pattern] = true;
135 }
136 }
137 }
138 kinds
139 }
140
141 pub fn joint_lookups_used(&self) -> bool {
144 for lookup_pattern in *self {
145 if lookup_pattern.max_joint_size() > 1 {
146 return true;
147 }
148 }
149 false
150 }
151}
152
153#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
159#[cfg_attr(
160 feature = "ocaml_types",
161 derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)
162)]
163#[cfg_attr(feature = "wasm_types", wasm_bindgen::prelude::wasm_bindgen)]
164pub struct LookupFeatures {
165 pub patterns: LookupPatterns,
167 pub joint_lookup_used: bool,
170 pub uses_runtime_tables: bool,
173}
174
175impl LookupFeatures {
176 pub fn from_gates<F: PrimeField>(gates: &[CircuitGate<F>], uses_runtime_tables: bool) -> Self {
181 let patterns = LookupPatterns::from_gates(gates);
182
183 let joint_lookup_used = patterns.joint_lookups_used();
184
185 LookupFeatures {
186 patterns,
187 uses_runtime_tables,
188 joint_lookup_used,
189 }
190 }
191}
192
193#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
195#[cfg_attr(feature = "wasm_types", wasm_bindgen::prelude::wasm_bindgen)]
196pub struct LookupInfo {
197 pub max_per_row: usize,
199 pub max_joint_size: u32,
201 pub features: LookupFeatures,
203}
204
205impl LookupInfo {
206 pub fn create(features: LookupFeatures) -> Self {
208 let max_per_row = max_lookups_per_row(features.patterns);
209
210 LookupInfo {
211 max_joint_size: features
212 .patterns
213 .into_iter()
214 .fold(0, |acc, v| core::cmp::max(acc, v.max_joint_size())),
215 max_per_row,
216 features,
217 }
218 }
219
220 pub fn create_from_gates<F: PrimeField>(
221 gates: &[CircuitGate<F>],
222 uses_runtime_tables: bool,
223 ) -> Option<Self> {
224 let features = LookupFeatures::from_gates(gates, uses_runtime_tables);
225
226 if features.patterns == LookupPatterns::default() {
227 None
228 } else {
229 Some(Self::create(features))
230 }
231 }
232
233 pub fn selector_polynomials_and_tables<F: PrimeField>(
236 &self,
237 domain: &EvaluationDomains<F>,
238 gates: &[CircuitGate<F>],
239 ) -> (LookupSelectors<Evaluations<F>>, Vec<LookupTable<F>>) {
240 let n = domain.d1.size();
241
242 let mut selector_values = LookupSelectors::default();
243 for kind in self.features.patterns {
244 selector_values[kind] = Some(vec![F::zero(); n]);
245 }
246
247 let mut gate_tables = BTreeSet::new();
248
249 let mut update_selector = |lookup_pattern, i| {
250 let selector = selector_values[lookup_pattern]
251 .as_mut()
252 .unwrap_or_else(|| panic!("has selector for {lookup_pattern:?}"));
253 selector[i] = F::one();
254 };
255
256 for (i, gate) in gates.iter().enumerate().take(n) {
258 let typ = gate.typ;
259
260 if let Some(lookup_pattern) = LookupPattern::from_gate(typ, CurrOrNext::Curr) {
261 update_selector(lookup_pattern, i);
262 if let Some(table_kind) = lookup_pattern.table() {
263 gate_tables.insert(table_kind);
264 }
265 }
266 if let Some(lookup_pattern) = LookupPattern::from_gate(typ, CurrOrNext::Next) {
267 update_selector(lookup_pattern, i + 1);
268 if let Some(table_kind) = lookup_pattern.table() {
269 gate_tables.insert(table_kind);
270 }
271 }
272 }
273
274 let selector_values8: LookupSelectors<_> = selector_values.map(|v| {
277 E::<F, D<F>>::from_vec_and_domain(v, domain.d1)
278 .interpolate()
279 .evaluate_over_domain(domain.d8)
280 });
281 let res_tables: Vec<_> = gate_tables.into_iter().map(get_table).collect();
282 (selector_values8, res_tables)
283 }
284
285 pub fn by_row<F: PrimeField>(&self, gates: &[CircuitGate<F>]) -> Vec<Vec<JointLookupSpec<F>>> {
287 let mut kinds = vec![vec![]; gates.len() + 1];
288 for i in 0..gates.len() {
289 let typ = gates[i].typ;
290
291 if let Some(lookup_pattern) = LookupPattern::from_gate(typ, CurrOrNext::Curr) {
292 kinds[i] = lookup_pattern.lookups();
293 }
294 if let Some(lookup_pattern) = LookupPattern::from_gate(typ, CurrOrNext::Next) {
295 kinds[i + 1] = lookup_pattern.lookups();
296 }
297 }
298 kinds
299 }
300}
301
302#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
304pub struct LocalPosition {
305 pub row: CurrOrNext,
306 pub column: usize,
307}
308
309#[derive(Clone, Serialize, Deserialize)]
312pub struct SingleLookup<F> {
313 pub value: Vec<(F, LocalPosition)>,
315}
316
317impl<F: Copy> SingleLookup<F> {
318 pub fn evaluate<K, G: Fn(LocalPosition) -> K>(&self, eval: G) -> K
320 where
321 K: Zero,
322 K: Mul<F, Output = K>,
323 {
324 self.value
325 .iter()
326 .fold(K::zero(), |acc, (c, p)| acc + eval(*p) * *c)
327 }
328}
329
330#[derive(Clone, Serialize, Deserialize, Debug)]
332pub enum LookupTableID {
333 Constant(i32),
335 WitnessColumn(usize),
337}
338
339#[derive(Clone, Serialize, Deserialize, Debug)]
341pub struct JointLookup<SingleLookup, LookupTableID> {
342 pub table_id: LookupTableID,
347 pub entry: Vec<SingleLookup>,
348}
349
350pub type JointLookupSpec<F> = JointLookup<SingleLookup<F>, LookupTableID>;
353
354pub type JointLookupValue<F> = JointLookup<F, F>;
356
357impl<F: Zero + One + Clone + Neg<Output = F> + From<u64>> JointLookupValue<F> {
358 pub fn evaluate(&self, joint_combiner: &F, table_id_combiner: &F) -> F {
360 combine_table_entry(
361 joint_combiner,
362 table_id_combiner,
363 self.entry.iter(),
364 &self.table_id,
365 )
366 }
367}
368
369impl<F: Copy> JointLookup<SingleLookup<F>, LookupTableID> {
370 pub fn reduce<K, G: Fn(LocalPosition) -> K>(&self, eval: &G) -> JointLookupValue<K>
373 where
374 K: Zero,
375 K: Mul<F, Output = K>,
376 K: Neg<Output = K>,
377 K: From<u64>,
378 {
379 let table_id = match self.table_id {
380 LookupTableID::Constant(table_id) => i32_to_field(table_id),
381 LookupTableID::WitnessColumn(column) => eval(LocalPosition {
382 row: CurrOrNext::Curr,
383 column,
384 }),
385 };
386 JointLookup {
387 table_id,
388 entry: self.entry.iter().map(|s| s.evaluate(eval)).collect(),
389 }
390 }
391
392 pub fn evaluate<K, G: Fn(LocalPosition) -> K>(
395 &self,
396 joint_combiner: &K,
397 table_id_combiner: &K,
398 eval: &G,
399 ) -> K
400 where
401 K: Zero + One + Clone,
402 K: Mul<F, Output = K>,
403 K: Neg<Output = K>,
404 K: From<u64>,
405 {
406 self.reduce(eval)
407 .evaluate(joint_combiner, table_id_combiner)
408 }
409}
410
411#[derive(
412 Copy, Clone, Serialize, Deserialize, Debug, EnumIter, PartialEq, Eq, PartialOrd, Ord, Hash,
413)]
414#[cfg_attr(
415 feature = "ocaml_types",
416 derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Enum)
417)]
418pub enum LookupPattern {
419 Xor,
420 Lookup,
421 RangeCheck,
422 ForeignFieldMul,
423}
424
425impl LookupPattern {
426 pub fn max_lookups_per_row(&self) -> usize {
428 match self {
429 LookupPattern::Xor | LookupPattern::RangeCheck | LookupPattern::ForeignFieldMul => 4,
430 LookupPattern::Lookup => 3,
431 }
432 }
433
434 pub fn max_joint_size(&self) -> u32 {
436 match self {
437 LookupPattern::Xor => 3,
438 LookupPattern::Lookup => 2,
439 LookupPattern::ForeignFieldMul | LookupPattern::RangeCheck => 1,
440 }
441 }
442
443 pub fn lookups<F: Field>(&self) -> Vec<JointLookupSpec<F>> {
449 let curr_row = |column| LocalPosition {
450 row: CurrOrNext::Curr,
451 column,
452 };
453 match self {
454 LookupPattern::Xor => {
455 (0..4)
456 .map(|i| {
457 let left = curr_row(3 + i);
466 let right = curr_row(7 + i);
467 let output = curr_row(11 + i);
468 let l = |loc: LocalPosition| SingleLookup {
469 value: vec![(F::one(), loc)],
470 };
471 JointLookup {
472 table_id: LookupTableID::Constant(XOR_TABLE_ID),
473 entry: vec![l(left), l(right), l(output)],
474 }
475 })
476 .collect()
477 }
478 LookupPattern::Lookup => {
479 (0..3)
480 .map(|i| {
481 let index = curr_row(2 * i + 1);
486 let value = curr_row(2 * i + 2);
487 let l = |loc: LocalPosition| SingleLookup {
488 value: vec![(F::one(), loc)],
489 };
490 JointLookup {
491 table_id: LookupTableID::WitnessColumn(0),
492 entry: vec![l(index), l(value)],
493 }
494 })
495 .collect()
496 }
497 LookupPattern::RangeCheck => {
498 (3..=6)
499 .map(|column| {
500 JointLookup {
503 table_id: LookupTableID::Constant(RANGE_CHECK_TABLE_ID),
504 entry: vec![SingleLookup {
505 value: vec![(F::one(), curr_row(column))],
506 }],
507 }
508 })
509 .collect()
510 }
511 LookupPattern::ForeignFieldMul => {
512 (7..=10)
513 .map(|col| {
514 JointLookup {
519 table_id: LookupTableID::Constant(RANGE_CHECK_TABLE_ID),
520 entry: vec![SingleLookup {
521 value: vec![(F::one(), curr_row(col))],
522 }],
523 }
524 })
525 .collect()
526 }
527 }
528 }
529
530 pub fn table(&self) -> Option<GateLookupTable> {
532 match self {
533 LookupPattern::Xor => Some(GateLookupTable::Xor),
534 LookupPattern::Lookup => None,
535 LookupPattern::RangeCheck => Some(GateLookupTable::RangeCheck),
536 LookupPattern::ForeignFieldMul => Some(GateLookupTable::RangeCheck),
537 }
538 }
539
540 pub fn from_gate(gate_type: GateType, curr_or_next: CurrOrNext) -> Option<Self> {
542 use CurrOrNext::{Curr, Next};
543 use GateType::*;
544 match (gate_type, curr_or_next) {
545 (Lookup, Curr) => Some(LookupPattern::Lookup),
546 (RangeCheck0, Curr) | (RangeCheck1, Curr | Next) | (Rot64, Curr) => {
547 Some(LookupPattern::RangeCheck)
548 }
549 (ForeignFieldMul, Curr | Next) => Some(LookupPattern::ForeignFieldMul),
550 (Xor16, Curr) => Some(LookupPattern::Xor),
551 _ => None,
552 }
553 }
554}
555
556impl GateType {
557 pub fn lookup_kinds() -> Vec<LookupPattern> {
559 vec![
560 LookupPattern::Xor,
561 LookupPattern::Lookup,
562 LookupPattern::RangeCheck,
563 LookupPattern::ForeignFieldMul,
564 ]
565 }
566}
567
568#[test]
569fn lookup_pattern_constants_correct() {
570 use strum::IntoEnumIterator;
571
572 for pat in LookupPattern::iter() {
573 let lookups = pat.lookups::<mina_curves::pasta::Fp>();
574 let max_joint_size = lookups
575 .iter()
576 .map(|lookup| lookup.entry.len())
577 .max()
578 .unwrap_or(0);
579 assert_eq!((pat, pat.max_lookups_per_row()), (pat, lookups.len()));
581 assert_eq!((pat, pat.max_joint_size()), (pat, max_joint_size as u32));
582 }
583}
584
585#[cfg(feature = "wasm_types")]
586pub mod wasm {
587 use super::*;
588
589 #[wasm_bindgen::prelude::wasm_bindgen]
590 impl LookupPatterns {
591 #[wasm_bindgen::prelude::wasm_bindgen(constructor)]
592 pub fn new(
593 xor: bool,
594 lookup: bool,
595 range_check: bool,
596 foreign_field_mul: bool,
597 ) -> LookupPatterns {
598 LookupPatterns {
599 xor,
600 lookup,
601 range_check,
602 foreign_field_mul,
603 }
604 }
605 }
606
607 #[wasm_bindgen::prelude::wasm_bindgen]
608 impl LookupFeatures {
609 #[wasm_bindgen::prelude::wasm_bindgen(constructor)]
610 pub fn new(
611 patterns: LookupPatterns,
612 joint_lookup_used: bool,
613 uses_runtime_tables: bool,
614 ) -> LookupFeatures {
615 LookupFeatures {
616 patterns,
617 joint_lookup_used,
618 uses_runtime_tables,
619 }
620 }
621 }
622
623 #[wasm_bindgen::prelude::wasm_bindgen]
624 impl LookupInfo {
625 #[wasm_bindgen::prelude::wasm_bindgen(constructor)]
626 pub fn new(
627 max_per_row: usize,
628 max_joint_size: u32,
629 features: LookupFeatures,
630 ) -> LookupInfo {
631 LookupInfo {
632 max_per_row,
633 max_joint_size,
634 features,
635 }
636 }
637 }
638}