Merge pull request #470 from powdr-labs/solver-use-polyid

Refactor Main Machine Witness Generation
This commit is contained in:
Georg Wiese
2023-08-14 14:00:51 +02:00
committed by GitHub
11 changed files with 944 additions and 600 deletions

View File

@@ -310,6 +310,10 @@ impl<T> Identity<T> {
assert_eq!(self.kind, IdentityKind::Polynomial);
self.left.selector.as_ref().unwrap()
}
pub fn contains_next_ref(&self) -> bool {
self.left.contains_next_ref() || self.right.contains_next_ref()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
@@ -326,6 +330,17 @@ pub struct SelectedExpressions<T> {
pub expressions: Vec<Expression<T>>,
}
impl<T> SelectedExpressions<T> {
/// @returns true if the expression contains a reference to a next value of a
/// (witness or fixed) column
pub fn contains_next_ref(&self) -> bool {
self.selector
.iter()
.chain(self.expressions.iter())
.any(|e| e.contains_next_ref())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Expression<T> {
Constant(String),

View File

@@ -56,28 +56,6 @@ impl<V> ColumnMap<V> {
}
}
impl<V: Clone + Default> ColumnMap<Option<V>> {
pub fn unwrap_or_default(self) -> ColumnMap<V> {
ColumnMap {
values: self
.values
.into_iter()
.map(|v| v.unwrap_or_default())
.collect(),
ptype: self.ptype,
}
}
}
impl<V: Clone> ColumnMap<V> {
pub fn wrap_some(self) -> ColumnMap<Option<V>> {
ColumnMap {
values: self.values.into_iter().map(|v| Some(v)).collect(),
ptype: self.ptype,
}
}
}
impl<V> Index<&PolyID> for ColumnMap<V> {
type Output = V;

View File

@@ -7,8 +7,6 @@ use super::range_constraints::RangeConstraint;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IncompleteCause<K = usize> {
/// Previous value of witness column not known when trying to derive a value in the next row. Example: `x' = x` where `x` is unknown
PreviousValueUnknown(K),
/// Some parts of an expression are not bit constrained. Example: `x + y == 0x3` with `x | 0x1`. Arguments: the indices of the unconstrained variables.
BitUnconstrained(Vec<K>),
/// Some bit constraints are overlapping. Example: `x + y == 0x3` with `x | 0x3` and `y | 0x3`

View File

@@ -1,66 +1,72 @@
use ast::analyzed::{Expression, Identity, IdentityKind, PolyID, PolynomialReference};
use ast::analyzed::{Identity, PolyID};
use itertools::Itertools;
use number::{DegreeType, FieldElement};
use parser_util::lines::indent;
use std::collections::{BTreeSet, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::time::Instant;
use super::affine_expression::{AffineExpression, AffineResult};
use crate::witgen::identity_processor::IdentityProcessor;
use crate::witgen::rows::RowUpdater;
use super::column_map::ColumnMap;
use super::global_constraints::RangeConstraintSet;
use super::query_processor::QueryProcessor;
use super::range_constraints::RangeConstraint;
use super::expression_evaluator::ExpressionEvaluator;
use super::machines::{FixedLookup, Machine};
use super::symbolic_witness_evaluator::{SymoblicWitnessEvaluator, WitnessColumnEvaluator};
use super::{
Constraint, EvalError, EvalResult, EvalValue, FixedData, IncompleteCause, WitnessColumn,
};
use super::rows::{Row, RowFactory, RowPair};
use super::{EvalError, EvalResult, FixedData};
/// Phase in which [Generator::compute_next_row_or_initialize] is called.
#[derive(Debug, PartialEq)]
enum ProcessingPhase {
Initialization,
Regular,
}
/// A list of identities with a flag whether it is complete.
struct CompletableIdentities<'a, T: FieldElement> {
identities_with_complete: Vec<(&'a Identity<T>, bool)>,
}
impl<'a, T: FieldElement> CompletableIdentities<'a, T> {
fn new(identities: impl Iterator<Item = &'a Identity<T>>) -> Self {
Self {
identities_with_complete: identities.map(|identity| (identity, false)).collect(),
}
}
/// Yields immutable references to the identity and mutable references to the complete flag.
fn iter_mut(&mut self) -> impl Iterator<Item = (&'a Identity<T>, &mut bool)> {
self.identities_with_complete
.iter_mut()
.map(|(identity, complete)| (*identity, complete))
}
}
pub struct Generator<'a, T: FieldElement, QueryCallback: Send + Sync> {
fixed_data: &'a FixedData<'a, T>,
fixed_lookup: &'a mut FixedLookup<T>,
identities: &'a [&'a Identity<T>],
/// The witness columns belonging to this machine
witnesses: BTreeSet<PolyID>,
machines: Vec<Box<dyn Machine<T>>>,
query_callback: Option<QueryCallback>,
global_range_constraints: ColumnMap<Option<RangeConstraint<T>>>,
row_factory: RowFactory<'a, T>,
identity_processor: IdentityProcessor<'a, T>,
query_processor: Option<QueryProcessor<'a, T, QueryCallback>>,
fixed_data: &'a FixedData<'a, T>,
/// The subset of identities that contains a reference to the next row
/// (precomputed once for performance reasons)
identities_with_next_ref: Vec<&'a Identity<T>>,
/// The subset of identities that does not contain a reference to the next row
/// (precomputed once for performance reasons)
identities_without_next_ref: Vec<&'a Identity<T>>,
/// Values of the witness polynomials in the previous row (needed to check proposed rows)
previous: Row<T>,
/// Values of the witness polynomials
current: ColumnMap<Option<T>>,
current: Row<T>,
/// Values of the witness polynomials in the next row
next: ColumnMap<Option<T>>,
/// Range constraints on the witness polynomials in the next row.
next_range_constraints: ColumnMap<Option<RangeConstraint<T>>>,
next_row: DegreeType,
failure_reasons: Vec<EvalError<T>>,
next: Row<T>,
current_row_index: DegreeType,
last_report: DegreeType,
last_report_time: Instant,
}
#[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,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum SolvingStrategy {
/// Only solve expressions that are affine in a single variable
/// (and use range constraints).
SingleVariableAffine,
/// Assume that all unknown values are zero and check that this does not generate
/// a conflict (but do not store the values as fixed zero to avoid relying on nondeterminism).
AssumeZero,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum EvaluateUnknown {
Symbolic,
AssumeZero,
}
impl<'a, T: FieldElement, QueryCallback> Generator<'a, T, QueryCallback>
where
QueryCallback: FnMut(&str) -> Option<T> + Send + Sync,
@@ -74,149 +80,236 @@ where
machines: Vec<Box<dyn Machine<T>>>,
query_callback: Option<QueryCallback>,
) -> Self {
Generator {
fixed_data,
fixed_lookup,
identities,
let query_processor =
query_callback.map(|query_callback| QueryProcessor::new(fixed_data, query_callback));
let identity_processor = IdentityProcessor::new(fixed_data, fixed_lookup, machines);
let row_factory = RowFactory::new(fixed_data, global_range_constraints);
let default_row = row_factory.fresh_row();
let (identities_with_next, identities_without_next): (Vec<_>, Vec<_>) = identities
.iter()
.partition(|identity| identity.contains_next_ref());
let mut generator = Generator {
row_factory,
witnesses,
machines,
query_callback,
global_range_constraints,
current: fixed_data.witness_map_with(None),
next: fixed_data.witness_map_with(None),
next_range_constraints: fixed_data.witness_map_with(None),
next_row: 0,
failure_reasons: vec![],
query_processor,
identity_processor,
fixed_data,
identities_with_next_ref: identities_with_next,
identities_without_next_ref: identities_without_next,
previous: default_row.clone(),
current: default_row.clone(),
next: default_row,
current_row_index: fixed_data.degree - 1,
last_report: 0,
last_report_time: Instant::now(),
}
};
// For identities like `pc' = (1 - first_step') * <...>`, we need to process the last
// row before processing the first row.
generator
.compute_next_row_or_initialize(generator.last_row(), ProcessingPhase::Initialization);
generator
}
fn last_row(&self) -> DegreeType {
self.fixed_data.degree - 1
}
pub fn compute_next_row(&mut self, next_row: DegreeType) -> ColumnMap<T> {
self.set_next_row_and_log(next_row);
self.compute_next_row_or_initialize(next_row, ProcessingPhase::Regular)
}
let mut complete_identities = vec![false; self.identities.len()];
log::trace!("Row: {}", next_row);
let mut identity_failed = false;
for strategy in [
SolvingStrategy::SingleVariableAffine,
SolvingStrategy::AssumeZero,
] {
if identity_failed {
break;
}
log::trace!(" Strategy: {:?}", strategy);
loop {
identity_failed = false;
let mut progress = false;
self.failure_reasons.clear();
for (identity, complete) in self
.identities
.iter()
.zip(complete_identities.iter_mut())
.filter(|(_, complete)| !**complete)
{
let result = self.process_identity(identity, strategy).map_err(|err| {
let msg = match strategy {
SolvingStrategy::SingleVariableAffine => "Solving failed on",
SolvingStrategy::AssumeZero => {
"Assuming zero for unknown columns failed in"
}
};
format!("{msg} {identity}:\n{}", indent(&format!("{err}"), " ")).into()
});
match &result {
Ok(e) => {
*complete = e.is_complete();
}
Err(_) => {
identity_failed = true;
}
};
progress |=
self.handle_eval_result(result, strategy, || format!("{}", identity));
}
if self.query_callback.is_some()
&& strategy == SolvingStrategy::SingleVariableAffine
{
for (poly_id, column) in self.fixed_data.witness_cols.iter() {
if !self.has_known_next_value(&poly_id) && column.query.is_some() {
let result = self.process_witness_query(&column);
progress |=
self.handle_eval_result(result, strategy, || "<query>".into());
}
}
}
if !progress || identity_failed {
break;
}
}
fn compute_next_row_or_initialize(
&mut self,
next_row: DegreeType,
phase: ProcessingPhase,
) -> ColumnMap<T> {
if phase == ProcessingPhase::Initialization {
assert_eq!(next_row, self.last_row());
self.current_row_index = next_row;
} else {
self.set_next_row_and_log(next_row);
}
if identity_failed {
let list_undetermined = |values: &ColumnMap<Option<T>>| {
values
.iter()
.filter_map(|(p, v)| {
if v.is_none() && self.is_relevant_witness(&p) {
Some(self.fixed_data.column_name(&p).to_string())
} else {
None
}
})
.collect::<Vec<String>>()
.join(", ")
};
log::error!(
"\nError: Row {next_row}: Identity check failed or unable to derive values for some witness columns.\nSet RUST_LOG=debug for more information.");
log::debug!(
"\nThe following columns were undetermined in the previous row and might have been needed to derive this row's values:\n{}",
list_undetermined(&self.current)
log::trace!("Row: {}", self.current_row_index);
log::trace!(" Going over all identities until no more progress is made");
// First, go over identities that don't reference the next row,
// Second, propagate values to the next row by going over identities that do reference the next row.
let mut identities_without_next_ref =
CompletableIdentities::new(self.identities_without_next_ref.iter().cloned());
let mut identities_with_next_ref =
CompletableIdentities::new(self.identities_with_next_ref.iter().cloned());
self.loop_until_no_progress(&mut identities_without_next_ref)
.and_then(|_| self.loop_until_no_progress(&mut identities_with_next_ref))
.map_err(|e| self.report_failure_and_panic_unsatisfiable(e))
.unwrap();
// Check that the computed row is "final" by asserting that all unknown values can
// be set to 0.
// This check is skipped in the initialization phase (run on the last row),
// because its only purpose is to transfer values to the first row,
// not to finalize the last row.
if phase == ProcessingPhase::Regular {
log::trace!(
" Checking that remaining identities hold when unknown values are set to 0"
);
log::debug!(
"\nThe following columns are still undetermined in the current row:\n{}",
list_undetermined(&self.next)
);
log::debug!(
"\nReasons:\n{}\n",
self.failure_reasons
.iter()
.map(|r| r.to_string())
.join("\n\n")
);
log::debug!(
"Determined range constraints for this row:\n{}",
self.next_range_constraints
.iter()
.filter_map(|(id, cons)| {
cons.as_ref()
.map(|cons| format!(" {}: {cons}", self.fixed_data.column_name(&id)))
})
.join("\n")
);
log::debug!(
"Current values (known nonzero first, then zero, unknown omitted):\n{}",
indent(&self.format_next_known_values().join("\n"), " ")
);
panic!("Witness generation failed.");
self.process_identities(&mut identities_without_next_ref, true)
.and_then(|_| self.process_identities(&mut identities_with_next_ref, true))
.map_err(|e| self.report_failure_and_panic_underconstrained(e))
.unwrap();
}
log::trace!(
"===== Row {next_row}:\n{}",
indent(&self.format_next_values().join("\n"), " ")
"{}",
self.current
.render(&format!("===== Row {}", self.current_row_index), true)
);
std::mem::swap(&mut self.next, &mut self.current);
self.next = self.fixed_data.witness_map_with(None);
self.next_range_constraints = self.fixed_data.witness_map_with(None);
self.current.clone().unwrap_or_default()
self.shift_rows();
self.previous.clone().into()
}
/// Loops over all identities and queries, until no further progress is made.
/// @returns the "incomplete" identities, i.e. identities that contain unknown values.
fn loop_until_no_progress(
&mut self,
identities: &mut CompletableIdentities<'a, T>,
) -> Result<(), Vec<EvalError<T>>> {
loop {
let mut progress = self.process_identities(identities, false)?;
if let Some(ref mut query_processor) = self.query_processor {
let row_pair = RowPair::new(
&self.current,
&self.next,
self.current_row_index,
self.fixed_data,
false,
);
let updates = query_processor.process_queries_on_current_row(&row_pair);
let mut row_updater =
RowUpdater::new(&mut self.current, &mut self.next, self.current_row_index);
progress |= row_updater.apply_updates(&updates, || "query".to_string());
}
if !progress {
break;
}
}
Ok(())
}
/// Loops over all identities once and updates the current row and next row.
/// Arguments:
/// * `identities`: Identities to process. Completed identities are removed from the list.
/// * `frozen`: If true, the identities are processed assuming that all unknown values are 0.
/// Also, no updates are applied to the current row.
/// Returns:
/// * `Ok(true)`: If progress was made.
/// * `Ok(false)`: If no progress was made.
/// * `Err(errors)`: If an error occurred.
fn process_identities(
&mut self,
identities: &mut CompletableIdentities<'a, T>,
frozen: bool,
) -> Result<bool, Vec<EvalError<T>>> {
let mut progress = false;
let mut errors = vec![];
for (identity, is_complete) in identities.iter_mut() {
if *is_complete {
continue;
}
let row_pair = RowPair::new(
&self.current,
&self.next,
self.current_row_index,
self.fixed_data,
frozen,
);
let result: EvalResult<'a, T> = self
.identity_processor
.process_identity(identity, &row_pair)
.map_err(|err| {
format!("{identity}:\n{}", indent(&format!("{err}"), " ")).into()
});
match result {
Ok(eval_value) => {
if frozen {
assert!(eval_value.constraints.is_empty())
} else {
*is_complete = eval_value.is_complete();
let mut row_updater = RowUpdater::new(
&mut self.current,
&mut self.next,
self.current_row_index,
);
progress |=
row_updater.apply_updates(&eval_value, || format!("{identity}"));
}
}
Err(e) => {
errors.push(e);
}
};
}
if errors.is_empty() {
Ok(progress)
} else {
Err(errors)
}
}
/// Shifts rows: fresh row -> next -> current -> previous
fn shift_rows(&mut self) {
let mut fresh_row = self.row_factory.fresh_row();
std::mem::swap(&mut self.previous, &mut fresh_row);
std::mem::swap(&mut self.current, &mut self.previous);
std::mem::swap(&mut self.next, &mut self.current);
}
fn report_failure_and_panic_unsatisfiable(&self, failures: Vec<EvalError<T>>) -> ! {
log::error!(
"\nError: Row {} failed. Set RUST_LOG=debug for more information.\n",
self.current_row_index
);
log::debug!("Some identities where not satisfiable after the following values were uniquely determined (known nonzero first, then zero, unknown omitted):");
log::debug!("{}", self.current.render("Current Row", false));
log::debug!("{}", self.next.render("Next Row", false));
log::debug!("Set RUST_LOG=trace to understand why these values were chosen.");
log::debug!(
"Assuming these values are correct, the following identities fail:\n{}\n",
failures
.iter()
.map(|r| indent(&r.to_string(), " "))
.join("\n")
);
panic!("Witness generation failed.");
}
fn report_failure_and_panic_underconstrained(&self, failures: Vec<EvalError<T>>) -> ! {
log::error!(
"\nError: Row {} failed. Set RUST_LOG=debug for more information.\n",
self.current_row_index
);
log::debug!("Some columns could not be determined, but setting them to zero does not satisfy the constraints. This typically means that the system is underconstrained!");
log::debug!("{}", self.current.render("Current Row", true));
log::debug!("{}", self.next.render("Next Row", true));
log::debug!("\nSet RUST_LOG=trace to understand why these values were (not) chosen.");
log::debug!(
"Assuming zero for unknown values, the following identities fail:\n{}\n",
failures
.iter()
.map(|r| indent(&r.to_string(), " "))
.join("\n")
);
panic!("Witness generation failed.");
}
/// Verifies the proposed values for the next row.
@@ -224,28 +317,84 @@ where
/// not used.
pub fn propose_next_row(&mut self, next_row: DegreeType, values: &ColumnMap<T>) -> bool {
self.set_next_row_and_log(next_row);
self.next = values.clone().wrap_some();
for identity in self.identities {
let proposed_row = self.row_factory.row_from_known_values(values);
let constraints_valid =
self.check_row_pair(&proposed_row, false) && self.check_row_pair(&proposed_row, true);
if constraints_valid {
self.previous = proposed_row;
} else {
// Note that we never update `current` if proposing a row succeeds (the happy path).
// If it doesn't, we re-run compute_next_row on the previous row in order to
// correctly forward-propagate values via next references.
std::mem::swap(&mut self.current, &mut self.previous);
self.next = self.row_factory.fresh_row();
self.compute_next_row(next_row - 1);
}
constraints_valid
}
fn check_row_pair(&mut self, proposed_row: &Row<T>, previous: bool) -> bool {
let row_pair = match previous {
// Check whether identities with a reference to the next row are satisfied
// when applied to the previous row and the proposed row.
true => RowPair::new(
&self.previous,
proposed_row,
self.current_row_index - 1,
self.fixed_data,
true,
),
// Check whether identities without a reference to the next row are satisfied
// when applied to the proposed row.
// Note that we also provide the next row here, but it is not used.
false => RowPair::new(
proposed_row,
&self.next,
self.current_row_index,
self.fixed_data,
true,
),
};
// Check identities depending on whether or not they have a reference to the next row:
// - Those that do should be checked on the previous and proposed row
// - All others should be checked on the proposed row
let identities = match previous {
true => &self.identities_with_next_ref,
false => &self.identities_without_next_ref,
};
for identity in identities.iter() {
if self
.process_identity(identity, SolvingStrategy::AssumeZero)
.identity_processor
.process_identity(identity, &row_pair)
.is_err()
{
self.next = self.fixed_data.witness_map_with(None);
self.next_range_constraints = self.fixed_data.witness_map_with(None);
log::debug!("Previous {:?}", self.previous);
log::debug!("Proposed {:?}", proposed_row);
log::debug!("Failed on identity: {}", identity);
return false;
}
}
std::mem::swap(&mut self.next, &mut self.current);
self.next = self.fixed_data.witness_map_with(None);
self.next_range_constraints = self.fixed_data.witness_map_with(None);
true
}
pub fn machine_witness_col_values(&mut self) -> HashMap<String, Vec<T>> {
pub fn machine_witness_col_values(&mut self) -> HashMap<PolyID, Vec<T>> {
let mut result: HashMap<_, _> = Default::default();
for m in &mut self.machines {
result.extend(m.witness_col_values(self.fixed_data));
let name_to_id = self
.fixed_data
.witness_column_names
.iter()
.map(|(poly_id, &name)| (name, poly_id))
.collect::<BTreeMap<_, _>>();
for m in &mut self.identity_processor.machines {
for (col_name, col) in m.witness_col_values(self.fixed_data) {
result.insert(*name_to_id.get(col_name.as_str()).unwrap(), col);
}
}
result
}
@@ -263,392 +412,11 @@ where
);
self.last_report = next_row;
}
self.next_row = next_row;
}
fn format_next_values(&self) -> Vec<String> {
self.format_next_values_iter(
self.next
.iter()
.filter(|(i, _)| self.is_relevant_witness(i)),
)
}
fn format_next_known_values(&self) -> Vec<String> {
self.format_next_values_iter(self.next.iter().filter(|(_, v)| v.is_some()))
}
fn format_next_values_iter<'b>(
&self,
values: impl IntoIterator<Item = (PolyID, &'b Option<T>)>,
) -> Vec<String> {
let mut values = values.into_iter().collect::<Vec<_>>();
values.sort_by_key(|(i, v1)| {
(
match v1 {
Some(v) if v.is_zero() => 1,
Some(_) => 0,
None => 2,
},
*i,
)
});
values
.into_iter()
.map(|(i, v)| {
format!(
"{} = {}",
self.fixed_data.column_name(&i),
v.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| "<unknown>".to_string())
)
})
.collect()
}
fn process_witness_query(&mut self, column: &&'a WitnessColumn<T>) -> EvalResult<'a, T> {
let query = column.query.as_ref().unwrap();
let query_string = match self.interpolate_query(query.expr) {
Ok(query) => query,
Err(incomplete) => return Ok(EvalValue::incomplete(incomplete)),
};
if let Some(value) = self
.query_callback
.as_mut()
.and_then(|c| (c)(&query_string))
{
Ok(EvalValue::complete(vec![(
&query.poly,
Constraint::Assignment(value),
)]))
} else {
Ok(EvalValue::incomplete(IncompleteCause::NoQueryAnswer(
query_string,
column.name.to_string(),
)))
}
}
fn interpolate_query<'b>(
&self,
query: &'b Expression<T>,
) -> Result<String, IncompleteCause<&'b PolynomialReference>> {
// 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('"', "\\\"")
)),
Expression::MatchExpression(scrutinee, arms) => {
self.interpolate_match_expression_for_query(scrutinee.as_ref(), arms)
}
_ => self
.evaluate(query, EvaluationRow::Next, EvaluateUnknown::Symbolic)?
.constant_value()
.map(|c| c.to_string())
.ok_or(IncompleteCause::NonConstantQueryElement),
}
}
fn interpolate_match_expression_for_query<'b>(
&self,
scrutinee: &'b Expression<T>,
arms: &'b [(Option<T>, Expression<T>)],
) -> Result<String, IncompleteCause<&'b PolynomialReference>> {
let v = self
.evaluate(scrutinee, EvaluationRow::Next, EvaluateUnknown::Symbolic)?
.constant_value()
.ok_or(IncompleteCause::NonConstantQueryMatchScrutinee)?;
let (_, expr) = arms
.iter()
.find(|(n, _)| n.is_none() || n.as_ref() == Some(&v))
.ok_or(IncompleteCause::NoMatchArmFound)?;
self.interpolate_query(expr)
}
fn process_identity<'b>(
&mut self,
identity: &'b Identity<T>,
strategy: SolvingStrategy,
) -> EvalResult<'b, T> {
match identity.kind {
IdentityKind::Polynomial => {
self.process_polynomial_identity(identity.expression_for_poly_id(), strategy)
}
IdentityKind::Plookup | IdentityKind::Permutation => {
self.process_plookup(identity, strategy)
}
kind => {
unimplemented!("Identity of kind {kind:?} is not supported in the executor")
}
}
}
fn process_polynomial_identity<'b>(
&self,
identity: &'b Expression<T>,
strategy: SolvingStrategy,
) -> EvalResult<'b, T> {
// If there is no "next" reference in the expression,
// we just evaluate it directly on the "next" row.
let row = if identity.contains_next_witness_ref() {
// TODO this is the only situation where we use "current"
// TODO this is the only that actually uses a window.
EvaluationRow::Current
} else {
EvaluationRow::Next
};
let evaluate_unknown = if strategy == SolvingStrategy::AssumeZero {
EvaluateUnknown::AssumeZero
} else {
EvaluateUnknown::Symbolic
};
let evaluated = match self.evaluate(identity, row, evaluate_unknown) {
Ok(evaluated) => evaluated,
Err(cause) => {
return Ok(EvalValue::incomplete(cause));
}
};
if evaluated.constant_value() == Some(0.into()) {
Ok(EvalValue::complete(vec![]))
} else {
evaluated.solve_with_range_constraints(&self.range_constraint_set())
}
}
fn process_plookup<'b>(
&mut self,
identity: &'b Identity<T>,
strategy: SolvingStrategy,
) -> EvalResult<'b, T> {
let evaluate_unknown = if strategy == SolvingStrategy::AssumeZero {
EvaluateUnknown::AssumeZero
} else {
EvaluateUnknown::Symbolic
};
if let Some(left_selector) = &identity.left.selector {
let value = match self.evaluate(left_selector, EvaluationRow::Next, evaluate_unknown) {
Ok(value) => value,
Err(cause) => return Ok(EvalValue::incomplete(cause)),
};
match value.constant_value() {
Some(v) if v.is_zero() => {
return Ok(EvalValue::complete(vec![]));
}
Some(v) if v.is_one() => {}
_ => {
return Ok(EvalValue::incomplete(
IncompleteCause::NonConstantLeftSelector,
))
}
};
}
let left = identity
.left
.expressions
.iter()
.map(|e| self.evaluate(e, EvaluationRow::Next, evaluate_unknown))
.collect::<Vec<_>>();
// Now query the machines.
// Note that we should always query all machines that match, because they might
// update their internal data, even if all values are already known.
// TODO could it be that multiple machines match?
// query the fixed lookup "machine"
if let Some(result) = self.fixed_lookup.process_plookup(
self.fixed_data,
identity.kind,
&left,
&identity.right,
) {
return result;
}
for m in &mut self.machines {
// TODO also consider the reasons above.
if let Some(result) = m.process_plookup(
self.fixed_data,
self.fixed_lookup,
identity.kind,
&left,
&identity.right,
) {
return result;
}
}
unimplemented!("No executor machine matched identity `{identity}`")
}
/// Processes the evaluation result: Stores failure reasons and updates next values.
/// Returns true if a new value or constraint was determined.
fn handle_eval_result(
&mut self,
result: EvalResult<T>,
strategy: SolvingStrategy,
source_name: impl Fn() -> String,
) -> bool {
let mut first = true;
match result {
Ok(constraints) => {
let progress = !constraints.is_empty();
// If we assume unknown variables to be zero, we cannot learn anything new.
if strategy == SolvingStrategy::AssumeZero {
assert!(!progress);
}
for (id, c) in constraints.constraints {
if first {
log::trace!(" Processing: {}", source_name());
first = false;
}
match c {
Constraint::Assignment(value) => {
log::trace!(" => {id} = {value}");
self.next[&id.poly_id()] = Some(value);
}
Constraint::RangeConstraint(cons) => {
log::trace!(" => Adding range constraint for {id}: {cons}");
let old = &mut self.next_range_constraints[&id.poly_id()];
let new = match old {
Some(c) => Some(cons.conjunction(c)),
None => Some(cons),
};
log::trace!(" (the conjunction is {})", new.as_ref().unwrap());
*old = new;
}
}
}
progress
}
Err(reason) => {
self.failure_reasons.push(reason);
false
}
}
}
fn has_known_next_value(&self, id: &PolyID) -> bool {
self.next[id].is_some()
self.current_row_index = next_row;
}
/// Returns true if this is a witness column we care about (instead of a sub-machine witness).
pub fn is_relevant_witness(&self, id: &PolyID) -> bool {
self.witnesses.contains(id)
}
/// Tries to evaluate the expression to an expression affine in the witness polynomials,
/// taking current values of polynomials into account.
/// @returns an expression affine in the witness polynomials
fn evaluate<'b>(
&self,
expr: &'b Expression<T>,
evaluate_row: EvaluationRow,
evaluate_unknown: EvaluateUnknown,
) -> AffineResult<&'b PolynomialReference, T> {
let degree = self.fixed_data.degree;
// We want to determine the values of next_row, but if the expression contains
// references to the next row, we want to evaluate the expression for the current row
// in order to determine values for next_row.
// Otherwise, we want to evaluate the expression on next_row directly.
let fixed_row = match evaluate_row {
EvaluationRow::Current => (self.next_row + degree - 1) % degree,
EvaluationRow::Next => self.next_row,
};
ExpressionEvaluator::new(SymoblicWitnessEvaluator::new(
self.fixed_data,
fixed_row,
EvaluationData {
current_witnesses: &self.current,
next_witnesses: &self.next,
evaluate_row,
evaluate_unknown,
},
))
.evaluate(expr)
}
fn range_constraint_set(&'a self) -> WitnessRangeConstraintSet<'a, T> {
WitnessRangeConstraintSet {
global_range_constraints: &self.global_range_constraints,
next_range_constraints: &self.next_range_constraints,
}
}
}
struct WitnessRangeConstraintSet<'a, T: FieldElement> {
/// Global constraints on witness and fixed polynomials.
global_range_constraints: &'a ColumnMap<Option<RangeConstraint<T>>>,
/// Range constraints on the witness polynomials in the next row.
next_range_constraints: &'a ColumnMap<Option<RangeConstraint<T>>>,
}
impl<'a, T: FieldElement> RangeConstraintSet<&PolynomialReference, T>
for WitnessRangeConstraintSet<'a, T>
{
fn range_constraint(&self, poly: &PolynomialReference) -> Option<RangeConstraint<T>> {
// Combine potential global range constraints with local range constraints.
let global = self.global_range_constraints[&poly.poly_id()].as_ref();
let local = self.next_range_constraints[&poly.poly_id()].as_ref();
match (global, local) {
(None, None) => None,
(None, Some(con)) | (Some(con), None) => Some(con.clone()),
(Some(g), Some(l)) => Some(g.conjunction(l)),
}
}
}
struct EvaluationData<'a, T> {
/// Values of the witness polynomials in the current / last row
pub current_witnesses: &'a ColumnMap<Option<T>>,
/// Values of the witness polynomials in the next row
pub next_witnesses: &'a ColumnMap<Option<T>>,
pub evaluate_row: EvaluationRow,
pub evaluate_unknown: EvaluateUnknown,
}
impl<'a, T: FieldElement> WitnessColumnEvaluator<T> for EvaluationData<'a, T> {
fn value<'b>(&self, poly: &'b PolynomialReference) -> AffineResult<&'b PolynomialReference, T> {
let id = poly.poly_id();
match (poly.next, self.evaluate_row) {
(false, 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_witnesses[&id]
.as_ref()
.map(|value| (*value).into())
.ok_or(IncompleteCause::PreviousValueUnknown(poly))
}
(false, EvaluationRow::Next) | (true, EvaluationRow::Current) => {
Ok(if let Some(value) = &self.next_witnesses[&id] {
// We already computed the concrete value
(*value).into()
} else if self.evaluate_unknown == EvaluateUnknown::AssumeZero {
T::from(0).into()
} else {
// We continue with a symbolic value
AffineExpression::from_variable_id(poly)
})
}
(true, EvaluationRow::Next) => {
unimplemented!(
"{poly} references the next-next row when evaluating on the current row."
);
}
}
}
}

