use std::sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc, Mutex, RwLock,
};
use rayon::prelude::*;
use proofman_fields::PrimeField64;
use proofman_witness::WitnessComponent;
use proofman_common::{AirInstance, BufferPool, ProofCtx, ProofmanError, ProofmanResult, SetupCtx, TraceInfo};
use proofman_hints::{get_hint_field_constant_a, get_hint_ids_by_name, HintFieldOptions, HintFieldValue};
use crate::{get_hint_field_constant_as, validate_binary_field, AirComponent, extract_field_element_as_usize};
#[derive(Debug, Clone)]
pub struct SpecifiedRange {
acc_height: usize,
min: i64,
}
pub struct SpecifiedRanges<F: PrimeField64> {
airgroup_id: usize,
air_id: usize,
shift: usize,
mask: usize,
num_rows: usize,
num_cols: usize,
multiplicities: Vec<AtomicU64>,
table_instance_id: AtomicU64,
calculated: AtomicBool,
ranges: Vec<SpecifiedRange>,
shared_tables: bool,
trace_buffer: Arc<Mutex<Option<Vec<F>>>>,
}
impl<F: PrimeField64> AirComponent<F> for SpecifiedRanges<F> {
fn new(
pctx: &ProofCtx<F>,
sctx: &SetupCtx<F>,
airgroup_id: usize,
air_id: usize,
shared_tables: bool,
) -> ProofmanResult<Arc<Self>> {
let num_rows = pctx.global_info.airs[airgroup_id][air_id].num_rows;
let setup = sctx.get_setup(airgroup_id, air_id)?;
let hint_opt = HintFieldOptions::default();
let hint_id = get_hint_ids_by_name(setup.p_setup.p_expressions_bin, "specified_ranges_data")[0] as usize;
let num_muls = get_hint_field_constant_as::<u64, F>(
pctx,
setup,
airgroup_id,
air_id,
hint_id,
"num_muls",
hint_opt.clone(),
)?;
let mins =
get_hint_field_constant_a::<F>(pctx, setup, airgroup_id, air_id, hint_id, "mins", hint_opt.clone())?.values;
let mins_neg =
get_hint_field_constant_a::<F>(pctx, setup, airgroup_id, air_id, hint_id, "mins_neg", hint_opt.clone())?
.values;
let opids_count = get_hint_field_constant_as::<u64, F>(
pctx,
setup,
airgroup_id,
air_id,
hint_id,
"opids_count",
hint_opt.clone(),
)?;
let acc_heights =
get_hint_field_constant_a::<F>(pctx, setup, airgroup_id, air_id, hint_id, "acc_heights", hint_opt)?.values;
let mut ranges = Vec::with_capacity(opids_count as usize);
for ((min_hint, min_neg_hint), acc_heights_hint) in mins.iter().zip(mins_neg.iter()).zip(acc_heights.iter()) {
let min = match min_hint {
HintFieldValue::Field(f) => f.as_canonical_u64(),
_ => return Err(ProofmanError::StdError("min hint must be a field element".to_string())),
};
let min_neg = match min_neg_hint {
HintFieldValue::Field(f) => validate_binary_field(*f, "Min neg")?,
_ => return Err(ProofmanError::StdError("min neg hint must be a field element".to_string())),
};
let min = if min_neg { min as i128 - F::ORDER_U64 as i128 } else { min as i128 };
let acc_heights = extract_field_element_as_usize(acc_heights_hint, "Acc Heights")?;
ranges.push(SpecifiedRange { acc_height: acc_heights, min: min as i64 });
}
let num_cols = num_muls as usize;
let multiplicities: Vec<AtomicU64> =
(0..(num_cols * num_rows)).into_par_iter().map(|_| AtomicU64::new(0)).collect();
let trace_buffer = Arc::new(Mutex::new(Some(vec![F::ZERO; num_cols * num_rows])));
Ok(Arc::new(Self {
airgroup_id,
air_id,
shift: num_rows.trailing_zeros() as usize,
mask: num_rows - 1,
num_cols,
num_rows,
multiplicities,
table_instance_id: AtomicU64::new(0),
calculated: AtomicBool::new(false),
ranges,
shared_tables,
trace_buffer,
}))
}
}
impl<F: PrimeField64> SpecifiedRanges<F> {
pub fn get_global_row(range_min: i64, value: i64) -> u64 {
(value - range_min) as u64
}
pub fn get_global_rows(range_min: i64, values: &[i64]) -> Vec<u64> {
values.iter().map(|&v| Self::get_global_row(range_min, v)).collect()
}
pub fn get_global_rows_into(range_min: i64, values: &[i64], out: &mut Vec<u64>) {
out.clear();
out.extend(values.iter().map(|&v| Self::get_global_row(range_min, v)));
}
#[inline]
fn update(&self, table_offset: usize, range_min: i64, iter: impl Iterator<Item = (i64, u64)>) {
if self.calculated.load(Ordering::Relaxed) {
return;
}
for (value, multiplicity) in iter {
if multiplicity == 0 {
continue;
}
let val_offset = (value - range_min) as usize;
let offset = table_offset + val_offset;
let mul_idx = offset >> self.shift;
let row_idx = offset & self.mask;
self.multiplicities[mul_idx * self.num_rows + row_idx].fetch_add(multiplicity, Ordering::Relaxed);
}
}
pub fn update_value(&self, id: usize, value: i64, multiplicity: u64) {
let range = &self.ranges[id];
self.update(range.acc_height, range.min, std::iter::once((value, multiplicity)));
}
pub fn update_values(&self, id: usize, values: &[i64], multiplicities: &[u64]) {
debug_assert!(!values.is_empty() && values.len() == multiplicities.len());
let range = &self.ranges[id];
self.update(range.acc_height, range.min, values.iter().copied().zip(multiplicities.iter().copied()));
}
pub fn update_values_same_mul(&self, id: usize, values: &[i64], multiplicity: u64) {
debug_assert!(!values.is_empty());
let range = &self.ranges[id];
self.update(range.acc_height, range.min, values.iter().copied().map(|v| (v, multiplicity)));
}
pub fn update_pairs(&self, id: usize, pairs: impl Iterator<Item = (i64, u64)>) {
let range = &self.ranges[id];
self.update(range.acc_height, range.min, pairs);
}
pub fn airgroup_id(&self) -> usize {
self.airgroup_id
}
pub fn air_id(&self) -> usize {
self.air_id
}
}
impl<F: PrimeField64 + Send + Sync + 'static> WitnessComponent<F> for SpecifiedRanges<F> {
fn execute(
&self,
pctx: Arc<ProofCtx<F>>,
_sctx: Arc<SetupCtx<F>>,
_global_ids: &RwLock<Vec<usize>>,
) -> ProofmanResult<()> {
let (instance_found, mut table_instance_id) = pctx.dctx_find_process_table(self.airgroup_id, self.air_id)?;
if !instance_found {
if !self.shared_tables {
table_instance_id = pctx.add_table_all(self.airgroup_id, self.air_id)?;
} else {
table_instance_id = pctx.add_table(self.airgroup_id, self.air_id)?;
}
}
self.calculated.store(false, Ordering::Relaxed);
self.multiplicities.par_iter().for_each(|v| {
v.store(0, Ordering::Relaxed);
});
self.table_instance_id.store(table_instance_id as u64, Ordering::SeqCst);
Ok(())
}
fn pre_calculate_witness(
&self,
_stage: u32,
_pctx: Arc<ProofCtx<F>>,
_sctx: Arc<SetupCtx<F>>,
_instance_ids: &[usize],
_n_cores: usize,
_buffer_pool: &dyn BufferPool<F>,
) -> ProofmanResult<()> {
Ok(())
}
fn calculate_witness(
&self,
stage: u32,
pctx: Arc<ProofCtx<F>>,
sctx: Arc<SetupCtx<F>>,
_instance_ids: &[usize],
_n_cores: usize,
_buffer_pool: &dyn BufferPool<F>,
) -> ProofmanResult<()> {
if stage == 1 {
let table_instance_id = self.table_instance_id.load(Ordering::Relaxed) as usize;
let instance_id = pctx.dctx_get_table_instance_idx(table_instance_id)?;
if !_instance_ids.contains(&instance_id) {
return Ok(());
}
self.calculated.store(true, Ordering::Relaxed);
if self.shared_tables {
let owner_idx = pctx.dctx_get_process_owner_instance(instance_id)?;
pctx.mpi_ctx.distribute_multiplicities(&self.multiplicities, self.num_cols, self.num_rows, owner_idx);
}
if !self.shared_tables || pctx.dctx_is_my_process_instance(instance_id)? {
let buffer_size = self.num_cols * self.num_rows;
let mut buffer = self
.trace_buffer
.lock()
.unwrap()
.take()
.expect("SpecifiedRanges trace_buffer must be populated by reclaim before calculate_witness");
debug_assert_eq!(buffer.len(), buffer_size);
let any_nonzero = AtomicBool::new(false);
let num_rows = self.num_rows;
buffer.par_chunks_mut(self.num_cols).enumerate().for_each(|(row, chunk)| {
for (col, slot) in chunk.iter_mut().enumerate() {
let v = self.multiplicities[col * num_rows + row].load(Ordering::Relaxed);
if v != 0 {
any_nonzero.store(true, Ordering::Relaxed);
}
*slot = F::from_u64(v);
}
});
if !any_nonzero.load(Ordering::Relaxed) {
tracing::info!(
"Skipping uninitialized specified ranges table (airgroup_id: {}, air_id: {})",
self.airgroup_id,
self.air_id
);
pctx.dctx_skip_process_instance(instance_id);
*self.trace_buffer.lock().unwrap() = Some(buffer);
return Ok(());
}
let setup = sctx.get_setup(self.airgroup_id, self.air_id)?;
let n_cols = setup.stark_info.map_sections_n["cm1"] as usize;
let air_instance = AirInstance::new(
TraceInfo::new(self.airgroup_id, self.air_id, n_cols, self.num_rows, buffer, false, false)
.with_reclaim_slot(self.trace_buffer.clone()),
);
pctx.add_air_instance(air_instance, instance_id);
}
}
Ok(())
}
}