1#[cfg(any(test, feature = "cpu-parity"))]
2use super::validation::validate_ifds_csr_inputs;
3
4#[cfg(any(test, feature = "cpu-parity"))]
14#[derive(Debug, Default, Clone)]
15pub struct ExplodedIfdsCpuScratch {
16 pub edges_flat: Vec<(u32, u32)>,
18 pub killed: Vec<bool>,
20 pub gen_offsets: Vec<usize>,
22 pub gen_cursor: Vec<usize>,
24 pub gen_facts: Vec<u32>,
26 pub cursor: Vec<usize>,
28}
29
30#[cfg(any(test, feature = "cpu-parity"))]
31impl ExplodedIfdsCpuScratch {
32 pub fn new() -> Self {
34 Self::default()
35 }
36}
37
38#[must_use]
48#[cfg(any(test, feature = "cpu-parity"))]
49pub fn build_cpu_reference(
50 num_procs: u32,
51 blocks_per_proc: u32,
52 facts_per_proc: u32,
53 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>) {
58 try_build_cpu_reference(
59 num_procs,
60 blocks_per_proc,
61 facts_per_proc,
62 intra_edges,
63 inter_edges,
64 flow_gen,
65 flow_kill,
66 )
67 .unwrap_or_else(|err| panic!("exploded IFDS CPU reference received malformed input. {err}"))
68}
69
70#[cfg(any(test, feature = "cpu-parity"))]
76pub fn try_build_cpu_reference(
77 num_procs: u32,
78 blocks_per_proc: u32,
79 facts_per_proc: u32,
80 intra_edges: &[(u32, u32, u32)],
81 inter_edges: &[(u32, u32, u32, u32)],
82 flow_gen: &[(u32, u32, u32)],
83 flow_kill: &[(u32, u32, u32)],
84) -> Result<(Vec<u32>, Vec<u32>), String> {
85 let mut row_ptr = Vec::new();
86 let mut col_idx = Vec::new();
87 let mut scratch = ExplodedIfdsCpuScratch::default();
88 try_build_cpu_reference_into(
89 num_procs,
90 blocks_per_proc,
91 facts_per_proc,
92 intra_edges,
93 inter_edges,
94 flow_gen,
95 flow_kill,
96 &mut row_ptr,
97 &mut col_idx,
98 &mut scratch,
99 )?;
100 Ok((row_ptr, col_idx))
101}
102
103#[cfg(any(test, feature = "cpu-parity"))]
109#[allow(clippy::too_many_arguments)]
110pub fn try_build_cpu_reference_into(
111 num_procs: u32,
112 blocks_per_proc: u32,
113 facts_per_proc: u32,
114 intra_edges: &[(u32, u32, u32)],
115 inter_edges: &[(u32, u32, u32, u32)],
116 flow_gen: &[(u32, u32, u32)],
117 flow_kill: &[(u32, u32, u32)],
118 row_ptr: &mut Vec<u32>,
119 col_idx: &mut Vec<u32>,
120 scratch: &mut ExplodedIfdsCpuScratch,
121) -> Result<(), String> {
122 let layout = validate_ifds_csr_inputs(
123 num_procs,
124 blocks_per_proc,
125 facts_per_proc,
126 intra_edges,
127 inter_edges,
128 flow_gen,
129 flow_kill,
130 )?;
131 if layout.empty {
132 return Err(format!(
133 "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."
134 ));
135 }
136
137 let slots_per_proc = layout.slots_per_proc as usize;
145 let total_nodes = layout.total_nodes as usize;
146 crate::graph::scratch::reserve_graph_items(
147 &mut scratch.edges_flat,
148 layout.max_col_count as usize,
149 "exploded IFDS CPU reference",
150 "flat exploded edge list",
151 )?;
152 scratch.edges_flat.clear();
153 let block_count = (num_procs as usize) * (blocks_per_proc as usize);
154
155 let idx = |p: u32, b: u32, f: u32| -> u32 {
156 ((p as usize) * slots_per_proc + (b as usize) * facts_per_proc as usize + f as usize) as u32
157 };
158 let block_idx =
159 |p: u32, b: u32| -> usize { (p as usize) * blocks_per_proc as usize + b as usize };
160 let in_space =
161 |p: u32, b: u32, f: u32| p < num_procs && b < blocks_per_proc && f < facts_per_proc;
162
163 crate::graph::scratch::reserve_graph_items(
164 &mut scratch.killed,
165 total_nodes,
166 "exploded IFDS CPU reference",
167 "kill bitmap",
168 )?;
169 scratch.killed.clear();
170 scratch.killed.resize(total_nodes, false);
171 for &(p, b, f) in flow_kill {
172 if in_space(p, b, f) {
173 scratch.killed[idx(p, b, f) as usize] = true;
174 }
175 }
176
177 let gen_offset_count = block_count
178 .checked_add(1)
179 .ok_or_else(|| "Fix: exploded IFDS block_count+1 overflows usize.".to_string())?;
180 crate::graph::scratch::reserve_graph_items(
181 &mut scratch.gen_offsets,
182 gen_offset_count,
183 "exploded IFDS CPU reference",
184 "GEN offsets",
185 )?;
186 scratch.gen_offsets.clear();
187 scratch.gen_offsets.resize(gen_offset_count, 0);
188 for &(p, b, f) in flow_gen {
189 if in_space(p, b, f) {
190 scratch.gen_offsets[block_idx(p, b) + 1] += 1;
191 }
192 }
193 for i in 1..scratch.gen_offsets.len() {
194 scratch.gen_offsets[i] += scratch.gen_offsets[i - 1];
195 }
196 crate::graph::scratch::reserve_graph_items(
197 &mut scratch.gen_cursor,
198 block_count,
199 "exploded IFDS CPU reference",
200 "GEN cursor",
201 )?;
202 scratch.gen_cursor.clear();
203 scratch
204 .gen_cursor
205 .extend_from_slice(&scratch.gen_offsets[..block_count]);
206 let gen_fact_count = scratch.gen_offsets[block_count];
207 crate::graph::scratch::reserve_graph_items(
208 &mut scratch.gen_facts,
209 gen_fact_count,
210 "exploded IFDS CPU reference",
211 "GEN fact table",
212 )?;
213 scratch.gen_facts.clear();
214 scratch.gen_facts.resize(gen_fact_count, 0);
215 for &(p, b, f) in flow_gen {
216 if in_space(p, b, f) {
217 let key = block_idx(p, b);
218 let slot = scratch.gen_cursor[key];
219 scratch.gen_facts[slot] = f;
220 scratch.gen_cursor[key] += 1;
221 }
222 }
223
224 for &(p, src_b, dst_b) in intra_edges {
229 if p >= num_procs || src_b >= blocks_per_proc || dst_b >= blocks_per_proc {
230 continue;
231 }
232 for f in 0..facts_per_proc {
233 if scratch.killed[idx(p, src_b, f) as usize] {
234 continue;
235 }
236 scratch
237 .edges_flat
238 .push((idx(p, src_b, f), idx(p, dst_b, f)));
239 }
240 let gen_key = block_idx(p, src_b);
246 for &gf in
247 &scratch.gen_facts[scratch.gen_offsets[gen_key]..scratch.gen_offsets[gen_key + 1]]
248 {
249 scratch
250 .edges_flat
251 .push((idx(p, src_b, 0), idx(p, dst_b, gf)));
252 }
253 }
254
255 for &(sp, sb, dp, db) in inter_edges {
260 if sp >= num_procs || dp >= num_procs || sb >= blocks_per_proc || db >= blocks_per_proc {
261 continue;
262 }
263 for f in 0..facts_per_proc {
264 scratch.edges_flat.push((idx(sp, sb, f), idx(dp, db, f)));
265 }
266 }
267
268 if scratch.edges_flat.len() > u32::MAX as usize {
270 return Err(format!(
271 "exploded IFDS CPU reference edge_count={} exceeds u32 CSR encoding. Fix: shard the IFDS graph before parity comparison.",
272 scratch.edges_flat.len()
273 ));
274 }
275 let row_ptr_len = layout.row_words;
276 crate::graph::scratch::reserve_graph_items(
277 row_ptr,
278 row_ptr_len,
279 "exploded IFDS CPU reference",
280 "CSR row_ptr",
281 )?;
282 row_ptr.clear();
283 row_ptr.resize(row_ptr_len, 0);
284 for &(src, _) in &scratch.edges_flat {
285 let row = src as usize;
286 row_ptr[row + 1] = row_ptr[row + 1].checked_add(1).ok_or_else(|| {
287 format!(
288 "exploded IFDS CPU reference row {row} edge count overflowed u32. Fix: shard the IFDS graph before parity comparison."
289 )
290 })?;
291 }
292 for row in 1..row_ptr.len() {
293 row_ptr[row] = row_ptr[row].checked_add(row_ptr[row - 1]).ok_or_else(|| {
294 format!(
295 "exploded IFDS CPU reference CSR prefix overflowed at row {row}. Fix: shard the IFDS graph before parity comparison."
296 )
297 })?;
298 }
299 crate::graph::scratch::reserve_graph_items(
300 &mut scratch.cursor,
301 total_nodes,
302 "exploded IFDS CPU reference",
303 "CSR cursor",
304 )?;
305 scratch.cursor.clear();
306 for &offset in &row_ptr[..total_nodes] {
307 scratch.cursor.push(offset as usize);
308 }
309 crate::graph::scratch::reserve_graph_items(
310 col_idx,
311 scratch.edges_flat.len(),
312 "exploded IFDS CPU reference",
313 "CSR col_idx",
314 )?;
315 col_idx.clear();
316 col_idx.resize(scratch.edges_flat.len(), 0);
317 for &(src, dst) in &scratch.edges_flat {
318 let row = src as usize;
319 let slot = scratch.cursor[row];
320 col_idx[slot] = dst;
321 scratch.cursor[row] += 1;
322 }
323 Ok(())
324}