halo2_base/gates/circuit/
builder.rs

1use std::sync::{Arc, Mutex};
2
3use getset::{Getters, MutGetters, Setters};
4use itertools::Itertools;
5
6use crate::{
7    gates::{
8        circuit::CircuitBuilderStage,
9        flex_gate::{
10            threads::{GateStatistics, MultiPhaseCoreManager, SinglePhaseCoreManager},
11            MultiPhaseThreadBreakPoints, MAX_PHASE,
12        },
13        range::RangeConfig,
14        RangeChip,
15    },
16    halo2_proofs::{
17        circuit::{Layouter, Region},
18        plonk::{Column, Instance},
19    },
20    utils::ScalarField,
21    virtual_region::{
22        copy_constraints::{CopyConstraintManager, SharedCopyConstraintManager},
23        lookups::LookupAnyManager,
24        manager::VirtualRegionManager,
25    },
26    AssignedValue, Context,
27};
28
29use super::BaseCircuitParams;
30
31/// Keeping the naming `RangeCircuitBuilder` for backwards compatibility.
32pub type RangeCircuitBuilder<F> = BaseCircuitBuilder<F>;
33
34/// A circuit builder is a collection of virtual region managers that together assign virtual
35/// regions into a single physical circuit.
36///
37/// [BaseCircuitBuilder] is a circuit builder to create a circuit where the columns correspond to [super::BaseConfig].
38/// This builder can hold multiple threads, but the `Circuit` implementation only evaluates the first phase.
39/// The user will have to implement a separate `Circuit` with multi-phase witness generation logic.
40///
41/// This is used to manage the virtual region corresponding to [super::FlexGateConfig] and (optionally) [RangeConfig].
42/// This can be used even if only using [`GateChip`](crate::gates::flex_gate::GateChip) without [RangeChip].
43///
44/// The circuit will have `NI` public instance (aka public inputs+outputs) columns.
45#[derive(Clone, Debug, Getters, MutGetters, Setters)]
46pub struct BaseCircuitBuilder<F: ScalarField> {
47    /// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic.
48    #[getset(get = "pub", get_mut = "pub", set = "pub")]
49    pub(super) core: MultiPhaseCoreManager<F>,
50    /// The range lookup manager
51    #[getset(get = "pub", get_mut = "pub", set = "pub")]
52    pub(super) lookup_manager: [LookupAnyManager<F, 1>; MAX_PHASE],
53    /// Configuration parameters for the circuit shape
54    pub config_params: BaseCircuitParams,
55    /// The assigned instances to expose publicly at the end of circuit synthesis
56    pub assigned_instances: Vec<Vec<AssignedValue<F>>>,
57}
58
59impl<F: ScalarField> Default for BaseCircuitBuilder<F> {
60    /// Quick start default circuit builder which can be used for MockProver, Keygen, and real prover.
61    /// For best performance during real proof generation, we recommend using [BaseCircuitBuilder::prover] instead.
62    fn default() -> Self {
63        Self::new(false)
64    }
65}
66
67impl<F: ScalarField> BaseCircuitBuilder<F> {
68    /// Creates a new [BaseCircuitBuilder] with all default managers.
69    /// * `witness_gen_only`:
70    ///     * If true, the builder only does witness asignments and does not store constraint information -- this should only be used for the real prover.
71    ///     * If false, the builder also imposes constraints (selectors, fixed columns, copy constraints). Primarily used for keygen and mock prover (but can also be used for real prover).
72    ///
73    /// By default, **no** circuit configuration parameters have been set.
74    /// These should be set separately using `use_params`, or `use_k`, `use_lookup_bits`, and `calculate_params`.
75    ///
76    /// Upon construction, there are no public instances (aka all witnesses are private).
77    /// The intended usage is that _before_ calling `synthesize`, witness generation can be done to populate
78    /// assigned instances, which are supplied as `assigned_instances` to this struct.
79    /// The `Circuit` implementation for this struct will then expose these instances and constrain
80    /// them using the Halo2 API.
81    pub fn new(witness_gen_only: bool) -> Self {
82        let core = MultiPhaseCoreManager::new(witness_gen_only);
83        let lookup_manager = [(); MAX_PHASE]
84            .map(|_| LookupAnyManager::new(witness_gen_only, core.copy_manager.clone()));
85        Self { core, lookup_manager, config_params: Default::default(), assigned_instances: vec![] }
86    }
87
88    /// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only.
89    pub fn from_stage(stage: CircuitBuilderStage) -> Self {
90        Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen)
91    }
92
93    /// Creates a new [BaseCircuitBuilder] with a pinned circuit configuration given by `config_params` and `break_points`.
94    pub fn prover(
95        config_params: BaseCircuitParams,
96        break_points: MultiPhaseThreadBreakPoints,
97    ) -> Self {
98        Self::new(true).use_params(config_params).use_break_points(break_points)
99    }
100
101    /// Sets the copy manager to the given one in all shared references.
102    pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager<F>) {
103        for lm in &mut self.lookup_manager {
104            lm.set_copy_manager(copy_manager.clone());
105        }
106        self.core.set_copy_manager(copy_manager);
107    }
108
109    /// Returns `self` with a given copy manager
110    pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager<F>) -> Self {
111        self.set_copy_manager(copy_manager);
112        self
113    }
114
115    /// Deep clone of `self`, where the underlying object of shared references in [SharedCopyConstraintManager] and [LookupAnyManager] are cloned.
116    pub fn deep_clone(&self) -> Self {
117        let cm: CopyConstraintManager<F> = self.core.copy_manager.lock().unwrap().clone();
118        let cm_ref = Arc::new(Mutex::new(cm));
119        let mut clone = self.clone().use_copy_manager(cm_ref.clone());
120        for lm in &mut clone.lookup_manager {
121            *lm = lm.deep_clone(cm_ref.clone());
122        }
123        clone
124    }
125
126    /// The log_2 size of the lookup table, if using.
127    pub fn lookup_bits(&self) -> Option<usize> {
128        self.config_params.lookup_bits
129    }
130
131    /// Set lookup bits
132    pub fn set_lookup_bits(&mut self, lookup_bits: usize) {
133        self.config_params.lookup_bits = Some(lookup_bits);
134    }
135
136    /// Returns new with lookup bits
137    pub fn use_lookup_bits(mut self, lookup_bits: usize) -> Self {
138        self.set_lookup_bits(lookup_bits);
139        self
140    }
141
142    /// Sets new `k` = log2 of domain
143    pub fn set_k(&mut self, k: usize) {
144        self.config_params.k = k;
145    }
146
147    /// Returns new with `k` set
148    pub fn use_k(mut self, k: usize) -> Self {
149        self.set_k(k);
150        self
151    }
152
153    /// Set the number of instance columns. This resizes `self.assigned_instances`.
154    pub fn set_instance_columns(&mut self, num_instance_columns: usize) {
155        self.config_params.num_instance_columns = num_instance_columns;
156        while self.assigned_instances.len() < num_instance_columns {
157            self.assigned_instances.push(vec![]);
158        }
159        assert_eq!(self.assigned_instances.len(), num_instance_columns);
160    }
161
162    /// Returns new with `self.assigned_instances` resized to specified number of instance columns.
163    pub fn use_instance_columns(mut self, num_instance_columns: usize) -> Self {
164        self.set_instance_columns(num_instance_columns);
165        self
166    }
167
168    /// Set config params
169    pub fn set_params(&mut self, params: BaseCircuitParams) {
170        self.set_instance_columns(params.num_instance_columns);
171        self.config_params = params;
172    }
173
174    /// Returns new with config params
175    pub fn use_params(mut self, params: BaseCircuitParams) -> Self {
176        self.set_params(params);
177        self
178    }
179
180    /// The break points of the circuit.
181    pub fn break_points(&self) -> MultiPhaseThreadBreakPoints {
182        self.core
183            .phase_manager
184            .iter()
185            .map(|pm| pm.break_points.borrow().as_ref().expect("break points not set").clone())
186            .collect()
187    }
188
189    /// Sets the break points of the circuit.
190    pub fn set_break_points(&mut self, break_points: MultiPhaseThreadBreakPoints) {
191        if break_points.is_empty() {
192            return;
193        }
194        self.core.touch(break_points.len() - 1);
195        for (pm, bp) in self.core.phase_manager.iter().zip_eq(break_points) {
196            *pm.break_points.borrow_mut() = Some(bp);
197        }
198    }
199
200    /// Returns new with break points
201    pub fn use_break_points(mut self, break_points: MultiPhaseThreadBreakPoints) -> Self {
202        self.set_break_points(break_points);
203        self
204    }
205
206    /// Returns if the circuit is only used for witness generation.
207    pub fn witness_gen_only(&self) -> bool {
208        self.core.witness_gen_only()
209    }
210
211    /// Creates a new [MultiPhaseCoreManager] with `use_unknown` flag set.
212    /// * `use_unknown`: If true, during key generation witness `Value`s are replaced with `Value::unknown()` for safety.
213    pub fn unknown(mut self, use_unknown: bool) -> Self {
214        self.core = self.core.unknown(use_unknown);
215        self
216    }
217
218    /// Clears state and copies, effectively resetting the circuit builder.
219    pub fn clear(&mut self) {
220        self.core.clear();
221        for lm in &mut self.lookup_manager {
222            lm.clear();
223        }
224        self.assigned_instances.iter_mut().for_each(|c| c.clear());
225    }
226
227    /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists.
228    /// * `phase`: The challenge phase (as an index) of the gate thread.
229    pub fn main(&mut self, phase: usize) -> &mut Context<F> {
230        self.core.main(phase)
231    }
232
233    /// Returns [SinglePhaseCoreManager] with the virtual region with all core threads in the given phase.
234    pub fn pool(&mut self, phase: usize) -> &mut SinglePhaseCoreManager<F> {
235        self.core.phase_manager.get_mut(phase).unwrap()
236    }
237
238    /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread.
239    /// * `phase`: The phase (index) of the gate thread.
240    pub fn new_thread(&mut self, phase: usize) -> &mut Context<F> {
241        self.core.new_thread(phase)
242    }
243
244    /// Returns some statistics about the virtual region.
245    pub fn statistics(&self) -> RangeStatistics {
246        let gate = self.core.statistics();
247        let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
248        RangeStatistics { gate, total_lookup_advice_per_phase }
249    }
250
251    fn total_lookup_advice_per_phase(&self) -> Vec<usize> {
252        self.lookup_manager.iter().map(|lm| lm.total_rows()).collect()
253    }
254
255    /// Auto-calculates configuration parameters for the circuit and sets them.
256    ///
257    /// * `k`: The number of in the circuit (i.e. numeber of rows = 2<sup>k</sup>)
258    /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0.
259    /// * `lookup_bits`: The fixed lookup table will consist of [0, 2<sup>lookup_bits</sup>)
260    pub fn calculate_params(&mut self, minimum_rows: Option<usize>) -> BaseCircuitParams {
261        let k = self.config_params.k;
262        let ni = self.config_params.num_instance_columns;
263        assert_ne!(k, 0, "k must be set");
264        let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
265        let gate_params = self.core.calculate_params(k, minimum_rows);
266        let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
267        let num_lookup_advice_per_phase = total_lookup_advice_per_phase
268            .iter()
269            .map(|count| count.div_ceil(max_rows))
270            .collect::<Vec<_>>();
271
272        let params = BaseCircuitParams {
273            k: gate_params.k,
274            num_advice_per_phase: gate_params.num_advice_per_phase,
275            num_fixed: gate_params.num_fixed,
276            num_lookup_advice_per_phase,
277            lookup_bits: self.lookup_bits(),
278            num_instance_columns: ni,
279        };
280        self.config_params = params.clone();
281        #[cfg(feature = "display")]
282        {
283            println!("Total range check advice cells to lookup per phase: {total_lookup_advice_per_phase:?}");
284            log::info!("Auto-calculated config params:\n {params:#?}");
285        }
286        params
287    }
288
289    /// Copies `assigned_instances` to the instance columns. Should only be called at the very end of
290    /// `synthesize` after virtual `assigned_instances` have been assigned to physical circuit.
291    pub fn assign_instances(
292        &self,
293        instance_columns: &[Column<Instance>],
294        mut layouter: impl Layouter<F>,
295    ) {
296        if !self.core.witness_gen_only() {
297            // expose public instances
298            for (instances, instance_col) in self.assigned_instances.iter().zip_eq(instance_columns)
299            {
300                for (i, instance) in instances.iter().enumerate() {
301                    let cell = instance.cell.unwrap();
302                    let copy_manager = self.core.copy_manager.lock().unwrap();
303                    let cell =
304                        copy_manager.assigned_advices.get(&cell).expect("instance not assigned");
305                    layouter.constrain_instance(*cell, *instance_col, i);
306                }
307            }
308        }
309    }
310
311    /// Creates a new [RangeChip] sharing the same [LookupAnyManager]s as `self`.
312    pub fn range_chip(&self) -> RangeChip<F> {
313        RangeChip::new(
314            self.config_params.lookup_bits.expect("lookup bits not set"),
315            self.lookup_manager.clone(),
316        )
317    }
318
319    /// Copies the queued cells to be range looked up in phase `phase` to special advice lookup columns
320    /// using [LookupAnyManager].
321    ///
322    /// ## Special case
323    /// Just for [RangeConfig], we have special handling for the case where there is a single (physical)
324    /// advice column in [super::FlexGateConfig]. In this case, `RangeConfig` does not create extra lookup advice columns,
325    /// the single advice column has lookup enabled, and there is a selector to toggle when lookup should
326    /// be turned on.
327    pub fn assign_lookups_in_phase(
328        &self,
329        config: &RangeConfig<F>,
330        region: &mut Region<F>,
331        phase: usize,
332    ) {
333        let lookup_manager = self.lookup_manager.get(phase).expect("too many phases");
334        if lookup_manager.total_rows() == 0 {
335            return;
336        }
337        if let Some(q_lookup) = config.q_lookup.get(phase).and_then(|q| *q) {
338            // if q_lookup is Some, that means there should be a single advice column and it has lookup enabled
339            assert_eq!(config.gate.basic_gates[phase].len(), 1);
340            if !self.witness_gen_only() {
341                let cells_to_lookup = lookup_manager.cells_to_lookup.lock().unwrap();
342                for advice in cells_to_lookup.iter().flat_map(|(_, advices)| advices) {
343                    let cell = advice[0].cell.as_ref().unwrap();
344                    let copy_manager = self.core.copy_manager.lock().unwrap();
345                    let acell = copy_manager.assigned_advices[cell];
346                    assert_eq!(
347                        acell.column,
348                        config.gate.basic_gates[phase][0].value.into(),
349                        "lookup column does not match"
350                    );
351                    q_lookup.enable(region, acell.row_offset).unwrap();
352                }
353            }
354        } else {
355            let lookup_cols = config
356                .lookup_advice
357                .get(phase)
358                .expect("No special lookup advice columns")
359                .iter()
360                .map(|c| [*c])
361                .collect_vec();
362            lookup_manager.assign_raw(&lookup_cols, region);
363        }
364        let _ = lookup_manager.assigned.set(());
365    }
366}
367
368/// Basic statistics
369pub struct RangeStatistics {
370    /// Number of advice cells for the basic gate and total constants used
371    pub gate: GateStatistics,
372    /// Total special advice cells that need to be looked up, per phase
373    pub total_lookup_advice_per_phase: Vec<usize>,
374}
375
376impl<F: ScalarField> AsRef<BaseCircuitBuilder<F>> for BaseCircuitBuilder<F> {
377    fn as_ref(&self) -> &BaseCircuitBuilder<F> {
378        self
379    }
380}
381
382impl<F: ScalarField> AsMut<BaseCircuitBuilder<F>> for BaseCircuitBuilder<F> {
383    fn as_mut(&mut self) -> &mut BaseCircuitBuilder<F> {
384        self
385    }
386}