View File

@@ -0,0 +1,133 @@
use ast::analyzed::{Expression, Identity, IdentityKind, PolynomialReference};
use number::FieldElement;
use super::{
machines::{FixedLookup, Machine},
rows::RowPair,
EvalResult, EvalValue, FixedData, IncompleteCause,
};
/// Computes (value or range constraint) updates given a [RowPair] and [Identity].
pub struct IdentityProcessor<'a, T: FieldElement> {
fixed_data: &'a FixedData<'a, T>,
fixed_lookup: &'a mut FixedLookup<T>,
pub machines: Vec<Box<dyn Machine<T>>>,
}
impl<'a, T: FieldElement> IdentityProcessor<'a, T> {
pub fn new(
fixed_data: &'a FixedData<'a, T>,
fixed_lookup: &'a mut FixedLookup<T>,
machines: Vec<Box<dyn Machine<T>>>,
) -> Self {
Self {
fixed_data,
fixed_lookup,
machines,
}
}
/// Given an identity and a row pair, tries to figure out additional values / range constraints
/// for the given cells.
/// Fails if any constraint was not satisfiable.
/// Returns the updates.
pub fn process_identity<'b>(
&mut self,
identity: &'b Identity<T>,
rows: &RowPair<T>,
) -> EvalResult<'b, T> {
match identity.kind {
IdentityKind::Polynomial => self.process_polynomial_identity(identity, rows),
IdentityKind::Plookup | IdentityKind::Permutation => {
self.process_plookup(identity, rows)
}
kind => {
unimplemented!(
"Identity of kind {kind:?} is not supported by the identity processor."
)
}
}
}
fn process_polynomial_identity<'b>(
&self,
identity: &'b Identity<T>,
rows: &RowPair<T>,
) -> EvalResult<'b, T> {
let expression = identity.expression_for_poly_id();
let evaluated = match rows.evaluate(expression) {
Err(inclomplete_cause) => return Ok(EvalValue::incomplete(inclomplete_cause)),
Ok(evaluated) => evaluated,
};
evaluated.solve_with_range_constraints(rows)
}
fn process_plookup<'b>(
&mut self,
identity: &'b Identity<T>,
rows: &RowPair<T>,
) -> EvalResult<'b, T> {
if let Some(left_selector) = &identity.left.selector {
if let Some(status) = self.handle_left_selector(left_selector, rows) {
return Ok(status);
}
}
let left = identity
.left
.expressions
.iter()
.map(|e| rows.evaluate(e))
.collect::<Vec<_>>();
// Now query the machines.
// Note that we should always query all machines that match, because they might
// update their internal data, even if all values are already known.
// TODO could it be that multiple machines match?
// query the fixed lookup "machine"
if let Some(result) = self.fixed_lookup.process_plookup(
self.fixed_data,
identity.kind,
&left,
&identity.right,
) {
return result;
}
for m in &mut self.machines {
// TODO also consider the reasons above.
if let Some(result) = m.process_plookup(
self.fixed_data,
self.fixed_lookup,
identity.kind,
&left,
&identity.right,
) {
return result;
}
}
unimplemented!("No executor machine matched identity `{identity}`")
}
/// Returns updates of the left selector cannot be evaluated to 1, otherwise None.
fn handle_left_selector<'b>(
&mut self,
left_selector: &'b Expression<T>,
rows: &RowPair<T>,
) -> Option<EvalValue<&'b PolynomialReference, T>> {
let value = match rows.evaluate(left_selector) {
Err(inclomplete_cause) => return Some(EvalValue::incomplete(inclomplete_cause)),
Ok(value) => value,
};
match value.constant_value() {
Some(v) if v.is_zero() => Some(EvalValue::complete(vec![])),
Some(v) if v.is_one() => None,
_ => Some(EvalValue::incomplete(
IncompleteCause::NonConstantLeftSelector,
)),
}
}
}

