use alloc::vec::Vec;
use miden_air::{
LiftedAir, ProcessorAir,
logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH},
lookup::{Challenges, LookupFractions, LookupMessage, build_lookup_fractions},
};
use miden_core::field::QuadFelt;
use miden_utils_testing::rand::rand_array;
use super::{ExecutionTrace, Felt};
pub(super) struct InteractionLog {
pub challenges: Challenges<QuadFelt>,
rows: Vec<Vec<(Felt, QuadFelt)>>,
}
impl InteractionLog {
pub fn new(trace: &ExecutionTrace) -> Self {
let main_trace = trace.main_trace().to_row_major();
let periodic = LiftedAir::<Felt, QuadFelt>::periodic_columns(&ProcessorAir);
let raw = rand_array::<Felt, 4>();
let alpha = QuadFelt::new([raw[0], raw[1]]);
let beta = QuadFelt::new([raw[2], raw[3]]);
let challenges =
Challenges::<QuadFelt>::new(alpha, beta, MIDEN_MAX_MESSAGE_WIDTH, BusId::COUNT);
let fractions = build_lookup_fractions(&ProcessorAir, &main_trace, &periodic, &challenges);
Self { challenges, rows: split_rows(&fractions) }
}
pub fn assert_contains(&self, expected: &Expectations) {
for &entry in &expected.entries {
let (row, mult, denom) = entry;
let want = expected.entries.iter().filter(|&&e| e == entry).count();
let have = self.rows[row].iter().filter(|&&(m, d)| m == mult && d == denom).count();
assert!(
have >= want,
"row {row}: expected at least {want}× (mult={mult:?}, denom={denom:?}), saw {have}.\n\
actual row bag: {:?}",
self.rows[row],
);
}
}
}
pub(super) struct Expectations<'a> {
challenges: &'a Challenges<QuadFelt>,
entries: Vec<(usize, Felt, QuadFelt)>,
}
impl<'a> Expectations<'a> {
pub fn new(log: &'a InteractionLog) -> Self {
Self {
challenges: &log.challenges,
entries: Vec::new(),
}
}
pub fn add<M>(&mut self, row: usize, msg: &M) -> &mut Self
where
M: LookupMessage<Felt, QuadFelt>,
{
self.push(row, Felt::ONE, msg)
}
pub fn remove<M>(&mut self, row: usize, msg: &M) -> &mut Self
where
M: LookupMessage<Felt, QuadFelt>,
{
self.push(row, -Felt::ONE, msg)
}
pub fn push<M>(&mut self, row: usize, mult: Felt, msg: &M) -> &mut Self
where
M: LookupMessage<Felt, QuadFelt>,
{
let denom = msg.encode(self.challenges);
self.entries.push((row, mult, denom));
self
}
pub fn count_adds(&self) -> usize {
self.entries.iter().filter(|(_, m, _)| *m == Felt::ONE).count()
}
pub fn count_removes(&self) -> usize {
self.entries.iter().filter(|(_, m, _)| *m == -Felt::ONE).count()
}
}
fn split_rows(fractions: &LookupFractions<Felt, QuadFelt>) -> Vec<Vec<(Felt, QuadFelt)>> {
let num_cols = fractions.num_columns();
let counts = fractions.counts();
let flat = fractions.fractions();
let num_rows = counts.len() / num_cols;
let mut rows = Vec::with_capacity(num_rows);
let mut cursor = 0usize;
for per_row in counts.chunks(num_cols) {
let total: usize = per_row.iter().sum();
rows.push(flat[cursor..cursor + total].to_vec());
cursor += total;
}
rows
}