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, 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> =
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            // For runtime tables, multiplicities are computed post
357            // factum, since we explicitly want (e.g. for RAM lookups)
358            // reads and writes to be parallel -- in many cases we
359            // haven't previously written the value we want to read.
360
361            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            // A runtime table resolver; the inverse of `runtime_tables`:
370            // maps runtime lookup table to `(column, row)`
371            let mut resolver: BTreeMap<Vec<F>, (usize, usize)> = BTreeMap::new();
372
373            {
374                // Populate resolver map either from "reads" or from "writes"
375                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            // Resolve reads and build multiplicities vector
400
401            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    /// Create a new empty-state witness builder.
432    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    /// Sets a fixed selector, the vector of length equal to the
461    /// domain size (circuit height).
462    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    /// Sets all fixed selectors directly. Each item in `selectors` is
471    /// a vector of `domain_size` length.
472    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        // Boxing to avoid stack overflow
478        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        // Filling actually used rows first
483        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        // Then filling witness rows up with zeroes to the domain size
490        // FIXME: Maybe this is not always wise, as default instance can be non-zero.
491        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        // Fill out dynamic selectors.
498        for i in 0..N_DSEL {
499            // TODO FIXME Fill out dynamic selectors!
500            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    /// Return all runtime tables collected so far, padded to the domain size.
517    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                // For runtime tables with no explicit writes, we
525                // store only read requests, so we assemble read
526                // requests into a column.
527                runtime_tables.insert(table_id, self.lookup_reads.get(&table_id).unwrap().clone());
528            } else {
529                // For runtime tables /with/ explicit writes, these
530                // writes are stored in self.runtime_tables.
531                runtime_tables.insert(
532                    table_id,
533                    self.runtime_lookup_writes.get(&table_id).unwrap().clone(),
534                );
535            }
536            // We pad the runtime table with dummies if it's too small.
537            for column in runtime_tables.get_mut(&table_id).unwrap() {
538                if column.len() < domain_size {
539                    let dummy_value = column[0].clone(); // we assume runtime tables are never empty
540                    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        // Building lookup values
553        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                // Find how many lookups are done per table.
557                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                // +1 for the fixed table
566                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        // FIXME add runtime tables, runtime_lookup_reads must be used here
589
590        let mut lookup_multiplicities: BTreeMap<LT, Vec<Vec<F>>> = BTreeMap::new();
591
592        // Counting multiplicities & adding fixed column into the last column of every table.
593        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                // Add multiplicity vectors for runtime tables.
616                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                // Temporary assertion; to be removed when we support bigger
637                // runtime table/RAMlookups functionality.
638                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                // Only add a table if it's used. Otherwise lookups fail.
647                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    /// Generates proof inputs, repacking/collecting internal witness builder state.
663    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}