rust_igraph/algorithms/properties/
adjacency.rs1use crate::core::{Graph, IgraphError, IgraphResult};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AdjacencyType {
15 Upper,
17 Lower,
19 Both,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum LoopHandling {
26 NoLoops,
28 Once,
30 Twice,
32}
33
34pub fn get_adjacency(
58 graph: &Graph,
59 adj_type: AdjacencyType,
60 weights: Option<&[f64]>,
61 loops: LoopHandling,
62) -> IgraphResult<Vec<Vec<f64>>> {
63 let n = graph.vcount() as usize;
64 let ecount = graph.ecount();
65
66 if let Some(w) = weights {
67 if w.len() != ecount {
68 return Err(IgraphError::InvalidArgument(format!(
69 "get_adjacency: weight vector length ({}) does not match edge count ({ecount})",
70 w.len()
71 )));
72 }
73 }
74
75 let mut res = vec![vec![0.0_f64; n]; n];
76 let directed = graph.is_directed();
77
78 if directed {
79 for eid in 0..ecount {
80 #[allow(clippy::cast_possible_truncation)]
81 let (from, to) = graph.edge(eid as u32)?;
82 let f = from as usize;
83 let t = to as usize;
84 let w = weights.map_or(1.0, |ws| ws[eid]);
85
86 if f != t || loops != LoopHandling::NoLoops {
87 res[f][t] += w;
88 }
89 }
90 } else {
91 match adj_type {
92 AdjacencyType::Upper => {
93 for eid in 0..ecount {
94 #[allow(clippy::cast_possible_truncation)]
95 let (from, to) = graph.edge(eid as u32)?;
96 let f = from as usize;
97 let t = to as usize;
98 let w = weights.map_or(1.0, |ws| ws[eid]);
99
100 if t < f {
101 res[t][f] += w;
102 } else {
103 res[f][t] += w;
104 }
105 if f == t && loops == LoopHandling::Twice {
106 res[t][t] += w;
107 }
108 }
109 }
110 AdjacencyType::Lower => {
111 for eid in 0..ecount {
112 #[allow(clippy::cast_possible_truncation)]
113 let (from, to) = graph.edge(eid as u32)?;
114 let f = from as usize;
115 let t = to as usize;
116 let w = weights.map_or(1.0, |ws| ws[eid]);
117
118 if t < f {
119 res[f][t] += w;
120 } else {
121 res[t][f] += w;
122 }
123 if f == t && loops == LoopHandling::Twice {
124 res[t][t] += w;
125 }
126 }
127 }
128 AdjacencyType::Both => {
129 for eid in 0..ecount {
130 #[allow(clippy::cast_possible_truncation)]
131 let (from, to) = graph.edge(eid as u32)?;
132 let f = from as usize;
133 let t = to as usize;
134 let w = weights.map_or(1.0, |ws| ws[eid]);
135
136 res[f][t] += w;
137 if f == t {
138 if loops == LoopHandling::Twice {
139 res[t][f] += w;
140 }
141 } else {
142 res[t][f] += w;
143 }
144 }
145 }
146 }
147 }
148
149 if loops == LoopHandling::NoLoops {
151 for (i, row) in res.iter_mut().enumerate() {
152 row[i] = 0.0;
153 }
154 }
155
156 Ok(res)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn approx_eq(a: f64, b: f64) -> bool {
164 (a - b).abs() < 1e-10
165 }
166
167 #[test]
168 fn empty_graph() {
169 let g = Graph::with_vertices(0);
170 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
171 assert!(adj.is_empty());
172 }
173
174 #[test]
175 fn single_vertex_no_edges() {
176 let g = Graph::with_vertices(1);
177 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
178 assert_eq!(adj.len(), 1);
179 assert!(approx_eq(adj[0][0], 0.0));
180 }
181
182 #[test]
183 fn undirected_single_edge_both() {
184 let mut g = Graph::with_vertices(2);
185 g.add_edge(0, 1).unwrap();
186 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
187 assert!(approx_eq(adj[0][1], 1.0));
188 assert!(approx_eq(adj[1][0], 1.0));
189 assert!(approx_eq(adj[0][0], 0.0));
190 assert!(approx_eq(adj[1][1], 0.0));
191 }
192
193 #[test]
194 fn undirected_single_edge_upper() {
195 let mut g = Graph::with_vertices(2);
196 g.add_edge(0, 1).unwrap();
197 let adj = get_adjacency(&g, AdjacencyType::Upper, None, LoopHandling::Once).unwrap();
198 assert!(approx_eq(adj[0][1], 1.0));
199 assert!(approx_eq(adj[1][0], 0.0));
200 }
201
202 #[test]
203 fn undirected_single_edge_lower() {
204 let mut g = Graph::with_vertices(2);
205 g.add_edge(0, 1).unwrap();
206 let adj = get_adjacency(&g, AdjacencyType::Lower, None, LoopHandling::Once).unwrap();
207 assert!(approx_eq(adj[0][1], 0.0));
208 assert!(approx_eq(adj[1][0], 1.0));
209 }
210
211 #[test]
212 fn undirected_triangle_both() {
213 let mut g = Graph::with_vertices(3);
214 g.add_edge(0, 1).unwrap();
215 g.add_edge(1, 2).unwrap();
216 g.add_edge(0, 2).unwrap();
217 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
218 for (i, row) in adj.iter().enumerate() {
220 for (j, &val) in row.iter().enumerate() {
221 assert!(approx_eq(val, adj[j][i]));
222 }
223 }
224 assert!(approx_eq(adj[0][1], 1.0));
225 assert!(approx_eq(adj[1][2], 1.0));
226 assert!(approx_eq(adj[0][2], 1.0));
227 }
228
229 #[test]
230 fn directed_basic() {
231 let mut g = Graph::new(3, true).unwrap();
232 g.add_edge(0, 1).unwrap();
233 g.add_edge(1, 2).unwrap();
234 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
235 assert!(approx_eq(adj[0][1], 1.0));
236 assert!(approx_eq(adj[1][0], 0.0)); assert!(approx_eq(adj[1][2], 1.0));
238 assert!(approx_eq(adj[2][1], 0.0));
239 }
240
241 #[test]
242 fn self_loop_no_loops() {
243 let mut g = Graph::with_vertices(2);
244 g.add_edge(0, 0).unwrap();
245 g.add_edge(0, 1).unwrap();
246 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::NoLoops).unwrap();
247 assert!(approx_eq(adj[0][0], 0.0));
248 assert!(approx_eq(adj[0][1], 1.0));
249 }
250
251 #[test]
252 fn self_loop_once() {
253 let mut g = Graph::with_vertices(2);
254 g.add_edge(0, 0).unwrap();
255 g.add_edge(0, 1).unwrap();
256 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
257 assert!(approx_eq(adj[0][0], 1.0));
258 }
259
260 #[test]
261 fn self_loop_twice_undirected() {
262 let mut g = Graph::with_vertices(2);
263 g.add_edge(0, 0).unwrap();
264 g.add_edge(0, 1).unwrap();
265 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Twice).unwrap();
266 assert!(approx_eq(adj[0][0], 2.0));
267 }
268
269 #[test]
270 fn self_loop_twice_directed() {
271 let mut g = Graph::new(2, true).unwrap();
272 g.add_edge(0, 0).unwrap();
273 g.add_edge(0, 1).unwrap();
274 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Twice).unwrap();
275 assert!(approx_eq(adj[0][0], 1.0));
277 }
278
279 #[test]
280 fn weighted() {
281 let mut g = Graph::with_vertices(3);
282 g.add_edge(0, 1).unwrap();
283 g.add_edge(1, 2).unwrap();
284 let weights = vec![2.5, 3.0];
285 let adj =
286 get_adjacency(&g, AdjacencyType::Both, Some(&weights), LoopHandling::Once).unwrap();
287 assert!(approx_eq(adj[0][1], 2.5));
288 assert!(approx_eq(adj[1][0], 2.5));
289 assert!(approx_eq(adj[1][2], 3.0));
290 assert!(approx_eq(adj[2][1], 3.0));
291 }
292
293 #[test]
294 fn multi_edge() {
295 let mut g = Graph::with_vertices(2);
296 g.add_edge(0, 1).unwrap();
297 g.add_edge(0, 1).unwrap();
298 let adj = get_adjacency(&g, AdjacencyType::Both, None, LoopHandling::Once).unwrap();
299 assert!(approx_eq(adj[0][1], 2.0));
300 assert!(approx_eq(adj[1][0], 2.0));
301 }
302
303 #[test]
304 fn weight_mismatch() {
305 let mut g = Graph::with_vertices(2);
306 g.add_edge(0, 1).unwrap();
307 let result = get_adjacency(
308 &g,
309 AdjacencyType::Both,
310 Some(&[1.0, 2.0]),
311 LoopHandling::Once,
312 );
313 assert!(result.is_err());
314 }
315
316 #[test]
317 fn upper_reversed_edge_order() {
318 let mut g = Graph::with_vertices(3);
320 g.add_edge(2, 0).unwrap();
321 let adj = get_adjacency(&g, AdjacencyType::Upper, None, LoopHandling::Once).unwrap();
322 assert!(approx_eq(adj[0][2], 1.0));
323 assert!(approx_eq(adj[2][0], 0.0));
324 }
325
326 #[test]
327 fn lower_reversed_edge_order() {
328 let mut g = Graph::with_vertices(3);
329 g.add_edge(0, 2).unwrap();
330 let adj = get_adjacency(&g, AdjacencyType::Lower, None, LoopHandling::Once).unwrap();
331 assert!(approx_eq(adj[2][0], 1.0));
332 assert!(approx_eq(adj[0][2], 0.0));
333 }
334}