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