1use crate::{
2 circuit_design::capabilities::{
3 ColAccessCap, ColWriteCap, DirectWitnessCap, HybridCopyCap, LookupCap, MultiRowReadCap,
4 },
5 columns::{Column, ColumnIndexer},
6 logup::{Logup, LogupWitness, LookupTableID},
7 proof::ProofInputs,
8 witness::Witness,
9};
10use ark_ff::PrimeField;
11use log::debug;
12use std::{collections::BTreeMap, marker::PhantomData};
13
14pub struct WitnessBuilderEnv<
18 F: PrimeField,
19 CIx: ColumnIndexer<usize>,
20 const N_WIT: usize,
21 const N_REL: usize,
22 const N_DSEL: usize,
23 const N_FSEL: usize,
24 LT: LookupTableID,
25> {
26 pub witness: Vec<Witness<N_WIT, F>>,
30
31 pub lookup_multiplicities: BTreeMap<LT, Vec<u64>>,
35
36 pub lookup_reads: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
43
44 pub runtime_lookup_writes: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
52
53 pub fixed_selectors: Vec<Vec<F>>,
56
57 pub assert_mapper: Box<dyn Fn(F) -> F>,
59
60 pub phantom_cix: PhantomData<CIx>,
67}
68
69impl<
70 F: PrimeField,
71 CIx: ColumnIndexer<usize>,
72 const N_WIT: usize,
73 const N_REL: usize,
74 const N_DSEL: usize,
75 const N_FSEL: usize,
76 LT: LookupTableID,
77 > ColAccessCap<F, CIx> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
78{
79 type Variable = F;
82
83 fn assert_zero(&mut self, cst: Self::Variable) {
84 assert_eq!((self.assert_mapper)(cst), F::zero());
85 }
86
87 fn set_assert_mapper(&mut self, mapper: Box<dyn Fn(Self::Variable) -> Self::Variable>) {
88 self.assert_mapper = mapper;
89 }
90
91 fn constant(value: F) -> Self::Variable {
92 value
93 }
94
95 fn read_column(&self, ix: CIx) -> Self::Variable {
96 match ix.to_column() {
97 Column::Relation(i) => self.witness.last().unwrap().cols[i],
98 Column::FixedSelector(i) => self.fixed_selectors[i][self.witness.len() - 1],
99 other => panic!("WitnessBuilderEnv::read_column does not support {other:?}"),
100 }
101 }
102}
103
104impl<
105 F: PrimeField,
106 CIx: ColumnIndexer<usize>,
107 const N_WIT: usize,
108 const N_REL: usize,
109 const N_DSEL: usize,
110 const N_FSEL: usize,
111 LT: LookupTableID,
112 > ColWriteCap<F, CIx> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
113{
114 fn write_column(&mut self, ix: CIx, value: &Self::Variable) {
115 self.write_column_raw(ix.to_column(), *value);
116 }
117}
118
119impl<
126 F: PrimeField,
127 CIx: ColumnIndexer<usize>,
128 const N_WIT: usize,
129 const N_REL: usize,
130 const N_DSEL: usize,
131 const N_FSEL: usize,
132 LT: LookupTableID,
133 > HybridCopyCap<F, CIx> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
134{
135 fn hcopy(&mut self, value: &Self::Variable, ix: CIx) -> Self::Variable {
136 <WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT> as ColWriteCap<F, CIx>>::write_column(
137 self, ix, value,
138 );
139 *value
140 }
141}
142
143impl<
144 F: PrimeField,
145 CIx: ColumnIndexer<usize>,
146 const N_WIT: usize,
147 const N_REL: usize,
148 const N_DSEL: usize,
149 const N_FSEL: usize,
150 LT: LookupTableID,
151 > MultiRowReadCap<F, CIx> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
152{
153 fn read_row_column(&mut self, row: usize, col: CIx) -> Self::Variable {
155 let Column::Relation(i) = col.to_column() else {
156 todo!()
157 };
158 self.witness[row].cols[i]
159 }
160
161 fn next_row(&mut self) {
163 self.next_row();
164 }
165
166 fn curr_row(&self) -> usize {
168 self.witness.len() - 1
169 }
170}
171
172impl<
173 F: PrimeField,
174 CIx: ColumnIndexer<usize>,
175 const N_WIT: usize,
176 const N_REL: usize,
177 const N_DSEL: usize,
178 const N_FSEL: usize,
179 LT: LookupTableID,
180 > DirectWitnessCap<F, CIx> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
181{
182 fn variable_to_field(value: Self::Variable) -> F {
184 value
185 }
186}
187
188impl<
189 F: PrimeField,
190 CIx: ColumnIndexer<usize>,
191 const N_WIT: usize,
192 const N_REL: usize,
193 const N_DSEL: usize,
194 const N_FSEL: usize,
195 LT: LookupTableID,
196 > LookupCap<F, CIx, LT> for WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
197{
198 fn lookup(&mut self, table_id: LT, value: Vec<<Self as ColAccessCap<F, CIx>>::Variable>) {
199 {
201 let curr_row = self.curr_row();
202
203 let lookup_read_table = self.lookup_reads.get_mut(&table_id).unwrap();
204
205 let curr_write_number =
206 (0..lookup_read_table.len()).find(|i| lookup_read_table[*i].len() <= curr_row);
207
208 let curr_write_number = if let Some(v) = curr_write_number {
211 v
212 } else {
213 if curr_row != 0 {
218 eprintln!(
219 "ERROR: Number of writes in row {curr_row:?} is different from row 0",
220 );
221 }
222 lookup_read_table.push(vec![]);
223 lookup_read_table.len() - 1
224 };
225
226 lookup_read_table[curr_write_number].push(value.clone());
227 }
228
229 if table_id.is_fixed() {
231 let value_ix = table_id
232 .ix_by_value(&value)
233 .expect("Could not resolve lookup for a fixed table");
234
235 let multiplicities = self.lookup_multiplicities.get_mut(&table_id).unwrap();
236 if !table_id.is_fixed() && value_ix > multiplicities.len() {
242 multiplicities.resize(value_ix, 0u64);
243 }
244 multiplicities[value_ix] += 1;
245 }
246 }
247
248 fn lookup_runtime_write(&mut self, table_id: LT, value: Vec<Self::Variable>) {
249 assert!(
250 !table_id.is_fixed() && !table_id.runtime_create_column(),
251 "lookup_runtime_write must be called on non-fixed tables that work with dynamic writes only"
252 );
253
254 let curr_row = self.witness.len() - 1;
256
257 let runtime_table = self.runtime_lookup_writes.get_mut(&table_id).unwrap();
258
259 let curr_write_number =
260 (0..runtime_table.len()).find(|i| runtime_table[*i].len() <= curr_row);
261
262 let curr_write_number = if let Some(v) = curr_write_number {
263 v
264 } else {
265 assert!(
266 curr_row == 0,
267 "Number of writes in row {curr_row:?} is different from row 0"
268 );
269 runtime_table.push(vec![]);
270 runtime_table.len() - 1
271 };
272
273 runtime_table[curr_write_number].push(value);
274 }
275}
276
277impl<
278 F: PrimeField,
279 CIx: ColumnIndexer<usize>,
280 const N_WIT: usize,
281 const N_REL: usize,
282 const N_DSEL: usize,
283 const N_FSEL: usize,
284 LT: LookupTableID,
285 > WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
286{
287 pub fn write_column_raw(&mut self, position: Column<usize>, value: F) {
288 match position {
289 Column::Relation(i) => self.witness.last_mut().unwrap().cols[i] = value,
290 Column::FixedSelector(_) => {
291 panic!("Witness environment can't write into fixed selector columns.");
292 }
293 Column::DynamicSelector(_) => {
294 panic!(
296 "This is a dynamic selector column. The environment is
297 supposed to write only in witness columns"
298 );
299 }
300 Column::LookupPartialSum(_) => {
301 panic!(
302 "This is a lookup related column. The environment is
303 supposed to write only in witness columns"
304 );
305 }
306 Column::LookupMultiplicity(_) => {
307 panic!(
308 "This is a lookup related column. The environment is
309 supposed to write only in witness columns"
310 );
311 }
312 Column::LookupAggregation => {
313 panic!(
314 "This is a lookup related column. The environment is
315 supposed to write only in witness columns"
316 );
317 }
318 Column::LookupFixedTable(_) => {
319 panic!(
320 "This is a lookup related column. The environment is
321 supposed to write only in witness columns"
322 );
323 }
324 }
325 }
326
327 pub fn next_row(&mut self) {
329 self.witness.push(Witness {
330 cols: Box::new([F::zero(); N_WIT]),
331 });
332 }
333
334 pub fn get_lookup_multiplicities(&self, domain_size: usize, table_id: LT) -> Vec<Vec<F>> {
339 if table_id.is_fixed() {
340 let mut m = Vec::with_capacity(domain_size);
341 m.extend(
342 self.lookup_multiplicities[&table_id]
343 .iter()
344 .map(|x| F::from(*x)),
345 );
346 if table_id.length() < domain_size {
347 let n_repeated_dummy_value: usize = domain_size - table_id.length() - 1;
348 let repeated_dummy_value: Vec<F> =
349 o1_utils::repeat_n(-F::one(), n_repeated_dummy_value).collect();
350 m.extend(repeated_dummy_value);
351 m.push(F::from(n_repeated_dummy_value as u64));
352 }
353 assert_eq!(m.len(), domain_size);
354 vec![m]
355 } else {
356 let runtime_table = self.runtime_lookup_writes.get(&table_id).unwrap();
362 let num_writes = if table_id.runtime_create_column() {
363 assert!(runtime_table.is_empty(), "runtime_table is expected to be unused for runtime tables with on-the-fly table creation");
364 1
365 } else {
366 runtime_table.len()
367 };
368
369 let mut resolver: BTreeMap<Vec<F>, (usize, usize)> = BTreeMap::new();
372
373 {
374 if table_id.runtime_create_column() {
376 let columns = &self.lookup_reads.get(&table_id).unwrap();
377 assert!(
378 columns.len() == 1,
379 "We only allow 1 read for runtime tables yet"
380 );
381 let column = &columns[0];
382 assert!(column.len() <= domain_size,);
383 for (row_i, value) in column.iter().enumerate() {
384 if resolver.get_mut(value).is_none() {
385 resolver.insert(value.clone(), (0, row_i));
386 }
387 }
388 } else {
389 for (col_i, col) in runtime_table.iter().take(num_writes).enumerate() {
390 for (row_i, value) in col.iter().enumerate() {
391 if resolver.get_mut(value).is_none() {
392 resolver.insert(value.clone(), (col_i, row_i));
393 }
394 }
395 }
396 }
397 }
398
399 let mut multiplicities = vec![vec![0u64; domain_size]; num_writes];
402
403 for lookup_read_column in self.lookup_reads.get(&table_id).unwrap().iter() {
404 for value in lookup_read_column.iter() {
405 if let Some((col_i, row_i)) = resolver.get_mut(value) {
406 multiplicities[*col_i][*row_i] += 1;
407 } else {
408 panic!("Could not resolve a runtime table read");
409 }
410 }
411 }
412
413 multiplicities
414 .into_iter()
415 .map(|v| v.into_iter().map(|x| F::from(x)).collect())
416 .collect()
417 }
418 }
419}
420
421impl<
422 F: PrimeField,
423 CIx: ColumnIndexer<usize>,
424 const N_WIT: usize,
425 const N_REL: usize,
426 const N_DSEL: usize,
427 const N_FSEL: usize,
428 LT: LookupTableID,
429 > WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
430{
431 pub fn create() -> Self {
433 let mut lookup_reads = BTreeMap::new();
434 let mut lookup_multiplicities = BTreeMap::new();
435 let mut runtime_lookup_writes = BTreeMap::new();
436 let fixed_selectors = vec![vec![]; N_FSEL];
437 for table_id in LT::all_variants().into_iter() {
438 lookup_reads.insert(table_id, vec![]);
439 if table_id.is_fixed() {
440 lookup_multiplicities.insert(table_id, vec![0u64; table_id.length()]);
441 } else {
442 runtime_lookup_writes.insert(table_id, vec![]);
443 }
444 }
445
446 Self {
447 witness: vec![Witness {
448 cols: Box::new([F::zero(); N_WIT]),
449 }],
450
451 lookup_multiplicities,
452 lookup_reads,
453 runtime_lookup_writes,
454 fixed_selectors,
455 phantom_cix: PhantomData,
456 assert_mapper: Box::new(|x| x),
457 }
458 }
459
460 pub fn set_fixed_selector_cix(&mut self, sel: CIx, sel_values: Vec<F>) {
463 if let Column::FixedSelector(i) = sel.to_column() {
464 self.fixed_selectors[i] = sel_values;
465 } else {
466 panic!("Tried to assign values to non-fixed-selector typed column {sel:?}");
467 }
468 }
469
470 pub fn set_fixed_selectors(&mut self, selectors: Vec<Vec<F>>) {
473 self.fixed_selectors = selectors
474 }
475
476 pub fn get_relation_witness(&self, domain_size: usize) -> Witness<N_WIT, Vec<F>> {
477 let mut witness: Box<Witness<N_WIT, Vec<F>>> = Box::new(Witness {
479 cols: Box::new(std::array::from_fn(|_| Vec::with_capacity(domain_size))),
480 });
481
482 for witness_row in self.witness.iter().take(domain_size) {
484 for j in 0..N_REL {
485 witness.cols[j].push(witness_row.cols[j]);
486 }
487 }
488
489 if self.witness.len() < domain_size {
492 for i in 0..N_REL {
493 witness.cols[i].extend(vec![F::zero(); domain_size - self.witness.len()]);
494 }
495 }
496
497 for i in 0..N_DSEL {
499 witness.cols[N_REL + i] = vec![F::zero(); domain_size];
501 }
502
503 for i in 0..(N_REL + N_DSEL) {
504 assert!(
505 witness.cols[i].len() == domain_size,
506 "Witness columns length {:?} for column {:?} does not match domain size {:?}",
507 witness.cols[i].len(),
508 i,
509 domain_size
510 );
511 }
512
513 *witness
514 }
515
516 pub fn get_runtime_tables(&self, domain_size: usize) -> BTreeMap<LT, Vec<Vec<Vec<F>>>> {
518 let mut runtime_tables: BTreeMap<LT, _> = BTreeMap::new();
519 for table_id in LT::all_variants()
520 .into_iter()
521 .filter(|table_id| !table_id.is_fixed())
522 {
523 if table_id.runtime_create_column() {
524 runtime_tables.insert(table_id, self.lookup_reads.get(&table_id).unwrap().clone());
528 } else {
529 runtime_tables.insert(
532 table_id,
533 self.runtime_lookup_writes.get(&table_id).unwrap().clone(),
534 );
535 }
536 for column in runtime_tables.get_mut(&table_id).unwrap() {
538 if column.len() < domain_size {
539 let dummy_value = column[0].clone(); column.append(&mut vec![dummy_value; domain_size - column.len()]);
541 }
542 }
543 }
544 runtime_tables
545 }
546
547 pub fn get_logup_witness(
548 &self,
549 domain_size: usize,
550 lookup_tables_data: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
551 ) -> BTreeMap<LT, LogupWitness<F, LT>> {
552 let mut lookup_tables: BTreeMap<LT, Vec<Vec<Logup<F, LT>>>> = BTreeMap::new();
554 if !lookup_tables_data.is_empty() {
555 for table_id in LT::all_variants().into_iter() {
556 let number_of_lookup_reads = self.lookup_reads.get(&table_id).unwrap().len();
558 let number_of_lookup_writes =
559 if table_id.is_fixed() || table_id.runtime_create_column() {
560 1
561 } else {
562 self.runtime_lookup_writes[&table_id].len()
563 };
564
565 lookup_tables.insert(
567 table_id,
568 vec![vec![]; number_of_lookup_reads + number_of_lookup_writes],
569 );
570 }
571 } else {
572 debug!("No lookup tables data provided. Skipping lookup tables.");
573 }
574
575 for (table_id, columns) in self.lookup_reads.iter() {
576 for (read_i, column) in columns.iter().enumerate() {
577 lookup_tables.get_mut(table_id).unwrap()[read_i] = column
578 .iter()
579 .map(|value| Logup {
580 table_id: *table_id,
581 numerator: F::one(),
582 value: value.clone(),
583 })
584 .collect();
585 }
586 }
587
588 let mut lookup_multiplicities: BTreeMap<LT, Vec<Vec<F>>> = BTreeMap::new();
591
592 for (table_id, table) in lookup_tables.iter_mut() {
594 let lookup_m: Vec<Vec<F>> = self.get_lookup_multiplicities(domain_size, *table_id);
595 lookup_multiplicities.insert(*table_id, lookup_m.clone());
596
597 if table_id.is_fixed() || table_id.runtime_create_column() {
598 assert!(lookup_m.len() == 1);
599 assert!(
600 lookup_tables_data[table_id].len() == 1,
601 "table {table_id:?} must have exactly one column, got {:?}",
602 lookup_tables_data[table_id].len()
603 );
604 let lookup_t = lookup_tables_data[table_id][0]
605 .iter()
606 .enumerate()
607 .map(|(i, v)| Logup {
608 table_id: *table_id,
609 numerator: -lookup_m[0][i],
610 value: v.clone(),
611 })
612 .collect();
613 *(table.last_mut().unwrap()) = lookup_t;
614 } else {
615 for (col_i, lookup_column) in lookup_tables_data[table_id].iter().enumerate() {
617 let lookup_t = lookup_column
618 .iter()
619 .enumerate()
620 .map(|(i, v)| Logup {
621 table_id: *table_id,
622 numerator: -lookup_m[col_i][i],
623 value: v.clone(),
624 })
625 .collect();
626
627 let pos = table.len() - self.runtime_lookup_writes[table_id].len() + col_i;
628
629 (*table)[pos] = lookup_t;
630 }
631 }
632 }
633
634 for (table_id, m) in lookup_multiplicities.iter() {
635 if !table_id.is_fixed() {
636 assert!(m.len() <= domain_size,
639 "We do not _yet_ support wrapping runtime tables that are bigger than domain size.");
640 }
641 }
642
643 lookup_tables
644 .iter()
645 .filter_map(|(table_id, table)| {
646 if !table.is_empty() && !table[0].is_empty() {
648 Some((
649 *table_id,
650 LogupWitness {
651 f: table.clone(),
652 m: lookup_multiplicities[table_id].clone(),
653 },
654 ))
655 } else {
656 None
657 }
658 })
659 .collect()
660 }
661
662 pub fn get_proof_inputs(
664 &self,
665 domain_size: usize,
666 lookup_tables_data: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
667 ) -> ProofInputs<N_WIT, F, LT> {
668 let evaluations = self.get_relation_witness(domain_size);
669 let logups = self.get_logup_witness(domain_size, lookup_tables_data);
670
671 ProofInputs {
672 evaluations,
673 logups,
674 }
675 }
676}