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}