#[cfg(any(test, feature = "cpu-parity"))]
use super::validation::validate_ifds_csr_inputs;
#[cfg(any(test, feature = "cpu-parity"))]
#[derive(Debug, Default, Clone)]
pub struct ExplodedIfdsCpuScratch {
pub edges_flat: Vec<(u32, u32)>,
pub killed: Vec<bool>,
pub gen_offsets: Vec<usize>,
pub gen_cursor: Vec<usize>,
pub gen_facts: Vec<u32>,
pub cursor: Vec<usize>,
}
#[cfg(any(test, feature = "cpu-parity"))]
impl ExplodedIfdsCpuScratch {
pub fn new() -> Self {
Self::default()
}
}
#[must_use]
#[cfg(any(test, feature = "cpu-parity"))]
pub fn build_cpu_reference(
num_procs: u32,
blocks_per_proc: u32,
facts_per_proc: u32,
intra_edges: &[(u32, u32, u32)], inter_edges: &[(u32, u32, u32, u32)], flow_gen: &[(u32, u32, u32)], flow_kill: &[(u32, u32, u32)], ) -> (Vec<u32>, Vec<u32>) {
try_build_cpu_reference(
num_procs,
blocks_per_proc,
facts_per_proc,
intra_edges,
inter_edges,
flow_gen,
flow_kill,
)
.unwrap_or_else(|err| panic!("exploded IFDS CPU reference received malformed input. {err}"))
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_build_cpu_reference(
num_procs: u32,
blocks_per_proc: u32,
facts_per_proc: u32,
intra_edges: &[(u32, u32, u32)],
inter_edges: &[(u32, u32, u32, u32)],
flow_gen: &[(u32, u32, u32)],
flow_kill: &[(u32, u32, u32)],
) -> Result<(Vec<u32>, Vec<u32>), String> {
let mut row_ptr = Vec::new();
let mut col_idx = Vec::new();
let mut scratch = ExplodedIfdsCpuScratch::default();
try_build_cpu_reference_into(
num_procs,
blocks_per_proc,
facts_per_proc,
intra_edges,
inter_edges,
flow_gen,
flow_kill,
&mut row_ptr,
&mut col_idx,
&mut scratch,
)?;
Ok((row_ptr, col_idx))
}
#[cfg(any(test, feature = "cpu-parity"))]
#[allow(clippy::too_many_arguments)]
pub fn try_build_cpu_reference_into(
num_procs: u32,
blocks_per_proc: u32,
facts_per_proc: u32,
intra_edges: &[(u32, u32, u32)],
inter_edges: &[(u32, u32, u32, u32)],
flow_gen: &[(u32, u32, u32)],
flow_kill: &[(u32, u32, u32)],
row_ptr: &mut Vec<u32>,
col_idx: &mut Vec<u32>,
scratch: &mut ExplodedIfdsCpuScratch,
) -> Result<(), String> {
let layout = validate_ifds_csr_inputs(
num_procs,
blocks_per_proc,
facts_per_proc,
intra_edges,
inter_edges,
flow_gen,
flow_kill,
)?;
if layout.empty {
return Err(format!(
"exploded IFDS CPU reference dimensions must be nonzero, got procs={num_procs}, blocks={blocks_per_proc}, facts={facts_per_proc}. Fix: pass a real exploded-supergraph domain before parity comparison."
));
}
let slots_per_proc = layout.slots_per_proc as usize;
let total_nodes = layout.total_nodes as usize;
crate::graph::scratch::reserve_graph_items(
&mut scratch.edges_flat,
layout.max_col_count as usize,
"exploded IFDS CPU reference",
"flat exploded edge list",
)?;
scratch.edges_flat.clear();
let block_count = (num_procs as usize) * (blocks_per_proc as usize);
let idx = |p: u32, b: u32, f: u32| -> u32 {
((p as usize) * slots_per_proc + (b as usize) * facts_per_proc as usize + f as usize) as u32
};
let block_idx =
|p: u32, b: u32| -> usize { (p as usize) * blocks_per_proc as usize + b as usize };
let in_space =
|p: u32, b: u32, f: u32| p < num_procs && b < blocks_per_proc && f < facts_per_proc;
crate::graph::scratch::reserve_graph_items(
&mut scratch.killed,
total_nodes,
"exploded IFDS CPU reference",
"kill bitmap",
)?;
scratch.killed.clear();
scratch.killed.resize(total_nodes, false);
for &(p, b, f) in flow_kill {
if in_space(p, b, f) {
scratch.killed[idx(p, b, f) as usize] = true;
}
}
let gen_offset_count = block_count
.checked_add(1)
.ok_or_else(|| "Fix: exploded IFDS block_count+1 overflows usize.".to_string())?;
crate::graph::scratch::reserve_graph_items(
&mut scratch.gen_offsets,
gen_offset_count,
"exploded IFDS CPU reference",
"GEN offsets",
)?;
scratch.gen_offsets.clear();
scratch.gen_offsets.resize(gen_offset_count, 0);
for &(p, b, f) in flow_gen {
if in_space(p, b, f) {
scratch.gen_offsets[block_idx(p, b) + 1] += 1;
}
}
for i in 1..scratch.gen_offsets.len() {
scratch.gen_offsets[i] += scratch.gen_offsets[i - 1];
}
crate::graph::scratch::reserve_graph_items(
&mut scratch.gen_cursor,
block_count,
"exploded IFDS CPU reference",
"GEN cursor",
)?;
scratch.gen_cursor.clear();
scratch
.gen_cursor
.extend_from_slice(&scratch.gen_offsets[..block_count]);
let gen_fact_count = scratch.gen_offsets[block_count];
crate::graph::scratch::reserve_graph_items(
&mut scratch.gen_facts,
gen_fact_count,
"exploded IFDS CPU reference",
"GEN fact table",
)?;
scratch.gen_facts.clear();
scratch.gen_facts.resize(gen_fact_count, 0);
for &(p, b, f) in flow_gen {
if in_space(p, b, f) {
let key = block_idx(p, b);
let slot = scratch.gen_cursor[key];
scratch.gen_facts[slot] = f;
scratch.gen_cursor[key] += 1;
}
}
for &(p, src_b, dst_b) in intra_edges {
if p >= num_procs || src_b >= blocks_per_proc || dst_b >= blocks_per_proc {
continue;
}
for f in 0..facts_per_proc {
if scratch.killed[idx(p, src_b, f) as usize] {
continue;
}
scratch
.edges_flat
.push((idx(p, src_b, f), idx(p, dst_b, f)));
}
let gen_key = block_idx(p, src_b);
for &gf in
&scratch.gen_facts[scratch.gen_offsets[gen_key]..scratch.gen_offsets[gen_key + 1]]
{
scratch
.edges_flat
.push((idx(p, src_b, 0), idx(p, dst_b, gf)));
}
}
for &(sp, sb, dp, db) in inter_edges {
if sp >= num_procs || dp >= num_procs || sb >= blocks_per_proc || db >= blocks_per_proc {
continue;
}
for f in 0..facts_per_proc {
scratch.edges_flat.push((idx(sp, sb, f), idx(dp, db, f)));
}
}
if scratch.edges_flat.len() > u32::MAX as usize {
return Err(format!(
"exploded IFDS CPU reference edge_count={} exceeds u32 CSR encoding. Fix: shard the IFDS graph before parity comparison.",
scratch.edges_flat.len()
));
}
let row_ptr_len = layout.row_words;
crate::graph::scratch::reserve_graph_items(
row_ptr,
row_ptr_len,
"exploded IFDS CPU reference",
"CSR row_ptr",
)?;
row_ptr.clear();
row_ptr.resize(row_ptr_len, 0);
for &(src, _) in &scratch.edges_flat {
let row = src as usize;
row_ptr[row + 1] = row_ptr[row + 1].checked_add(1).ok_or_else(|| {
format!(
"exploded IFDS CPU reference row {row} edge count overflowed u32. Fix: shard the IFDS graph before parity comparison."
)
})?;
}
for row in 1..row_ptr.len() {
row_ptr[row] = row_ptr[row].checked_add(row_ptr[row - 1]).ok_or_else(|| {
format!(
"exploded IFDS CPU reference CSR prefix overflowed at row {row}. Fix: shard the IFDS graph before parity comparison."
)
})?;
}
crate::graph::scratch::reserve_graph_items(
&mut scratch.cursor,
total_nodes,
"exploded IFDS CPU reference",
"CSR cursor",
)?;
scratch.cursor.clear();
for &offset in &row_ptr[..total_nodes] {
scratch.cursor.push(offset as usize);
}
crate::graph::scratch::reserve_graph_items(
col_idx,
scratch.edges_flat.len(),
"exploded IFDS CPU reference",
"CSR col_idx",
)?;
col_idx.clear();
col_idx.resize(scratch.edges_flat.len(), 0);
for &(src, dst) in &scratch.edges_flat {
let row = src as usize;
let slot = scratch.cursor[row];
col_idx[slot] = dst;
scratch.cursor[row] += 1;
}
Ok(())
}