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, iter, 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> = iter::repeat(-F::one())
349 .take(n_repeated_dummy_value)
350 .collect();
351 m.extend(repeated_dummy_value);
352 m.push(F::from(n_repeated_dummy_value as u64));
353 }
354 assert_eq!(m.len(), domain_size);
355 vec![m]
356 } else {
357 let runtime_table = self.runtime_lookup_writes.get(&table_id).unwrap();
363 let num_writes = if table_id.runtime_create_column() {
364 assert!(runtime_table.is_empty(), "runtime_table is expected to be unused for runtime tables with on-the-fly table creation");
365 1
366 } else {
367 runtime_table.len()
368 };
369
370 let mut resolver: BTreeMap<Vec<F>, (usize, usize)> = BTreeMap::new();
373
374 {
375 if table_id.runtime_create_column() {
377 let columns = &self.lookup_reads.get(&table_id).unwrap();
378 assert!(
379 columns.len() == 1,
380 "We only allow 1 read for runtime tables yet"
381 );
382 let column = &columns[0];
383 assert!(column.len() <= domain_size,);
384 for (row_i, value) in column.iter().enumerate() {
385 if resolver.get_mut(value).is_none() {
386 resolver.insert(value.clone(), (0, row_i));
387 }
388 }
389 } else {
390 for (col_i, col) in runtime_table.iter().take(num_writes).enumerate() {
391 for (row_i, value) in col.iter().enumerate() {
392 if resolver.get_mut(value).is_none() {
393 resolver.insert(value.clone(), (col_i, row_i));
394 }
395 }
396 }
397 }
398 }
399
400 let mut multiplicities = vec![vec![0u64; domain_size]; num_writes];
403
404 for lookup_read_column in self.lookup_reads.get(&table_id).unwrap().iter() {
405 for value in lookup_read_column.iter() {
406 if let Some((col_i, row_i)) = resolver.get_mut(value) {
407 multiplicities[*col_i][*row_i] += 1;
408 } else {
409 panic!("Could not resolve a runtime table read");
410 }
411 }
412 }
413
414 multiplicities
415 .into_iter()
416 .map(|v| v.into_iter().map(|x| F::from(x)).collect())
417 .collect()
418 }
419 }
420}
421
422impl<
423 F: PrimeField,
424 CIx: ColumnIndexer<usize>,
425 const N_WIT: usize,
426 const N_REL: usize,
427 const N_DSEL: usize,
428 const N_FSEL: usize,
429 LT: LookupTableID,
430 > WitnessBuilderEnv<F, CIx, N_WIT, N_REL, N_DSEL, N_FSEL, LT>
431{
432 pub fn create() -> Self {
434 let mut lookup_reads = BTreeMap::new();
435 let mut lookup_multiplicities = BTreeMap::new();
436 let mut runtime_lookup_writes = BTreeMap::new();
437 let fixed_selectors = vec![vec![]; N_FSEL];
438 for table_id in LT::all_variants().into_iter() {
439 lookup_reads.insert(table_id, vec![]);
440 if table_id.is_fixed() {
441 lookup_multiplicities.insert(table_id, vec![0u64; table_id.length()]);
442 } else {
443 runtime_lookup_writes.insert(table_id, vec![]);
444 }
445 }
446
447 Self {
448 witness: vec![Witness {
449 cols: Box::new([F::zero(); N_WIT]),
450 }],
451
452 lookup_multiplicities,
453 lookup_reads,
454 runtime_lookup_writes,
455 fixed_selectors,
456 phantom_cix: PhantomData,
457 assert_mapper: Box::new(|x| x),
458 }
459 }
460
461 pub fn set_fixed_selector_cix(&mut self, sel: CIx, sel_values: Vec<F>) {
464 if let Column::FixedSelector(i) = sel.to_column() {
465 self.fixed_selectors[i] = sel_values;
466 } else {
467 panic!("Tried to assign values to non-fixed-selector typed column {sel:?}");
468 }
469 }
470
471 pub fn set_fixed_selectors(&mut self, selectors: Vec<Vec<F>>) {
474 self.fixed_selectors = selectors
475 }
476
477 pub fn get_relation_witness(&self, domain_size: usize) -> Witness<N_WIT, Vec<F>> {
478 let mut witness: Box<Witness<N_WIT, Vec<F>>> = Box::new(Witness {
480 cols: Box::new(std::array::from_fn(|_| Vec::with_capacity(domain_size))),
481 });
482
483 for witness_row in self.witness.iter().take(domain_size) {
485 for j in 0..N_REL {
486 witness.cols[j].push(witness_row.cols[j]);
487 }
488 }
489
490 if self.witness.len() < domain_size {
493 for i in 0..N_REL {
494 witness.cols[i].extend(vec![F::zero(); domain_size - self.witness.len()]);
495 }
496 }
497
498 for i in 0..N_DSEL {
500 witness.cols[N_REL + i] = vec![F::zero(); domain_size];
502 }
503
504 for i in 0..(N_REL + N_DSEL) {
505 assert!(
506 witness.cols[i].len() == domain_size,
507 "Witness columns length {:?} for column {:?} does not match domain size {:?}",
508 witness.cols[i].len(),
509 i,
510 domain_size
511 );
512 }
513
514 *witness
515 }
516
517 pub fn get_runtime_tables(&self, domain_size: usize) -> BTreeMap<LT, Vec<Vec<Vec<F>>>> {
519 let mut runtime_tables: BTreeMap<LT, _> = BTreeMap::new();
520 for table_id in LT::all_variants()
521 .into_iter()
522 .filter(|table_id| !table_id.is_fixed())
523 {
524 if table_id.runtime_create_column() {
525 runtime_tables.insert(table_id, self.lookup_reads.get(&table_id).unwrap().clone());
529 } else {
530 runtime_tables.insert(
533 table_id,
534 self.runtime_lookup_writes.get(&table_id).unwrap().clone(),
535 );
536 }
537 for column in runtime_tables.get_mut(&table_id).unwrap() {
539 if column.len() < domain_size {
540 let dummy_value = column[0].clone(); column.append(&mut vec![dummy_value; domain_size - column.len()]);
542 }
543 }
544 }
545 runtime_tables
546 }
547
548 pub fn get_logup_witness(
549 &self,
550 domain_size: usize,
551 lookup_tables_data: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
552 ) -> BTreeMap<LT, LogupWitness<F, LT>> {
553 let mut lookup_tables: BTreeMap<LT, Vec<Vec<Logup<F, LT>>>> = BTreeMap::new();
555 if !lookup_tables_data.is_empty() {
556 for table_id in LT::all_variants().into_iter() {
557 let number_of_lookup_reads = self.lookup_reads.get(&table_id).unwrap().len();
559 let number_of_lookup_writes =
560 if table_id.is_fixed() || table_id.runtime_create_column() {
561 1
562 } else {
563 self.runtime_lookup_writes[&table_id].len()
564 };
565
566 lookup_tables.insert(
568 table_id,
569 vec![vec![]; number_of_lookup_reads + number_of_lookup_writes],
570 );
571 }
572 } else {
573 debug!("No lookup tables data provided. Skipping lookup tables.");
574 }
575
576 for (table_id, columns) in self.lookup_reads.iter() {
577 for (read_i, column) in columns.iter().enumerate() {
578 lookup_tables.get_mut(table_id).unwrap()[read_i] = column
579 .iter()
580 .map(|value| Logup {
581 table_id: *table_id,
582 numerator: F::one(),
583 value: value.clone(),
584 })
585 .collect();
586 }
587 }
588
589 let mut lookup_multiplicities: BTreeMap<LT, Vec<Vec<F>>> = BTreeMap::new();
592
593 for (table_id, table) in lookup_tables.iter_mut() {
595 let lookup_m: Vec<Vec<F>> = self.get_lookup_multiplicities(domain_size, *table_id);
596 lookup_multiplicities.insert(*table_id, lookup_m.clone());
597
598 if table_id.is_fixed() || table_id.runtime_create_column() {
599 assert!(lookup_m.len() == 1);
600 assert!(
601 lookup_tables_data[table_id].len() == 1,
602 "table {table_id:?} must have exactly one column, got {:?}",
603 lookup_tables_data[table_id].len()
604 );
605 let lookup_t = lookup_tables_data[table_id][0]
606 .iter()
607 .enumerate()
608 .map(|(i, v)| Logup {
609 table_id: *table_id,
610 numerator: -lookup_m[0][i],
611 value: v.clone(),
612 })
613 .collect();
614 *(table.last_mut().unwrap()) = lookup_t;
615 } else {
616 for (col_i, lookup_column) in lookup_tables_data[table_id].iter().enumerate() {
618 let lookup_t = lookup_column
619 .iter()
620 .enumerate()
621 .map(|(i, v)| Logup {
622 table_id: *table_id,
623 numerator: -lookup_m[col_i][i],
624 value: v.clone(),
625 })
626 .collect();
627
628 let pos = table.len() - self.runtime_lookup_writes[table_id].len() + col_i;
629
630 (*table)[pos] = lookup_t;
631 }
632 }
633 }
634
635 for (table_id, m) in lookup_multiplicities.iter() {
636 if !table_id.is_fixed() {
637 assert!(m.len() <= domain_size,
640 "We do not _yet_ support wrapping runtime tables that are bigger than domain size.");
641 }
642 }
643
644 lookup_tables
645 .iter()
646 .filter_map(|(table_id, table)| {
647 if !table.is_empty() && !table[0].is_empty() {
649 Some((
650 *table_id,
651 LogupWitness {
652 f: table.clone(),
653 m: lookup_multiplicities[table_id].clone(),
654 },
655 ))
656 } else {
657 None
658 }
659 })
660 .collect()
661 }
662
663 pub fn get_proof_inputs(
665 &self,
666 domain_size: usize,
667 lookup_tables_data: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
668 ) -> ProofInputs<N_WIT, F, LT> {
669 let evaluations = self.get_relation_witness(domain_size);
670 let logups = self.get_logup_witness(domain_size, lookup_tables_data);
671
672 ProofInputs {
673 evaluations,
674 logups,
675 }
676 }
677}