View File

@@ -596,7 +596,7 @@ impl<T: FieldElement> BlockMachine<T> {
ExpressionEvaluator::new(SymoblicWitnessEvaluator::new(
fixed_data,
self.row,
WitnessData {
&WitnessData {
fixed_data,
data: &self.data,
row: self.row,

View File

@@ -21,12 +21,24 @@ mod expression_evaluator;
pub mod fixed_evaluator;
mod generator;
mod global_constraints;
mod identity_processor;
mod machines;
mod query_processor;
mod range_constraints;
mod rows;
pub mod symbolic_evaluator;
mod symbolic_witness_evaluator;
mod util;
// TODO: This is done once for the witness column names, so that they can be referenced
// by the `Cell`. The reason is that we want machines (e.g. `BlockMachine`) to own
// their rows, but because they live in a `Box`, they can't have a non-static lifetime
// parameter. #488 prototypes solving this, so this can be removed in the future.
/// Leaks a string and returns a reference with static lifetime.
fn leak_string(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
/// Generates the committed polynomial values
/// @returns the values (in source order) and the degree of the polynomials.
pub fn generate<'a, T: FieldElement, QueryCallback>(
@@ -139,11 +151,7 @@ where
}
// Overwrite all machine witness columns
for (name, data) in generator.machine_witness_col_values() {
let poly_id = *poly_ids
.iter()
.find(|&p| fixed.column_name(p) == name)
.unwrap();
for (poly_id, data) in generator.machine_witness_col_values() {
columns[&poly_id] = data;
}
@@ -197,20 +205,31 @@ fn rows_are_repeating<T: PartialEq>(
/// Data that is fixed for witness generation.
pub struct FixedData<'a, T> {
degree: DegreeType,
witness_column_names: ColumnMap<&'static str>,
fixed_cols: ColumnMap<FixedColumn<'a, T>>,
witness_cols: ColumnMap<WitnessColumn<'a, T>>,
}
impl<'a, T> FixedData<'a, T> {
impl<'a, T: FieldElement> FixedData<'a, T> {
pub fn new(
degree: DegreeType,
fixed_cols: ColumnMap<FixedColumn<'a, T>>,
witness_cols: ColumnMap<WitnessColumn<'a, T>>,
) -> Self {
// Leak the column names once, so that they can be referenced without
// having to worry about lifetimes.
let witness_column_names = ColumnMap::from(
witness_cols
.values()
.map(|witness_col| leak_string(witness_col.name.clone())),
PolynomialType::Committed,
);
FixedData {
degree,
fixed_cols,
witness_cols,
witness_column_names,
}
}

View File

@@ -0,0 +1,94 @@
use ast::analyzed::{Expression, PolynomialReference};
use number::FieldElement;
use super::{rows::RowPair, Constraint, EvalValue, FixedData, IncompleteCause, Query};
/// Computes value updates that result from a query.
pub struct QueryProcessor<'a, T: FieldElement, QueryCallback: Send + Sync> {
fixed_data: &'a FixedData<'a, T>,
query_callback: QueryCallback,
}
impl<'a, T: FieldElement, QueryCallback> QueryProcessor<'a, T, QueryCallback>
where
QueryCallback: FnMut(&str) -> Option<T> + Send + Sync,
{
pub fn new(fixed_data: &'a FixedData<'a, T>, query_callback: QueryCallback) -> Self {
Self {
fixed_data,
query_callback,
}
}
pub fn process_queries_on_current_row(
&mut self,
rows: &RowPair<T>,
) -> EvalValue<&'a PolynomialReference, T> {
let mut eval_value = EvalValue::complete(vec![]);
for column in self.fixed_data.witness_cols.values() {
if let Some(query) = column.query.as_ref() {
if rows.get_value(&query.poly).is_none() {
eval_value.combine(self.process_witness_query(query, rows));
}
}
}
eval_value
}
fn process_witness_query(
&mut self,
query: &'a Query<'_, T>,
rows: &RowPair<T>,
) -> EvalValue<&'a PolynomialReference, T> {
let query_str = match interpolate_query(query.expr, rows) {
Ok(query) => query,
Err(incomplete) => return EvalValue::incomplete(incomplete),
};
if let Some(value) = (self.query_callback)(&query_str) {
EvalValue::complete(vec![(&query.poly, Constraint::Assignment(value))])
} else {
EvalValue::incomplete(IncompleteCause::NoQueryAnswer(
query_str,
query.poly.name.to_string(),
))
}
}
}
fn interpolate_query<'b, T: FieldElement>(
query: &'b Expression<T>,
rows: &RowPair<T>,
) -> Result<String, IncompleteCause<&'b PolynomialReference>> {
// TODO combine that with the constant evaluator and the commit evaluator...
match query {
Expression::Tuple(items) => Ok(items
.iter()
.map(|i| interpolate_query(i, rows))
.collect::<Result<Vec<_>, _>>()?
.join(", ")),
Expression::LocalVariableReference(i) => {
assert!(*i == 0);
Ok(format!("{}", rows.current_row_index))
}
Expression::String(s) => Ok(format!(
"\"{}\"",
s.replace('\\', "\\\\").replace('"', "\\\"")
)),
Expression::MatchExpression(scrutinee, arms) => {
let v = rows
.evaluate(scrutinee)?
.constant_value()
.ok_or(IncompleteCause::NonConstantQueryMatchScrutinee)?;
let (_, expr) = arms
.iter()
.find(|(n, _)| n.is_none() || n.as_ref() == Some(&v))
.ok_or(IncompleteCause::NoMatchArmFound)?;
interpolate_query(expr, rows)
}
_ => rows
.evaluate(query)?
.constant_value()
.map(|c| c.to_string())
.ok_or(IncompleteCause::NonConstantQueryElement),
}
}

339
executor/src/witgen/rows.rs Normal file
View File

@@ -0,0 +1,339 @@
use std::fmt::Debug;
use ast::analyzed::{Expression, PolynomialReference, PolynomialType};
use itertools::Itertools;
use number::{DegreeType, FieldElement};
use crate::witgen::Constraint;
use super::{
affine_expression::{AffineExpression, AffineResult},
column_map::ColumnMap,
expression_evaluator::ExpressionEvaluator,
global_constraints::RangeConstraintSet,
range_constraints::RangeConstraint,
symbolic_witness_evaluator::{SymoblicWitnessEvaluator, WitnessColumnEvaluator},
EvalValue, FixedData,
};
#[derive(Clone)]
enum CellValue<T: FieldElement> {
Known(T),
RangeConstraint(RangeConstraint<T>),
Unknown,
}
impl<T: FieldElement> CellValue<T> {
fn is_known(&self) -> bool {
matches!(self, CellValue::Known(_))
}
fn unwrap_or_default(&self) -> T {
match self {
CellValue::Known(v) => *v,
_ => Default::default(),
}
}
}
/// A single cell, holding an optional value and range constraint.
#[derive(Clone)]
pub struct Cell<T: FieldElement> {
/// The column name, for debugging purposes.
pub name: &'static str,
value: CellValue<T>,
}
impl<T: FieldElement> Debug for Cell<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let debug_str = match &self.value {
CellValue::Known(v) => format!("{} = {}", self.name, v),
CellValue::RangeConstraint(rc) => {
format!("{} = <unknown>\n (range constraint: {})", self.name, rc)
}
CellValue::Unknown => format!("{} = <unknown>", self.name),
};
f.write_str(&debug_str)
}
}
/// A row of cells, indexed by polynomial ID.
pub type Row<T> = ColumnMap<Cell<T>>;
impl<T: FieldElement> Debug for Row<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.render("Row:", true))
}
}
impl<T: FieldElement> Row<T> {
/// Builds a string representing the current row
pub fn render(&self, title: &str, include_unknown: bool) -> String {
format!("{}:\n{}", title, self.render_values(include_unknown))
}
/// Builds a string listing all values, one by row. Nonzero entries are
/// first, then zero, then unknown (if `include_unknown == true`).
pub fn render_values(&self, include_unknown: bool) -> String {
let mut cells = self
.iter()
.filter(|(_, cell)| cell.value.is_known() || include_unknown)
.collect::<Vec<_>>();
// Nonzero first, then zero, then unknown
cells.sort_by_key(|(i, cell)| {
(
match cell.value {
CellValue::Known(v) if v.is_zero() => 1,
CellValue::Known(_) => 0,
_ => 2,
},
*i,
)
});
cells
.into_iter()
.map(|(_, cell)| format!(" {:?}", cell))
.join("\n")
}
}
/// A factory for rows, which knows the global range constraints and has pointers to column names.
pub struct RowFactory<'a, T: FieldElement> {
fixed_data: &'a FixedData<'a, T>,
global_range_constraints: ColumnMap<Option<RangeConstraint<T>>>,
}
impl<'a, T: FieldElement> RowFactory<'a, T> {
pub fn new(
fixed_data: &'a FixedData<'a, T>,
global_range_constraints: ColumnMap<Option<RangeConstraint<T>>>,
) -> Self {
Self {
fixed_data,
global_range_constraints,
}
}
pub fn fresh_row(&self) -> Row<T> {
ColumnMap::from(
self.global_range_constraints
.iter()
.map(|(poly_id, range_constraint)| Cell {
name: self.fixed_data.witness_column_names[&poly_id],
value: match range_constraint.as_ref() {
Some(rc) => CellValue::RangeConstraint(rc.clone()),
None => CellValue::Unknown,
},
}),
PolynomialType::Committed,
)
}
pub fn row_from_known_values(&self, values: &ColumnMap<T>) -> Row<T> {
ColumnMap::from(
values.iter().map(|(poly_id, &v)| Cell {
name: self.fixed_data.witness_column_names[&poly_id],
value: CellValue::Known(v),
}),
PolynomialType::Committed,
)
}
}
impl<T: FieldElement> From<Row<T>> for ColumnMap<T> {
/// Builds a map from polynomial ID to value. Unknown values are set to zero.
fn from(val: Row<T>) -> Self {
ColumnMap::from(
val.into_iter()
.map(|(_, cell)| cell.value.unwrap_or_default()),
PolynomialType::Committed,
)
}
}
/// A pair of mutable row references which knows how to apply updates.
pub struct RowUpdater<'row, T: FieldElement> {
current: &'row mut Row<T>,
next: &'row mut Row<T>,
current_row_index: DegreeType,
}
impl<'row, T: FieldElement> RowUpdater<'row, T> {
pub fn new(
current: &'row mut Row<T>,
next: &'row mut Row<T>,
current_row_index: DegreeType,
) -> Self {
Self {
current,
next,
current_row_index,
}
}
/// Applies the updates to the underlying rows. Returns true if any updates
/// were applied.
///
/// # Panics
/// Panics if any updates are redundant, as this indicates a bug that would
/// potentially cause infinite loops otherwise.
pub fn apply_updates(
&mut self,
updates: &EvalValue<&PolynomialReference, T>,
source_name: impl Fn() -> String,
) -> bool {
if updates.constraints.is_empty() {
return false;
}
log::trace!(" Updates from: {}", source_name());
for (poly, c) in &updates.constraints {
match c {
Constraint::Assignment(value) => {
self.set_value(poly, *value);
}
Constraint::RangeConstraint(constraint) => {
self.update_range_constraint(poly, constraint);
}
}
}
true
}
fn get_cell_mut<'b>(&'b mut self, poly: &PolynomialReference) -> &'b mut Cell<T> {
match poly.next {
false => &mut self.current[&poly.poly_id()],
true => &mut self.next[&poly.poly_id()],
}
}
fn row_number(&self, poly: &PolynomialReference) -> DegreeType {
match poly.next {
false => self.current_row_index,
true => self.current_row_index + 1,
}
}
fn set_value(&mut self, poly: &PolynomialReference, value: T) {
log::trace!(
" => {} (Row {}) = {}",
poly.name,
self.row_number(poly),
value
);
let cell = self.get_cell_mut(poly);
// Note that this is a problem even if the value that was set is the same,
// because we would return that progress was made when it wasn't.
assert!(!cell.value.is_known(), "Value was already set");
cell.value = CellValue::Known(value);
}
fn update_range_constraint(
&mut self,
poly: &PolynomialReference,
constraint: &RangeConstraint<T>,
) {
log::trace!(
" => Adding range constraint for {} (Row {}): {}",
poly.name,
self.row_number(poly),
constraint
);
let cell = self.get_cell_mut(poly);
let new = match &cell.value {
CellValue::RangeConstraint(c) => constraint.conjunction(c),
_ => constraint.clone(),
};
if let CellValue::RangeConstraint(old) = &cell.value {
assert!(*old != new, "Range constraint was already set");
}
assert!(
!cell.value.is_known(),
"Range constraint was updated but value is already known"
);
log::trace!(" (the conjunction is {})", new);
cell.value = CellValue::RangeConstraint(new);
}
}
/// A pair of row references which knows which value / range constraint
/// to return for a given [PolynomialReference].
pub struct RowPair<'row, 'a, T: FieldElement> {
pub current: &'row Row<T>,
pub next: &'row Row<T>,
pub current_row_index: DegreeType,
fixed_data: &'a FixedData<'a, T>,
evaluate_unknown_to_zero: bool,
}
impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> {
pub fn new(
current: &'row Row<T>,
next: &'row Row<T>,
current_row_index: DegreeType,
fixed_data: &'a FixedData<'a, T>,
evaluate_unknown_to_zero: bool,
) -> Self {
Self {
current,
next,
current_row_index,
fixed_data,
evaluate_unknown_to_zero,
}
}
fn get_cell(&self, poly: &PolynomialReference) -> &Cell<T> {
match poly.next {
false => &self.current[&poly.poly_id()],
true => &self.next[&poly.poly_id()],
}
}
pub fn get_value(&self, poly: &PolynomialReference) -> Option<T> {
match self.get_cell(poly).value {
CellValue::Known(value) => Some(value),
_ => {
if self.evaluate_unknown_to_zero {
Some(T::zero())
} else {
None
}
}
}
}
/// Tries to evaluate the expression to an expression affine in the witness polynomials,
/// taking current values of polynomials into account.
/// @returns an expression affine in the witness polynomials
pub fn evaluate<'b>(
&self,
expr: &'b Expression<T>,
) -> AffineResult<&'b PolynomialReference, T> {
ExpressionEvaluator::new(SymoblicWitnessEvaluator::new(
self.fixed_data,
self.current_row_index,
self,
))
.evaluate(expr)
}
}
impl<T: FieldElement> WitnessColumnEvaluator<T> for RowPair<'_, '_, T> {
fn value<'b>(&self, poly: &'b PolynomialReference) -> AffineResult<&'b PolynomialReference, T> {
Ok(match self.get_value(poly) {
Some(v) => v.into(),
None => AffineExpression::from_variable_id(poly),
})
}
}
impl<T: FieldElement> RangeConstraintSet<&PolynomialReference, T> for RowPair<'_, '_, T> {
fn range_constraint(&self, poly: &PolynomialReference) -> Option<RangeConstraint<T>> {
match self.get_cell(poly).value {
CellValue::RangeConstraint(ref c) => Some(c.clone()),
_ => None,
}
}
}

View File

@@ -16,7 +16,7 @@ pub trait WitnessColumnEvaluator<T> {
pub struct SymoblicWitnessEvaluator<'a, T, WA: WitnessColumnEvaluator<T>> {
fixed_data: &'a FixedData<'a, T>,
row: DegreeType,
witness_access: WA,
witness_access: &'a WA,
}
impl<'a, T, WA> SymoblicWitnessEvaluator<'a, T, WA>
@@ -26,7 +26,7 @@ where
/// Constructs a new SymbolicWitnessEvaluator
/// @param row the row on which to evaluate plain fixed
/// columns ("next columns" - f' - are evaluated on row + 1).
pub fn new(fixed_data: &'a FixedData<'a, T>, row: DegreeType, witness_access: WA) -> Self {
pub fn new(fixed_data: &'a FixedData<'a, T>, row: DegreeType, witness_access: &'a WA) -> Self {
Self {
fixed_data,
row,

View File

@@ -103,7 +103,7 @@ mod test {
#[test]
fn secondary_block_machine_add2() {
mock_prove_asm("../test_data/asm/secondary_block_machine_add2.asm", &vec![]);
mock_prove_asm("../test_data/asm/secondary_block_machine_add2.asm", &[]);
}
#[test]