Skip to content

Commit

Permalink
Extract analysis utils. (#2067)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseth authored Nov 11, 2024
1 parent ab1f43a commit 83c5894
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 114 deletions.
125 changes: 125 additions & 0 deletions executor/src/witgen/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::{
collections::{BTreeMap, BTreeSet},
iter::once,
};

use itertools::Itertools;
use powdr_ast::{
analyzed::AlgebraicExpression as Expression, parsed::visitor::ExpressionVisitable,
};
use powdr_number::FieldElement;

use super::{
machines::{Connection, ConnectionKind},
util::try_to_simple_poly,
FixedData,
};

/// Analyses a set of connections (assumed to go into the same machine) and tries to infer
/// the block size.
/// On success, return the connection kind, block size and latch row.
pub fn detect_connection_type_and_block_size<'a, T: FieldElement>(
fixed_data: &'a FixedData<'a, T>,
connections: &BTreeMap<u64, Connection<'a, T>>,
) -> Option<(ConnectionKind, usize, usize)> {
// TODO we should check that the other constraints/fixed columns are also periodic.

// Connecting identities should either all be permutations or all lookups.
let connection_type = connections
.values()
.map(|id| id.kind)
.unique()
.exactly_one()
.ok()?;

// Detect the block size.
let (latch_row, block_size) = match connection_type {
ConnectionKind::Lookup => {
// We'd expect all RHS selectors to be fixed columns of the same period.
connections
.values()
.map(|id| try_to_period(&id.right.selector, fixed_data))
.unique()
.exactly_one()
.ok()??
}
ConnectionKind::Permutation => {
// We check all fixed columns appearing in RHS selectors. If there is none, the block size is 1.

let find_max_period = |latch_candidates: BTreeSet<Expression<T>>| {
latch_candidates
.iter()
.filter_map(|e| try_to_period(e, fixed_data))
// If there is more than one period, the block size is the maximum period.
.max_by_key(|&(_, period)| period)
};
let mut latch_candidates = BTreeSet::new();
for id in connections.values() {
collect_fixed_cols(&id.right.selector, &mut latch_candidates);
}
if latch_candidates.is_empty() {
(0, 1)
} else {
find_max_period(latch_candidates)?
}
}
};
Some((connection_type, block_size, latch_row))
}

/// Check if `expr` is a reference to a function of the form
/// f(i) { if (i + o) % k == 0 { 1 } else { 0 } }
/// for some k < degree / 2, o.
/// If so, returns (o, k).
fn try_to_period<T: FieldElement>(
expr: &Expression<T>,
fixed_data: &FixedData<T>,
) -> Option<(usize, usize)> {
if let Expression::Number(ref n) = expr {
if *n == T::one() {
return Some((0, 1));
}
}

let poly = try_to_simple_poly(expr)?;
if !poly.is_fixed() {
return None;
}

let degree = fixed_data.common_degree_range(once(&poly.poly_id)).max;

let values = fixed_data.fixed_cols[&poly.poly_id].values(degree);

let offset = values.iter().position(|v| v.is_one())?;
let period = 1 + values.iter().skip(offset + 1).position(|v| v.is_one())?;
if period > degree as usize / 2 {
// This filters out columns like [0]* + [1], which might appear in a block machine
// but shouldn't be detected as the latch.
return None;
}
values
.iter()
.enumerate()
.all(|(i, v)| {
let expected = if i % period == offset {
1.into()
} else {
0.into()
};
*v == expected
})
.then_some((offset, period))
}

fn collect_fixed_cols<T: FieldElement>(
expression: &Expression<T>,
result: &mut BTreeSet<Expression<T>>,
) {
expression.pre_visit_expressions(&mut |e| {
if let Expression::Reference(r) = e {
if r.is_fixed() {
result.insert(e.clone());
}
}
});
}
119 changes: 5 additions & 114 deletions executor/src/witgen/machines/block_machine.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::collections::{BTreeMap, HashMap};
use std::fmt::Display;
use std::iter::{self, once};
use std::iter::{self};

use super::{
compute_size_and_log, Connection, ConnectionKind, EvalResult, FixedData, MachineParts,
};
use super::{compute_size_and_log, ConnectionKind, EvalResult, FixedData, MachineParts};

