Skip to main content

rust_igraph/algorithms/operators/
reverse.rs

1//! Graph reversal operators (ALGO-OP-008, ALGO-OP-025).
2//!
3//! Reverses edge directions — all edges or a specified subset.
4
5use crate::core::{Graph, IgraphError, IgraphResult};
6
7/// Returns a new graph with all edge directions reversed.
8///
9/// For directed graphs, every edge `(u, v)` becomes `(v, u)`.
10/// For undirected graphs, returns a structural copy (edges unchanged).
11///
12/// # Examples
13///
14/// ```
15/// use rust_igraph::{Graph, reverse};
16///
17/// let mut g = Graph::new(3, true).unwrap();
18/// g.add_edge(0, 1).unwrap();
19/// g.add_edge(1, 2).unwrap();
20///
21/// let rev = reverse(&g).unwrap();
22/// assert!(rev.is_directed());
23/// assert_eq!(rev.ecount(), 2);
24/// // Edge 0→1 becomes 1→0, edge 1→2 becomes 2→1
25/// ```
26pub fn reverse(graph: &Graph) -> IgraphResult<Graph> {
27    let n = graph.vcount();
28    let directed = graph.is_directed();
29    let ecount = graph.ecount();
30
31    let mut edges: Vec<(u32, u32)> = Vec::with_capacity(ecount);
32
33    for eid in 0..ecount {
34        #[allow(clippy::cast_possible_truncation)]
35        let eid_u32 = eid as u32;
36        let (src, tgt) = graph.edge(eid_u32)?;
37        if directed {
38            edges.push((tgt, src));
39        } else {
40            edges.push((src, tgt));
41        }
42    }
43
44    let mut result = Graph::new(n, directed)?;
45    result.add_edges(edges)?;
46    Ok(result)
47}
48
49/// Returns a new graph with only the specified edges reversed.
50///
51/// For directed graphs, each edge in `eids` has its direction flipped
52/// (`(u, v)` becomes `(v, u)`). Edges not listed remain unchanged.
53/// For undirected graphs, this is a no-op (returns a structural copy).
54///
55/// Duplicate edge IDs in `eids` are silently ignored (double-reversing
56/// a single edge still results in one reversal).
57///
58/// # Errors
59///
60/// Returns `InvalidArgument` if any edge ID in `eids` is out of range.
61///
62/// # Examples
63///
64/// ```
65/// use rust_igraph::{Graph, reverse_edges};
66///
67/// let mut g = Graph::new(3, true).unwrap();
68/// g.add_edge(0, 1).unwrap(); // eid 0
69/// g.add_edge(1, 2).unwrap(); // eid 1
70///
71/// // Reverse only edge 0
72/// let rev = reverse_edges(&g, &[0]).unwrap();
73/// assert_eq!(rev.edge(0).unwrap(), (1, 0)); // reversed
74/// assert_eq!(rev.edge(1).unwrap(), (1, 2)); // unchanged
75/// ```
76pub fn reverse_edges(graph: &Graph, eids: &[u32]) -> IgraphResult<Graph> {
77    let n = graph.vcount();
78    let directed = graph.is_directed();
79    let ecount = graph.ecount();
80
81    if !directed {
82        // No-op for undirected graphs — just copy
83        return reverse_edges_copy_undirected(graph, n, ecount);
84    }
85
86    // Build a set of edges to reverse
87    let mut to_reverse: Vec<bool> = vec![false; ecount];
88    for &eid in eids {
89        if eid as usize >= ecount {
90            return Err(IgraphError::InvalidArgument(format!(
91                "reverse_edges: edge ID {eid} out of range (graph has {ecount} edges)"
92            )));
93        }
94        to_reverse[eid as usize] = true;
95    }
96
97    let mut result = Graph::new(n, true)?;
98    for (eid_usize, &rev) in to_reverse.iter().enumerate() {
99        #[allow(clippy::cast_possible_truncation)]
100        let eid = eid_usize as u32;
101        let (src, tgt) = graph.edge(eid)?;
102        if rev {
103            result.add_edge(tgt, src)?;
104        } else {
105            result.add_edge(src, tgt)?;
106        }
107    }
108
109    Ok(result)
110}
111
112fn reverse_edges_copy_undirected(graph: &Graph, n: u32, ecount: usize) -> IgraphResult<Graph> {
113    let mut result = Graph::with_vertices(n);
114    for eid_usize in 0..ecount {
115        #[allow(clippy::cast_possible_truncation)]
116        let eid = eid_usize as u32;
117        let (src, tgt) = graph.edge(eid)?;
118        result.add_edge(src, tgt)?;
119    }
120    Ok(result)
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_reverse_directed() {
129        let mut g = Graph::new(4, true).unwrap();
130        g.add_edge(0, 1).unwrap();
131        g.add_edge(1, 2).unwrap();
132        g.add_edge(2, 3).unwrap();
133
134        let rev = reverse(&g).unwrap();
135        assert!(rev.is_directed());
136        assert_eq!(rev.vcount(), 4);
137        assert_eq!(rev.ecount(), 3);
138
139        // Check edges are reversed
140        let (s, t) = rev.edge(0).unwrap();
141        assert_eq!((s, t), (1, 0));
142        let (s, t) = rev.edge(1).unwrap();
143        assert_eq!((s, t), (2, 1));
144        let (s, t) = rev.edge(2).unwrap();
145        assert_eq!((s, t), (3, 2));
146    }
147
148    #[test]
149    fn test_reverse_undirected() {
150        let mut g = Graph::with_vertices(3);
151        g.add_edge(0, 1).unwrap();
152        g.add_edge(1, 2).unwrap();
153
154        let rev = reverse(&g).unwrap();
155        assert!(!rev.is_directed());
156        assert_eq!(rev.vcount(), 3);
157        assert_eq!(rev.ecount(), 2);
158    }
159
160    #[test]
161    fn test_reverse_empty() {
162        let g = Graph::new(5, true).unwrap();
163        let rev = reverse(&g).unwrap();
164        assert_eq!(rev.vcount(), 5);
165        assert_eq!(rev.ecount(), 0);
166    }
167
168    #[test]
169    fn test_reverse_self_loop() {
170        let mut g = Graph::new(2, true).unwrap();
171        g.add_edge(0, 0).unwrap();
172        g.add_edge(0, 1).unwrap();
173
174        let rev = reverse(&g).unwrap();
175        let (s, t) = rev.edge(0).unwrap();
176        assert_eq!((s, t), (0, 0)); // self-loop unchanged
177        let (s, t) = rev.edge(1).unwrap();
178        assert_eq!((s, t), (1, 0)); // reversed
179    }
180
181    #[test]
182    fn test_reverse_involution() {
183        // Reversing twice gives back the original
184        let mut g = Graph::new(4, true).unwrap();
185        g.add_edge(0, 1).unwrap();
186        g.add_edge(1, 2).unwrap();
187        g.add_edge(3, 0).unwrap();
188
189        let rev2 = reverse(&reverse(&g).unwrap()).unwrap();
190        assert_eq!(rev2.ecount(), 3);
191        for eid in 0..3u32 {
192            assert_eq!(g.edge(eid).unwrap(), rev2.edge(eid).unwrap());
193        }
194    }
195
196    #[test]
197    fn test_reverse_preserves_multi_edges() {
198        let mut g = Graph::new(2, true).unwrap();
199        g.add_edge(0, 1).unwrap();
200        g.add_edge(0, 1).unwrap();
201
202        let rev = reverse(&g).unwrap();
203        assert_eq!(rev.ecount(), 2);
204        assert_eq!(rev.edge(0).unwrap(), (1, 0));
205        assert_eq!(rev.edge(1).unwrap(), (1, 0));
206    }
207
208    // ---- reverse_edges tests ----
209
210    #[test]
211    fn test_reverse_edges_subset() {
212        let mut g = Graph::new(4, true).unwrap();
213        g.add_edge(0, 1).unwrap(); // 0
214        g.add_edge(1, 2).unwrap(); // 1
215        g.add_edge(2, 3).unwrap(); // 2
216
217        let rev = reverse_edges(&g, &[0, 2]).unwrap();
218        assert_eq!(rev.edge(0).unwrap(), (1, 0)); // reversed
219        assert_eq!(rev.edge(1).unwrap(), (1, 2)); // unchanged
220        assert_eq!(rev.edge(2).unwrap(), (3, 2)); // reversed
221    }
222
223    #[test]
224    fn test_reverse_edges_empty_set() {
225        let mut g = Graph::new(3, true).unwrap();
226        g.add_edge(0, 1).unwrap();
227        g.add_edge(1, 2).unwrap();
228
229        let rev = reverse_edges(&g, &[]).unwrap();
230        assert_eq!(rev.edge(0).unwrap(), (0, 1));
231        assert_eq!(rev.edge(1).unwrap(), (1, 2));
232    }
233
234    #[test]
235    fn test_reverse_edges_all() {
236        let mut g = Graph::new(3, true).unwrap();
237        g.add_edge(0, 1).unwrap();
238        g.add_edge(1, 2).unwrap();
239
240        let rev = reverse_edges(&g, &[0, 1]).unwrap();
241        assert_eq!(rev.edge(0).unwrap(), (1, 0));
242        assert_eq!(rev.edge(1).unwrap(), (2, 1));
243    }
244
245    #[test]
246    fn test_reverse_edges_undirected_noop() {
247        let mut g = Graph::with_vertices(3);
248        g.add_edge(0, 1).unwrap();
249        g.add_edge(1, 2).unwrap();
250
251        let rev = reverse_edges(&g, &[0]).unwrap();
252        assert!(!rev.is_directed());
253        assert_eq!(rev.ecount(), 2);
254    }
255
256    #[test]
257    fn test_reverse_edges_out_of_range() {
258        let mut g = Graph::new(2, true).unwrap();
259        g.add_edge(0, 1).unwrap();
260
261        let err = reverse_edges(&g, &[5]).unwrap_err();
262        assert!(matches!(err, IgraphError::InvalidArgument(_)));
263    }
264
265    #[test]
266    fn test_reverse_edges_duplicates() {
267        let mut g = Graph::new(3, true).unwrap();
268        g.add_edge(0, 1).unwrap(); // 0
269        g.add_edge(1, 2).unwrap(); // 1
270
271        // Duplicate IDs should still result in a single reversal
272        let rev = reverse_edges(&g, &[0, 0, 0]).unwrap();
273        assert_eq!(rev.edge(0).unwrap(), (1, 0));
274        assert_eq!(rev.edge(1).unwrap(), (1, 2));
275    }
276
277    #[test]
278    fn test_reverse_edges_self_loop() {
279        let mut g = Graph::new(2, true).unwrap();
280        g.add_edge(0, 0).unwrap(); // self-loop
281        g.add_edge(0, 1).unwrap();
282
283        let rev = reverse_edges(&g, &[0, 1]).unwrap();
284        assert_eq!(rev.edge(0).unwrap(), (0, 0)); // self-loop unchanged
285        assert_eq!(rev.edge(1).unwrap(), (1, 0)); // reversed
286    }
287}