mirror of
https://github.com/powdr-labs/powdr.git
synced 2026-05-13 03:00:26 -04:00
Split out evaluator.
This commit is contained in:
615
src/commit_evaluator/evaluator.rs
Normal file
615
src/commit_evaluator/evaluator.rs
Normal file
@@ -0,0 +1,615 @@
|
||||
use crate::analyzer::{BinaryOperator, Expression, Identity, IdentityKind, UnaryOperator};
|
||||
use crate::number::{abstract_to_degree, is_zero};
|
||||
use crate::utils::indent;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
// TODO should use finite field instead of abstract number
|
||||
use crate::number::{AbstractNumberType, DegreeType};
|
||||
|
||||
use super::affine_expression::AffineExpression;
|
||||
use super::eval_error::{self, EvalError};
|
||||
use super::WitnessColumn;
|
||||
|
||||
type EvalResult = Result<Vec<(usize, AbstractNumberType)>, EvalError>;
|
||||
|
||||
pub struct Evaluator<'a, QueryCallback>
|
||||
where
|
||||
QueryCallback: FnMut(&'a str) -> Option<AbstractNumberType>,
|
||||
{
|
||||
identities: Vec<&'a Identity>,
|
||||
constants: &'a HashMap<String, AbstractNumberType>,
|
||||
fixed_cols: HashMap<&'a String, &'a Vec<AbstractNumberType>>,
|
||||
query_callback: Option<QueryCallback>,
|
||||
/// Maps the committed polynomial names to their IDs internal to this component
|
||||
/// and optional parameter and query string.
|
||||
committed: BTreeMap<&'a String, &'a WitnessColumn<'a>>,
|
||||
committed_names: Vec<&'a String>,
|
||||
/// Values of the committed polynomials
|
||||
current: Vec<Option<AbstractNumberType>>,
|
||||
/// Values of the committed polynomials in the next row
|
||||
next: Vec<Option<AbstractNumberType>>,
|
||||
next_row: DegreeType,
|
||||
failure_reasons: Vec<String>,
|
||||
progress: bool,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum EvaluationRow {
|
||||
/// p is p[next_row - 1], p' is p[next_row]
|
||||
Current,
|
||||
/// p is p[next_row], p' is p[next_row + 1]
|
||||
Next,
|
||||
/// p is p[arg], p' is p[arg + 1]
|
||||
Specific(DegreeType),
|
||||
}
|
||||
|
||||
impl<'a, QueryCallback> Evaluator<'a, QueryCallback>
|
||||
where
|
||||
QueryCallback: FnMut(&str) -> Option<AbstractNumberType>,
|
||||
{
|
||||
pub fn new(
|
||||
constants: &'a HashMap<String, AbstractNumberType>,
|
||||
identities: Vec<&'a Identity>,
|
||||
fixed_cols: HashMap<&'a String, &'a Vec<AbstractNumberType>>,
|
||||
witness_cols: &'a Vec<WitnessColumn<'a>>,
|
||||
query_callback: Option<QueryCallback>,
|
||||
) -> Self {
|
||||
Evaluator {
|
||||
constants,
|
||||
identities,
|
||||
fixed_cols,
|
||||
query_callback,
|
||||
committed: witness_cols.iter().map(|p| (p.name, p)).collect(),
|
||||
committed_names: witness_cols.iter().map(|p| p.name).collect(),
|
||||
current: vec![None; witness_cols.len()],
|
||||
next: vec![None; witness_cols.len()],
|
||||
next_row: 0,
|
||||
failure_reasons: vec![],
|
||||
progress: true,
|
||||
verbose: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_verbose(&mut self, verbose: bool) {
|
||||
self.verbose = verbose;
|
||||
}
|
||||
|
||||
pub fn compute_next_row(&mut self, next_row: DegreeType) -> Vec<AbstractNumberType> {
|
||||
self.next_row = next_row;
|
||||
|
||||
// TODO maybe better to generate a dependency graph than looping multiple times.
|
||||
// TODO at least we could cache the affine expressions between loops.
|
||||
|
||||
let mut identity_failed;
|
||||
loop {
|
||||
identity_failed = false;
|
||||
self.progress = false;
|
||||
self.failure_reasons.clear();
|
||||
|
||||
// TODO avoid clone
|
||||
for identity in &self.identities.clone() {
|
||||
let result = match identity.kind {
|
||||
IdentityKind::Polynomial => {
|
||||
self.process_polynomial_identity(identity.left.selector.as_ref().unwrap())
|
||||
}
|
||||
IdentityKind::Plookup => self.process_plookup(identity),
|
||||
_ => Ok(vec![]),
|
||||
}
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"No progress on {identity}:\n{}",
|
||||
indent(&format!("{err}"), " ")
|
||||
)
|
||||
.into()
|
||||
});
|
||||
if result.is_err() {
|
||||
identity_failed = true;
|
||||
}
|
||||
self.handle_eval_result(result);
|
||||
}
|
||||
if self.query_callback.is_some() {
|
||||
// TODO avoid clone
|
||||
for column in self.committed.clone().values() {
|
||||
if !self.has_known_next_value(column.id) && column.query.is_some() {
|
||||
let result = self.process_witness_query(column);
|
||||
self.handle_eval_result(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.progress {
|
||||
break;
|
||||
}
|
||||
if self.next.iter().all(|v| v.is_some()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Identity check failure on the first row is not fatal. We will proceed with
|
||||
// "unknown", report zero and re-check the wrap-around against the zero values at the end.
|
||||
if identity_failed && next_row != 0 {
|
||||
eprintln!(
|
||||
"\nError: Row {next_row}: Identity check failer or unable to derive values for committed polynomials: {}\n",
|
||||
self.next
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if v.is_none() {
|
||||
Some(self.committed_names[i].clone())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
eprintln!("Reasons:\n{}\n", self.failure_reasons.join("\n\n"));
|
||||
eprintln!(
|
||||
"Current values:\n{}",
|
||||
indent(&self.format_next_values().join("\n"), " ")
|
||||
);
|
||||
panic!();
|
||||
} else {
|
||||
if self.verbose {
|
||||
println!(
|
||||
"===== Row {next_row}:\n{}",
|
||||
indent(&self.format_next_values().join("\n"), " ")
|
||||
);
|
||||
}
|
||||
std::mem::swap(&mut self.next, &mut self.current);
|
||||
self.next = vec![None; self.current.len()];
|
||||
// TODO check a bit better that "None" values do not
|
||||
// violate constraints.
|
||||
self.current
|
||||
.iter()
|
||||
.map(|v| v.clone().unwrap_or_default())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_next_values(&self) -> Vec<String> {
|
||||
self.next
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
format!(
|
||||
"{} = {}",
|
||||
self.committed_names[i],
|
||||
v.as_ref()
|
||||
.map(format_number)
|
||||
.unwrap_or("<unknown>".to_string())
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn process_witness_query(
|
||||
&mut self,
|
||||
column: &&WitnessColumn,
|
||||
) -> Result<Vec<(usize, AbstractNumberType)>, EvalError> {
|
||||
let query = self.interpolate_query(column.query.unwrap())?;
|
||||
if let Some(value) = self.query_callback.as_mut().unwrap()(&query) {
|
||||
Ok(vec![(column.id, value)])
|
||||
} else {
|
||||
Err(format!(
|
||||
"No query answer for {} query: {query}.",
|
||||
self.committed_names[column.id]
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_query(&self, query: &Expression) -> Result<String, String> {
|
||||
if let Ok(v) = self.evaluate(query, EvaluationRow::Next) {
|
||||
if v.is_constant() {
|
||||
return Ok(self.format_affine_expression(&v));
|
||||
}
|
||||
}
|
||||
// TODO combine that with the constant evaluator and the commit evaluator...
|
||||
match query {
|
||||
Expression::Tuple(items) => Ok(items
|
||||
.iter()
|
||||
.map(|i| self.interpolate_query(i))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.join(", ")),
|
||||
Expression::LocalVariableReference(i) => {
|
||||
assert!(*i == 0);
|
||||
Ok(format!("{}", self.next_row))
|
||||
}
|
||||
Expression::String(s) => Ok(format!(
|
||||
"\"{}\"",
|
||||
s.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
)),
|
||||
_ => Err(format!("Cannot handle / evaluate {query}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_polynomial_identity(&self, identity: &Expression) -> EvalResult {
|
||||
// If there is no "next" reference in the expression,
|
||||
// we just evaluate it directly on the "next" row.
|
||||
let row = if self.contains_next_ref(identity) {
|
||||
EvaluationRow::Current
|
||||
} else {
|
||||
EvaluationRow::Next
|
||||
};
|
||||
let evaluated = self.evaluate(identity, row)?;
|
||||
if evaluated.constant_value() == Some(0.into()) {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => {
|
||||
let formatted = self.format_affine_expression(&evaluated);
|
||||
Err(if evaluated.is_invalid() {
|
||||
format!("Constraint is invalid ({formatted} != 0).").into()
|
||||
} else {
|
||||
format!("Could not solve expression {formatted} = 0.").into()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_plookup(&self, identity: &Identity) -> EvalResult {
|
||||
if let Some(left_selector) = &identity.left.selector {
|
||||
let value = self.evaluate(left_selector, EvaluationRow::Next)?;
|
||||
match value.constant_value() {
|
||||
Some(v) if v == 0.into() => {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
Some(v) if v == 1.into() => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Value of the selector on the left hand side unknown or not boolean: {}",
|
||||
self.format_affine_expression(&value)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
if identity.right.selector.is_some() {
|
||||
return Err("Selectors not yet supported.".to_string().into());
|
||||
}
|
||||
let left = identity
|
||||
.left
|
||||
.expressions
|
||||
.iter()
|
||||
.map(|e| self.evaluate(e, EvaluationRow::Next))
|
||||
.collect::<Vec<_>>();
|
||||
// If we already know the LHS, skip it.
|
||||
if left
|
||||
.iter()
|
||||
.all(|v| v.is_ok() && v.as_ref().unwrap().is_constant())
|
||||
{
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let left_key = left[0].clone().and_then(|v| match v.constant_value() {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(format!(
|
||||
"First expression needs to be constant but is not: {}.",
|
||||
self.format_affine_expression(&v)
|
||||
)
|
||||
.into()),
|
||||
})?;
|
||||
|
||||
let right_key = identity.right.expressions.first().unwrap();
|
||||
let rhs_row = if let Expression::PolynomialReference(poly) = right_key {
|
||||
// TODO we really need a search index on this.
|
||||
self.fixed_cols
|
||||
.get(&poly.name)
|
||||
.and_then(|values| values.iter().position(|v| *v == left_key))
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Unable to find matching row on the RHS where the first element is {left_key} - only fixed columns supported there."
|
||||
)
|
||||
})
|
||||
.map(|i| i as DegreeType)
|
||||
} else {
|
||||
Err("First item on the RHS must be a polynomial reference.".to_string())
|
||||
}?;
|
||||
|
||||
// TODO we only support the following case:
|
||||
// - The first component on the LHS has to be known
|
||||
// - The first component on the RHS has to be a direct fixed column reference
|
||||
// - The first match of those uniquely determines the rest of the RHS.
|
||||
|
||||
//TODO there should be a shortcut to succeed if any of an iterator is "Ok" and combine the errors otherwise.
|
||||
let mut result = vec![];
|
||||
let mut reasons = vec![];
|
||||
for (l, r) in identity
|
||||
.left
|
||||
.expressions
|
||||
.iter()
|
||||
.zip(&identity.right.expressions)
|
||||
.skip(1)
|
||||
{
|
||||
match self.equate_to_constant_rhs(l, r, rhs_row) {
|
||||
Ok(assignments) => result.extend(assignments),
|
||||
Err(err) => reasons.push(err),
|
||||
}
|
||||
}
|
||||
if result.is_empty() {
|
||||
Err(reasons.into_iter().reduce(eval_error::combine).unwrap())
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn equate_to_constant_rhs(
|
||||
&self,
|
||||
l: &Expression,
|
||||
r: &Expression,
|
||||
rhs_row: DegreeType,
|
||||
) -> EvalResult {
|
||||
let r = self
|
||||
.evaluate(r, EvaluationRow::Specific(rhs_row))
|
||||
.and_then(|r| {
|
||||
r.constant_value().ok_or_else(|| {
|
||||
format!(
|
||||
"Constant value required: {}",
|
||||
self.format_affine_expression(&r)
|
||||
)
|
||||
.into()
|
||||
})
|
||||
})?;
|
||||
|
||||
let expr = Expression::BinaryOperation(
|
||||
Box::new(l.clone()),
|
||||
BinaryOperator::Sub,
|
||||
Box::new(Expression::Number(r)),
|
||||
);
|
||||
let evaluated = self.evaluate(&expr, EvaluationRow::Next)?;
|
||||
match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => {
|
||||
// TODO somehow also add `l` and `r` to the error message.
|
||||
let formatted = self.format_affine_expression(&evaluated);
|
||||
Err(if evaluated.is_invalid() {
|
||||
format!("Constraint is invalid ({formatted} != 0).").into()
|
||||
} else {
|
||||
format!("Could not solve expression {formatted} = 0.").into()
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_eval_result(&mut self, result: EvalResult) {
|
||||
match result {
|
||||
Ok(assignments) => {
|
||||
for (id, value) in assignments {
|
||||
//println!("{} = {value}", self.committed_names[id]);
|
||||
self.next[id] = Some(value);
|
||||
self.progress = true;
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
self.failure_reasons.push(format!("{reason}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_known_next_value(&self, id: usize) -> bool {
|
||||
self.next[id].is_some()
|
||||
}
|
||||
|
||||
/// Tries to evaluate the expression to an expression affine in the committed polynomials,
|
||||
/// taking current values of polynomials into account.
|
||||
/// @returns an expression affine in the committed polynomials
|
||||
fn evaluate(
|
||||
&self,
|
||||
expr: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
// @TODO if we iterate on processing the constraints in the same row,
|
||||
// we could store the simplified values.
|
||||
match expr {
|
||||
Expression::Constant(name) => Ok(self.constants[name].clone().into()),
|
||||
Expression::PolynomialReference(poly) => {
|
||||
// TODO arrays
|
||||
if let Some(WitnessColumn { id, .. }) = self.committed.get(&poly.name) {
|
||||
// Committed polynomial
|
||||
if !poly.next && row == EvaluationRow::Current {
|
||||
// All values in the "current" row should usually be known.
|
||||
// The exception is when we start the analysis on the first row.
|
||||
self.current[*id]
|
||||
.as_ref()
|
||||
.map(|value| value.clone().into())
|
||||
.ok_or_else(|| EvalError::PreviousValueUnknown(poly.name.clone()))
|
||||
} else if (poly.next && row == EvaluationRow::Current)
|
||||
|| (!poly.next && row == EvaluationRow::Next)
|
||||
{
|
||||
Ok(if let Some(value) = self.next[*id].clone() {
|
||||
// We already computed the concrete value
|
||||
value.into()
|
||||
} else {
|
||||
// We continue with a symbolic value
|
||||
AffineExpression::from_committed_poly_value(*id)
|
||||
})
|
||||
} else {
|
||||
// "double next" or evaluation of a witness on a specific row
|
||||
Err(format!(
|
||||
"{}' references the next-next row when evaluating on the current row.",
|
||||
self.committed_names[*id]
|
||||
)
|
||||
.into())
|
||||
}
|
||||
} else {
|
||||
// Constant polynomial (or something else)
|
||||
let values = self.fixed_cols[&poly.name];
|
||||
let degree = values.len() as DegreeType;
|
||||
let mut row = match row {
|
||||
EvaluationRow::Current => (self.next_row + degree - 1) % degree,
|
||||
EvaluationRow::Next => self.next_row,
|
||||
EvaluationRow::Specific(r) => r,
|
||||
};
|
||||
if poly.next {
|
||||
row = (row + 1) % degree;
|
||||
}
|
||||
Ok(values[row as usize].clone().into())
|
||||
}
|
||||
}
|
||||
Expression::Number(n) => Ok(n.clone().into()),
|
||||
Expression::BinaryOperation(left, op, right) => {
|
||||
self.evaluate_binary_operation(left, op, right, row)
|
||||
}
|
||||
Expression::UnaryOperation(op, expr) => self.evaluate_unary_operation(op, expr, row),
|
||||
Expression::Tuple(_) => Err("Tuple not implemented.".to_string().into()),
|
||||
Expression::String(_) => Err("String not implemented.".to_string().into()),
|
||||
Expression::LocalVariableReference(_) => {
|
||||
Err("Local variable references not implemented."
|
||||
.to_string()
|
||||
.into())
|
||||
}
|
||||
Expression::PublicReference(_) => {
|
||||
Err("Public references not implemented.".to_string().into())
|
||||
}
|
||||
Expression::FunctionCall(_, _) => {
|
||||
Err("Function calls not implemented.".to_string().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_binary_operation(
|
||||
&self,
|
||||
left: &Expression,
|
||||
op: &BinaryOperator,
|
||||
right: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
match (self.evaluate(left, row), self.evaluate(right, row)) {
|
||||
(Ok(left), Ok(right)) => match op {
|
||||
BinaryOperator::Add => Ok(left + right),
|
||||
BinaryOperator::Sub => Ok(left - right),
|
||||
BinaryOperator::Mul => {
|
||||
if let Some(f) = left.constant_value() {
|
||||
Ok(right.mul(f))
|
||||
} else if let Some(f) = right.constant_value() {
|
||||
Ok(left.mul(f))
|
||||
} else {
|
||||
Err(format!(
|
||||
"Multiplication of two non-constants: ({}) * ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Div => {
|
||||
if let (Some(l), Some(r)) = (left.constant_value(), right.constant_value()) {
|
||||
// TODO Maybe warn about division by zero here.
|
||||
if l == 0.into() {
|
||||
Ok(0.into())
|
||||
} else {
|
||||
// TODO We have to do division in the proper field.
|
||||
Ok((l / r).into())
|
||||
}
|
||||
} else {
|
||||
Err(format!(
|
||||
"Division of two non-constants: ({}) / ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Pow => {
|
||||
if let (Some(l), Some(r)) = (left.constant_value(), right.constant_value()) {
|
||||
Ok(l.pow(abstract_to_degree(&r) as u32).into())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Pow of two non-constants: ({}) ** ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Mod
|
||||
| BinaryOperator::BinaryAnd
|
||||
| BinaryOperator::BinaryOr
|
||||
| BinaryOperator::ShiftLeft
|
||||
| BinaryOperator::ShiftRight => {
|
||||
if let (Some(left), Some(right)) =
|
||||
(left.constant_value(), right.constant_value())
|
||||
{
|
||||
let result = match op {
|
||||
BinaryOperator::Mod => left % right,
|
||||
BinaryOperator::BinaryAnd => left & right,
|
||||
BinaryOperator::BinaryOr => left | right,
|
||||
BinaryOperator::ShiftLeft => left << abstract_to_degree(&right),
|
||||
BinaryOperator::ShiftRight => left >> abstract_to_degree(&right),
|
||||
_ => panic!(),
|
||||
};
|
||||
Ok(result.into())
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
},
|
||||
(Ok(_), Err(reason)) | (Err(reason), Ok(_)) => Err(reason),
|
||||
(Err(r1), Err(r2)) => Err(eval_error::combine(r1, r2)),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_unary_operation(
|
||||
&self,
|
||||
op: &UnaryOperator,
|
||||
expr: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
self.evaluate(expr, row).map(|v| match op {
|
||||
UnaryOperator::Plus => v,
|
||||
UnaryOperator::Minus => -v,
|
||||
})
|
||||
}
|
||||
|
||||
/// @returns true if the expression contains a reference to a next value of a witness column.
|
||||
fn contains_next_ref(&self, expr: &Expression) -> bool {
|
||||
match expr {
|
||||
Expression::PolynomialReference(poly) => {
|
||||
poly.next && self.committed.contains_key(&poly.name)
|
||||
}
|
||||
Expression::Tuple(items) => items.iter().any(|e| self.contains_next_ref(e)),
|
||||
Expression::BinaryOperation(l, _, r) => {
|
||||
self.contains_next_ref(l) || self.contains_next_ref(r)
|
||||
}
|
||||
Expression::UnaryOperation(_, e) => self.contains_next_ref(e),
|
||||
Expression::FunctionCall(_, args) => args.iter().any(|e| self.contains_next_ref(e)),
|
||||
Expression::Constant(_)
|
||||
| Expression::LocalVariableReference(_)
|
||||
| Expression::PublicReference(_)
|
||||
| Expression::Number(_)
|
||||
| Expression::String(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_affine_expression(&self, e: &AffineExpression) -> String {
|
||||
e.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| !is_zero(c))
|
||||
.map(|(i, c)| {
|
||||
let name = self.committed_names[i];
|
||||
if *c == 1.into() {
|
||||
name.clone()
|
||||
} else if *c == (-1).into() {
|
||||
format!("-{name}")
|
||||
} else {
|
||||
format!("{} * {name}", format_number(c))
|
||||
}
|
||||
})
|
||||
.chain(e.constant_value().map(|v| format!("{v}")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + ")
|
||||
}
|
||||
}
|
||||
|
||||
const GOLDILOCKS_MOD: u64 = 0xffffffff00000001u64;
|
||||
|
||||
fn format_number(x: &AbstractNumberType) -> String {
|
||||
if *x > (GOLDILOCKS_MOD / 2).into() {
|
||||
format!("{}", GOLDILOCKS_MOD - x)
|
||||
} else {
|
||||
format!("{x}")
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,9 @@
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use crate::analyzer::{
|
||||
Analyzed, BinaryOperator, Expression, FunctionValueDefinition, Identity, IdentityKind,
|
||||
UnaryOperator,
|
||||
};
|
||||
// TODO should use finite field instead of abstract number
|
||||
use crate::number::{abstract_to_degree, is_zero, AbstractNumberType, DegreeType};
|
||||
use crate::utils::indent;
|
||||
use crate::analyzer::{Analyzed, Expression, FunctionValueDefinition};
|
||||
use crate::number::{AbstractNumberType, DegreeType};
|
||||
|
||||
mod affine_expression;
|
||||
mod eval_error;
|
||||
|
||||
use affine_expression::AffineExpression;
|
||||
|
||||
use self::eval_error::EvalError;
|
||||
mod evaluator;
|
||||
|
||||
/// Generates the committed polynomial values
|
||||
/// @returns the values (in source order) and the degree of the polynomials.
|
||||
@@ -37,7 +27,7 @@ pub fn generate<'a>(
|
||||
.collect();
|
||||
let mut values: Vec<(&String, Vec<AbstractNumberType>)> =
|
||||
witness_cols.iter().map(|p| (p.name, Vec::new())).collect();
|
||||
let mut evaluator = Evaluator::new(
|
||||
let mut evaluator = evaluator::Evaluator::new(
|
||||
&analyzed.constants,
|
||||
analyzed.identities.iter().collect(),
|
||||
fixed_cols.iter().map(|(n, v)| (*n, v)).collect(),
|
||||
@@ -60,7 +50,7 @@ pub fn generate<'a>(
|
||||
values
|
||||
}
|
||||
|
||||
struct WitnessColumn<'a> {
|
||||
pub struct WitnessColumn<'a> {
|
||||
id: usize,
|
||||
name: &'a String,
|
||||
query: Option<&'a Expression>,
|
||||
@@ -80,608 +70,3 @@ impl<'a> WitnessColumn<'a> {
|
||||
WitnessColumn { id, name, query }
|
||||
}
|
||||
}
|
||||
|
||||
type EvalResult = Result<Vec<(usize, AbstractNumberType)>, EvalError>;
|
||||
|
||||
struct Evaluator<'a, QueryCallback>
|
||||
where
|
||||
QueryCallback: FnMut(&'a str) -> Option<AbstractNumberType>,
|
||||
{
|
||||
identities: Vec<&'a Identity>,
|
||||
constants: &'a HashMap<String, AbstractNumberType>,
|
||||
fixed_cols: HashMap<&'a String, &'a Vec<AbstractNumberType>>,
|
||||
query_callback: Option<QueryCallback>,
|
||||
/// Maps the committed polynomial names to their IDs internal to this component
|
||||
/// and optional parameter and query string.
|
||||
committed: BTreeMap<&'a String, &'a WitnessColumn<'a>>,
|
||||
committed_names: Vec<&'a String>,
|
||||
/// Values of the committed polynomials
|
||||
current: Vec<Option<AbstractNumberType>>,
|
||||
/// Values of the committed polynomials in the next row
|
||||
next: Vec<Option<AbstractNumberType>>,
|
||||
next_row: DegreeType,
|
||||
failure_reasons: Vec<String>,
|
||||
progress: bool,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum EvaluationRow {
|
||||
/// p is p[next_row - 1], p' is p[next_row]
|
||||
Current,
|
||||
/// p is p[next_row], p' is p[next_row + 1]
|
||||
Next,
|
||||
/// p is p[arg], p' is p[arg + 1]
|
||||
Specific(DegreeType),
|
||||
}
|
||||
|
||||
impl<'a, QueryCallback> Evaluator<'a, QueryCallback>
|
||||
where
|
||||
QueryCallback: FnMut(&str) -> Option<AbstractNumberType>,
|
||||
{
|
||||
pub fn new(
|
||||
constants: &'a HashMap<String, AbstractNumberType>,
|
||||
identities: Vec<&'a Identity>,
|
||||
fixed_cols: HashMap<&'a String, &'a Vec<AbstractNumberType>>,
|
||||
witness_cols: &'a Vec<WitnessColumn<'a>>,
|
||||
query_callback: Option<QueryCallback>,
|
||||
) -> Self {
|
||||
Evaluator {
|
||||
constants,
|
||||
identities,
|
||||
fixed_cols,
|
||||
query_callback,
|
||||
committed: witness_cols.iter().map(|p| (p.name, p)).collect(),
|
||||
committed_names: witness_cols.iter().map(|p| p.name).collect(),
|
||||
current: vec![None; witness_cols.len()],
|
||||
next: vec![None; witness_cols.len()],
|
||||
next_row: 0,
|
||||
failure_reasons: vec![],
|
||||
progress: true,
|
||||
verbose: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_verbose(&mut self, verbose: bool) {
|
||||
self.verbose = verbose;
|
||||
}
|
||||
|
||||
pub fn compute_next_row(&mut self, next_row: DegreeType) -> Vec<AbstractNumberType> {
|
||||
self.next_row = next_row;
|
||||
|
||||
// TODO maybe better to generate a dependency graph than looping multiple times.
|
||||
// TODO at least we could cache the affine expressions between loops.
|
||||
|
||||
let mut identity_failed;
|
||||
loop {
|
||||
identity_failed = false;
|
||||
self.progress = false;
|
||||
self.failure_reasons.clear();
|
||||
|
||||
// TODO avoid clone
|
||||
for identity in &self.identities.clone() {
|
||||
let result = match identity.kind {
|
||||
IdentityKind::Polynomial => {
|
||||
self.process_polynomial_identity(identity.left.selector.as_ref().unwrap())
|
||||
}
|
||||
IdentityKind::Plookup => self.process_plookup(identity),
|
||||
_ => Ok(vec![]),
|
||||
}
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"No progress on {identity}:\n{}",
|
||||
indent(&format!("{err}"), " ")
|
||||
)
|
||||
.into()
|
||||
});
|
||||
if result.is_err() {
|
||||
identity_failed = true;
|
||||
}
|
||||
self.handle_eval_result(result);
|
||||
}
|
||||
if self.query_callback.is_some() {
|
||||
// TODO avoid clone
|
||||
for column in self.committed.clone().values() {
|
||||
if !self.has_known_next_value(column.id) && column.query.is_some() {
|
||||
let result = self.process_witness_query(column);
|
||||
self.handle_eval_result(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.progress {
|
||||
break;
|
||||
}
|
||||
if self.next.iter().all(|v| v.is_some()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Identity check failure on the first row is not fatal. We will proceed with
|
||||
// "unknown", report zero and re-check the wrap-around against the zero values at the end.
|
||||
if identity_failed && next_row != 0 {
|
||||
eprintln!(
|
||||
"\nError: Row {next_row}: Identity check failer or unable to derive values for committed polynomials: {}\n",
|
||||
self.next
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if v.is_none() {
|
||||
Some(self.committed_names[i].clone())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
eprintln!("Reasons:\n{}\n", self.failure_reasons.join("\n\n"));
|
||||
eprintln!(
|
||||
"Current values:\n{}",
|
||||
indent(&self.format_next_values().join("\n"), " ")
|
||||
);
|
||||
panic!();
|
||||
} else {
|
||||
if self.verbose {
|
||||
println!(
|
||||
"===== Row {next_row}:\n{}",
|
||||
indent(&self.format_next_values().join("\n"), " ")
|
||||
);
|
||||
}
|
||||
std::mem::swap(&mut self.next, &mut self.current);
|
||||
self.next = vec![None; self.current.len()];
|
||||
// TODO check a bit better that "None" values do not
|
||||
// violate constraints.
|
||||
self.current
|
||||
.iter()
|
||||
.map(|v| v.clone().unwrap_or_default())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_next_values(&self) -> Vec<String> {
|
||||
self.next
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
format!(
|
||||
"{} = {}",
|
||||
self.committed_names[i],
|
||||
v.as_ref()
|
||||
.map(format_number)
|
||||
.unwrap_or("<unknown>".to_string())
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn process_witness_query(
|
||||
&mut self,
|
||||
column: &&WitnessColumn,
|
||||
) -> Result<Vec<(usize, AbstractNumberType)>, EvalError> {
|
||||
let query = self.interpolate_query(column.query.unwrap())?;
|
||||
if let Some(value) = self.query_callback.as_mut().unwrap()(&query) {
|
||||
Ok(vec![(column.id, value)])
|
||||
} else {
|
||||
Err(format!(
|
||||
"No query answer for {} query: {query}.",
|
||||
self.committed_names[column.id]
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_query(&self, query: &Expression) -> Result<String, String> {
|
||||
if let Ok(v) = self.evaluate(query, EvaluationRow::Next) {
|
||||
if v.is_constant() {
|
||||
return Ok(self.format_affine_expression(&v));
|
||||
}
|
||||
}
|
||||
// TODO combine that with the constant evaluator and the commit evaluator...
|
||||
match query {
|
||||
Expression::Tuple(items) => Ok(items
|
||||
.iter()
|
||||
.map(|i| self.interpolate_query(i))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.join(", ")),
|
||||
Expression::LocalVariableReference(i) => {
|
||||
assert!(*i == 0);
|
||||
Ok(format!("{}", self.next_row))
|
||||
}
|
||||
Expression::String(s) => Ok(format!(
|
||||
"\"{}\"",
|
||||
s.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
)),
|
||||
_ => Err(format!("Cannot handle / evaluate {query}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_polynomial_identity(&self, identity: &Expression) -> EvalResult {
|
||||
// If there is no "next" reference in the expression,
|
||||
// we just evaluate it directly on the "next" row.
|
||||
let row = if self.contains_next_ref(identity) {
|
||||
EvaluationRow::Current
|
||||
} else {
|
||||
EvaluationRow::Next
|
||||
};
|
||||
let evaluated = self.evaluate(identity, row)?;
|
||||
if evaluated.constant_value() == Some(0.into()) {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => {
|
||||
let formatted = self.format_affine_expression(&evaluated);
|
||||
Err(if evaluated.is_invalid() {
|
||||
format!("Constraint is invalid ({formatted} != 0).").into()
|
||||
} else {
|
||||
format!("Could not solve expression {formatted} = 0.").into()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_plookup(&self, identity: &Identity) -> EvalResult {
|
||||
if let Some(left_selector) = &identity.left.selector {
|
||||
let value = self.evaluate(left_selector, EvaluationRow::Next)?;
|
||||
match value.constant_value() {
|
||||
Some(v) if v == 0.into() => {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
Some(v) if v == 1.into() => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Value of the selector on the left hand side unknown or not boolean: {}",
|
||||
self.format_affine_expression(&value)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
if identity.right.selector.is_some() {
|
||||
return Err("Selectors not yet supported.".to_string().into());
|
||||
}
|
||||
let left = identity
|
||||
.left
|
||||
.expressions
|
||||
.iter()
|
||||
.map(|e| self.evaluate(e, EvaluationRow::Next))
|
||||
.collect::<Vec<_>>();
|
||||
// If we already know the LHS, skip it.
|
||||
if left
|
||||
.iter()
|
||||
.all(|v| v.is_ok() && v.as_ref().unwrap().is_constant())
|
||||
{
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let left_key = left[0].clone().and_then(|v| match v.constant_value() {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(format!(
|
||||
"First expression needs to be constant but is not: {}.",
|
||||
self.format_affine_expression(&v)
|
||||
)
|
||||
.into()),
|
||||
})?;
|
||||
|
||||
let right_key = identity.right.expressions.first().unwrap();
|
||||
let rhs_row = if let Expression::PolynomialReference(poly) = right_key {
|
||||
// TODO we really need a search index on this.
|
||||
self.fixed_cols
|
||||
.get(&poly.name)
|
||||
.and_then(|values| values.iter().position(|v| *v == left_key))
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Unable to find matching row on the RHS where the first element is {left_key} - only fixed columns supported there."
|
||||
)
|
||||
})
|
||||
.map(|i| i as DegreeType)
|
||||
} else {
|
||||
Err("First item on the RHS must be a polynomial reference.".to_string())
|
||||
}?;
|
||||
|
||||
// TODO we only support the following case:
|
||||
// - The first component on the LHS has to be known
|
||||
// - The first component on the RHS has to be a direct fixed column reference
|
||||
// - The first match of those uniquely determines the rest of the RHS.
|
||||
|
||||
//TODO there should be a shortcut to succeed if any of an iterator is "Ok" and combine the errors otherwise.
|
||||
let mut result = vec![];
|
||||
let mut reasons = vec![];
|
||||
for (l, r) in identity
|
||||
.left
|
||||
.expressions
|
||||
.iter()
|
||||
.zip(&identity.right.expressions)
|
||||
.skip(1)
|
||||
{
|
||||
match self.equate_to_constant_rhs(l, r, rhs_row) {
|
||||
Ok(assignments) => result.extend(assignments),
|
||||
Err(err) => reasons.push(err),
|
||||
}
|
||||
}
|
||||
if result.is_empty() {
|
||||
Err(reasons.into_iter().reduce(eval_error::combine).unwrap())
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn equate_to_constant_rhs(
|
||||
&self,
|
||||
l: &Expression,
|
||||
r: &Expression,
|
||||
rhs_row: DegreeType,
|
||||
) -> EvalResult {
|
||||
let r = self
|
||||
.evaluate(r, EvaluationRow::Specific(rhs_row))
|
||||
.and_then(|r| {
|
||||
r.constant_value().ok_or_else(|| {
|
||||
format!(
|
||||
"Constant value required: {}",
|
||||
self.format_affine_expression(&r)
|
||||
)
|
||||
.into()
|
||||
})
|
||||
})?;
|
||||
|
||||
let expr = Expression::BinaryOperation(
|
||||
Box::new(l.clone()),
|
||||
BinaryOperator::Sub,
|
||||
Box::new(Expression::Number(r)),
|
||||
);
|
||||
let evaluated = self.evaluate(&expr, EvaluationRow::Next)?;
|
||||
match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => match evaluated.solve() {
|
||||
Some((id, value)) => Ok(vec![(id, value)]),
|
||||
None => {
|
||||
// TODO somehow also add `l` and `r` to the error message.
|
||||
let formatted = self.format_affine_expression(&evaluated);
|
||||
Err(if evaluated.is_invalid() {
|
||||
format!("Constraint is invalid ({formatted} != 0).").into()
|
||||
} else {
|
||||
format!("Could not solve expression {formatted} = 0.").into()
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_eval_result(&mut self, result: EvalResult) {
|
||||
match result {
|
||||
Ok(assignments) => {
|
||||
for (id, value) in assignments {
|
||||
//println!("{} = {value}", self.committed_names[id]);
|
||||
self.next[id] = Some(value);
|
||||
self.progress = true;
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
self.failure_reasons.push(format!("{reason}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_known_next_value(&self, id: usize) -> bool {
|
||||
self.next[id].is_some()
|
||||
}
|
||||
|
||||
/// Tries to evaluate the expression to an expression affine in the committed polynomials,
|
||||
/// taking current values of polynomials into account.
|
||||
/// @returns an expression affine in the committed polynomials
|
||||
fn evaluate(
|
||||
&self,
|
||||
expr: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
// @TODO if we iterate on processing the constraints in the same row,
|
||||
// we could store the simplified values.
|
||||
match expr {
|
||||
Expression::Constant(name) => Ok(self.constants[name].clone().into()),
|
||||
Expression::PolynomialReference(poly) => {
|
||||
// TODO arrays
|
||||
if let Some(WitnessColumn { id, .. }) = self.committed.get(&poly.name) {
|
||||
// Committed polynomial
|
||||
if !poly.next && row == EvaluationRow::Current {
|
||||
// All values in the "current" row should usually be known.
|
||||
// The exception is when we start the analysis on the first row.
|
||||
self.current[*id]
|
||||
.as_ref()
|
||||
.map(|value| value.clone().into())
|
||||
.ok_or_else(|| EvalError::PreviousValueUnknown(poly.name.clone()))
|
||||
} else if (poly.next && row == EvaluationRow::Current)
|
||||
|| (!poly.next && row == EvaluationRow::Next)
|
||||
{
|
||||
Ok(if let Some(value) = self.next[*id].clone() {
|
||||
// We already computed the concrete value
|
||||
value.into()
|
||||
} else {
|
||||
// We continue with a symbolic value
|
||||
AffineExpression::from_committed_poly_value(*id)
|
||||
})
|
||||
} else {
|
||||
// "double next" or evaluation of a witness on a specific row
|
||||
Err(format!(
|
||||
"{}' references the next-next row when evaluating on the current row.",
|
||||
self.committed_names[*id]
|
||||
)
|
||||
.into())
|
||||
}
|
||||
} else {
|
||||
// Constant polynomial (or something else)
|
||||
let values = self.fixed_cols[&poly.name];
|
||||
let degree = values.len() as DegreeType;
|
||||
let mut row = match row {
|
||||
EvaluationRow::Current => (self.next_row + degree - 1) % degree,
|
||||
EvaluationRow::Next => self.next_row,
|
||||
EvaluationRow::Specific(r) => r,
|
||||
};
|
||||
if poly.next {
|
||||
row = (row + 1) % degree;
|
||||
}
|
||||
Ok(values[row as usize].clone().into())
|
||||
}
|
||||
}
|
||||
Expression::Number(n) => Ok(n.clone().into()),
|
||||
Expression::BinaryOperation(left, op, right) => {
|
||||
self.evaluate_binary_operation(left, op, right, row)
|
||||
}
|
||||
Expression::UnaryOperation(op, expr) => self.evaluate_unary_operation(op, expr, row),
|
||||
Expression::Tuple(_) => Err("Tuple not implemented.".to_string().into()),
|
||||
Expression::String(_) => Err("String not implemented.".to_string().into()),
|
||||
Expression::LocalVariableReference(_) => {
|
||||
Err("Local variable references not implemented."
|
||||
.to_string()
|
||||
.into())
|
||||
}
|
||||
Expression::PublicReference(_) => {
|
||||
Err("Public references not implemented.".to_string().into())
|
||||
}
|
||||
Expression::FunctionCall(_, _) => {
|
||||
Err("Function calls not implemented.".to_string().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_binary_operation(
|
||||
&self,
|
||||
left: &Expression,
|
||||
op: &BinaryOperator,
|
||||
right: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
match (self.evaluate(left, row), self.evaluate(right, row)) {
|
||||
(Ok(left), Ok(right)) => match op {
|
||||
BinaryOperator::Add => Ok(left + right),
|
||||
BinaryOperator::Sub => Ok(left - right),
|
||||
BinaryOperator::Mul => {
|
||||
if let Some(f) = left.constant_value() {
|
||||
Ok(right.mul(f))
|
||||
} else if let Some(f) = right.constant_value() {
|
||||
Ok(left.mul(f))
|
||||
} else {
|
||||
Err(format!(
|
||||
"Multiplication of two non-constants: ({}) * ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Div => {
|
||||
if let (Some(l), Some(r)) = (left.constant_value(), right.constant_value()) {
|
||||
// TODO Maybe warn about division by zero here.
|
||||
if l == 0.into() {
|
||||
Ok(0.into())
|
||||
} else {
|
||||
// TODO We have to do division in the proper field.
|
||||
Ok((l / r).into())
|
||||
}
|
||||
} else {
|
||||
Err(format!(
|
||||
"Division of two non-constants: ({}) / ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Pow => {
|
||||
if let (Some(l), Some(r)) = (left.constant_value(), right.constant_value()) {
|
||||
Ok(l.pow(abstract_to_degree(&r) as u32).into())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Pow of two non-constants: ({}) ** ({})",
|
||||
self.format_affine_expression(&left),
|
||||
self.format_affine_expression(&right)
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
BinaryOperator::Mod
|
||||
| BinaryOperator::BinaryAnd
|
||||
| BinaryOperator::BinaryOr
|
||||
| BinaryOperator::ShiftLeft
|
||||
| BinaryOperator::ShiftRight => {
|
||||
if let (Some(left), Some(right)) =
|
||||
(left.constant_value(), right.constant_value())
|
||||
{
|
||||
let result = match op {
|
||||
BinaryOperator::Mod => left % right,
|
||||
BinaryOperator::BinaryAnd => left & right,
|
||||
BinaryOperator::BinaryOr => left | right,
|
||||
BinaryOperator::ShiftLeft => left << abstract_to_degree(&right),
|
||||
BinaryOperator::ShiftRight => left >> abstract_to_degree(&right),
|
||||
_ => panic!(),
|
||||
};
|
||||
Ok(result.into())
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
},
|
||||
(Ok(_), Err(reason)) | (Err(reason), Ok(_)) => Err(reason),
|
||||
(Err(r1), Err(r2)) => Err(eval_error::combine(r1, r2)),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_unary_operation(
|
||||
&self,
|
||||
op: &UnaryOperator,
|
||||
expr: &Expression,
|
||||
row: EvaluationRow,
|
||||
) -> Result<AffineExpression, EvalError> {
|
||||
self.evaluate(expr, row).map(|v| match op {
|
||||
UnaryOperator::Plus => v,
|
||||
UnaryOperator::Minus => -v,
|
||||
})
|
||||
}
|
||||
|
||||
/// @returns true if the expression contains a reference to a next value of a witness column.
|
||||
fn contains_next_ref(&self, expr: &Expression) -> bool {
|
||||
match expr {
|
||||
Expression::PolynomialReference(poly) => {
|
||||
poly.next && self.committed.contains_key(&poly.name)
|
||||
}
|
||||
Expression::Tuple(items) => items.iter().any(|e| self.contains_next_ref(e)),
|
||||
Expression::BinaryOperation(l, _, r) => {
|
||||
self.contains_next_ref(l) || self.contains_next_ref(r)
|
||||
}
|
||||
Expression::UnaryOperation(_, e) => self.contains_next_ref(e),
|
||||
Expression::FunctionCall(_, args) => args.iter().any(|e| self.contains_next_ref(e)),
|
||||
Expression::Constant(_)
|
||||
| Expression::LocalVariableReference(_)
|
||||
| Expression::PublicReference(_)
|
||||
| Expression::Number(_)
|
||||
| Expression::String(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_affine_expression(&self, e: &AffineExpression) -> String {
|
||||
e.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| !is_zero(c))
|
||||
.map(|(i, c)| {
|
||||
let name = self.committed_names[i];
|
||||
if *c == 1.into() {
|
||||
name.clone()
|
||||
} else if *c == (-1).into() {
|
||||
format!("-{name}")
|
||||
} else {
|
||||
format!("{} * {name}", format_number(c))
|
||||
}
|
||||
})
|
||||
.chain(e.constant_value().map(|v| format!("{v}")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + ")
|
||||
}
|
||||
}
|
||||
|
||||
const GOLDILOCKS_MOD: u64 = 0xffffffff00000001u64;
|
||||
|
||||
fn format_number(x: &AbstractNumberType) -> String {
|
||||
if *x > (GOLDILOCKS_MOD / 2).into() {
|
||||
format!("{}", GOLDILOCKS_MOD - x)
|
||||
} else {
|
||||
format!("{x}")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user