1use alloc::vec;
2use alloc::vec::Vec;
3
4#[cfg(debug_assertions)]
5use p3_air::DebugConstraintBuilder;
6use p3_air::symbolic::{AirLayout, SymbolicAirBuilder, SymbolicExpressionExt};
7use p3_air::{Air, RowWindow};
8use p3_challenger::{CanObserve, FieldChallenger};
9use p3_commit::{Pcs, PolynomialSpace};
10use p3_field::{
11 Algebra, BasedVectorSpace, PackedFieldExtension, PackedValue, PrimeCharacteristicRing,
12};
13use p3_lookup::AirWithLookups;
14use p3_lookup::folder::ProverConstraintFolderWithLookups;
15use p3_lookup::logup::LogUpGadget;
16use p3_lookup::lookup_traits::{Kind, Lookup, LookupData, LookupGadget};
17use p3_matrix::Matrix;
18use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
19use p3_maybe_rayon::prelude::*;
20use p3_uni_stark::{OpenedValues, PackedChallenge, PackedVal, ProverConstraintFolder};
21use p3_util::log2_strict_usize;
22use tracing::{debug_span, info_span, instrument};
23
24use crate::common::{CommonData, ProverData, get_perm_challenges};
25use crate::config::{Challenge, Domain, StarkGenericConfig as SGC, Val, observe_instance_binding};
26use crate::proof::{BatchCommitments, BatchOpenedValues, BatchProof, OpenedValuesWithLookups};
27use crate::symbolic::{
28 get_constraint_layout, get_log_num_quotient_chunks, get_symbolic_constraints,
29};
30
31#[derive(Debug)]
32pub struct StarkInstance<'a, SC: SGC, A> {
33 pub air: &'a A,
34 pub trace: &'a RowMajorMatrix<Val<SC>>,
35 pub public_values: Vec<Val<SC>>,
36 pub lookups: Vec<Lookup<Val<SC>>>,
37}
38
39impl<'a, SC: SGC, A> StarkInstance<'a, SC, A> {
40 pub fn new_multiple(
41 airs: &'a [A],
42 traces: &'a [&'a RowMajorMatrix<Val<SC>>],
43 public_values: &[Vec<Val<SC>>],
44 common_data: &CommonData<SC>,
45 ) -> Vec<Self> {
46 airs.iter()
47 .zip(traces.iter())
48 .zip(public_values.iter())
49 .zip(common_data.lookups.iter())
50 .map(|(((air, trace), public_values), lookups)| Self {
51 air,
52 trace,
53 public_values: public_values.clone(),
54 lookups: lookups.clone(),
55 })
56 .collect()
57 }
58}
59
60#[instrument(skip_all)]
61pub fn prove_batch<
62 SC,
63 #[cfg(debug_assertions)] A: for<'a> Air<DebugConstraintBuilder<'a, Val<SC>, SC::Challenge>>
64 + Air<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
65 + for<'a> Air<ProverConstraintFolderWithLookups<'a, SC>>
66 + Clone,
67 #[cfg(not(debug_assertions))] A: for<'a> Air<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
68 + for<'a> Air<ProverConstraintFolderWithLookups<'a, SC>>
69 + Clone,
70>(
71 config: &SC,
72 instances: &[StarkInstance<'_, SC, A>],
73 prover_data: &ProverData<SC>,
74) -> BatchProof<SC>
75where
76 SC: SGC,
77 SymbolicExpressionExt<Val<SC>, SC::Challenge>: Algebra<SC::Challenge>,
78{
79 let common = &prover_data.common;
80 let lookup_gadget = LogUpGadget::new();
82
83 let pcs = config.pcs();
84 let mut challenger = config.initialise_challenger();
85
86 let degrees: Vec<usize> = instances.iter().map(|i| i.trace.height()).collect();
88 let log_degrees: Vec<usize> = degrees.iter().copied().map(log2_strict_usize).collect();
89 let log_ext_degrees: Vec<usize> = log_degrees.iter().map(|&d| d + config.is_zk()).collect();
90
91 let (all_lookups, mut lookup_data): (Vec<Vec<_>>, Vec<Vec<_>>) = instances
93 .iter()
94 .map(|inst| {
95 (
96 inst.lookups.clone(),
97 inst.lookups
99 .iter()
100 .filter_map(|lookup| match &lookup.kind {
101 Kind::Global(name) => Some(LookupData {
102 name: name.clone(),
103 aux_idx: lookup.columns[0],
104 expected_cumulated: SC::Challenge::ZERO,
105 }),
106 _ => None,
107 })
108 .collect::<Vec<_>>(),
109 )
110 })
111 .unzip();
112
113 let (trace_domains, ext_trace_domains): (Vec<Domain<SC>>, Vec<Domain<SC>>) = degrees
115 .iter()
116 .map(|°| {
117 (
118 pcs.natural_domain_for_degree(deg),
119 pcs.natural_domain_for_degree(deg * (config.is_zk() + 1)),
120 )
121 })
122 .unzip();
123
124 let airs: Vec<&A> = instances.iter().map(|i| i.air).collect();
126 let pub_vals: Vec<Vec<Val<SC>>> = instances.iter().map(|i| i.public_values.clone()).collect();
127
128 let mut preprocessed_widths = Vec::with_capacity(airs.len());
129 let (log_num_quotient_chunks, num_quotient_chunks): (Vec<usize>, Vec<usize>) = airs
130 .iter()
131 .zip(pub_vals.iter())
132 .enumerate()
133 .map(|(i, (air, _pv))| {
134 let pre_w = common
135 .preprocessed
136 .as_ref()
137 .and_then(|g| g.instances[i].as_ref().map(|m| m.width))
138 .unwrap_or(0);
139 preprocessed_widths.push(pre_w);
140 let layout = AirLayout {
141 preprocessed_width: pre_w,
142 main_width: air.width(),
143 num_public_values: air.num_public_values(),
144 ..Default::default()
145 };
146 let lq_chunks =
147 info_span!("infer log of constraint degree", air_idx = i).in_scope(|| {
148 get_log_num_quotient_chunks::<Val<SC>, SC::Challenge, A, LogUpGadget>(
149 air,
150 layout,
151 &all_lookups[i],
152 config.is_zk(),
153 &lookup_gadget,
154 )
155 });
156 let n_chunks = 1 << (lq_chunks + config.is_zk());
157 (lq_chunks, n_chunks)
158 })
159 .unzip();
160
161 let n_instances = airs.len();
164 challenger.observe_base_as_algebra_element::<Challenge<SC>>(Val::<SC>::from_usize(n_instances));
165
166 for i in 0..n_instances {
168 observe_instance_binding::<SC>(
169 &mut challenger,
170 log_ext_degrees[i],
171 log_degrees[i],
172 A::width(airs[i]),
173 num_quotient_chunks[i],
174 );
175 }
176
177 let main_commit_inputs = instances
179 .iter()
180 .zip(ext_trace_domains.iter().cloned())
181 .map(|(inst, dom)| (dom, inst.trace.clone()))
182 .collect::<Vec<_>>();
183 let (main_commit, main_data) = pcs.commit(main_commit_inputs);
184
185 challenger.observe(main_commit.clone());
187 for pv in &pub_vals {
188 challenger.observe_slice(pv);
189 }
190
191 for &pre_w in preprocessed_widths.iter() {
195 challenger.observe_base_as_algebra_element::<Challenge<SC>>(Val::<SC>::from_usize(pre_w));
196 }
197 if let Some(global) = &common.preprocessed {
198 challenger.observe(global.commitment.clone());
199 }
200
201 let challenges_per_instance =
203 get_perm_challenges::<SC, LogUpGadget>(&mut challenger, &all_lookups, &lookup_gadget);
204
205 let mut permutation_commit_inputs = Vec::with_capacity(n_instances);
207 instances
208 .iter()
209 .enumerate()
210 .zip(ext_trace_domains.iter().cloned())
211 .for_each(|((i, inst), ext_domain)| {
212 if !all_lookups[i].is_empty() {
213 let generated_perm = lookup_gadget.generate_permutation::<SC>(
214 inst.trace,
215 &inst.air.preprocessed_trace(),
216 &inst.public_values,
217 &all_lookups[i],
218 &mut lookup_data[i],
219 &challenges_per_instance[i],
220 );
221 permutation_commit_inputs
222 .push((ext_domain, generated_perm.clone().flatten_to_base()));
223
224 #[cfg(debug_assertions)]
225 {
226 use crate::check_constraints::check_constraints;
227
228 let preprocessed_trace = inst.air.preprocessed_trace();
229
230 let perm_vals: Vec<SC::Challenge> = lookup_data[i]
231 .iter()
232 .map(|ld| ld.expected_cumulated)
233 .collect();
234 let lookup_constraints_inputs = (all_lookups[i].as_slice(), &lookup_gadget);
235 check_constraints(
236 inst.air,
237 inst.trace,
238 &preprocessed_trace,
239 &generated_perm,
240 &challenges_per_instance[i],
241 &perm_vals,
242 &inst.public_values,
243 lookup_constraints_inputs,
244 );
245 }
246 }
247 });
248
249 #[cfg(debug_assertions)]
251 {
252 use p3_lookup::debug_util::{LookupDebugInstance, check_lookups};
253
254 let preprocessed_traces: Vec<Option<RowMajorMatrix<Val<SC>>>> = instances
255 .iter()
256 .map(|inst| inst.air.preprocessed_trace())
257 .collect();
258 let debug_instances: Vec<LookupDebugInstance<'_, Val<SC>>> = instances
259 .iter()
260 .zip(preprocessed_traces.iter())
261 .map(|(inst, prep)| LookupDebugInstance {
262 main_trace: inst.trace,
263 preprocessed_trace: prep,
264 public_values: &inst.public_values,
265 lookups: &inst.lookups,
266 permutation_challenges: &[],
267 })
268 .collect();
269 check_lookups(&debug_instances);
270 }
271
272 let permutation_commit_and_data = if !permutation_commit_inputs.is_empty() {
274 let commitment = pcs.commit(permutation_commit_inputs);
275 challenger.observe(commitment.0.clone());
276 for data in lookup_data.iter().flatten() {
277 challenger.observe_algebra_element(data.expected_cumulated);
278 }
279 Some(commitment)
280 } else {
281 None
282 };
283
284 let alpha: Challenge<SC> = challenger.sample_algebra_element();
288
289 let mut quotient_chunk_domains: Vec<Domain<SC>> = Vec::new();
291 let mut quotient_chunk_mats: Vec<RowMajorMatrix<Val<SC>>> = Vec::new();
292 let mut quotient_chunk_ranges: Vec<(usize, usize)> = Vec::with_capacity(n_instances);
294
295 let mut perm_counter = 0;
296
297 for (i, trace_domain) in trace_domains.iter().enumerate() {
299 let _air_span = info_span!("compute quotient", air_idx = i).entered();
300
301 let log_chunks = log_num_quotient_chunks[i];
302 let n_chunks = num_quotient_chunks[i];
303 let quotient_domain =
306 ext_trace_domains[i].create_disjoint_domain(1 << (log_ext_degrees[i] + log_chunks));
307
308 let sym_layout = AirLayout {
309 preprocessed_width: preprocessed_widths[i],
310 main_width: airs[i].width(),
311 num_public_values: airs[i].num_public_values(),
312 ..Default::default()
313 };
314
315 debug_assert!(
317 airs[i].num_constraints().is_none_or(|n| {
318 n == get_symbolic_constraints(airs[i], sym_layout, &all_lookups[i], &lookup_gadget)
319 .0
320 .len()
321 }),
322 "num_constraints() = {} but symbolic evaluation found {} base constraints",
323 airs[i].num_constraints().unwrap(),
324 get_symbolic_constraints(airs[i], sym_layout, &all_lookups[i], &lookup_gadget,)
325 .0
326 .len(),
327 );
328
329 let trace_on_quotient_domain =
331 pcs.get_evaluations_on_domain(&main_data, i, quotient_domain);
332
333 let permutation_on_quotient_domain = permutation_commit_and_data
334 .as_ref()
335 .filter(|_| !all_lookups[i].is_empty())
336 .map(|(_, perm_data)| {
337 let evals = pcs.get_evaluations_on_domain(perm_data, perm_counter, quotient_domain);
338 perm_counter += 1;
339 evals
340 });
341
342 let preprocessed_on_quotient_domain = common
344 .preprocessed
345 .as_ref()
346 .and_then(|g| g.instances[i].as_ref())
347 .map(|meta| {
348 let preprocessed_prover_data = prover_data
349 .prover_only
350 .preprocessed_prover_data
351 .as_ref()
352 .expect("preprocessed_prover_data must exist when preprocessed columns exist");
353 pcs.get_evaluations_on_domain_no_random(
354 preprocessed_prover_data,
355 meta.matrix_index,
356 quotient_domain,
357 )
358 });
359
360 let perm_vals: Vec<SC::Challenge> = lookup_data[i]
362 .iter()
363 .map(|ld| ld.expected_cumulated)
364 .collect();
365 let q_values = quotient_values::<SC, A, _, LogUpGadget>(
366 airs[i],
367 &pub_vals[i],
368 sym_layout,
369 *trace_domain,
370 quotient_domain,
371 &trace_on_quotient_domain,
372 permutation_on_quotient_domain.as_ref(),
373 &all_lookups[i],
374 &perm_vals,
375 &lookup_gadget,
376 &challenges_per_instance[i],
377 preprocessed_on_quotient_domain.as_ref(),
378 alpha,
379 );
380
381 let q_flat = RowMajorMatrix::new_col(q_values).flatten_to_base();
383 let chunk_mats = quotient_domain.split_evals(n_chunks, q_flat);
384 let chunk_domains = quotient_domain.split_domains(n_chunks);
385
386 let evals = chunk_domains.iter().zip(chunk_mats).map(|(d, m)| (*d, m));
387 let ldes = pcs.get_quotient_ldes(evals, n_chunks);
388
389 let start = quotient_chunk_domains.len();
390 quotient_chunk_domains.extend(chunk_domains);
391 quotient_chunk_mats.extend(ldes);
392 let end = quotient_chunk_domains.len();
393 quotient_chunk_ranges.push((start, end));
394 }
395
396 let (quotient_commit, quotient_data) = pcs.commit_ldes(quotient_chunk_mats);
398 challenger.observe(quotient_commit.clone());
399
400 let (opt_r_commit, opt_r_data) = if SC::Pcs::ZK {
409 let (r_commit, r_data) = pcs
410 .get_opt_randomization_poly_commitment(ext_trace_domains.iter().copied())
411 .expect("ZK is enabled, so we should have randomization commitments");
412 (Some(r_commit), Some(r_data))
413 } else {
414 (None, None)
415 };
416
417 if let Some(r_commit) = &opt_r_commit {
418 challenger.observe(r_commit.clone());
419 }
420
421 let zeta: Challenge<SC> = challenger.sample_algebra_element();
423
424 let (opened_values, opening_proof) = {
426 let mut rounds = Vec::new();
427
428 let round0 = opt_r_data.as_ref().map(|r_data| {
429 let round0_points = trace_domains.iter().map(|_| vec![zeta]).collect::<Vec<_>>();
430 (r_data, round0_points)
431 });
432 rounds.extend(round0);
433 let round1_points = trace_domains
435 .iter()
436 .enumerate()
437 .map(|(i, dom)| {
438 if !airs[i].main_next_row_columns().is_empty() {
439 vec![
440 zeta,
441 dom.next_point(zeta)
442 .expect("domain should support next_point operation"),
443 ]
444 } else {
445 vec![zeta]
446 }
447 })
448 .collect::<Vec<_>>();
449 rounds.push((&main_data, round1_points));
450
451 let round2_points = quotient_chunk_ranges
453 .iter()
454 .cloned()
455 .flat_map(|(s, e)| (s..e).map(|_| vec![zeta]))
456 .collect::<Vec<_>>();
457 rounds.push(("ient_data, round2_points));
458
459 if let Some(global) = &common.preprocessed {
464 let preprocessed_prover_data = prover_data
465 .prover_only
466 .preprocessed_prover_data
467 .as_ref()
468 .expect("preprocessed_prover_data must exist when preprocessed columns exist");
469 let pre_points = global
470 .matrix_to_instance
471 .iter()
472 .map(|&inst_idx| {
473 if !airs[inst_idx].preprocessed_next_row_columns().is_empty() {
474 let zeta_next_i = trace_domains[inst_idx]
475 .next_point(zeta)
476 .expect("domain should support next_point operation");
477 vec![zeta, zeta_next_i]
478 } else {
479 vec![zeta]
480 }
481 })
482 .collect::<Vec<_>>();
483 rounds.push((preprocessed_prover_data, pre_points));
484 }
485
486 let lookup_points: Vec<_> = trace_domains
487 .iter()
488 .zip(&all_lookups)
489 .filter(|&(_, lookups)| !lookups.is_empty())
490 .map(|(dom, _)| {
491 vec![
492 zeta,
493 dom.next_point(zeta)
494 .expect("domain should support next_point operation"),
495 ]
496 })
497 .collect();
498
499 if let Some((_, perm_data)) = &permutation_commit_and_data {
500 let lookup_round = (perm_data, lookup_points);
501 rounds.push(lookup_round);
502 }
503
504 pcs.open_with_preprocessing(rounds, &mut challenger, common.preprocessed.is_some())
505 };
506
507 let trace_idx = SC::Pcs::TRACE_IDX;
509 let quotient_idx = SC::Pcs::QUOTIENT_IDX;
510 let preprocessed_idx = SC::Pcs::PREPROCESSED_TRACE_IDX;
511 let permutation_idx = if common.preprocessed.is_some() {
512 preprocessed_idx + 1
513 } else {
514 preprocessed_idx
515 };
516
517 let trace_values_for_mats = &opened_values[trace_idx];
519 assert_eq!(trace_values_for_mats.len(), n_instances);
520
521 let mut per_instance: Vec<OpenedValuesWithLookups<Challenge<SC>>> =
523 Vec::with_capacity(n_instances);
524
525 let preprocessed_openings = common
527 .preprocessed
528 .as_ref()
529 .map(|_| &opened_values[SC::Pcs::PREPROCESSED_TRACE_IDX]);
530
531 let is_lookup = permutation_commit_and_data.is_some();
532 let permutation_values_for_mats = if is_lookup {
533 &opened_values[permutation_idx]
534 } else {
535 &vec![]
536 };
537 let mut permutation_values_for_mats = permutation_values_for_mats.iter();
538
539 let mut quotient_openings_iter = opened_values[quotient_idx].iter();
540 for (i, (s, e)) in quotient_chunk_ranges.iter().copied().enumerate() {
541 let random = if opt_r_data.is_some() {
542 Some(opened_values[0][i][0].clone())
543 } else {
544 None
545 };
546 let tv = &trace_values_for_mats[i];
548 let trace_local = tv[0].clone();
549 let trace_next = if !airs[i].main_next_row_columns().is_empty() {
550 Some(tv[1].clone())
551 } else {
552 None
553 };
554
555 let mut qcs = Vec::with_capacity(e - s);
557 for _ in s..e {
558 let mat_vals = quotient_openings_iter
559 .next()
560 .expect("chunk index in bounds");
561 qcs.push(mat_vals[0].clone());
562 }
563
564 let (preprocessed_local, preprocessed_next) = if let (Some(global), Some(pre_round)) =
566 (&common.preprocessed, preprocessed_openings)
567 {
568 global.instances[i].as_ref().map_or((None, None), |meta| {
569 let vals = &pre_round[meta.matrix_index];
570 if !airs[i].preprocessed_next_row_columns().is_empty() {
571 assert_eq!(
572 vals.len(),
573 2,
574 "expected two opening points (zeta, zeta_next) for preprocessed trace"
575 );
576 (Some(vals[0].clone()), Some(vals[1].clone()))
577 } else {
578 assert_eq!(
579 vals.len(),
580 1,
581 "expected one opening point (zeta) for preprocessed trace"
582 );
583 (Some(vals[0].clone()), None)
584 }
585 })
586 } else {
587 (None, None)
588 };
589
590 let (permutation_local, permutation_next) = if !all_lookups[i].is_empty() {
592 let perm_v = permutation_values_for_mats
593 .next()
594 .expect("instance should have permutation openings");
595 (perm_v[0].clone(), perm_v[1].clone())
596 } else {
597 (vec![], vec![])
598 };
599
600 let base_opened = OpenedValues {
601 trace_local,
602 trace_next,
603 preprocessed_local,
604 preprocessed_next,
605 quotient_chunks: qcs,
606 random,
607 };
608
609 per_instance.push(OpenedValuesWithLookups {
610 base_opened_values: base_opened,
611 permutation_local,
612 permutation_next,
613 });
614 }
615
616 let permutation = permutation_commit_and_data
617 .as_ref()
618 .map(|(comm, _)| comm.clone());
619
620 BatchProof {
621 commitments: BatchCommitments {
622 main: main_commit,
623 quotient_chunks: quotient_commit,
624 random: opt_r_commit,
625 permutation,
626 },
627 opened_values: BatchOpenedValues {
628 instances: per_instance,
629 },
630 opening_proof,
631 global_lookup_data: lookup_data,
632 degree_bits: log_ext_degrees,
633 }
634}
635
636#[instrument(name = "compute quotient polynomial", skip_all)]
637#[allow(clippy::too_many_arguments)]
639pub fn quotient_values<SC, A, Mat, LG>(
640 air: &A,
641 public_values: &[Val<SC>],
642 layout: AirLayout,
643 trace_domain: Domain<SC>,
644 quotient_domain: Domain<SC>,
645 trace_on_quotient_domain: &Mat,
646 opt_permutation_on_quotient_domain: Option<&Mat>,
647 lookups: &[Lookup<Val<SC>>],
648 permutation_vals: &[SC::Challenge],
649 lookup_gadget: &LG,
650 permutation_challenges: &[SC::Challenge],
651 preprocessed_on_quotient_domain: Option<&Mat>,
652 alpha: SC::Challenge,
653) -> Vec<SC::Challenge>
654where
655 SC: SGC,
656 A: Air<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
657 + for<'a> Air<ProverConstraintFolderWithLookups<'a, SC>>,
658 Mat: Matrix<Val<SC>> + Sync,
659 LG: LookupGadget + Sync,
660 SymbolicExpressionExt<Val<SC>, SC::Challenge>: Algebra<SC::Challenge>,
661{
662 let quotient_size = quotient_domain.size();
663 let main_width = trace_on_quotient_domain.width();
664 let (perm_width, perm_height) = opt_permutation_on_quotient_domain
665 .as_ref()
666 .map_or((0, 0), |mat| (mat.width(), mat.height()));
667
668 let ext_degree = SC::Challenge::DIMENSION;
669
670 let mut sels = debug_span!("Compute Selectors")
671 .in_scope(|| trace_domain.selectors_on_coset(quotient_domain));
672
673 let qdb = log2_strict_usize(quotient_domain.size()) - log2_strict_usize(trace_domain.size());
674 let next_step = 1 << qdb;
675
676 let pack_width = PackedVal::<SC>::WIDTH;
678 if quotient_size < pack_width {
679 let pad_len = pack_width;
680 let pad = |v: &mut Vec<_>| v.resize(pad_len, Val::<SC>::default());
682 pad(&mut sels.is_first_row);
683 pad(&mut sels.is_last_row);
684 pad(&mut sels.is_transition);
685 pad(&mut sels.inv_vanishing);
686 }
687
688 let constraint_layout = get_constraint_layout(air, layout, lookups, lookup_gadget);
689 let (base_alpha_powers, ext_alpha_powers) = constraint_layout.decompose_alpha(alpha);
690
691 let packed_perm_challenges: Vec<PackedChallenge<SC>> = permutation_challenges
693 .iter()
694 .map(|&p_c| PackedChallenge::<SC>::from(p_c))
695 .collect();
696 let permutation_vals_packed: Vec<PackedChallenge<SC>> = permutation_vals
697 .iter()
698 .map(|&v| PackedChallenge::<SC>::from(v))
699 .collect();
700
701 (0..quotient_size)
702 .into_par_iter()
703 .step_by(PackedVal::<SC>::WIDTH)
704 .flat_map_iter(|i_start| {
705 let i_range = i_start..i_start + PackedVal::<SC>::WIDTH;
706
707 let is_first_row = *PackedVal::<SC>::from_slice(&sels.is_first_row[i_range.clone()]);
708 let is_last_row = *PackedVal::<SC>::from_slice(&sels.is_last_row[i_range.clone()]);
709 let is_transition = *PackedVal::<SC>::from_slice(&sels.is_transition[i_range.clone()]);
710 let inv_vanishing = *PackedVal::<SC>::from_slice(&sels.inv_vanishing[i_range]);
711
712 let main = RowMajorMatrix::new(
714 trace_on_quotient_domain.vertically_packed_row_pair(i_start, next_step),
715 main_width,
716 );
717
718 let preprocessed = preprocessed_on_quotient_domain.map(|preprocessed| {
719 let preprocessed_width = preprocessed.width();
720 RowMajorMatrix::new(
721 preprocessed.vertically_packed_row_pair(i_start, next_step),
722 preprocessed_width,
723 )
724 });
725
726 let permutation = opt_permutation_on_quotient_domain.as_ref().map_or_else(
728 || RowMajorMatrix::new(vec![], 0),
729 |permutation_on_quotient_domain| {
730 let perms = (0..perm_width)
731 .step_by(ext_degree)
732 .map(|col| {
733 PackedChallenge::<SC>::from_basis_coefficients_fn(|i| {
734 PackedVal::<SC>::from_fn(|offset| {
735 permutation_on_quotient_domain
736 .get((i_start + offset) % perm_height, col + i)
737 .unwrap()
738 })
739 })
740 })
741 .chain((0..perm_width).step_by(ext_degree).map(|col| {
742 PackedChallenge::<SC>::from_basis_coefficients_fn(|i| {
743 PackedVal::<SC>::from_fn(|offset| {
744 permutation_on_quotient_domain
745 .get((i_start + next_step + offset) % perm_height, col + i)
746 .unwrap()
747 })
748 })
749 }));
750
751 RowMajorMatrix::new(perms.collect::<Vec<_>>(), perm_width / ext_degree)
752 },
753 );
754
755 let preprocessed_view = preprocessed
756 .as_ref()
757 .map_or_else(|| RowMajorMatrixView::new(&[], 0), |m| m.as_view());
758 let inner_folder = ProverConstraintFolder {
759 main: main.as_view(),
760 preprocessed: preprocessed_view,
761 preprocessed_window: RowWindow::from_view(&preprocessed_view),
762 public_values,
763 is_first_row,
764 is_last_row,
765 is_transition,
766 base_alpha_powers: &base_alpha_powers,
767 ext_alpha_powers: &ext_alpha_powers,
768 base_constraints: Vec::with_capacity(constraint_layout.base_indices.len()),
769 ext_constraints: Vec::with_capacity(constraint_layout.ext_indices.len()),
770 constraint_index: 0,
771 constraint_count: constraint_layout.total_constraints(),
772 };
773
774 let mut folder = ProverConstraintFolderWithLookups {
775 inner: inner_folder,
776 permutation: permutation.as_view(),
777 permutation_challenges: &packed_perm_challenges,
778 permutation_values: &permutation_vals_packed,
779 };
780 air.eval_with_lookups(&mut folder, lookups, lookup_gadget);
781
782 let quotient = folder.inner.finalize_constraints() * inv_vanishing;
784
785 (0..core::cmp::min(quotient_size, PackedVal::<SC>::WIDTH))
787 .map(move |idx_in_packing| quotient.extract(idx_in_packing))
788 })
789 .collect()
790}