kimchi_msm/circuit_design/
witness.rs

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
14/// Witness builder environment. Operates on multiple rows at the same
15/// time. `CIx::N_COL` must be equal to `N_WIT + N_FSEL`; passing these two
16/// separately is due to a rust limitation.
17pub 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    /// The witness columns that the environment is working with.
27    /// Every element of the vector is a row, and the builder is
28    /// always processing the last row.
29    pub witness: Vec<Witness<N_WIT, F>>,
30
31    /// Lookup multiplicities, a vector of values `m_i` per lookup
32    /// table, where `m_i` is how many times the lookup value number
33    /// `i` was looked up.
34    pub lookup_multiplicities: BTreeMap<LT, Vec<u64>>,
35
36    /// Lookup "read" requests per table. Each element of the map is a
37    /// vector of <#number_of_reads_per_row> columns. Each column is a
38    /// vector of elements. Each element is vector of field elements.
39    ///
40    /// - `lookup_reads[table_id][read_i]` is a column corresponding to a read #`read_i` per row.
41    /// - `lookup_reads[table_id][read_i][row_i]` is a value-vector that's looked up at `row_i`
42    pub lookup_reads: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
43
44    /// Values for runtime tables. Each element (value) in the map is
45    /// a set of on-the-fly built columns, one column per write.
46    ///
47    /// Format is the same as `lookup_reads`.
48    ///
49    /// - `runtime_tables[table_id][write_i]` is a column corresponding to a write #`write_i` per row.
50    /// - `runtime_tables[table_id][write_i][row_i]` is a value-vector that's looked up at `row_i`
51    pub runtime_lookup_writes: BTreeMap<LT, Vec<Vec<Vec<F>>>>,
52
53    /// Fixed values for selector columns. `fixed_selectors[i][j]` is the
54    /// value for row #j of the selector #i.
55    pub fixed_selectors: Vec<Vec<F>>,
56
57    /// Function used to map assertions.
58    pub assert_mapper: Box<dyn Fn(F) -> F>,
59
60    // A Phantom Data for CIx -- right now WitnessBUilderEnv does not
61    // depend on CIx, but in the future (with associated generics
62    // enabled?) it might be convenient to put all the `NT_COL` (and
63    // other) constants into `CIx`. Logically, all these constants
64    // "belong" to CIx, so there's an extra type parameter, and a
65    // phantom data to support it.
66    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    // Requiring an F element as we would need to compute values up to 180 bits
80    // in the 15 bits decomposition.
81    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
119/// If `Env` implements real write ("for sure" writes), you can implement
120/// hybrid copy (that is only required to "maybe" copy). The other way
121/// around violates the semantics.
122///
123/// Sadly, rust does not allow "cover" instances to define this impl
124/// for every `T: ColWriteCap`.
125impl<
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    /// Read value from a (row,column) position.
154    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    /// Progresses to the next row.
162    fn next_row(&mut self) {
163        self.next_row();
164    }
165
166    /// Returns the current row.
167    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    /// Convert an abstract variable to a field element! Inverse of Env::constant().
183    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        // Recording the lookup read into the corresponding slot in `lookup_reads`.
200        {
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            // If we're at row 0, we can declare as many reads as we want.
209            // If we're at non-zero row, we cannot declare more reads than before.
210            let curr_write_number = if let Some(v) = curr_write_number {
211                v
212            } else {
213                // TODO: This must be a panic; however, we don't yet have support
214                // different number of lookups on different rows.
215                //
216                // See https://github.com/o1-labs/proof-systems/issues/2440
217                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 the table is fixed we also compute multiplicities on the fly.
230        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            // Since we allow multiple lookups per row, runtime tables
237            // can in theory grow bigger than the domain size. We
238            // still collect multiplicities as if runtime table vector
239            // is not height-bounded, but we will split it into chunks
240            // later.
241            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        // We insert value into runtime table in any case, for each row.
255        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                // TODO: Do we want to allow writing to dynamic selector columns only 1 or 0?
295                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    /// Progress to the computations on the next row.
328    pub fn next_row(&mut self) {
329        self.witness.push(Witness {
330            cols: Box::new([F::zero(); N_WIT]),
331        });
332    }
333
334    /// Getting multiplicities for range check tables less or equal
335    /// than 15 bits. Return value is a vector of columns, where each
336    /// column represents a "read". Fixed lookup tables always return
337    /// a single-column vector, while runtime tables may return more.
338    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            // For runtime tables, multiplicities are computed post
358            // factum, since we explicitly want (e.g. for RAM lookups)
359            // reads and writes to be parallel -- in many cases we
360            // haven't previously written the value we want to read.
361
362            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            // A runtime table resolver; the inverse of `runtime_tables`:
371            // maps runtime lookup table to `(column, row)`
372            let mut resolver: BTreeMap<Vec<F>, (usize, usize)> = BTreeMap::new();
373
374            {
375                // Populate resolver map either from "reads" or from "writes"
376                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            // Resolve reads and build multiplicities vector
401
402            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    /// Create a new empty-state witness builder.
433    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    /// Sets a fixed selector, the vector of length equal to the
462    /// domain size (circuit height).
463    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    /// Sets all fixed selectors directly. Each item in `selectors` is
472    /// a vector of `domain_size` length.
473    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        // Boxing to avoid stack overflow
479        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        // Filling actually used rows first
484        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        // Then filling witness rows up with zeroes to the domain size
491        // FIXME: Maybe this is not always wise, as default instance can be non-zero.
492        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        // Fill out dynamic selectors.
499        for i in 0..N_DSEL {
500            // TODO FIXME Fill out dynamic selectors!
501            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    /// Return all runtime tables collected so far, padded to the domain size.
518    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                // For runtime tables with no explicit writes, we
526                // store only read requests, so we assemble read
527                // requests into a column.
528                runtime_tables.insert(table_id, self.lookup_reads.get(&table_id).unwrap().clone());
529            } else {
530                // For runtime tables /with/ explicit writes, these
531                // writes are stored in self.runtime_tables.
532                runtime_tables.insert(
533                    table_id,
534                    self.runtime_lookup_writes.get(&table_id).unwrap().clone(),
535                );
536            }
537            // We pad the runtime table with dummies if it's too small.
538            for column in runtime_tables.get_mut(&table_id).unwrap() {
539                if column.len() < domain_size {
540                    let dummy_value = column[0].clone(); // we assume runtime tables are never empty
541                    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        // Building lookup values
554        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                // Find how many lookups are done per table.
558                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                // +1 for the fixed table
567                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        // FIXME add runtime tables, runtime_lookup_reads must be used here
590
591        let mut lookup_multiplicities: BTreeMap<LT, Vec<Vec<F>>> = BTreeMap::new();
592
593        // Counting multiplicities & adding fixed column into the last column of every table.
594        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                // Add multiplicity vectors for runtime tables.
617                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                // Temporary assertion; to be removed when we support bigger
638                // runtime table/RAMlookups functionality.
639                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                // Only add a table if it's used. Otherwise lookups fail.
648                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    /// Generates proof inputs, repacking/collecting internal witness builder state.
664    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}