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