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