rust_igraph/algorithms/layout/
bipartite.rs1use crate::algorithms::layout::sugiyama::{SugiyamaParams, layout_sugiyama};
7use crate::core::Graph;
8use crate::core::{IgraphError, IgraphResult};
9
10pub fn layout_bipartite(
57 graph: &Graph,
58 types: &[bool],
59 hgap: f64,
60 vgap: f64,
61 maxiter: u32,
62) -> IgraphResult<Vec<[f64; 2]>> {
63 let n = graph.vcount() as usize;
64
65 if types.len() != n {
66 return Err(IgraphError::InvalidArgument(format!(
67 "types length ({}) must equal vertex count ({})",
68 types.len(),
69 n
70 )));
71 }
72 if hgap < 0.0 {
73 return Err(IgraphError::InvalidArgument(
74 "hgap cannot be negative".into(),
75 ));
76 }
77
78 let layers: Vec<u32> = types.iter().map(|&t| u32::from(!t)).collect();
79
80 let params = SugiyamaParams {
81 hgap,
82 vgap,
83 maxiter,
84 };
85
86 let result = layout_sugiyama(graph, Some(&layers), ¶ms)?;
87 Ok(result.positions)
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_bipartite_k23() {
96 let mut g = Graph::with_vertices(5);
97 for &u in &[0u32, 1] {
98 for &v in &[2u32, 3, 4] {
99 g.add_edge(u, v).unwrap();
100 }
101 }
102 let types = vec![true, true, false, false, false];
103 let pos = layout_bipartite(&g, &types, 1.0, 1.0, 100).unwrap();
104 assert_eq!(pos.len(), 5);
105 assert!((pos[0][1] - pos[1][1]).abs() < 1e-10);
106 assert!((pos[2][1] - pos[3][1]).abs() < 1e-10);
107 assert!((pos[2][1] - pos[4][1]).abs() < 1e-10);
108 assert!((pos[0][1] - pos[2][1]).abs() > 0.5);
109 }
110
111 #[test]
112 fn test_bipartite_empty() {
113 let g = Graph::with_vertices(0);
114 let pos = layout_bipartite(&g, &[], 1.0, 1.0, 100).unwrap();
115 assert!(pos.is_empty());
116 }
117
118 #[test]
119 fn test_bipartite_single_vertex() {
120 let g = Graph::with_vertices(1);
121 let pos = layout_bipartite(&g, &[true], 1.0, 1.0, 100).unwrap();
122 assert_eq!(pos.len(), 1);
123 }
124
125 #[test]
126 fn test_bipartite_wrong_types_len() {
127 let g = Graph::with_vertices(3);
128 let result = layout_bipartite(&g, &[true, false], 1.0, 1.0, 100);
129 assert!(result.is_err());
130 }
131
132 #[test]
133 fn test_bipartite_negative_hgap() {
134 let g = Graph::with_vertices(2);
135 let result = layout_bipartite(&g, &[true, false], -1.0, 1.0, 100);
136 assert!(result.is_err());
137 }
138
139 #[test]
140 fn test_bipartite_all_same_type() {
141 let mut g = Graph::with_vertices(4);
142 g.add_edge(0, 1).unwrap();
143 g.add_edge(2, 3).unwrap();
144 let types = vec![true, true, true, true];
145 let pos = layout_bipartite(&g, &types, 1.0, 1.0, 100).unwrap();
146 assert_eq!(pos.len(), 4);
147 for i in 1..4 {
149 assert!((pos[i][1] - pos[0][1]).abs() < 1e-10);
150 }
151 }
152
153 #[test]
154 fn test_bipartite_no_edges() {
155 let g = Graph::with_vertices(4);
156 let types = vec![true, true, false, false];
157 let pos = layout_bipartite(&g, &types, 1.0, 1.0, 100).unwrap();
158 assert_eq!(pos.len(), 4);
159 assert!((pos[0][1] - pos[1][1]).abs() < 1e-10);
161 assert!((pos[2][1] - pos[3][1]).abs() < 1e-10);
162 }
163
164 #[test]
165 fn test_bipartite_separation() {
166 let mut g = Graph::with_vertices(4);
167 g.add_edge(0, 2).unwrap();
168 g.add_edge(1, 3).unwrap();
169 let types = vec![true, true, false, false];
170 let pos = layout_bipartite(&g, &types, 2.0, 3.0, 100).unwrap();
171 let diff_x = (pos[0][0] - pos[1][0]).abs();
173 assert!(diff_x >= 2.0 - 1e-10, "hgap not respected: diff_x={diff_x}");
174 let diff_y = (pos[0][1] - pos[2][1]).abs();
176 assert!(
177 (diff_y - 3.0).abs() < 1e-10,
178 "vgap not respected: diff_y={diff_y}"
179 );
180 }
181}