Skip to main content

rust_igraph/core/
ops.rs

1//! `std::ops` trait implementations for [`Graph`].
2//!
3//! These provide idiomatic Rust operator syntax for common graph
4//! operations:
5//!
6//! | Operator | Operation | Function |
7//! |----------|-----------|----------|
8//! | `g1 + g2` | Disjoint union | [`disjoint_union`] |
9//! | `g1 \| g2` | Union (max-multiplicity) | [`union`] |
10//! | `g1 & g2` | Intersection (min-multiplicity) | [`intersection`] |
11//! | `g1 - g2` | Difference | [`difference`] |
12//! | `!g` | Complement | [`complementer`] |
13//!
14//! All operators panic on error (e.g. directedness mismatch). For
15//! fallible versions, use the underlying functions directly.
16//!
17//! [`disjoint_union`]: crate::algorithms::operators::disjoint_union::disjoint_union
18//! [`union`]: crate::algorithms::operators::union::union
19//! [`intersection`]: crate::algorithms::operators::intersection::intersection
20//! [`difference`]: crate::algorithms::operators::difference::difference
21//! [`complementer`]: crate::algorithms::operators::complementer::complementer
22
23use std::ops::{Add, BitAnd, BitOr, Not, Sub};
24
25use super::graph::Graph;
26use crate::algorithms::operators::complementer::complementer;
27use crate::algorithms::operators::difference::difference;
28use crate::algorithms::operators::disjoint_union::disjoint_union;
29use crate::algorithms::operators::intersection::intersection;
30use crate::algorithms::operators::union::union;
31
32/// Disjoint union: `g1 + g2` concatenates vertex sets and edge sets.
33///
34/// # Panics
35///
36/// Panics if the graphs have mismatched directedness.
37///
38/// # Examples
39///
40/// ```
41/// use rust_igraph::Graph;
42///
43/// let a = Graph::try_from(vec![(0u32, 1)].as_slice()).unwrap();
44/// let b = Graph::try_from(vec![(0u32, 1), (1, 2)].as_slice()).unwrap();
45/// let c = a + b;
46/// assert_eq!(c.vcount(), 5);
47/// assert_eq!(c.ecount(), 3);
48/// ```
49impl Add for Graph {
50    type Output = Graph;
51
52    fn add(self, rhs: Graph) -> Graph {
53        disjoint_union(&self, &rhs).expect("disjoint_union failed (directedness mismatch?)")
54    }
55}
56
57/// Disjoint union by reference.
58impl Add for &Graph {
59    type Output = Graph;
60
61    fn add(self, rhs: &Graph) -> Graph {
62        disjoint_union(self, rhs).expect("disjoint_union failed (directedness mismatch?)")
63    }
64}
65
66/// Union (max-multiplicity merge): `g1 | g2`.
67///
68/// # Panics
69///
70/// Panics if the graphs have mismatched directedness.
71///
72/// # Examples
73///
74/// ```
75/// use rust_igraph::Graph;
76///
77/// let mut a = Graph::new(3, false).unwrap();
78/// a.add_edge(0, 1).unwrap();
79/// let mut b = Graph::new(3, false).unwrap();
80/// b.add_edge(1, 2).unwrap();
81/// let c = a | b;
82/// assert_eq!(c.ecount(), 2);
83/// ```
84impl BitOr for Graph {
85    type Output = Graph;
86
87    fn bitor(self, rhs: Graph) -> Graph {
88        union(&self, &rhs).expect("union failed (directedness mismatch?)")
89    }
90}
91
92/// Union by reference.
93impl BitOr for &Graph {
94    type Output = Graph;
95
96    fn bitor(self, rhs: &Graph) -> Graph {
97        union(self, rhs).expect("union failed (directedness mismatch?)")
98    }
99}
100
101/// Intersection (min-multiplicity): `g1 & g2`.
102///
103/// # Panics
104///
105/// Panics if the graphs have mismatched directedness.
106///
107/// # Examples
108///
109/// ```
110/// use rust_igraph::Graph;
111///
112/// let mut a = Graph::new(3, false).unwrap();
113/// a.add_edge(0, 1).unwrap();
114/// a.add_edge(1, 2).unwrap();
115/// let mut b = Graph::new(3, false).unwrap();
116/// b.add_edge(1, 2).unwrap();
117/// b.add_edge(2, 0).unwrap();
118/// let c = a & b;
119/// assert_eq!(c.ecount(), 1);
120/// assert!(c.has_edge(1, 2));
121/// ```
122impl BitAnd for Graph {
123    type Output = Graph;
124
125    fn bitand(self, rhs: Graph) -> Graph {
126        intersection(&self, &rhs).expect("intersection failed (directedness mismatch?)")
127    }
128}
129
130/// Intersection by reference.
131impl BitAnd for &Graph {
132    type Output = Graph;
133
134    fn bitand(self, rhs: &Graph) -> Graph {
135        intersection(self, rhs).expect("intersection failed (directedness mismatch?)")
136    }
137}
138
139/// Difference: `g1 - g2` (edges in `g1` not in `g2`).
140///
141/// # Panics
142///
143/// Panics if the graphs have mismatched directedness.
144///
145/// # Examples
146///
147/// ```
148/// use rust_igraph::Graph;
149///
150/// let mut a = Graph::new(3, false).unwrap();
151/// a.add_edge(0, 1).unwrap();
152/// a.add_edge(1, 2).unwrap();
153/// let mut b = Graph::new(3, false).unwrap();
154/// b.add_edge(1, 2).unwrap();
155/// let c = a - b;
156/// assert_eq!(c.ecount(), 1);
157/// assert!(c.has_edge(0, 1));
158/// ```
159impl Sub for Graph {
160    type Output = Graph;
161
162    fn sub(self, rhs: Graph) -> Graph {
163        difference(&self, &rhs).expect("difference failed (directedness mismatch?)")
164    }
165}
166
167/// Difference by reference.
168impl Sub for &Graph {
169    type Output = Graph;
170
171    fn sub(self, rhs: &Graph) -> Graph {
172        difference(self, rhs).expect("difference failed (directedness mismatch?)")
173    }
174}
175
176/// Complement: `!g` (all edges not in `g`, no self-loops).
177///
178/// # Examples
179///
180/// ```
181/// use rust_igraph::Graph;
182///
183/// // Complement of K_3 is an empty graph on 3 vertices.
184/// let mut k3 = Graph::new(3, false).unwrap();
185/// k3.add_edge(0, 1).unwrap();
186/// k3.add_edge(1, 2).unwrap();
187/// k3.add_edge(0, 2).unwrap();
188/// let c = !k3;
189/// assert_eq!(c.vcount(), 3);
190/// assert_eq!(c.ecount(), 0);
191/// ```
192impl Not for Graph {
193    type Output = Graph;
194
195    fn not(self) -> Graph {
196        complementer(&self, false).expect("complementer failed")
197    }
198}
199
200/// Complement by reference.
201impl Not for &Graph {
202    type Output = Graph;
203
204    fn not(self) -> Graph {
205        complementer(self, false).expect("complementer failed")
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    fn triangle() -> Graph {
214        let mut g = Graph::new(3, false).expect("graph");
215        g.add_edge(0, 1).expect("e");
216        g.add_edge(1, 2).expect("e");
217        g.add_edge(2, 0).expect("e");
218        g
219    }
220
221    fn path3() -> Graph {
222        let mut g = Graph::new(3, false).expect("graph");
223        g.add_edge(0, 1).expect("e");
224        g.add_edge(1, 2).expect("e");
225        g
226    }
227
228    #[test]
229    fn add_is_disjoint_union() {
230        let a = triangle();
231        let b = path3();
232        let c = &a + &b;
233        assert_eq!(c.vcount(), 6);
234        assert_eq!(c.ecount(), 5);
235    }
236
237    #[test]
238    fn bitor_is_union() {
239        let a = triangle();
240        let mut b = Graph::new(3, false).expect("graph");
241        b.add_edge(0, 1).expect("e");
242        let c = &a | &b;
243        assert_eq!(c.vcount(), 3);
244        assert_eq!(c.ecount(), 3);
245    }
246
247    #[test]
248    fn bitand_is_intersection() {
249        let a = triangle();
250        let b = path3();
251        let c = &a & &b;
252        assert_eq!(c.vcount(), 3);
253        assert_eq!(c.ecount(), 2);
254    }
255
256    #[test]
257    fn sub_is_difference() {
258        let a = triangle();
259        let b = path3();
260        let c = &a - &b;
261        assert_eq!(c.vcount(), 3);
262        assert_eq!(c.ecount(), 1);
263    }
264
265    #[test]
266    fn not_is_complement() {
267        let k3 = triangle();
268        let c = !&k3;
269        assert_eq!(c.vcount(), 3);
270        assert_eq!(c.ecount(), 0);
271
272        let empty = Graph::new(4, false).expect("graph");
273        let full = !∅
274        assert_eq!(full.ecount(), 6); // K_4 has 6 edges
275    }
276
277    #[test]
278    fn owned_operators_work() {
279        let a = triangle();
280        let b = path3();
281        let c = a + b;
282        assert_eq!(c.vcount(), 6);
283    }
284}