rust_igraph/algorithms/properties/
hosoya_index.rs1#![allow(
13 clippy::cast_possible_truncation,
14 clippy::cast_precision_loss,
15 clippy::many_single_char_names,
16 clippy::needless_range_loop,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn hosoya_index(graph: &Graph) -> IgraphResult<u64> {
40 let seq = matching_count_sequence(graph)?;
41 let total: u64 = seq.iter().sum();
42 Ok(total)
43}
44
45pub fn matching_count_sequence(graph: &Graph) -> IgraphResult<Vec<u64>> {
60 let n = graph.vcount() as usize;
61 let edges: Vec<(usize, usize)> = graph
62 .edges()
63 .map(|(u, v)| (u as usize, v as usize))
64 .collect();
65
66 let max_k = n / 2;
67 let mut counts = vec![0_u64; max_k.saturating_add(1)];
68 counts[0] = 1;
69
70 if edges.is_empty() {
71 return Ok(counts);
72 }
73
74 let unique_edges = deduplicate_edges(&edges, graph.is_directed());
75 let m = unique_edges.len();
76
77 let mut used = vec![false; n];
78 enumerate_matchings(&unique_edges, m, 0, 0, &mut used, &mut counts);
79
80 Ok(counts)
81}
82
83fn deduplicate_edges(edges: &[(usize, usize)], directed: bool) -> Vec<(usize, usize)> {
84 let mut seen = std::collections::HashSet::new();
85 let mut result = Vec::new();
86 for &(u, v) in edges {
87 if u == v {
88 continue;
89 }
90 let key = if directed {
91 (u, v)
92 } else {
93 (u.min(v), u.max(v))
94 };
95 if seen.insert(key) {
96 result.push(key);
97 }
98 }
99 result
100}
101
102fn enumerate_matchings(
103 edges: &[(usize, usize)],
104 m: usize,
105 start: usize,
106 k: usize,
107 used: &mut [bool],
108 counts: &mut [u64],
109) {
110 for i in start..m {
111 let (u, v) = edges[i];
112 if used[u] || used[v] {
113 continue;
114 }
115 used[u] = true;
116 used[v] = true;
117 let new_k = k.saturating_add(1);
118 if new_k < counts.len() {
119 counts[new_k] = counts[new_k].saturating_add(1);
120 enumerate_matchings(edges, m, i.saturating_add(1), new_k, used, counts);
121 }
122 used[u] = false;
123 used[v] = false;
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn empty() -> Graph {
132 Graph::with_vertices(0)
133 }
134
135 fn single() -> Graph {
136 Graph::with_vertices(1)
137 }
138
139 fn no_edges() -> Graph {
140 Graph::with_vertices(4)
141 }
142
143 fn single_edge() -> Graph {
144 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
145 }
146
147 fn path3() -> Graph {
148 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
149 }
150
151 fn path4() -> Graph {
152 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
153 }
154
155 fn path5() -> Graph {
156 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
157 }
158
159 fn k3() -> Graph {
160 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
161 }
162
163 fn k4() -> Graph {
164 Graph::from_edges(
165 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
166 false,
167 Some(4),
168 )
169 .unwrap()
170 }
171
172 fn cycle4() -> Graph {
173 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
174 }
175
176 fn cycle5() -> Graph {
177 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
178 }
179
180 fn star4() -> Graph {
181 Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, Some(4)).unwrap()
182 }
183
184 #[test]
187 fn hi_empty() {
188 assert_eq!(hosoya_index(&empty()).unwrap(), 1);
189 }
190
191 #[test]
192 fn hi_single() {
193 assert_eq!(hosoya_index(&single()).unwrap(), 1);
194 }
195
196 #[test]
197 fn hi_no_edges() {
198 assert_eq!(hosoya_index(&no_edges()).unwrap(), 1);
199 }
200
201 #[test]
202 fn hi_single_edge() {
203 assert_eq!(hosoya_index(&single_edge()).unwrap(), 2);
205 }
206
207 #[test]
208 fn hi_path3() {
209 assert_eq!(hosoya_index(&path3()).unwrap(), 3);
211 }
212
213 #[test]
214 fn hi_path4() {
215 assert_eq!(hosoya_index(&path4()).unwrap(), 5);
218 }
219
220 #[test]
221 fn hi_path5() {
222 assert_eq!(hosoya_index(&path5()).unwrap(), 8);
224 }
225
226 #[test]
227 fn hi_k3() {
228 assert_eq!(hosoya_index(&k3()).unwrap(), 4);
230 }
231
232 #[test]
233 fn hi_k4() {
234 assert_eq!(hosoya_index(&k4()).unwrap(), 10);
237 }
238
239 #[test]
240 fn hi_cycle4() {
241 assert_eq!(hosoya_index(&cycle4()).unwrap(), 7);
243 }
244
245 #[test]
246 fn hi_cycle5() {
247 assert_eq!(hosoya_index(&cycle5()).unwrap(), 11);
249 }
250
251 #[test]
252 fn hi_star4() {
253 assert_eq!(hosoya_index(&star4()).unwrap(), 4);
256 }
257
258 #[test]
261 fn mcs_empty() {
262 assert_eq!(matching_count_sequence(&empty()).unwrap(), vec![1]);
263 }
264
265 #[test]
266 fn mcs_single_edge() {
267 assert_eq!(matching_count_sequence(&single_edge()).unwrap(), vec![1, 1]);
268 }
269
270 #[test]
271 fn mcs_path3() {
272 assert_eq!(matching_count_sequence(&path3()).unwrap(), vec![1, 2]);
273 }
274
275 #[test]
276 fn mcs_path4() {
277 assert_eq!(matching_count_sequence(&path4()).unwrap(), vec![1, 3, 1]);
279 }
280
281 #[test]
282 fn mcs_k4() {
283 assert_eq!(matching_count_sequence(&k4()).unwrap(), vec![1, 6, 3]);
285 }
286
287 #[test]
288 fn mcs_cycle4() {
289 assert_eq!(matching_count_sequence(&cycle4()).unwrap(), vec![1, 4, 2]);
291 }
292
293 #[test]
294 fn mcs_cycle5() {
295 assert_eq!(matching_count_sequence(&cycle5()).unwrap(), vec![1, 5, 5]);
297 }
298
299 #[test]
302 fn hosoya_equals_sum_of_sequence() {
303 for g in &[path3(), path4(), path5(), k3(), k4(), cycle4(), cycle5()] {
304 let z = hosoya_index(g).unwrap();
305 let seq = matching_count_sequence(g).unwrap();
306 let sum: u64 = seq.iter().sum();
307 assert_eq!(z, sum);
308 }
309 }
310
311 #[test]
312 fn m0_always_one() {
313 for g in &[empty(), single(), no_edges(), path4(), k4()] {
314 let seq = matching_count_sequence(g).unwrap();
315 assert_eq!(seq[0], 1);
316 }
317 }
318
319 #[test]
320 fn m1_equals_edge_count() {
321 for g in &[single_edge(), path3(), path4(), k3(), k4(), cycle4()] {
322 let seq = matching_count_sequence(g).unwrap();
323 if seq.len() > 1 {
324 assert_eq!(seq[1], g.ecount() as u64);
325 }
326 }
327 }
328
329 #[test]
330 fn path_hosoya_fibonacci() {
331 let fib = [1, 1, 2, 3, 5, 8, 13];
333 for n in 1_u32..=6 {
334 let edges: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
335 let g = if n == 1 {
336 Graph::with_vertices(1)
337 } else {
338 Graph::from_edges(&edges, false, Some(n)).unwrap()
339 };
340 assert_eq!(
341 hosoya_index(&g).unwrap(),
342 fib[n as usize],
343 "Z(P_{n}) should be F({})",
344 n + 1
345 );
346 }
347 }
348}