use vyre_foundation::ir::Program;
use vyre_primitives::math::scallop_join;
pub const DEFAULT_PROVENANCE_MAX_ITERATIONS: u32 = 64;
#[derive(Default, Debug, Clone)]
pub struct ScallopProvenanceScratch {
closure: Vec<u32>,
join_scratch: Vec<u32>,
}
impl ScallopProvenanceScratch {
#[must_use]
pub fn closure(&self) -> &[u32] {
&self.closure
}
#[must_use]
pub fn closure_mut(&mut self) -> &mut Vec<u32> {
&mut self.closure
}
}
#[must_use]
pub fn build_provenance_program(n: u32, max_iterations: u32) -> Program {
scallop_join::scallop_join(
"provenance_state",
"provenance_next",
"provenance_join_rules",
"provenance_changed",
n,
max_iterations,
)
}
#[must_use]
pub fn cpu_provenance_closure(
state: &[u32],
join_rules: &[u32],
n: u32,
max_iterations: u32,
) -> Vec<u32> {
let mut scratch = ScallopProvenanceScratch::default();
cpu_provenance_closure_with_scratch(state, join_rules, n, max_iterations, &mut scratch);
scratch.closure
}
pub fn cpu_provenance_closure_with_scratch(
state: &[u32],
join_rules: &[u32],
n: u32,
max_iterations: u32,
scratch: &mut ScallopProvenanceScratch,
) -> u32 {
use crate::observability::{bump, scallop_provenance_calls};
bump(&scallop_provenance_calls);
scallop_join::cpu_ref_into(
state,
join_rules,
n,
max_iterations,
&mut scratch.closure,
&mut scratch.join_scratch,
)
}
#[must_use]
pub fn lineage_for_output(closure: &[u32], n: u32, out: u32) -> Vec<u32> {
let mut row = Vec::new();
lineage_for_output_into(closure, n, out, &mut row);
row
}
pub fn lineage_for_output_into(closure: &[u32], n: u32, out: u32, row: &mut Vec<u32>) {
use crate::observability::{bump, scallop_provenance_calls};
bump(&scallop_provenance_calls);
row.clear();
row.extend_from_slice(lineage_for_output_slice(closure, n, out));
}
#[must_use]
pub fn lineage_for_output_slice(closure: &[u32], n: u32, out: u32) -> &[u32] {
assert!(out < n, "Fix: lineage_for_output requires out < n.");
let row = (out as usize) * (n as usize);
&closure[row..row + (n as usize)]
}
#[cfg(test)]
mod tests {
#![allow(clippy::identity_op, clippy::erasing_op)]
use super::*;
#[test]
fn build_program_declares_four_buffers() {
let p = build_provenance_program(4, 16);
let bufs = p.buffers();
assert_eq!(bufs.len(), 4);
let names: Vec<&str> = bufs.iter().map(|b| b.name()).collect();
assert!(names.iter().any(|n| n.contains("provenance_state")));
assert!(names.iter().any(|n| n.contains("provenance_join_rules")));
}
#[test]
fn cpu_closure_transitive_provenance() {
let mut state = vec![0u32; 9];
state[2 * 3 + 0] = 0b01; state[2 * 3 + 1] = 0b10; let mut join_rules = vec![0u32; 9];
join_rules[1 * 3 + 0] = 0b01; let closure = cpu_provenance_closure(&state, &join_rules, 3, 16);
assert!(
closure[2 * 3 + 0] & 0b01 != 0,
"transitive provenance of rule_2 from rule_0 must include clause 0"
);
}
#[test]
fn cpu_closure_reuses_scratch() {
let mut state = vec![0u32; 9];
state[0 * 3 + 1] = 0b001;
state[1 * 3 + 2] = 0b010;
let join_rules = state.clone();
let mut scratch = ScallopProvenanceScratch {
closure: Vec::with_capacity(64),
join_scratch: Vec::with_capacity(64),
};
let closure_ptr = scratch.closure.as_ptr();
let join_ptr = scratch.join_scratch.as_ptr();
let iters = cpu_provenance_closure_with_scratch(&state, &join_rules, 3, 16, &mut scratch);
assert!(iters <= 8);
assert_eq!(scratch.closure()[0 * 3 + 2] & 0b011, 0b011);
assert_eq!(scratch.closure.as_ptr(), closure_ptr);
assert_eq!(scratch.join_scratch.as_ptr(), join_ptr);
}
#[test]
fn lineage_for_output_projection() {
let closure = vec![0u32, 0u32, 0b11u32, 0b10u32];
let row = lineage_for_output(&closure, 2, 1);
assert_eq!(row, vec![0b11u32, 0b10u32]);
}
#[test]
fn lineage_for_output_into_reuses_row_buffer() {
let closure = vec![0u32, 0u32, 0b11u32, 0b10u32];
let mut row = Vec::with_capacity(8);
let ptr = row.as_ptr();
lineage_for_output_into(&closure, 2, 1, &mut row);
assert_eq!(row, vec![0b11u32, 0b10u32]);
assert_eq!(row.as_ptr(), ptr);
}
#[test]
fn empty_seed_yields_empty_closure() {
let state = vec![0u32; 16];
let join_rules = vec![0u32; 16];
let closure = cpu_provenance_closure(&state, &join_rules, 4, 16);
assert!(closure.iter().all(|&w| w == 0));
}
#[test]
fn idempotent_seeded_closure_is_stable() {
let mut state = vec![0u32; 9];
state[0] = 0b01;
let join_rules = vec![0u32; 9];
let closure = cpu_provenance_closure(&state, &join_rules, 3, 16);
assert_eq!(closure[0], 0b01);
for &cell in &closure[1..] {
assert_eq!(cell, 0, "no spurious derivations under empty join rules");
}
}
#[test]
fn default_iterations_caps_runaway() {
const _: () = assert!(DEFAULT_PROVENANCE_MAX_ITERATIONS >= 16);
}
}