1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
12use std::collections::VecDeque;
13
14#[derive(Debug, Clone)]
16pub struct SugiyamaResult {
17 pub positions: Vec<[f64; 2]>,
19 pub dummy_positions: Vec<[f64; 2]>,
21 pub extended_graph: Graph,
24 pub extended_to_orig_eids: Vec<u32>,
28}
29
30#[derive(Debug, Clone)]
32pub struct SugiyamaParams {
33 pub hgap: f64,
35 pub vgap: f64,
37 pub maxiter: u32,
39}
40
41impl Default for SugiyamaParams {
42 fn default() -> Self {
43 Self {
44 hgap: 1.0,
45 vgap: 1.0,
46 maxiter: 100,
47 }
48 }
49}
50
51pub fn layout_sugiyama(
91 graph: &Graph,
92 layers: Option<&[u32]>,
93 params: &SugiyamaParams,
94) -> IgraphResult<SugiyamaResult> {
95 let n = graph.vcount() as usize;
96
97 if n == 0 {
98 return Ok(SugiyamaResult {
99 positions: Vec::new(),
100 dummy_positions: Vec::new(),
101 extended_graph: Graph::new(0, graph.is_directed())?,
102 extended_to_orig_eids: Vec::new(),
103 });
104 }
105
106 let mut layer_assign = if let Some(l) = layers {
108 if l.len() != n {
109 return Err(IgraphError::InvalidArgument(
110 "layer vector length must equal vertex count".into(),
111 ));
112 }
113 l.to_vec()
114 } else {
115 compute_layers(graph)?
116 };
117
118 let layer_to_y = normalize_layers(&mut layer_assign, params.vgap);
120
121 let ext = build_extended_graph(graph, &layer_assign)?;
123
124 let mut positions = vec![[0.0_f64; 2]; n];
126 let mut dummy_positions: Vec<[f64; 2]> = Vec::new();
127
128 let total_nodes = ext.node_count;
130 let ext_layers = ext.layers.clone();
131
132 let num_layers = ext_layers.iter().copied().max().unwrap_or(0) as usize + 1;
134 let mut layering: Vec<Vec<usize>> = vec![Vec::new(); num_layers];
135 for (v, &lyr) in ext_layers.iter().enumerate() {
136 layering[lyr as usize].push(v);
137 }
138
139 let mut layout_y = vec![0.0_f64; total_nodes];
141 for (v, &lyr) in ext_layers.iter().enumerate() {
142 let y_idx = lyr as usize;
143 layout_y[v] = if y_idx < layer_to_y.len() {
144 layer_to_y[y_idx]
145 } else {
146 f64::from(lyr) * params.vgap
147 };
148 }
149
150 let mut layout_x = vec![0.0_f64; total_nodes];
152 order_horizontally(
153 &ext.edges,
154 total_nodes,
155 &layering,
156 &mut layout_x,
157 params.maxiter,
158 );
159
160 place_horizontally(
162 &ext.edges,
163 total_nodes,
164 &layering,
165 &mut layout_x,
166 &ext_layers,
167 params.hgap,
168 n,
169 );
170
171 for v in 0..n {
173 positions[v] = [layout_x[v], layout_y[v]];
174 }
175 for v in n..total_nodes {
176 dummy_positions.push([layout_x[v], layout_y[v]]);
177 }
178
179 let mut ext_graph = Graph::new(total_nodes as u32, graph.is_directed())?;
181 for &(src, tgt) in &ext.edges {
182 ext_graph.add_edge(src as VertexId, tgt as VertexId)?;
183 }
184
185 Ok(SugiyamaResult {
186 positions,
187 dummy_positions,
188 extended_graph: ext_graph,
189 extended_to_orig_eids: ext.edge_to_orig,
190 })
191}
192
193fn compute_layers(graph: &Graph) -> IgraphResult<Vec<u32>> {
198 let n = graph.vcount() as usize;
199 let directed = graph.is_directed();
200
201 if directed {
204 longest_path_layering(graph)
205 } else {
206 let mut layers = vec![0u32; n];
208 let mut visited = vec![false; n];
209 let mut queue = VecDeque::new();
210
211 let root = select_highest_degree(graph);
212 visited[root] = true;
213 queue.push_back(root);
214
215 while let Some(v) = queue.pop_front() {
216 let neighbors = all_neighbors(graph, v as VertexId);
217 for nei in neighbors {
218 let nei_idx = nei as usize;
219 if !visited[nei_idx] {
220 visited[nei_idx] = true;
221 layers[nei_idx] = layers[v]
222 .checked_add(1)
223 .ok_or_else(|| IgraphError::InvalidArgument("layer overflow".into()))?;
224 queue.push_back(nei_idx);
225 }
226 }
227 }
228
229 for i in 0..n {
231 if !visited[i] {
232 layers[i] = 0;
233 }
234 }
235
236 Ok(layers)
237 }
238}
239
240fn longest_path_layering(graph: &Graph) -> IgraphResult<Vec<u32>> {
241 let n = graph.vcount() as usize;
242 let ecount = graph.ecount();
243
244 let mut in_degree = vec![0u32; n];
247 let mut adj_out: Vec<Vec<usize>> = vec![Vec::new(); n];
248 let mut back_edges = vec![false; ecount];
249
250 let mut color = vec![0u8; n]; let mut stack: Vec<(usize, usize)> = Vec::new(); for start in 0..n {
255 if color[start] != 0 {
256 continue;
257 }
258 stack.push((start, 0));
259 color[start] = 1;
260
261 while let Some((v, idx)) = stack.last_mut() {
262 let v_id = *v;
263 let out_edges = out_edges_of(graph, v_id as VertexId);
264 if *idx >= out_edges.len() {
265 color[v_id] = 2;
266 stack.pop();
267 } else {
268 let (eid, tgt) = out_edges[*idx];
269 *idx += 1;
270 let tgt_idx = tgt as usize;
271 if color[tgt_idx] == 1 {
272 back_edges[eid] = true;
274 } else if color[tgt_idx] == 0 {
275 color[tgt_idx] = 1;
276 stack.push((tgt_idx, 0));
277 }
278 }
279 }
280 }
281
282 for eid in 0..ecount {
284 if back_edges[eid] {
285 continue;
286 }
287 if let Ok((src, tgt)) = graph.edge(eid as u32) {
288 let src_idx = src as usize;
289 let tgt_idx = tgt as usize;
290 adj_out[src_idx].push(tgt_idx);
291 in_degree[tgt_idx] = in_degree[tgt_idx]
292 .checked_add(1)
293 .ok_or_else(|| IgraphError::InvalidArgument("in-degree overflow".into()))?;
294 }
295 }
296
297 let mut layers = vec![0u32; n];
300 let mut queue: VecDeque<usize> = VecDeque::new();
301 let mut remaining_in = in_degree.clone();
302
303 for v in 0..n {
305 if remaining_in[v] == 0 {
306 queue.push_back(v);
307 }
308 }
309
310 while let Some(v) = queue.pop_front() {
311 for &w in &adj_out[v] {
312 let new_layer = layers[v]
313 .checked_add(1)
314 .ok_or_else(|| IgraphError::InvalidArgument("layer overflow".into()))?;
315 if new_layer > layers[w] {
316 layers[w] = new_layer;
317 }
318 remaining_in[w] -= 1;
319 if remaining_in[w] == 0 {
320 queue.push_back(w);
321 }
322 }
323 }
324
325 Ok(layers)
328}
329
330fn normalize_layers(layers: &mut [u32], vgap: f64) -> Vec<f64> {
331 if layers.is_empty() {
332 return Vec::new();
333 }
334
335 let mut unique: Vec<u32> = layers.to_vec();
337 unique.sort_unstable();
338 unique.dedup();
339
340 let mut layer_to_y = Vec::with_capacity(unique.len());
341 for (new_idx, &old_val) in unique.iter().enumerate() {
342 layer_to_y.push(f64::from(old_val) * vgap);
343 for l in layers.iter_mut() {
344 if *l == old_val {
345 *l = new_idx as u32;
346 }
347 }
348 }
349
350 let num_layers = unique.len();
352 let mut result = vec![0.0; num_layers];
353 for i in 0..num_layers {
354 result[i] = i as f64 * vgap;
355 }
356 result
357}
358
359struct ExtendedGraph {
364 node_count: usize,
365 edges: Vec<(usize, usize)>,
366 edge_to_orig: Vec<u32>,
367 layers: Vec<u32>,
368}
369
370fn build_extended_graph(graph: &Graph, layers: &[u32]) -> IgraphResult<ExtendedGraph> {
371 let n = graph.vcount() as usize;
372 let ecount = graph.ecount() as u32;
373 let mut next_node = n;
374 let mut edges: Vec<(usize, usize)> = Vec::new();
375 let mut edge_to_orig: Vec<u32> = Vec::new();
376 let mut node_layers: Vec<u32> = layers.to_vec();
377
378 for eid in 0..ecount {
379 let (src, tgt) = graph.edge(eid)?;
380 let src_idx = src as usize;
381 let tgt_idx = tgt as usize;
382 let src_layer = layers[src_idx];
383 let tgt_layer = layers[tgt_idx];
384
385 if src_layer == tgt_layer {
386 edges.push((src_idx, tgt_idx));
388 edge_to_orig.push(eid);
389 } else {
390 let (from, to, from_layer, to_layer) = if src_layer < tgt_layer {
392 (src_idx, tgt_idx, src_layer, tgt_layer)
393 } else {
394 (tgt_idx, src_idx, tgt_layer, src_layer)
395 };
396
397 if to_layer - from_layer == 1 {
398 edges.push((from, to));
400 edge_to_orig.push(eid);
401 } else {
402 let mut prev = from;
404 for lyr in (from_layer + 1)..to_layer {
405 let dummy = next_node;
406 next_node += 1;
407 node_layers.push(lyr);
408 edges.push((prev, dummy));
409 edge_to_orig.push(eid);
410 prev = dummy;
411 }
412 edges.push((prev, to));
413 edge_to_orig.push(eid);
414 }
415 }
416 }
417
418 Ok(ExtendedGraph {
419 node_count: next_node,
420 edges,
421 edge_to_orig,
422 layers: node_layers,
423 })
424}
425
426fn order_horizontally(
431 edges: &[(usize, usize)],
432 node_count: usize,
433 layering: &[Vec<usize>],
434 layout_x: &mut [f64],
435 maxiter: u32,
436) {
437 let num_layers = layering.len();
438 if num_layers <= 1 {
439 for (i, &v) in layering
441 .first()
442 .map_or(&[][..], |l| l.as_slice())
443 .iter()
444 .enumerate()
445 {
446 layout_x[v] = i as f64;
447 }
448 return;
449 }
450
451 let mut predecessors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
453 let mut successors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
454 for &(src, tgt) in edges {
455 successors[src].push(tgt);
456 predecessors[tgt].push(src);
457 }
458
459 let mut layer_order: Vec<Vec<usize>> = layering.to_vec();
461 for (layer_idx, layer) in layer_order.iter().enumerate() {
462 for (pos, &v) in layer.iter().enumerate() {
463 layout_x[v] = pos as f64;
464 }
465 let _ = layer_idx;
466 }
467
468 let mut changed = true;
470 let mut iter = 0u32;
471 while changed && iter < maxiter {
472 changed = false;
473
474 for layer_idx in 1..num_layers {
477 let layer = &layer_order[layer_idx];
478 let layer_size = layer.len();
479 if layer_size == 0 {
480 continue;
481 }
482
483 let mut barycenters: Vec<(f64, usize)> = Vec::with_capacity(layer_size);
484 for &v in layer {
485 let preds = &predecessors[v];
486 let bc = if preds.is_empty() {
487 layout_x[v]
488 } else {
489 let sum: f64 = preds.iter().map(|&p| layout_x[p]).sum();
490 sum / preds.len() as f64
491 };
492 barycenters.push((bc, v));
493 }
494
495 barycenters.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
496
497 let new_order: Vec<usize> = barycenters.iter().map(|&(_, v)| v).collect();
498 if new_order != layer_order[layer_idx] {
499 changed = true;
500 for (pos, &v) in new_order.iter().enumerate() {
501 layout_x[v] = pos as f64;
502 }
503 layer_order[layer_idx] = new_order;
504 }
505 }
506
507 for layer_idx in (0..num_layers.saturating_sub(1)).rev() {
510 let layer = &layer_order[layer_idx];
511 let layer_size = layer.len();
512 if layer_size == 0 {
513 continue;
514 }
515
516 let mut barycenters: Vec<(f64, usize)> = Vec::with_capacity(layer_size);
517 for &v in layer {
518 let succs = &successors[v];
519 let bc = if succs.is_empty() {
520 layout_x[v]
521 } else {
522 let sum: f64 = succs.iter().map(|&s| layout_x[s]).sum();
523 sum / succs.len() as f64
524 };
525 barycenters.push((bc, v));
526 }
527
528 barycenters.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
529
530 let new_order: Vec<usize> = barycenters.iter().map(|&(_, v)| v).collect();
531 if new_order != layer_order[layer_idx] {
532 changed = true;
533 for (pos, &v) in new_order.iter().enumerate() {
534 layout_x[v] = pos as f64;
535 }
536 layer_order[layer_idx] = new_order;
537 }
538 }
539
540 iter += 1;
541 }
542}
543
544fn place_horizontally(
549 edges: &[(usize, usize)],
550 node_count: usize,
551 layering: &[Vec<usize>],
552 layout_x: &mut [f64],
553 _node_layers: &[u32],
554 hgap: f64,
555 _no_of_real_nodes: usize,
556) {
557 let mut predecessors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
559 for &(src, tgt) in edges {
560 predecessors[tgt].push(src);
561 }
562
563 let mut all_xs: [Vec<f64>; 4] = [
566 vec![0.0; node_count],
567 vec![0.0; node_count],
568 vec![0.0; node_count],
569 vec![0.0; node_count],
570 ];
571
572 for combo in 0..4u8 {
573 let reverse = combo / 2 == 1;
574 let align_right = combo % 2 == 1;
575
576 let mut roots = (0..node_count).collect::<Vec<_>>();
578 let mut align = (0..node_count).collect::<Vec<_>>();
579
580 vertical_alignment(
581 edges,
582 node_count,
583 layering,
584 layout_x,
585 reverse,
586 align_right,
587 &mut roots,
588 &mut align,
589 &predecessors,
590 );
591
592 horizontal_compaction(
594 node_count,
595 layering,
596 &roots,
597 &align,
598 hgap,
599 &mut all_xs[combo as usize],
600 );
601 }
602
603 let mut mins = [0.0_f64; 4];
605 let mut maxs = [0.0_f64; 4];
606 for i in 0..4 {
607 mins[i] = all_xs[i].iter().copied().fold(f64::INFINITY, f64::min);
608 maxs[i] = all_xs[i].iter().copied().fold(f64::NEG_INFINITY, f64::max);
609 }
610
611 let mut best = 0;
613 let mut best_width = f64::INFINITY;
614 for i in 0..4 {
615 let w = maxs[i] - mins[i];
616 if w < best_width {
617 best_width = w;
618 best = i;
619 }
620 }
621
622 for i in 0..4 {
624 if i == best {
625 continue;
626 }
627 let diff = if i % 2 == 0 {
628 mins[best] - mins[i]
629 } else {
630 maxs[best] - maxs[i]
631 };
632 for x in &mut all_xs[i] {
633 *x += diff;
634 }
635 }
636
637 for v in 0..node_count {
639 let mut vals = [all_xs[0][v], all_xs[1][v], all_xs[2][v], all_xs[3][v]];
640 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
641 layout_x[v] = (vals[1] + vals[2]) * 0.5;
642 }
643}
644
645fn vertical_alignment(
646 edges: &[(usize, usize)],
647 node_count: usize,
648 layering: &[Vec<usize>],
649 layout_x: &[f64],
650 reverse: bool,
651 align_right: bool,
652 roots: &mut [usize],
653 align: &mut [usize],
654 predecessors: &[Vec<usize>],
655) {
656 let num_layers = layering.len();
657
658 let mut successors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
660 for &(src, tgt) in edges {
661 successors[src].push(tgt);
662 }
663
664 for i in 0..node_count {
666 roots[i] = i;
667 align[i] = i;
668 }
669
670 let layer_range: Vec<usize> = if reverse {
672 (0..num_layers.saturating_sub(1)).rev().collect()
673 } else {
674 (1..num_layers).collect()
675 };
676
677 for &layer_idx in &layer_range {
678 let layer = &layering[layer_idx];
679 let mut r: i64 = if align_right { i64::MAX } else { -1 };
680
681 let vertex_iter: Vec<usize> = if align_right {
682 layer.iter().copied().rev().collect()
683 } else {
684 layer.clone()
685 };
686
687 for vertex in vertex_iter {
688 if align[vertex] != vertex {
689 continue;
690 }
691
692 let neighbors: &Vec<usize> = if reverse {
694 &successors[vertex]
695 } else {
696 &predecessors[vertex]
697 };
698
699 if neighbors.is_empty() {
700 continue;
701 }
702
703 let mut sorted_neis: Vec<usize> = neighbors.clone();
705 sorted_neis.sort_by(|&a, &b| {
706 layout_x[a]
707 .partial_cmp(&layout_x[b])
708 .unwrap_or(std::cmp::Ordering::Equal)
709 });
710
711 let n_neis = sorted_neis.len();
713 let medians = if n_neis == 1 {
714 vec![sorted_neis[0]]
715 } else if n_neis % 2 == 1 {
716 vec![sorted_neis[n_neis / 2]]
717 } else if align_right {
718 vec![sorted_neis[n_neis / 2], sorted_neis[n_neis / 2 - 1]]
719 } else {
720 vec![sorted_neis[n_neis / 2 - 1], sorted_neis[n_neis / 2]]
721 };
722
723 for median in medians {
725 if align[vertex] != vertex {
726 break;
727 }
728 let pos = layout_x[median] as i64;
729 if (align_right && r > pos) || (!align_right && r < pos) {
730 align[median] = vertex;
731 roots[vertex] = roots[median];
732 align[vertex] = roots[median];
733 r = pos;
734 }
735 }
736 }
737 }
738}
739
740fn horizontal_compaction(
741 node_count: usize,
742 layering: &[Vec<usize>],
743 roots: &[usize],
744 align: &[usize],
745 hgap: f64,
746 xs: &mut [f64],
747) {
748 let mut vertex_to_left: Vec<usize> = (0..node_count).collect();
750 for layer in layering {
751 if layer.len() <= 1 {
752 continue;
753 }
754 for i in 1..layer.len() {
755 vertex_to_left[layer[i]] = layer[i - 1];
756 }
757 }
758
759 let mut sinks: Vec<usize> = (0..node_count).collect();
760 let mut shifts = vec![f64::INFINITY; node_count];
761
762 for x in xs.iter_mut() {
764 *x = -1.0;
765 }
766
767 for i in 0..node_count {
769 if roots[i] == i {
770 place_block(
771 i,
772 &vertex_to_left,
773 roots,
774 align,
775 &mut sinks,
776 &mut shifts,
777 hgap,
778 xs,
779 );
780 }
781 }
782
783 let old_xs = xs.to_vec();
785 for i in 0..node_count {
786 let root = roots[i];
787 xs[i] = old_xs[root];
788 let shift = shifts[sinks[root]];
789 if shift < f64::INFINITY {
790 xs[i] += shift;
791 }
792 }
793}
794
795fn place_block(
796 v: usize,
797 vertex_to_left: &[usize],
798 roots: &[usize],
799 align: &[usize],
800 sinks: &mut [usize],
801 shifts: &mut [f64],
802 hgap: f64,
803 xs: &mut [f64],
804) {
805 if xs[v] >= 0.0 {
806 return;
807 }
808
809 xs[v] = 0.0;
810
811 let mut w = v;
812 loop {
813 let u_left = vertex_to_left[w];
814 if u_left != w {
815 let u = roots[u_left];
816 place_block(u, vertex_to_left, roots, align, sinks, shifts, hgap, xs);
817
818 let u_sink = sinks[u];
819 let v_sink = sinks[v];
820 if v_sink == v {
821 sinks[v] = u_sink;
822 }
823
824 let current_v_sink = sinks[v];
825 if current_v_sink != u_sink {
826 let candidate = xs[v] - xs[u] - hgap;
827 if shifts[u_sink] > candidate {
828 shifts[u_sink] = candidate;
829 }
830 } else if xs[v] < xs[u] + hgap {
831 xs[v] = xs[u] + hgap;
832 }
833 }
834
835 w = align[w];
836 if w == v {
837 break;
838 }
839 }
840}
841
842fn select_highest_degree(graph: &Graph) -> usize {
847 let n = graph.vcount() as usize;
848 if n == 0 {
849 return 0;
850 }
851 let mut best = 0;
852 let mut best_deg = 0;
853 for v in 0..n {
854 if let Ok(d) = graph.degree(v as VertexId) {
855 if d > best_deg {
856 best_deg = d;
857 best = v;
858 }
859 }
860 }
861 best
862}
863
864fn all_neighbors(graph: &Graph, v: VertexId) -> Vec<VertexId> {
865 let mut result = Vec::new();
866 let ecount = graph.ecount();
867 for eid in 0..ecount as u32 {
868 if let Ok((src, tgt)) = graph.edge(eid) {
869 if src == v && tgt != v {
870 result.push(tgt);
871 } else if tgt == v && src != v {
872 result.push(src);
873 }
874 }
875 }
876 result
877}
878
879fn out_edges_of(graph: &Graph, v: VertexId) -> Vec<(usize, VertexId)> {
880 let mut result = Vec::new();
881 let ecount = graph.ecount();
882 for eid in 0..ecount as u32 {
883 if let Ok((src, tgt)) = graph.edge(eid) {
884 if graph.is_directed() {
885 if src == v && tgt != v {
886 result.push((eid as usize, tgt));
887 }
888 } else if src == v && tgt != v {
889 result.push((eid as usize, tgt));
890 } else if tgt == v && src != v {
891 result.push((eid as usize, src));
892 }
893 }
894 }
895 result
896}
897
898#[cfg(test)]
903mod tests {
904 use super::*;
905
906 fn simple_dag() -> Graph {
907 let mut g = Graph::new(4, true).unwrap();
909 g.add_edge(0, 1).unwrap();
910 g.add_edge(0, 2).unwrap();
911 g.add_edge(1, 3).unwrap();
912 g.add_edge(2, 3).unwrap();
913 g
914 }
915
916 #[test]
917 fn test_sugiyama_basic_dag() {
918 let g = simple_dag();
919 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
920 assert_eq!(result.positions.len(), 4);
921 assert!(result.positions[0][1].abs() < 1e-10);
923 assert!((result.positions[3][1] - 2.0).abs() < 1e-10);
925 assert!((result.positions[1][1] - 1.0).abs() < 1e-10);
927 assert!((result.positions[2][1] - 1.0).abs() < 1e-10);
928 }
929
930 #[test]
931 fn test_sugiyama_empty() {
932 let g = Graph::new(0, true).unwrap();
933 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
934 assert!(result.positions.is_empty());
935 }
936
937 #[test]
938 fn test_sugiyama_single_vertex() {
939 let g = Graph::new(1, true).unwrap();
940 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
941 assert_eq!(result.positions.len(), 1);
942 assert!(result.positions[0][0].abs() < 1e-10);
943 assert!(result.positions[0][1].abs() < 1e-10);
944 }
945
946 #[test]
947 fn test_sugiyama_linear_chain() {
948 let mut g = Graph::new(4, true).unwrap();
950 g.add_edge(0, 1).unwrap();
951 g.add_edge(1, 2).unwrap();
952 g.add_edge(2, 3).unwrap();
953 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
954 for i in 0..4 {
956 assert!(
957 (result.positions[i][1] - i as f64).abs() < 1e-10,
958 "vertex {i} y={}, expected {i}",
959 result.positions[i][1]
960 );
961 }
962 }
963
964 #[test]
965 fn test_sugiyama_with_layers() {
966 let g = simple_dag();
967 let layers = vec![0, 1, 1, 2];
968 let result = layout_sugiyama(&g, Some(&layers), &SugiyamaParams::default()).unwrap();
969 assert_eq!(result.positions.len(), 4);
970 assert!(result.positions[0][1].abs() < 1e-10);
971 assert!((result.positions[3][1] - 2.0).abs() < 1e-10);
972 }
973
974 #[test]
975 fn test_sugiyama_invalid_layers() {
976 let g = simple_dag();
977 let layers = vec![0, 1]; let result = layout_sugiyama(&g, Some(&layers), &SugiyamaParams::default());
979 assert!(result.is_err());
980 }
981
982 #[test]
983 fn test_sugiyama_long_edge_dummies() {
984 let mut g = Graph::new(3, true).unwrap();
987 g.add_edge(0, 1).unwrap();
988 g.add_edge(0, 2).unwrap();
989 let layers = vec![0, 2, 1];
990 let result = layout_sugiyama(&g, Some(&layers), &SugiyamaParams::default()).unwrap();
991 assert_eq!(result.positions.len(), 3);
992 assert_eq!(result.dummy_positions.len(), 1);
994 assert!((result.dummy_positions[0][1] - 1.0).abs() < 1e-10);
996 }
997
998 #[test]
999 fn test_sugiyama_diamond_symmetry() {
1000 let g = simple_dag();
1001 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
1002 let mid_x = result.positions[0][0];
1004 let left = result.positions[1][0] - mid_x;
1005 let right = result.positions[2][0] - mid_x;
1006 assert!(
1007 (left + right).abs() < 1e-10,
1008 "not symmetric: left={left}, right={right}"
1009 );
1010 }
1011
1012 #[test]
1013 fn test_sugiyama_no_overlap_same_layer() {
1014 let g = simple_dag();
1015 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
1016 let x1 = result.positions[1][0];
1018 let x2 = result.positions[2][0];
1019 assert!((x1 - x2).abs() >= 1.0 - 1e-10, "overlap: x1={x1}, x2={x2}");
1020 }
1021
1022 #[test]
1023 fn test_sugiyama_undirected() {
1024 let mut g = Graph::with_vertices(4);
1025 g.add_edge(0, 1).unwrap();
1026 g.add_edge(1, 2).unwrap();
1027 g.add_edge(2, 3).unwrap();
1028 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
1029 assert_eq!(result.positions.len(), 4);
1030 for p in &result.positions {
1032 assert!(p[0].is_finite());
1033 assert!(p[1].is_finite());
1034 }
1035 }
1036
1037 #[test]
1038 fn test_sugiyama_with_cycle() {
1039 let mut g = Graph::new(3, true).unwrap();
1041 g.add_edge(0, 1).unwrap();
1042 g.add_edge(1, 2).unwrap();
1043 g.add_edge(2, 0).unwrap();
1044 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
1045 assert_eq!(result.positions.len(), 3);
1046 for p in &result.positions {
1048 assert!(p[0].is_finite());
1049 assert!(p[1].is_finite());
1050 }
1051 }
1052
1053 #[test]
1054 fn test_sugiyama_custom_gaps() {
1055 let g = simple_dag();
1056 let params = SugiyamaParams {
1057 hgap: 2.0,
1058 vgap: 3.0,
1059 maxiter: 50,
1060 };
1061 let result = layout_sugiyama(&g, None, ¶ms).unwrap();
1062 assert!((result.positions[3][1] - 6.0).abs() < 1e-10);
1064 let x1 = result.positions[1][0];
1066 let x2 = result.positions[2][0];
1067 assert!(
1068 (x1 - x2).abs() >= 2.0 - 1e-10,
1069 "gap too small: |{x1} - {x2}| = {}",
1070 (x1 - x2).abs()
1071 );
1072 }
1073
1074 #[test]
1075 fn test_sugiyama_extended_graph() {
1076 let g = simple_dag();
1077 let result = layout_sugiyama(&g, None, &SugiyamaParams::default()).unwrap();
1078 assert!(result.extended_graph.vcount() >= g.vcount());
1080 for &orig_eid in &result.extended_to_orig_eids {
1082 assert!(orig_eid < g.ecount() as u32);
1083 }
1084 }
1085}