use crate::witgen::affine_expression::AlgebraicVariable;
use crate::witgen::analysis::detect_connection_type_and_block_size;
use crate::witgen::block_processor::BlockProcessor;
use crate::witgen::data_structures::finalizable_data::FinalizableData;
use crate::witgen::data_structures::multiplicity_counter::MultiplicityCounter;
Expand All @@ -18,9 +17,7 @@ use crate::witgen::sequence_iterator::{
use crate::witgen::util::try_to_simple_poly;
use crate::witgen::{machines::Machine, EvalError, EvalValue, IncompleteCause};
use crate::witgen::{MutableState, QueryCallback};
use itertools::Itertools;
use powdr_ast::analyzed::{AlgebraicExpression as Expression, DegreeRange, PolyID, PolynomialType};
use powdr_ast::parsed::visitor::ExpressionVisitable;
use powdr_ast::analyzed::{DegreeRange, PolyID, PolynomialType};
use powdr_number::{DegreeType, FieldElement};

enum ProcessResult<'a, T: FieldElement> {
Expand All @@ -37,19 +34,6 @@ impl<'a, T: FieldElement> ProcessResult<'a, T> {
}
}

fn collect_fixed_cols<T: FieldElement>(
expression: &Expression<T>,
result: &mut BTreeSet<Expression<T>>,
) {
expression.pre_visit_expressions(&mut |e| {
if let Expression::Reference(r) = e {
if r.is_fixed() {
result.insert(e.clone());
}
}
});
}

impl<'a, T: FieldElement> Display for BlockMachine<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
Expand Down Expand Up @@ -150,99 +134,6 @@ impl<'a, T: FieldElement> BlockMachine<'a, T> {
}
}

fn detect_connection_type_and_block_size<'a, T: FieldElement>(
fixed_data: &'a FixedData<'a, T>,
connections: &BTreeMap<u64, Connection<'a, T>>,
) -> Option<(ConnectionKind, usize, usize)> {
// TODO we should check that the other constraints/fixed columns are also periodic.

// Connecting identities should either all be permutations or all lookups.
let connection_type = connections
.values()
.map(|id| id.kind)
.unique()
.exactly_one()
.ok()?;

// Detect the block size.
let (latch_row, block_size) = match connection_type {
ConnectionKind::Lookup => {
// We'd expect all RHS selectors to be fixed columns of the same period.
connections
.values()
.map(|id| try_to_period(&id.right.selector, fixed_data))
.unique()
.exactly_one()
.ok()??
}
ConnectionKind::Permutation => {
// We check all fixed columns appearing in RHS selectors. If there is none, the block size is 1.

let find_max_period = |latch_candidates: BTreeSet<Expression<T>>| {
latch_candidates
.iter()
.filter_map(|e| try_to_period(e, fixed_data))
// If there is more than one period, the block size is the maximum period.
.max_by_key(|&(_, period)| period)
};
let mut latch_candidates = BTreeSet::new();
for id in connections.values() {
collect_fixed_cols(&id.right.selector, &mut latch_candidates);
}
if latch_candidates.is_empty() {
(0, 1)
} else {
find_max_period(latch_candidates)?
}
}
};
Some((connection_type, block_size, latch_row))
}

/// Check if `expr` is a reference to a function of the form
/// f(i) { if (i + o) % k == 0 { 1 } else { 0 } }
/// for some k < degree / 2, o.
/// If so, returns (o, k).
fn try_to_period<T: FieldElement>(
expr: &Expression<T>,
fixed_data: &FixedData<T>,
) -> Option<(usize, usize)> {
if let Expression::Number(ref n) = expr {
if *n == T::one() {
return Some((0, 1));
}
}

let poly = try_to_simple_poly(expr)?;
if !poly.is_fixed() {
return None;
}

let degree = fixed_data.common_degree_range(once(&poly.poly_id)).max;

let values = fixed_data.fixed_cols[&poly.poly_id].values(degree);

let offset = values.iter().position(|v| v.is_one())?;
let period = 1 + values.iter().skip(offset + 1).position(|v| v.is_one())?;
if period > degree as usize / 2 {
// This filters out columns like [0]* + [1], which might appear in a block machine
// but shouldn't be detected as the latch.
return None;
}
values
.iter()
.enumerate()
.all(|(i, v)| {
let expected = if i % period == offset {
1.into()
} else {
0.into()
};
*v == expected
})
.then_some((offset, period))
}

impl<'a, T: FieldElement> Machine<'a, T> for BlockMachine<'a, T> {
fn identity_ids(&self) -> Vec<u64> {
self.parts.connections.keys().copied().collect()
Expand Down
1 change: 1 addition & 0 deletions executor/src/witgen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use self::machines::profiling::{record_end, record_start, reset_and_print_profil
use self::machines::Machine;

mod affine_expression;
pub(crate) mod analysis;
mod block_processor;
mod data_structures;
mod eval_result;
Expand Down

0 comments on commit 83c5894

Please sign in to comment.