Split out evaluator.

This commit is contained in:
chriseth
2023-03-06 13:26:59 +01:00
parent ec3924d757
commit 96f0cce4f9
2 changed files with 620 additions and 620 deletions

View 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}")
}
}

View File

@@ -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}")
}
}