rust_igraph/algorithms/operators/
to_undirected.rs1use crate::core::{Graph, IgraphResult};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ToUndirectedMode {
11 Each,
14 Collapse,
18 Mutual,
21}
22
23pub fn to_undirected(graph: &Graph, mode: ToUndirectedMode) -> IgraphResult<Graph> {
58 let n = graph.vcount();
59
60 if !graph.is_directed() {
61 let mut result = Graph::with_vertices(n);
63 let ecount = graph.ecount();
64 for eid in 0..ecount {
65 #[allow(clippy::cast_possible_truncation)]
66 let (src, tgt) = graph.edge(eid as u32)?;
67 result.add_edge(src, tgt)?;
68 }
69 return Ok(result);
70 }
71
72 let mut result = Graph::with_vertices(n);
73 let ecount = graph.ecount();
74
75 match mode {
76 ToUndirectedMode::Each => {
77 for eid in 0..ecount {
78 #[allow(clippy::cast_possible_truncation)]
79 let (src, tgt) = graph.edge(eid as u32)?;
80 result.add_edge(src, tgt)?;
81 }
82 }
83 ToUndirectedMode::Collapse => {
84 let mut seen = std::collections::HashSet::with_capacity(ecount);
85 for eid in 0..ecount {
86 #[allow(clippy::cast_possible_truncation)]
87 let (src, tgt) = graph.edge(eid as u32)?;
88 let key = if src <= tgt { (src, tgt) } else { (tgt, src) };
89 if seen.insert(key) {
90 result.add_edge(key.0, key.1)?;
91 }
92 }
93 }
94 ToUndirectedMode::Mutual => {
95 let mut forward = std::collections::HashSet::with_capacity(ecount);
97 for eid in 0..ecount {
98 #[allow(clippy::cast_possible_truncation)]
99 let (src, tgt) = graph.edge(eid as u32)?;
100 forward.insert((src, tgt));
101 }
102 let mut added = std::collections::HashSet::new();
104 for &(src, tgt) in &forward {
105 if src != tgt && forward.contains(&(tgt, src)) {
106 let key = if src <= tgt { (src, tgt) } else { (tgt, src) };
107 if added.insert(key) {
108 result.add_edge(key.0, key.1)?;
109 }
110 }
111 }
112 }
113 }
114
115 Ok(result)
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_already_undirected() {
124 let mut g = Graph::with_vertices(3);
125 g.add_edge(0, 1).unwrap();
126 g.add_edge(1, 2).unwrap();
127 let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
128 assert!(!u.is_directed());
129 assert_eq!(u.vcount(), 3);
130 assert_eq!(u.ecount(), 2);
131 }
132
133 #[test]
134 fn test_each_mode_mutual_pair() {
135 let mut g = Graph::new(2, true).unwrap();
136 g.add_edge(0, 1).unwrap();
137 g.add_edge(1, 0).unwrap();
138 let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
139 assert!(!u.is_directed());
140 assert_eq!(u.ecount(), 2); }
142
143 #[test]
144 fn test_each_mode_single_edge() {
145 let mut g = Graph::new(3, true).unwrap();
146 g.add_edge(0, 1).unwrap();
147 g.add_edge(1, 2).unwrap();
148 let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
149 assert_eq!(u.ecount(), 2);
150 }
151
152 #[test]
153 fn test_collapse_mode_mutual() {
154 let mut g = Graph::new(3, true).unwrap();
155 g.add_edge(0, 1).unwrap();
156 g.add_edge(1, 0).unwrap();
157 g.add_edge(1, 2).unwrap();
158 let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
159 assert_eq!(u.ecount(), 2); }
161
162 #[test]
163 fn test_collapse_mode_parallel() {
164 let mut g = Graph::new(2, true).unwrap();
165 g.add_edge(0, 1).unwrap();
166 g.add_edge(0, 1).unwrap();
167 g.add_edge(0, 1).unwrap();
168 let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
169 assert_eq!(u.ecount(), 1); }
171
172 #[test]
173 fn test_mutual_mode_basic() {
174 let mut g = Graph::new(3, true).unwrap();
175 g.add_edge(0, 1).unwrap();
176 g.add_edge(1, 0).unwrap();
177 g.add_edge(1, 2).unwrap();
178 let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
179 assert_eq!(u.ecount(), 1); }
181
182 #[test]
183 fn test_mutual_mode_no_mutual() {
184 let mut g = Graph::new(3, true).unwrap();
185 g.add_edge(0, 1).unwrap();
186 g.add_edge(1, 2).unwrap();
187 g.add_edge(2, 0).unwrap();
188 let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
189 assert_eq!(u.ecount(), 0); }
191
192 #[test]
193 fn test_mutual_mode_all_mutual() {
194 let mut g = Graph::new(3, true).unwrap();
195 for i in 0..3u32 {
196 for j in 0..3u32 {
197 if i != j {
198 g.add_edge(i, j).unwrap();
199 }
200 }
201 }
202 let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
203 assert_eq!(u.ecount(), 3); }
205
206 #[test]
207 fn test_empty_graph() {
208 let g = Graph::new(0, true).unwrap();
209 let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
210 assert!(!u.is_directed());
211 assert_eq!(u.vcount(), 0);
212 assert_eq!(u.ecount(), 0);
213 }
214
215 #[test]
216 fn test_no_edges() {
217 let g = Graph::new(5, true).unwrap();
218 let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
219 assert_eq!(u.vcount(), 5);
220 assert_eq!(u.ecount(), 0);
221 }
222
223 #[test]
224 fn test_self_loops_each() {
225 let mut g = Graph::new(2, true).unwrap();
226 g.add_edge(0, 0).unwrap();
227 g.add_edge(0, 1).unwrap();
228 let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
229 assert_eq!(u.ecount(), 2);
230 }
231
232 #[test]
233 fn test_self_loops_mutual() {
234 let mut g = Graph::new(2, true).unwrap();
236 g.add_edge(0, 0).unwrap();
237 g.add_edge(0, 1).unwrap();
238 let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
239 assert_eq!(u.ecount(), 0);
242 }
243
244 #[test]
245 fn test_vcount_preserved() {
246 let mut g = Graph::new(10, true).unwrap();
247 g.add_edge(0, 5).unwrap();
248 g.add_edge(5, 0).unwrap();
249 let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
250 assert_eq!(u.vcount(), 10);
251 }
252}