Get rid of dummy blocks in BlockMachines

This commit is contained in:
Georg Wiese
2024-02-21 09:44:27 +01:00
parent 40ff9db41a
commit fdfdc7984d
7 changed files with 186 additions and 71 deletions

View File

@@ -8,7 +8,7 @@ use powdr_number::FieldElement;
use super::{
data_structures::finalizable_data::FinalizableData,
processor::{OuterQuery, Processor},
rows::UnknownStrategy,
rows::{RowIndex, UnknownStrategy},
sequence_iterator::{Action, ProcessingSequenceIterator, SequenceStep},
EvalError, EvalValue, FixedData, IncompleteCause, MutableState, QueryCallback,
};
@@ -27,7 +27,7 @@ pub struct BlockProcessor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> {
impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> BlockProcessor<'a, 'b, 'c, T, Q> {
pub fn new(
row_offset: u64,
row_offset: RowIndex,
data: FinalizableData<'a, T>,
mutable_state: &'c mut MutableState<'a, 'b, T, Q>,
identities: &'c [&'a Identity<Expression<T>>],
@@ -105,12 +105,11 @@ mod tests {
use crate::{
constant_evaluator::generate,
witgen::{
data_structures::column_map::FixedColumnMap,
data_structures::finalizable_data::FinalizableData,
data_structures::{column_map::FixedColumnMap, finalizable_data::FinalizableData},
global_constraints::GlobalConstraints,
identity_processor::Machines,
machines::FixedLookup,
rows::RowFactory,
rows::{RowFactory, RowIndex},
sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator},
unused_query_callback, FixedData, MutableState, QueryCallback,
},
@@ -161,7 +160,8 @@ mod tests {
.collect();
let data = FinalizableData::with_initial_rows_in_progress(
&columns,
(0..fixed_data.degree).map(|i| row_factory.fresh_row(i)),
(0..fixed_data.degree)
.map(|i| row_factory.fresh_row(RowIndex::from_degree(i, fixed_data.degree))),
);
let mut mutable_state = MutableState {
@@ -169,7 +169,7 @@ mod tests {
machines: Machines::from(machines.iter_mut()),
query_callback: &mut query_callback,
};
let row_offset = 0;
let row_offset = RowIndex::from_degree(0, fixed_data.degree);
let identities = analyzed.identities.iter().collect::<Vec<_>>();
let witness_cols = fixed_data.witness_cols.keys().collect();

View File

@@ -16,7 +16,7 @@ use super::block_processor::BlockProcessor;
use super::data_structures::column_map::WitnessColumnMap;
use super::global_constraints::GlobalConstraints;
use super::machines::{FixedLookup, Machine};
use super::rows::{Row, RowFactory};
use super::rows::{Row, RowFactory, RowIndex};
use super::sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator};
use super::vm_processor::VmProcessor;
use super::{EvalResult, FixedData, MutableState, QueryCallback};
@@ -174,8 +174,8 @@ impl<'a, T: FieldElement> Generator<'a, T> {
let data = FinalizableData::with_initial_rows_in_progress(
&self.witnesses,
[
row_factory.fresh_row(self.fixed_data.degree - 1),
row_factory.fresh_row(0),
row_factory.fresh_row(RowIndex::from_i64(-1, self.fixed_data.degree)),
row_factory.fresh_row(RowIndex::from_i64(0, self.fixed_data.degree)),
]
.into_iter(),
);
@@ -190,7 +190,7 @@ impl<'a, T: FieldElement> Generator<'a, T> {
.filter_map(|identity| identity.contains_next_ref().then_some(*identity))
.collect::<Vec<_>>();
let mut processor = BlockProcessor::new(
self.fixed_data.degree - 1,
RowIndex::from_i64(-1, self.fixed_data.degree),
data,
mutable_state,
&identities_with_next_reference,
@@ -223,7 +223,7 @@ impl<'a, T: FieldElement> Generator<'a, T> {
[first_row].into_iter(),
);
let mut processor = VmProcessor::new(
row_offset,
RowIndex::from_degree(row_offset, self.fixed_data.degree),
self.fixed_data,
&self.identities,
&self.witnesses,

View File

@@ -8,7 +8,7 @@ use crate::witgen::data_structures::finalizable_data::FinalizableData;
use crate::witgen::global_constraints::GlobalConstraints;
use crate::witgen::identity_processor::IdentityProcessor;
use crate::witgen::processor::OuterQuery;
use crate::witgen::rows::{CellValue, RowFactory, RowPair, UnknownStrategy};
use crate::witgen::rows::{CellValue, Row, RowFactory, RowIndex, RowPair, UnknownStrategy};
use crate::witgen::sequence_iterator::{ProcessingSequenceCache, ProcessingSequenceIterator};
use crate::witgen::util::try_to_simple_poly;
use crate::witgen::{machines::Machine, EvalError, EvalValue, IncompleteCause};
@@ -114,12 +114,15 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
.map(|(block_size, connecting_rhs)| {
assert!(block_size <= fixed_data.degree as usize);
let row_factory = RowFactory::new(fixed_data, global_range_constraints.clone());
// Start out with a block filled with unknown values so that we do not have to deal with wrap-around
// when storing machine witness data.
// This will be filled with the default block in `take_witness_col_values`
// Because block shapes are not always rectangular, we add the last block to the data at the
// beginning. It starts out with unknown values. Should the first block decide to write to
// rows < 0, they will be writen to this block.
// In `take_witness_col_values()`, this block will be removed and its values will be used to
// construct the "default" block used to fill up unused rows.
let start_index = RowIndex::from_i64(-(block_size as i64), fixed_data.degree);
let data = FinalizableData::with_initial_rows_in_progress(
witness_cols,
(0..block_size).map(|i| row_factory.fresh_row(i as DegreeType)),
(0..block_size).map(|i| row_factory.fresh_row(start_index + i)),
);
BlockMachine {
name,
@@ -192,7 +195,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for BlockMachine<'a, T> {
if !self.connecting_rhs.contains(right) || kind != IdentityKind::Plookup {
return None;
}
let previous_len = self.rows() as usize;
let previous_len = self.data.len();
Some({
let result = self.process_plookup_internal(mutable_state, left, right);
if let Ok(assignments) = &result {
@@ -231,25 +234,38 @@ impl<'a, T: FieldElement> Machine<'a, T> for BlockMachine<'a, T> {
.map(|(v, known)| known.then_some(v))
.collect::<Vec<_>>();
// Remove the "last" block added to the beginning of self.data.
// It contains the values the first block wrote to it and otherwise unknown values.
let dummy_block = values.drain(0..self.block_size).collect::<Vec<_>>();
// For all constraints to be satisfied, unused cells have to be filled with valid values.
// We do this, we construct a default block, by repeating the first input to the block machine.
values.resize(self.fixed_data.degree as usize, None);
let second_block_values = values.iter().skip(self.block_size).take(self.block_size);
// The first block is a dummy block (filled mostly with None), the second block is the first block
// resulting of an actual evaluation.
// However, if the block machine already sets some registers in the last row of the previous block,
// they will be set in the "dummy block". In this case, we want to use these values.
// As a result, the default block consists of values of the first block if they are set, otherwise
// the values of the second block.
// Use the block as the default block. However, it needs to be merged with the dummy block,
// to handle blocks of non-rectangular shape.
// For example, let's say, the situation might look like this (block size = 3 in this example):
// Row Latch C1 C2 C3
// -3 0
// -2 0
// -1 1 X <- This value belongs to the first block
// 0 0 X X X
// 1 0 X X X
// 2 1 X X X <- This value belongs to the second block
//
// The following code constructs the default block as follows:
// - All values will come from rows 0-2, EXCEPT
// - In the last row, the value of C3 is whatever value was written to the dummy block
//
// Constructed like this, we can repeat the default block forever.
//
// TODO: Determine the row-extend per column
let default_block = values
.iter()
.take(self.block_size)
.zip(second_block_values)
.map(|(first_block, second_block)| {
first_block.or(*second_block).unwrap_or_default()
let first_block_values = values.iter().take(self.block_size);
let default_block = dummy_block
.into_iter()
.zip(first_block_values)
.map(|(dummy_block, first_block)| {
dummy_block.or(*first_block).unwrap_or_default()
})
.collect::<Vec<_>>();
@@ -292,8 +308,20 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
}
}
/// Returns the current number of rows *not counting the dummy block* inserted at the beginning
/// (with row numbers (-block_size..0)).
fn rows(&self) -> DegreeType {
self.data.len() as DegreeType
(self.data.len() - self.block_size) as DegreeType
}
fn last_row_index(&self) -> RowIndex {
RowIndex::from_i64(self.rows() as i64 - 1, self.fixed_data.degree)
}
fn get_row(&self, row: RowIndex) -> &Row<'a, T> {
// The first block is a dummy block corresponding to rows (-block_size, 0),
// so we have to add the block size to the row index.
&self.data[(row + self.block_size).into()]
}
fn process_plookup_internal<'b, Q: QueryCallback<T>>(
@@ -314,16 +342,16 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
if left.iter().all(|v| v.is_constant()) && self.rows() > 0 {
// All values on the left hand side are known, check if this is a query
// to the last row.
let row = self.rows() - 1;
let row_index = self.last_row_index();
let current = &self.data[row as usize];
let current = &self.get_row(row_index);
// We don't have the next row, because it would be the first row of the next block.
// We'll use a fresh row instead.
let next = self.row_factory.fresh_row(row + 1);
let next = self.row_factory.fresh_row(row_index + 1);
let row_pair = RowPair::new(
current,
&next,
row,
row_index,
self.fixed_data,
UnknownStrategy::Unknown,
);
@@ -400,13 +428,12 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
sequence_iterator: &mut ProcessingSequenceIterator,
) -> Result<ProcessResult<'a, T>, EvalError<T>> {
// We start at the last row of the previous block.
let row_offset = self.rows() - 1;
let row_offset = self.last_row_index();
// Make the block two rows larger than the block size, it includes the last row of the previous block
// and the first row of the next block.
let block = FinalizableData::with_initial_rows_in_progress(
&self.witness_cols,
(0..(self.block_size + 2))
.map(|i| self.row_factory.fresh_row(i as DegreeType + row_offset)),
(0..(self.block_size + 2)).map(|i| self.row_factory.fresh_row(row_offset + i)),
);
let mut processor = BlockProcessor::new(
row_offset,
@@ -439,9 +466,8 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
// 1. Ignore the first row of the next block:
new_block.pop();
// 2. Merge the last row of the previous block
let last_row_index = self.rows() as usize - 1;
let updated_last_row = new_block.get_mut(0).unwrap();
for (poly_id, existing_value) in self.data[last_row_index].iter() {
for (poly_id, existing_value) in self.get_row(self.last_row_index()).iter() {
if let CellValue::Known(v) = existing_value.value {
if updated_last_row[&poly_id].value.is_known()
&& updated_last_row[&poly_id].value != existing_value.value

View File

@@ -13,7 +13,7 @@ use super::{
affine_expression::AffineExpression,
data_structures::{column_map::WitnessColumnMap, finalizable_data::FinalizableData},
identity_processor::IdentityProcessor,
rows::{CellValue, Row, RowPair, RowUpdater, UnknownStrategy},
rows::{CellValue, Row, RowIndex, RowPair, RowUpdater, UnknownStrategy},
Constraints, EvalError, EvalValue, FixedData, MutableState, QueryCallback,
};
@@ -53,7 +53,7 @@ pub struct IdentityResult {
/// - `'c`: The duration of this Processor's lifetime (e.g. the reference to the identity processor)
pub struct Processor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> {
/// The global index of the first row of [Processor::data].
row_offset: u64,
row_offset: RowIndex,
/// The rows that are being processed.
data: FinalizableData<'a, T>,
/// The mutable state
@@ -72,7 +72,7 @@ pub struct Processor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> {
impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> Processor<'a, 'b, 'c, T, Q> {
pub fn new(
row_offset: u64,
row_offset: RowIndex,
data: FinalizableData<'a, T>,
mutable_state: &'c mut MutableState<'a, 'b, T, Q>,
fixed_data: &'a FixedData<'a, T>,
@@ -129,7 +129,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> Processor<'a, 'b, 'c, T,
pub fn latch_value(&self, row_index: usize) -> Option<bool> {
let row_pair = RowPair::from_single_row(
&self.data[row_index],
row_index as u64 + self.row_offset,
self.row_offset + row_index as u64,
self.fixed_data,
UnknownStrategy::Unknown,
);
@@ -377,7 +377,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> Processor<'a, 'b, 'c, T,
RowPair::new(
&self.data[row_index - 1],
proposed_row,
row_index as DegreeType + self.row_offset - 1,
self.row_offset + (row_index - 1) as DegreeType,
self.fixed_data,
UnknownStrategy::Zero,
)
@@ -387,7 +387,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> Processor<'a, 'b, 'c, T,
// Because we never access the next row, we can use [RowPair::from_single_row] here.
false => RowPair::from_single_row(
proposed_row,
row_index as DegreeType + self.row_offset,
self.row_offset + row_index as DegreeType,
self.fixed_data,
UnknownStrategy::Zero,
),

View File

@@ -8,7 +8,10 @@ use powdr_ast::analyzed::{
use powdr_number::{DegreeType, FieldElement};
use powdr_pil_analyzer::evaluator::{self, Custom, EvalError, SymbolLookup, Value};
use super::{rows::RowPair, Constraint, EvalResult, EvalValue, FixedData, IncompleteCause};
use super::{
rows::{RowIndex, RowPair},
Constraint, EvalResult, EvalValue, FixedData, IncompleteCause,
};
/// Computes value updates that result from a query.
pub struct QueryProcessor<'a, 'b, T: FieldElement, QueryCallback: Send + Sync> {
@@ -81,7 +84,7 @@ impl<'a, 'b, T: FieldElement, QueryCallback: super::QueryCallback<T>>
rows: &RowPair<T>,
) -> Result<String, EvalError> {
let arguments = vec![Rc::new(Value::Integer(num_bigint::BigInt::from(
rows.current_row_index,
u64::from(rows.current_row_index),
)))];
let symbols = Symbols {
fixed_data: self.fixed_data,
@@ -141,12 +144,13 @@ impl<'a, T: FieldElement> SymbolLookup<'a, T, Reference<'a>> for Symbols<'a, T>
};
Ok(Value::FieldElement(match function.poly_id.ptype {
PolynomialType::Committed | PolynomialType::Intermediate => {
let next = self
.rows
.is_row_number_next(DegreeType::try_from(row).unwrap())
.map_err(|_| {
EvalError::OutOfBounds(format!("Referenced row outside of window: {row}"))
})?;
let row_index = RowIndex::from_degree(
DegreeType::try_from(row).unwrap(),
self.fixed_data.degree,
);
let next = self.rows.is_row_number_next(row_index).map_err(|_| {
EvalError::OutOfBounds(format!("Referenced row outside of window: {row}"))
})?;
let poly_ref = AlgebraicReference {
name: function.name.to_string(),
poly_id: function.poly_id,

View File

@@ -1,4 +1,8 @@
use std::{collections::HashSet, fmt::Debug};
use std::{
collections::HashSet,
fmt::Debug,
ops::{Add, Sub},
};
use itertools::Itertools;
use powdr_ast::analyzed::{AlgebraicExpression as Expression, AlgebraicReference, PolyID};
@@ -16,6 +20,86 @@ use super::{
FixedData,
};
/// A small wrapper around a row index, which knows the total number of rows.
/// When converted to DegreeType or usize, it will be reduced modulo the number of rows
/// (handling negative indices as well).
#[derive(Clone, Copy)]
pub struct RowIndex {
index: i64,
num_rows: DegreeType,
}
impl From<RowIndex> for DegreeType {
fn from(row_index: RowIndex) -> Self {
// Ensure that 0 <= index < num_rows
if row_index.index >= 0 {
(row_index.index as DegreeType) % row_index.num_rows
} else {
assert!(row_index.index > -(row_index.num_rows as i64));
row_index.num_rows - (-row_index.index as DegreeType)
}
}
}
impl From<RowIndex> for usize {
fn from(row_index: RowIndex) -> Self {
DegreeType::from(row_index).try_into().unwrap()
}
}
impl RowIndex {
pub fn from_i64(index: i64, num_rows: DegreeType) -> Self {
Self { index, num_rows }
}
pub fn from_degree(index: DegreeType, num_rows: DegreeType) -> Self {
Self {
index: index.try_into().unwrap(),
num_rows,
}
}
}
impl<T> Add<T> for RowIndex
where
i64: TryFrom<T>,
<i64 as TryFrom<T>>::Error: std::fmt::Debug,
{
type Output = RowIndex;
fn add(self, rhs: T) -> RowIndex {
RowIndex {
index: self.index + i64::try_from(rhs).unwrap(),
num_rows: self.num_rows,
}
}
}
impl Sub<RowIndex> for RowIndex {
type Output = i64;
fn sub(self, rhs: RowIndex) -> i64 {
assert_eq!(self.num_rows, rhs.num_rows);
let num_rows = i64::try_from(self.num_rows).unwrap();
let lhs = i64::try_from(DegreeType::from(self)).unwrap();
let rhs = i64::try_from(DegreeType::from(rhs)).unwrap();
let diff = lhs - rhs;
if diff <= -num_rows / 2 {
diff + num_rows
} else if diff >= num_rows / 2 {
diff - num_rows
} else {
diff
}
}
}
impl std::fmt::Display for RowIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.index)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum CellValue<T: FieldElement> {
Known(T),
@@ -165,7 +249,7 @@ impl<'a, T: FieldElement> RowFactory<'a, T> {
}
}
pub fn fresh_row(&self, row: DegreeType) -> Row<'a, T> {
pub fn fresh_row(&self, row: RowIndex) -> Row<'a, T> {
WitnessColumnMap::from(
self.global_range_constraints
.witness_constraints
@@ -173,7 +257,7 @@ impl<'a, T: FieldElement> RowFactory<'a, T> {
.map(|(poly_id, range_constraint)| {
let name = self.fixed_data.column_name(&poly_id);
let value = match (
self.fixed_data.external_witness(row, &poly_id),
self.fixed_data.external_witness(row.into(), &poly_id),
range_constraint.as_ref(),
) {
(Some(external_witness), _) => CellValue::Known(external_witness),
@@ -202,14 +286,14 @@ impl<T: FieldElement> From<Row<'_, T>> for WitnessColumnMap<T> {
pub struct RowUpdater<'row, 'a, T: FieldElement> {
current: &'row mut Row<'a, T>,
next: &'row mut Row<'a, T>,
current_row_index: DegreeType,
current_row_index: RowIndex,
}
impl<'row, 'a, T: FieldElement> RowUpdater<'row, 'a, T> {
pub fn new(
current: &'row mut Row<'a, T>,
next: &'row mut Row<'a, T>,
current_row_index: DegreeType,
current_row_index: RowIndex,
) -> Self {
Self {
current,
@@ -247,7 +331,7 @@ impl<'row, 'a, T: FieldElement> RowUpdater<'row, 'a, T> {
}
}
fn row_number(&self, poly: &AlgebraicReference) -> DegreeType {
fn row_number(&self, poly: &AlgebraicReference) -> RowIndex {
match poly.next {
false => self.current_row_index,
true => self.current_row_index + 1,
@@ -268,7 +352,7 @@ pub enum UnknownStrategy {
pub struct RowPair<'row, 'a, T: FieldElement> {
pub current: &'row Row<'a, T>,
pub next: Option<&'row Row<'a, T>>,
pub current_row_index: DegreeType,
pub current_row_index: RowIndex,
fixed_data: &'a FixedData<'a, T>,
unknown_strategy: UnknownStrategy,
}
@@ -277,7 +361,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> {
pub fn new(
current: &'row Row<'a, T>,
next: &'row Row<'a, T>,
current_row_index: DegreeType,
current_row_index: RowIndex,
fixed_data: &'a FixedData<'a, T>,
unknown_strategy: UnknownStrategy,
) -> Self {
@@ -293,7 +377,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> {
/// Creates a new row pair from a single row, setting the next row to None.
pub fn from_single_row(
current: &'row Row<'a, T>,
current_row_index: DegreeType,
current_row_index: RowIndex,
fixed_data: &'a FixedData<'a, T>,
unknown_strategy: UnknownStrategy,
) -> Self {
@@ -335,7 +419,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> {
pub fn evaluate<'b>(&self, expr: &'b Expression<T>) -> AffineResult<&'b AlgebraicReference, T> {
ExpressionEvaluator::new(SymoblicWitnessEvaluator::new(
self.fixed_data,
self.current_row_index,
self.current_row_index.into(),
self,
))
.evaluate(expr)
@@ -343,7 +427,7 @@ impl<'row, 'a, T: FieldElement> RowPair<'row, 'a, T> {
/// Returns Ok(true) if the given row number references the "next" row,
/// Ok(false) if it references the "current" row and Err if it is out of range.
pub fn is_row_number_next(&self, row_number: DegreeType) -> Result<bool, ()> {
pub fn is_row_number_next(&self, row_number: RowIndex) -> Result<bool, ()> {
match row_number - self.current_row_index {
0 => Ok(false),
1 => Ok(true),

View File

@@ -15,7 +15,7 @@ use crate::witgen::IncompleteCause;
use super::data_structures::finalizable_data::FinalizableData;
use super::processor::{OuterQuery, Processor};
use super::rows::{Row, RowFactory, UnknownStrategy};
use super::rows::{Row, RowFactory, RowIndex, UnknownStrategy};
use super::{Constraints, EvalError, EvalValue, FixedData, MutableState, QueryCallback};
/// Maximal period checked during loop detection.
@@ -64,7 +64,7 @@ pub struct VmProcessor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> {
impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> VmProcessor<'a, 'b, 'c, T, Q> {
pub fn new(
row_offset: DegreeType,
row_offset: RowIndex,
fixed_data: &'a FixedData<'a, T>,
identities: &[&'a Identity<Expression<T>>],
witnesses: &'c HashSet<PolyID>,
@@ -86,7 +86,7 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> VmProcessor<'a, 'b, 'c, T
);
VmProcessor {
row_offset,
row_offset: row_offset.into(),
witnesses: witnesses.clone(),
fixed_data,
identities_with_next_ref: identities_with_next,
@@ -238,7 +238,8 @@ impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback<T>> VmProcessor<'a, 'b, 'c, T
if row_index == self.processor.len() as DegreeType - 1 {
self.processor.set_row(
self.processor.len(),
self.row_factory.fresh_row(row_index + 1),
self.row_factory
.fresh_row(RowIndex::from_degree(row_index, self.fixed_data.degree) + 1),
);
}
}