1use std::collections::VecDeque;
23
24use crate::error::{GraphError, GraphResult};
25use crate::graph::ComputeGraph;
26use crate::node::NodeId;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct Wavefront {
36 pub level: usize,
38 pub nodes: Vec<NodeId>,
40 pub max_cost: u64,
43 pub total_cost: u64,
46}
47
48impl Wavefront {
49 #[must_use]
51 pub fn width(&self) -> usize {
52 self.nodes.len()
53 }
54}
55
56#[derive(Debug, Clone)]
63pub struct Schedule {
64 waves: Vec<Wavefront>,
66 levels: Vec<usize>,
68 critical_path_cost: u64,
70}
71
72impl Schedule {
73 pub fn levelize(graph: &ComputeGraph) -> GraphResult<Self> {
83 if graph.is_empty() {
84 return Err(GraphError::EmptyGraph);
85 }
86 let n = graph.node_count();
87
88 let mut levels = vec![0usize; n];
90 let mut in_degree: Vec<u32> = (0..n)
91 .map(|i| {
92 graph
93 .predecessors(NodeId(i as u32))
94 .map(|p| p.len() as u32)
95 .unwrap_or(0)
96 })
97 .collect();
98 let mut queue: VecDeque<NodeId> = (0..n)
99 .filter(|&i| in_degree[i] == 0)
100 .map(|i| NodeId(i as u32))
101 .collect();
102 let mut processed = 0usize;
103 while let Some(id) = queue.pop_front() {
104 processed += 1;
105 let lv = levels[id.0 as usize];
106 for &succ in graph.successors(id)? {
107 let nl = lv + 1;
108 if nl > levels[succ.0 as usize] {
109 levels[succ.0 as usize] = nl;
110 }
111 let d = &mut in_degree[succ.0 as usize];
112 *d -= 1;
113 if *d == 0 {
114 queue.push_back(succ);
115 }
116 }
117 }
118 debug_assert_eq!(processed, n, "levelization did not visit every node");
120
121 let max_level = *levels.iter().max().unwrap_or(&0);
123 let mut buckets: Vec<Vec<NodeId>> = vec![Vec::new(); max_level + 1];
124 for i in 0..n {
125 buckets[levels[i]].push(NodeId(i as u32));
126 }
127
128 let order = graph.topological_order()?;
131 let mut dist = vec![0u64; n];
132 for &id in &order {
133 let cost = graph.node(id)?.cost_hint;
134 let mut best_pred = 0u64;
135 for &pred in graph.predecessors(id)? {
136 best_pred = best_pred.max(dist[pred.0 as usize]);
137 }
138 dist[id.0 as usize] = best_pred + cost;
139 }
140 let critical_path_cost = dist.iter().copied().max().unwrap_or(0);
141
142 let waves: Vec<Wavefront> = buckets
143 .into_iter()
144 .enumerate()
145 .map(|(level, mut nodes)| {
146 nodes.sort();
147 let max_cost = nodes
148 .iter()
149 .map(|&id| graph.nodes()[id.0 as usize].cost_hint)
150 .max()
151 .unwrap_or(0);
152 let total_cost: u64 = nodes
153 .iter()
154 .map(|&id| graph.nodes()[id.0 as usize].cost_hint)
155 .sum();
156 Wavefront {
157 level,
158 nodes,
159 max_cost,
160 total_cost,
161 }
162 })
163 .collect();
164
165 Ok(Self {
166 waves,
167 levels,
168 critical_path_cost,
169 })
170 }
171
172 #[must_use]
174 pub fn wavefronts(&self) -> &[Wavefront] {
175 &self.waves
176 }
177
178 #[must_use]
180 pub fn depth(&self) -> usize {
181 self.waves.len()
182 }
183
184 pub fn level_of(&self, id: NodeId) -> GraphResult<usize> {
190 self.levels
191 .get(id.0 as usize)
192 .copied()
193 .ok_or(GraphError::NodeNotFound(id))
194 }
195
196 #[must_use]
198 pub fn max_width(&self) -> usize {
199 self.waves.iter().map(Wavefront::width).max().unwrap_or(0)
200 }
201
202 #[must_use]
205 pub fn critical_path_cost(&self) -> u64 {
206 self.critical_path_cost
207 }
208
209 #[must_use]
222 pub fn bounded_makespan(&self, max_streams: usize) -> u64 {
223 let lanes = max_streams.max(1);
224 self.waves.iter().map(|w| wave_makespan(w, lanes)).sum()
225 }
226
227 #[must_use]
230 pub fn unbounded_makespan(&self) -> u64 {
231 self.waves.iter().map(|w| w.max_cost).sum()
232 }
233}
234
235fn wave_makespan(wave: &Wavefront, lanes: usize) -> u64 {
237 if wave.nodes.is_empty() {
238 return 0;
239 }
240 if lanes >= wave.nodes.len() {
241 return wave.max_cost;
242 }
243 let balanced = wave.total_cost.div_ceil(lanes as u64);
248 wave.max_cost.max(balanced)
249}
250
251#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::builder::GraphBuilder;
259
260 fn cost_node(b: &mut GraphBuilder, name: &str, cost: u64) -> NodeId {
261 b.add_raw(
262 crate::node::GraphNode::new(NodeId(0), crate::node::NodeKind::Barrier)
263 .with_name(name)
264 .with_cost(cost),
265 )
266 }
267
268 #[test]
269 fn levelize_empty_errors() {
270 let g = ComputeGraph::new();
271 assert!(matches!(
272 Schedule::levelize(&g),
273 Err(GraphError::EmptyGraph)
274 ));
275 }
276
277 #[test]
278 fn linear_chain_one_node_per_wave() {
279 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
280 let a = b.add_barrier("a");
281 let c = b.add_barrier("b");
282 let d = b.add_barrier("c");
283 b.chain(&[a, c, d]);
284 let g = b.build().expect("builds");
285 let sch = Schedule::levelize(&g).expect("levelize");
286 assert_eq!(sch.depth(), 3);
287 for w in sch.wavefronts() {
288 assert_eq!(w.width(), 1);
289 }
290 assert_eq!(sch.max_width(), 1);
291 }
292
293 #[test]
294 fn fork_join_groups_independent_nodes() {
295 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
297 let src = b.add_barrier("src");
298 let a = b.add_barrier("a");
299 let bb = b.add_barrier("b");
300 let c = b.add_barrier("c");
301 let sink = b.add_barrier("sink");
302 b.fan_out(src, &[a, bb, c]);
303 b.fan_in(&[a, bb, c], sink);
304 let g = b.build().expect("builds");
305 let sch = Schedule::levelize(&g).expect("levelize");
306 assert_eq!(sch.depth(), 3);
307 let mid = &sch.wavefronts()[1];
308 assert_eq!(mid.width(), 3);
309 let mut got = mid.nodes.clone();
310 got.sort();
311 let mut want = vec![a, bb, c];
312 want.sort();
313 assert_eq!(got, want);
314 assert_eq!(sch.max_width(), 3);
315 }
316
317 #[test]
318 fn wavefront_nodes_are_mutually_independent() {
319 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
321 let a = b.add_barrier("a");
322 let bb = b.add_barrier("b");
323 let c = b.add_barrier("c");
324 let d = b.add_barrier("d");
325 let e = b.add_barrier("e");
326 b.dep(a, c);
328 b.dep(a, d);
329 b.dep(bb, d);
330 b.dep(bb, e);
331 let g = b.build().expect("builds");
332 let sch = Schedule::levelize(&g).expect("levelize");
333 for wave in sch.wavefronts() {
334 for &u in &wave.nodes {
335 for &v in &wave.nodes {
336 if u != v {
337 assert!(
338 !g.is_reachable(u, v),
339 "wave-mates {u} and {v} must be independent"
340 );
341 }
342 }
343 }
344 }
345 }
346
347 #[test]
348 fn levels_match_longest_path() {
349 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
351 let a = b.add_barrier("a");
352 let bb = b.add_barrier("b");
353 let c = b.add_barrier("c");
354 let d = b.add_barrier("d");
355 b.dep(a, bb).dep(a, c).dep(bb, d).dep(c, d);
356 let g = b.build().expect("builds");
357 let sch = Schedule::levelize(&g).expect("levelize");
358 assert_eq!(sch.level_of(a).expect("a"), 0);
359 assert_eq!(sch.level_of(bb).expect("b"), 1);
360 assert_eq!(sch.level_of(c).expect("c"), 1);
361 assert_eq!(sch.level_of(d).expect("d"), 2);
362 }
363
364 #[test]
365 fn level_of_out_of_range() {
366 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
367 b.add_barrier("a");
368 let g = b.build().expect("builds");
369 let sch = Schedule::levelize(&g).expect("levelize");
370 assert!(matches!(
371 sch.level_of(NodeId(50)),
372 Err(GraphError::NodeNotFound(_))
373 ));
374 }
375
376 #[test]
377 fn critical_path_cost_weighted() {
378 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
380 let a = cost_node(&mut b, "a", 1);
381 let bb = cost_node(&mut b, "b", 10);
382 let c = cost_node(&mut b, "c", 1);
383 b.chain(&[a, bb, c]);
384 let g = b.build().expect("builds");
385 let sch = Schedule::levelize(&g).expect("levelize");
386 assert_eq!(sch.critical_path_cost(), 12);
387 }
388
389 #[test]
390 fn critical_path_takes_longest_branch() {
391 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
393 let a = cost_node(&mut b, "a", 1);
394 let bb = cost_node(&mut b, "b", 5);
395 let c = cost_node(&mut b, "c", 20);
396 let d = cost_node(&mut b, "d", 1);
397 b.dep(a, bb).dep(a, c).dep(bb, d).dep(c, d);
398 let g = b.build().expect("builds");
399 let sch = Schedule::levelize(&g).expect("levelize");
400 assert_eq!(sch.critical_path_cost(), 22);
401 }
402
403 #[test]
404 fn bounded_makespan_serializes_wide_wave() {
405 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
408 let src = cost_node(&mut b, "src", 0);
409 let leaves: Vec<NodeId> = (0..4)
410 .map(|i| cost_node(&mut b, &format!("l{i}"), 10))
411 .collect();
412 b.fan_out(src, &leaves);
413 let g = b.build().expect("builds");
414 let sch = Schedule::levelize(&g).expect("levelize");
415 assert_eq!(sch.unbounded_makespan(), 10);
417 assert_eq!(sch.bounded_makespan(4), 10);
418 assert_eq!(sch.bounded_makespan(2), 20);
419 assert_eq!(sch.bounded_makespan(1), 40);
420 assert_eq!(sch.bounded_makespan(0), 40);
422 }
423
424 #[test]
425 fn bounded_makespan_respects_max_cost_lower_bound() {
426 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
429 let src = cost_node(&mut b, "src", 0);
430 let big = cost_node(&mut b, "big", 30);
431 let s1 = cost_node(&mut b, "s1", 1);
432 let s2 = cost_node(&mut b, "s2", 1);
433 let s3 = cost_node(&mut b, "s3", 1);
434 b.fan_out(src, &[big, s1, s2, s3]);
435 let g = b.build().expect("builds");
436 let sch = Schedule::levelize(&g).expect("levelize");
437 assert_eq!(sch.bounded_makespan(2), 30);
438 }
439
440 #[test]
441 fn wave_cost_aggregates() {
442 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
443 let src = cost_node(&mut b, "src", 0);
444 let a = cost_node(&mut b, "a", 3);
445 let c = cost_node(&mut b, "c", 7);
446 b.fan_out(src, &[a, c]);
447 let g = b.build().expect("builds");
448 let sch = Schedule::levelize(&g).expect("levelize");
449 let wave1 = &sch.wavefronts()[1];
450 assert_eq!(wave1.max_cost, 7);
451 assert_eq!(wave1.total_cost, 10);
452 }
453
454 #[test]
455 fn isolated_nodes_all_in_wave_zero() {
456 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
457 b.add_barrier("a");
458 b.add_barrier("b");
459 b.add_barrier("c");
460 let g = b.build().expect("builds");
461 let sch = Schedule::levelize(&g).expect("levelize");
462 assert_eq!(sch.depth(), 1);
463 assert_eq!(sch.wavefronts()[0].width(), 3);
464 }
465}