#[cfg(any(test, feature = "cpu-parity"))]
use super::validate::validate_persistent_bfs_inputs;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(any(test, feature = "cpu-parity"))]
pub struct PersistentBfsConvergence {
pub changed: u32,
pub converged: bool,
pub stop_iter: u32,
}
#[must_use]
#[cfg(any(test, feature = "cpu-parity"))]
pub fn cpu_ref(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
) -> (Vec<u32>, u32) {
try_cpu_ref(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
)
.expect(
"Fix: reject malformed CSR/frontier via try_cpu_ref; parity wrappers must not pass hostile layouts",
)
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_cpu_ref(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
) -> Result<(Vec<u32>, u32), String> {
let mut out = Vec::new();
let changed = try_cpu_ref_into(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
&mut out,
)?;
Ok((out, changed))
}
#[cfg(any(test, feature = "cpu-parity"))]
#[derive(Debug, Default, Clone)]
pub(crate) struct PersistentBfsCpuScratch {
pub step: Vec<u32>,
}
#[cfg(any(test, feature = "cpu-parity"))]
impl PersistentBfsCpuScratch {
pub(crate) fn new() -> Self {
Self::default()
}
}
#[cfg(any(test, feature = "cpu-parity"))]
pub(crate) fn cpu_ref_into(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
frontier_out: &mut Vec<u32>,
) -> u32 {
let mut scratch = PersistentBfsCpuScratch::default();
try_cpu_ref_into_with_scratch(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
frontier_out,
&mut scratch,
)
.expect(
"Fix: reject malformed CSR/frontier via try_cpu_ref_into; parity wrappers must not pass hostile layouts",
)
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_cpu_ref_into(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
frontier_out: &mut Vec<u32>,
) -> Result<u32, String> {
let mut scratch = PersistentBfsCpuScratch::default();
try_cpu_ref_into_with_scratch(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
frontier_out,
&mut scratch,
)
}
#[cfg(any(test, feature = "cpu-parity"))]
pub(crate) fn try_cpu_ref_into_with_scratch(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
frontier_out: &mut Vec<u32>,
scratch: &mut PersistentBfsCpuScratch,
) -> Result<u32, String> {
Ok(try_cpu_ref_converged_into_with_scratch(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
frontier_out,
scratch,
None,
)?
.changed)
}
#[cfg(any(test, feature = "cpu-parity"))]
pub(crate) fn try_cpu_ref_converged_into_with_scratch(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
frontier_out: &mut Vec<u32>,
scratch: &mut PersistentBfsCpuScratch,
mut density_active: Option<&mut Vec<u32>>,
) -> Result<PersistentBfsConvergence, String> {
let layout = validate_persistent_bfs_inputs(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
)?;
let words = layout.words;
crate::graph::scratch::reserve_graph_items(
frontier_out,
words,
"persistent BFS CPU oracle",
"frontier output",
)?;
crate::graph::scratch::reserve_graph_items(
&mut scratch.step,
words,
"persistent BFS CPU oracle",
"per-iteration frontier scratch",
)?;
frontier_out.clear();
frontier_out.extend_from_slice(frontier_in);
frontier_out.resize(words, 0);
scratch.step.clear();
scratch.step.resize(words, 0);
if let Some(density) = density_active.as_deref_mut() {
density.clear();
density.reserve(max_iters as usize);
}
let mut changed = 0u32;
let mut converged = false;
let mut stop_iter = 0u32;
for iter in 0..max_iters {
crate::graph::csr_forward_traverse::cpu_ref_into(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_out,
allow_mask,
&mut scratch.step,
);
stop_iter = iter + 1;
let mut step_changed = false;
for w in 0..words {
let old = frontier_out[w];
frontier_out[w] |= scratch.step[w];
if frontier_out[w] != old {
step_changed = true;
}
}
if let Some(density) = density_active.as_deref_mut() {
density.push(frontier_out.iter().map(|w| w.count_ones()).sum());
}
if step_changed {
changed = 1;
} else {
converged = true;
break;
}
}
if let Some(density) = density_active {
let fill = density.last().copied().unwrap_or(0);
while (density.len() as u32) < max_iters {
density.push(fill);
}
}
Ok(PersistentBfsConvergence {
changed,
converged,
stop_iter,
})
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_cpu_ref_converged(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
) -> Result<(Vec<u32>, PersistentBfsConvergence), String> {
let mut out = Vec::new();
let mut scratch = PersistentBfsCpuScratch::default();
let outcome = try_cpu_ref_converged_into_with_scratch(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
&mut out,
&mut scratch,
None,
)?;
Ok((out, outcome))
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_cpu_ref_density(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_in: &[u32],
allow_mask: u32,
max_iters: u32,
) -> Result<(Vec<u32>, PersistentBfsConvergence, Vec<u32>), String> {
let mut out = Vec::new();
let mut active = Vec::new();
let mut scratch = PersistentBfsCpuScratch::default();
let outcome = try_cpu_ref_converged_into_with_scratch(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_in,
allow_mask,
max_iters,
&mut out,
&mut scratch,
Some(&mut active),
)?;
Ok((out, outcome, active))
}