use std::collections::{BTreeMap, HashMap};
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, OnceLock};
use itertools::Itertools;
use rayon::slice::ParallelSliceMut;
use crate::halo2_proofs::{
circuit::{Cell, Region},
plonk::{Assigned, Column, Fixed},
};
use crate::utils::halo2::{raw_assign_fixed, raw_constrain_equal, Halo2AssignedCell};
use crate::AssignedValue;
use crate::{ff::Field, ContextCell};
use super::manager::VirtualRegionManager;
pub const EXTERNAL_CELL_TYPE_ID: &str = "halo2-base:External Raw Halo2 Cell";
pub type SharedCopyConstraintManager<F> = Arc<Mutex<CopyConstraintManager<F>>>;
#[derive(Clone, Default, Debug)]
pub struct CopyConstraintManager<F: Field + Ord> {
pub advice_equalities: Vec<(ContextCell, ContextCell)>,
pub constant_equalities: Vec<(F, ContextCell)>,
external_cell_count: usize,
pub assigned_advices: HashMap<ContextCell, Cell>,
pub assigned_constants: BTreeMap<F, Cell>,
assigned: OnceLock<()>,
}
impl<F: Field + Ord> CopyConstraintManager<F> {
pub fn num_distinct_constants(&self) -> usize {
self.constant_equalities.iter().map(|(x, _)| x).sorted().dedup().count()
}
pub fn load_external_assigned(
&mut self,
assigned_cell: Halo2AssignedCell<F>,
) -> AssignedValue<F> {
let context_cell = self.load_external_cell(assigned_cell.cell());
let mut value = Assigned::Trivial(F::ZERO);
assigned_cell.value().map(|v| {
value = **v;
});
AssignedValue { value, cell: Some(context_cell) }
}
pub fn load_external_cell(&mut self, cell: Cell) -> ContextCell {
self.load_external_cell_impl(Some(cell))
}
pub fn mock_external_assigned(&mut self, v: F) -> AssignedValue<F> {
let context_cell = self.load_external_cell_impl(None);
AssignedValue { value: Assigned::Trivial(v), cell: Some(context_cell) }
}
fn load_external_cell_impl(&mut self, cell: Option<Cell>) -> ContextCell {
let context_cell = ContextCell::new(EXTERNAL_CELL_TYPE_ID, 0, self.external_cell_count);
self.external_cell_count += 1;
if let Some(cell) = cell {
if let Some(old_cell) = self.assigned_advices.insert(context_cell, cell) {
assert!(
old_cell.row_offset == cell.row_offset && old_cell.column == cell.column,
"External cell already assigned"
)
}
}
context_cell
}
pub fn clear(&mut self) {
self.advice_equalities.clear();
self.constant_equalities.clear();
self.assigned_advices.clear();
self.assigned_constants.clear();
self.external_cell_count = 0;
self.assigned.take();
}
}
impl<F: Field + Ord> Drop for CopyConstraintManager<F> {
fn drop(&mut self) {
if self.assigned.get().is_some() {
return;
}
if !self.advice_equalities.is_empty() {
log::warn!("WARNING: advice_equalities not empty");
}
if !self.constant_equalities.is_empty() {
log::warn!("WARNING: constant_equalities not empty");
}
}
}
impl<F: Field + Ord> VirtualRegionManager<F> for SharedCopyConstraintManager<F> {
type Config = Vec<Column<Fixed>>;
type Assignment = ();
fn assign_raw(&self, config: &Self::Config, region: &mut Region<F>) -> Self::Assignment {
let mut guard = self.lock().unwrap();
let manager = guard.deref_mut();
manager
.constant_equalities
.par_sort_unstable_by(|(c1, cell1), (c2, cell2)| c1.cmp(c2).then(cell1.cmp(cell2)));
let mut fixed_col = 0;
let mut fixed_offset = 0;
for (c, _) in manager.constant_equalities.iter() {
if !manager.assigned_constants.contains_key(c) {
let cell = raw_assign_fixed(region, config[fixed_col], fixed_offset, *c);
manager.assigned_constants.insert(*c, cell);
fixed_col += 1;
if fixed_col >= config.len() {
fixed_col = 0;
fixed_offset += 1;
}
}
}
manager.advice_equalities.par_sort_unstable();
for (left, right) in &manager.advice_equalities {
let left = manager.assigned_advices.get(left).expect("virtual cell not assigned");
let right = manager.assigned_advices.get(right).expect("virtual cell not assigned");
raw_constrain_equal(region, *left, *right);
}
for (left, right) in &manager.constant_equalities {
let left = manager.assigned_constants[left];
let right = manager.assigned_advices.get(right).expect("virtual cell not assigned");
raw_constrain_equal(region, left, *right);
}
let _ = manager.assigned.set(());
manager.assigned_constants.clear();
}
}