mod tests;
use rand::distributions::{Distribution, Uniform};
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use std::{
collections::{HashSet, VecDeque},
fmt,
panic::{catch_unwind, AssertUnwindSafe},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
time::{Duration, Instant},
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum CircuitError {
#[error("Cannot evaluate an empty circuit")]
EmptyCircuit,
#[error("Error evaluating node: {0}")]
NodeEvaluationError(String),
#[error("Constraint check failed")]
ConstraintCheckFailure,
#[error("Failed to acquire a necessary lock: {0}")]
LockAcquisitionError(String),
#[error("Internal error: expected non-empty layers")]
EmptyLayersError,
}
#[derive(Clone)]
pub enum Gate {
Add(usize, usize),
Multiply(usize, usize),
Hint(usize, Arc<dyn Fn(u32) -> u32 + Send + Sync>),
}
#[derive(Clone)]
pub enum Node {
Input(u32),
Variable,
Operation(Gate, Vec<usize>),
}
pub struct Circuit {
nodes: Vec<Node>, equalities: Vec<(usize, usize)>, layers: Option<Vec<Vec<usize>>>, results: Vec<u32>, total_duration: Duration, number_of_layers: usize, number_of_constraints: usize, total_hint_gates: AtomicUsize, total_gates_processed: AtomicUsize, gates_per_second: f64, }
impl Default for Circuit {
fn default() -> Self {
Self::new()
}
}
impl Circuit {
pub fn new() -> Self {
Circuit {
nodes: Vec::new(),
equalities: Vec::new(),
layers: None,
results: Vec::new(),
total_gates_processed: AtomicUsize::new(0),
total_hint_gates: AtomicUsize::new(0),
total_duration: Duration::new(0, 0),
number_of_layers: 0,
number_of_constraints: 0,
gates_per_second: 0.0,
}
}
fn insert_gate(&mut self, gate: Gate) -> usize {
let dependencies = match &gate {
Gate::Add(left, right) => vec![*left, *right],
Gate::Multiply(left, right) => vec![*left, *right],
Gate::Hint(idx, _) => vec![*idx],
};
self.nodes.push(Node::Operation(gate, dependencies));
self.nodes.len() - 1
}
pub fn init(&mut self) -> usize {
self.nodes.push(Node::Variable);
self.nodes.len() - 1
}
pub fn constant(&mut self, value: u32) -> usize {
self.nodes.push(Node::Input(value));
self.nodes.len() - 1
}
pub fn add(&mut self, idx: usize, idx2: usize) -> usize {
self.insert_gate(Gate::Add(idx, idx2))
}
pub fn mul(&mut self, idx1: usize, idx2: usize) -> usize {
self.insert_gate(Gate::Multiply(idx1, idx2))
}
pub fn hint(&mut self, idx: usize, func: Arc<dyn Fn(u32) -> u32 + Send + Sync>) -> usize {
self.insert_gate(Gate::Hint(idx, func))
}
pub fn assert_equal(&mut self, idx1: usize, idx2: usize) {
self.equalities.push((idx1, idx2));
}
pub fn check_constraints(&self) -> Result<(), CircuitError> {
if self
.equalities
.iter()
.all(|&(idx1, idx2)| self.results[idx1] == self.results[idx2])
{
Ok(())
} else {
Err(CircuitError::ConstraintCheckFailure)
}
}
pub fn evaluate(&mut self, input_vals: &[u32], debug: bool) -> Result<(), CircuitError> {
if self.nodes.is_empty() {
return Err(CircuitError::EmptyCircuit);
}
let mut results = vec![0; self.nodes.len()];
let start_time = Instant::now();
let total_gates_processed = AtomicUsize::new(0);
let total_hint_gates = AtomicUsize::new(0);
self.layerize()?;
self.number_of_layers = self.layers.as_ref().map_or(0, Vec::len);
self.number_of_constraints = self.equalities.len();
if let Some(layers) = &self.layers {
for (i, layer) in layers.iter().enumerate() {
let layer_start = Instant::now();
let layer_results: Result<Vec<_>, CircuitError> = layer
.par_iter() .map(|&node_idx| {
let node = &self.nodes[node_idx];
match node {
Node::Input(value) => Ok(*value),
Node::Variable => Ok(input_vals[node_idx]),
Node::Operation(gate, _) => {
if matches!(gate, Gate::Hint(_, _)) {
total_hint_gates.fetch_add(1, Ordering::Relaxed);
}
total_gates_processed.fetch_add(1, Ordering::Relaxed);
self.evaluate_gate(gate, &results)
}
}
})
.collect();
let layer_results = layer_results?;
let layer_duration = layer_start.elapsed();
for (&node_idx, &result) in layer.iter().zip(layer_results.iter()) {
results[node_idx] = result;
}
if debug {
println!("Layer {}: Processed in {:?}", i + 1, layer_duration);
}
}
}
self.total_hint_gates = total_hint_gates;
self.results = results;
self.total_duration = start_time.elapsed();
if self.total_duration > Duration::ZERO {
self.gates_per_second = total_gates_processed.load(Ordering::Relaxed) as f64
/ self.total_duration.as_secs_f64();
}
self.total_gates_processed = total_gates_processed;
if debug {
println!("{}", self)
}
Ok(())
}
fn evaluate_gate(&self, gate: &Gate, results: &[u32]) -> Result<u32, CircuitError> {
match gate {
Gate::Add(left, right) => Ok(results[*left].saturating_add(results[*right])),
Gate::Multiply(left, right) => Ok(results[*left].saturating_mul(results[*right])),
Gate::Hint(idx, func) => {
let result = catch_unwind(AssertUnwindSafe(|| func(results[*idx])));
result.map_err(|_| CircuitError::NodeEvaluationError("Function panic".to_string()))
}
}
}
fn layerize(&mut self) -> Result<(), CircuitError> {
let nodes = &self.nodes;
let num_nodes = nodes.len();
let in_degree = Arc::new(Mutex::new(vec![0; num_nodes]));
let graph = Arc::new(Mutex::new(vec![vec![]; num_nodes]));
(0..num_nodes)
.into_par_iter()
.try_for_each(|node_idx| -> Result<(), CircuitError> {
if let Node::Operation(_, deps) = &nodes[node_idx] {
let mut graph_lock = graph.lock().map_err(|e| {
CircuitError::LockAcquisitionError(format!("Failed to lock graph: {}", e))
})?;
let mut in_degree_lock = in_degree.lock().map_err(|e| {
CircuitError::LockAcquisitionError(format!(
"Failed to lock in_degree: {}",
e
))
})?;
for &dep in deps {
graph_lock[dep].push(node_idx);
in_degree_lock[node_idx] += 1;
}
}
Ok(())
})?;
let mut queue = VecDeque::new();
let mut layers = Vec::new();
{
let in_deg = in_degree.lock().map_err(|e| {
CircuitError::LockAcquisitionError(format!(
"Failed to lock in_degree for reading: {}",
e
))
})?;
for (i, °ree) in in_deg.iter().enumerate() {
if degree == 0 {
queue.push_back(i);
}
}
}
while !queue.is_empty() {
let current_layer = queue.drain(..).collect::<Vec<_>>();
layers.push(current_layer);
let mut next_layer = HashSet::new();
{
let graph_lock = graph.lock().map_err(|e| {
CircuitError::LockAcquisitionError(format!(
"Failed to lock graph for processing: {}",
e
))
})?;
let mut in_deg_lock = in_degree.lock().map_err(|e| {
CircuitError::LockAcquisitionError(format!(
"Failed to lock in_degree for updating: {}",
e
))
})?;
let last_layer = layers.last().ok_or(CircuitError::EmptyLayersError)?;
for &node_idx in last_layer {
for &dependent in &graph_lock[node_idx] {
in_deg_lock[dependent] -= 1;
if in_deg_lock[dependent] == 0 {
next_layer.insert(dependent);
}
}
}
}
for node in next_layer {
queue.push_back(node);
}
}
self.layers = Some(layers);
Ok(())
}
pub fn generate_random(&mut self, num_gates: usize) {
let num_inputs = 10;
for _ in 0..num_inputs {
self.constant(rand::random::<u32>() % 100);
}
let custom_funcs: Vec<Arc<dyn Fn(u32) -> u32 + Send + Sync>> =
vec![Arc::new(|x| (x as f32).sqrt().round() as u32)];
let mut rng = rand::thread_rng();
let gate_dist = Uniform::from(0..3); let index_dist = Uniform::from(0..self.nodes.len());
let func_dist = Uniform::from(0..custom_funcs.len());
for _ in 0..num_gates {
let gate_type = gate_dist.sample(&mut rng);
let idx1 = index_dist.sample(&mut rng);
match gate_type {
0 => {
self.add(idx1, index_dist.sample(&mut rng));
}
1 => {
self.mul(idx1, index_dist.sample(&mut rng));
}
2 => {
let func_idx = func_dist.sample(&mut rng);
let func_node = self.hint(idx1, custom_funcs[func_idx].clone());
let verification_node =
self.apply_equality_constraint(func_node, func_idx, idx1);
self.assert_equal(idx1, verification_node);
}
_ => unreachable!(),
}
}
}
fn apply_equality_constraint(
&mut self,
func_node: usize,
func_idx: usize,
original_idx: usize,
) -> usize {
match func_idx {
3 => self.hint(func_node, Arc::new(|x| x * x)), _ => original_idx,
}
}
}
impl fmt::Display for Circuit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Circuit Evaluation Summary:")?;
writeln!(f, "Total evaluation time: {:?}", self.total_duration)?;
writeln!(f, "Number of layers: {}", self.number_of_layers)?;
writeln!(f, "Number of constraints: {}", self.number_of_constraints)?;
writeln!(
f,
"Number of hint gates processed: {}",
self.total_hint_gates.load(Ordering::Relaxed)
)?;
writeln!(
f,
"Total gates processed: {}",
self.total_gates_processed.load(Ordering::Relaxed)
)?;
writeln!(
f,
"Gates processed per second: {:.2}",
self.gates_per_second
)?;
Ok(())
}
}