1#[cfg(any(test, feature = "cpu-parity"))]
2use super::validate::validate_persistent_bfs_inputs;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg(any(test, feature = "cpu-parity"))]
15pub struct PersistentBfsConvergence {
16 pub changed: u32,
18 pub converged: bool,
23 pub stop_iter: u32,
26}
27
28#[must_use]
32#[cfg(any(test, feature = "cpu-parity"))]
33pub fn cpu_ref(
34 node_count: u32,
35 edge_offsets: &[u32],
36 edge_targets: &[u32],
37 edge_kind_mask: &[u32],
38 frontier_in: &[u32],
39 allow_mask: u32,
40 max_iters: u32,
41) -> (Vec<u32>, u32) {
42 try_cpu_ref(
43 node_count,
44 edge_offsets,
45 edge_targets,
46 edge_kind_mask,
47 frontier_in,
48 allow_mask,
49 max_iters,
50 )
51 .expect(
52 "Fix: reject malformed CSR/frontier via try_cpu_ref; parity wrappers must not pass hostile layouts",
53 )
54}
55
56#[cfg(any(test, feature = "cpu-parity"))]
61pub fn try_cpu_ref(
62 node_count: u32,
63 edge_offsets: &[u32],
64 edge_targets: &[u32],
65 edge_kind_mask: &[u32],
66 frontier_in: &[u32],
67 allow_mask: u32,
68 max_iters: u32,
69) -> Result<(Vec<u32>, u32), String> {
70 let mut out = Vec::new();
71 let changed = try_cpu_ref_into(
72 node_count,
73 edge_offsets,
74 edge_targets,
75 edge_kind_mask,
76 frontier_in,
77 allow_mask,
78 max_iters,
79 &mut out,
80 )?;
81 Ok((out, changed))
82}
83
84#[cfg(any(test, feature = "cpu-parity"))]
90#[derive(Debug, Default, Clone)]
91pub(crate) struct PersistentBfsCpuScratch {
92 pub step: Vec<u32>,
94}
95
96#[cfg(any(test, feature = "cpu-parity"))]
97impl PersistentBfsCpuScratch {
98 pub(crate) fn new() -> Self {
100 Self::default()
101 }
102}
103
104#[cfg(any(test, feature = "cpu-parity"))]
109pub(crate) fn cpu_ref_into(
110 node_count: u32,
111 edge_offsets: &[u32],
112 edge_targets: &[u32],
113 edge_kind_mask: &[u32],
114 frontier_in: &[u32],
115 allow_mask: u32,
116 max_iters: u32,
117 frontier_out: &mut Vec<u32>,
118) -> u32 {
119 let mut scratch = PersistentBfsCpuScratch::default();
120 try_cpu_ref_into_with_scratch(
121 node_count,
122 edge_offsets,
123 edge_targets,
124 edge_kind_mask,
125 frontier_in,
126 allow_mask,
127 max_iters,
128 frontier_out,
129 &mut scratch,
130 )
131 .expect(
132 "Fix: reject malformed CSR/frontier via try_cpu_ref_into; parity wrappers must not pass hostile layouts",
133 )
134}
135
136#[cfg(any(test, feature = "cpu-parity"))]
142pub fn try_cpu_ref_into(
143 node_count: u32,
144 edge_offsets: &[u32],
145 edge_targets: &[u32],
146 edge_kind_mask: &[u32],
147 frontier_in: &[u32],
148 allow_mask: u32,
149 max_iters: u32,
150 frontier_out: &mut Vec<u32>,
151) -> Result<u32, String> {
152 let mut scratch = PersistentBfsCpuScratch::default();
153 try_cpu_ref_into_with_scratch(
154 node_count,
155 edge_offsets,
156 edge_targets,
157 edge_kind_mask,
158 frontier_in,
159 allow_mask,
160 max_iters,
161 frontier_out,
162 &mut scratch,
163 )
164}
165
166#[cfg(any(test, feature = "cpu-parity"))]
173pub(crate) fn try_cpu_ref_into_with_scratch(
174 node_count: u32,
175 edge_offsets: &[u32],
176 edge_targets: &[u32],
177 edge_kind_mask: &[u32],
178 frontier_in: &[u32],
179 allow_mask: u32,
180 max_iters: u32,
181 frontier_out: &mut Vec<u32>,
182 scratch: &mut PersistentBfsCpuScratch,
183) -> Result<u32, String> {
184 Ok(try_cpu_ref_converged_into_with_scratch(
185 node_count,
186 edge_offsets,
187 edge_targets,
188 edge_kind_mask,
189 frontier_in,
190 allow_mask,
191 max_iters,
192 frontier_out,
193 scratch,
194 None,
195 )?
196 .changed)
197}
198
199#[cfg(any(test, feature = "cpu-parity"))]
217pub(crate) fn try_cpu_ref_converged_into_with_scratch(
218 node_count: u32,
219 edge_offsets: &[u32],
220 edge_targets: &[u32],
221 edge_kind_mask: &[u32],
222 frontier_in: &[u32],
223 allow_mask: u32,
224 max_iters: u32,
225 frontier_out: &mut Vec<u32>,
226 scratch: &mut PersistentBfsCpuScratch,
227 mut density_active: Option<&mut Vec<u32>>,
228) -> Result<PersistentBfsConvergence, String> {
229 let layout = validate_persistent_bfs_inputs(
230 node_count,
231 edge_offsets,
232 edge_targets,
233 edge_kind_mask,
234 frontier_in,
235 )?;
236 let words = layout.words;
237 crate::graph::scratch::reserve_graph_items(
238 frontier_out,
239 words,
240 "persistent BFS CPU oracle",
241 "frontier output",
242 )?;
243 crate::graph::scratch::reserve_graph_items(
244 &mut scratch.step,
245 words,
246 "persistent BFS CPU oracle",
247 "per-iteration frontier scratch",
248 )?;
249 frontier_out.clear();
250 frontier_out.extend_from_slice(frontier_in);
251 frontier_out.resize(words, 0);
252 scratch.step.clear();
253 scratch.step.resize(words, 0);
254 if let Some(density) = density_active.as_deref_mut() {
255 density.clear();
256 density.reserve(max_iters as usize);
257 }
258 let mut changed = 0u32;
259 let mut converged = false;
260 let mut stop_iter = 0u32;
261
262 for iter in 0..max_iters {
263 crate::graph::csr_forward_traverse::cpu_ref_into(
264 node_count,
265 edge_offsets,
266 edge_targets,
267 edge_kind_mask,
268 frontier_out,
269 allow_mask,
270 &mut scratch.step,
271 );
272 stop_iter = iter + 1;
273 let mut step_changed = false;
274 for w in 0..words {
275 let old = frontier_out[w];
276 frontier_out[w] |= scratch.step[w];
277 if frontier_out[w] != old {
278 step_changed = true;
279 }
280 }
281 if let Some(density) = density_active.as_deref_mut() {
282 density.push(frontier_out.iter().map(|w| w.count_ones()).sum());
283 }
284 if step_changed {
285 changed = 1;
286 } else {
287 converged = true;
288 break;
289 }
290 }
291 if let Some(density) = density_active {
295 let fill = density.last().copied().unwrap_or(0);
296 while (density.len() as u32) < max_iters {
297 density.push(fill);
298 }
299 }
300 Ok(PersistentBfsConvergence {
301 changed,
302 converged,
303 stop_iter,
304 })
305}
306
307#[cfg(any(test, feature = "cpu-parity"))]
314pub fn try_cpu_ref_converged(
315 node_count: u32,
316 edge_offsets: &[u32],
317 edge_targets: &[u32],
318 edge_kind_mask: &[u32],
319 frontier_in: &[u32],
320 allow_mask: u32,
321 max_iters: u32,
322) -> Result<(Vec<u32>, PersistentBfsConvergence), String> {
323 let mut out = Vec::new();
324 let mut scratch = PersistentBfsCpuScratch::default();
325 let outcome = try_cpu_ref_converged_into_with_scratch(
326 node_count,
327 edge_offsets,
328 edge_targets,
329 edge_kind_mask,
330 frontier_in,
331 allow_mask,
332 max_iters,
333 &mut out,
334 &mut scratch,
335 None,
336 )?;
337 Ok((out, outcome))
338}
339
340#[cfg(any(test, feature = "cpu-parity"))]
352pub fn try_cpu_ref_density(
353 node_count: u32,
354 edge_offsets: &[u32],
355 edge_targets: &[u32],
356 edge_kind_mask: &[u32],
357 frontier_in: &[u32],
358 allow_mask: u32,
359 max_iters: u32,
360) -> Result<(Vec<u32>, PersistentBfsConvergence, Vec<u32>), String> {
361 let mut out = Vec::new();
362 let mut active = Vec::new();
363 let mut scratch = PersistentBfsCpuScratch::default();
364 let outcome = try_cpu_ref_converged_into_with_scratch(
365 node_count,
366 edge_offsets,
367 edge_targets,
368 edge_kind_mask,
369 frontier_in,
370 allow_mask,
371 max_iters,
372 &mut out,
373 &mut scratch,
374 Some(&mut active),
375 )?;
376 Ok((out, outcome, active))
377}