Skip to main content

rust_igraph/algorithms/properties/
merrifield_simmons.rs

1//! Merrifield–Simmons index (ALGO-TR-037).
2//!
3//! The **Merrifield–Simmons index** `σ(G)` is the total number of
4//! independent sets (including the empty set). An independent set is a
5//! subset of vertices with no two adjacent.
6//!
7//! Also provides `independent_set_count_sequence` returning the vector
8//! `[i(G,0), i(G,1), ..., i(G,α)]` where `i(G,k)` counts the number
9//! of independent sets of size *k* and `α` is the independence number.
10
11#![allow(
12    clippy::cast_possible_truncation,
13    clippy::cast_precision_loss,
14    clippy::many_single_char_names,
15    clippy::needless_range_loop,
16    clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21/// Compute the Merrifield–Simmons index of a graph.
22///
23/// Returns the total number of independent sets (including the empty
24/// set). For a graph with no edges, `σ(G) = 2^n`.
25///
26/// Only feasible for small graphs — the count can be exponential.
27///
28/// # Examples
29///
30/// ```
31/// use rust_igraph::{Graph, merrifield_simmons_index};
32///
33/// // Path 0-1-2: independent sets: {}, {0}, {1}, {2}, {0,2} → σ = 5
34/// let g = Graph::from_edges(&[(0,1),(1,2)], false, Some(3)).unwrap();
35/// assert_eq!(merrifield_simmons_index(&g).unwrap(), 5);
36/// ```
37pub fn merrifield_simmons_index(graph: &Graph) -> IgraphResult<u64> {
38    let seq = independent_set_count_sequence(graph)?;
39    let total: u64 = seq.iter().sum();
40    Ok(total)
41}
42
43/// Compute the independence polynomial coefficient sequence.
44///
45/// Returns `[i(G,0), i(G,1), ..., i(G,n)]` where `i(G,k)` is the
46/// number of independent sets of size `k`. Trailing zeros are trimmed.
47/// `i(G,0) = 1` always (the empty set).
48///
49/// # Examples
50///
51/// ```
52/// use rust_igraph::{Graph, independent_set_count_sequence};
53///
54/// // K_3 (triangle): {}, {0}, {1}, {2} → [1, 3]
55/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
56/// assert_eq!(independent_set_count_sequence(&g).unwrap(), vec![1, 3]);
57/// ```
58pub fn independent_set_count_sequence(graph: &Graph) -> IgraphResult<Vec<u64>> {
59    let n = graph.vcount() as usize;
60
61    let mut counts = vec![0_u64; n.saturating_add(1)];
62    counts[0] = 1;
63
64    if n == 0 {
65        return Ok(counts);
66    }
67
68    let adj = build_adj_matrix(graph, n);
69
70    let mut selected = vec![false; n];
71    enumerate_independent_sets(&adj, n, 0, 0, &mut selected, &mut counts);
72
73    while counts.len() > 1 && counts[counts.len() - 1] == 0 {
74        counts.pop();
75    }
76
77    Ok(counts)
78}
79
80fn build_adj_matrix(graph: &Graph, n: usize) -> Vec<bool> {
81    let mut adj = vec![false; n * n];
82    for (u, v) in graph.edges() {
83        let ui = u as usize;
84        let vi = v as usize;
85        if ui != vi {
86            adj[ui * n + vi] = true;
87            if !graph.is_directed() {
88                adj[vi * n + ui] = true;
89            }
90        }
91    }
92    adj
93}
94
95fn enumerate_independent_sets(
96    adj: &[bool],
97    n: usize,
98    start: usize,
99    k: usize,
100    selected: &mut [bool],
101    counts: &mut [u64],
102) {
103    for v in start..n {
104        let mut conflict = false;
105        for u in 0..v {
106            if selected[u] && adj[u * n + v] {
107                conflict = true;
108                break;
109            }
110        }
111        if conflict {
112            continue;
113        }
114
115        selected[v] = true;
116        let new_k = k.saturating_add(1);
117        if new_k < counts.len() {
118            counts[new_k] = counts[new_k].saturating_add(1);
119            enumerate_independent_sets(adj, n, v.saturating_add(1), new_k, selected, counts);
120        }
121        selected[v] = false;
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    fn empty() -> Graph {
130        Graph::with_vertices(0)
131    }
132
133    fn single() -> Graph {
134        Graph::with_vertices(1)
135    }
136
137    fn no_edges3() -> Graph {
138        Graph::with_vertices(3)
139    }
140
141    fn single_edge() -> Graph {
142        Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
143    }
144
145    fn path3() -> Graph {
146        Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
147    }
148
149    fn path4() -> Graph {
150        Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
151    }
152
153    fn k3() -> Graph {
154        Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
155    }
156
157    fn k4() -> Graph {
158        Graph::from_edges(
159            &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
160            false,
161            Some(4),
162        )
163        .unwrap()
164    }
165
166    fn cycle4() -> Graph {
167        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
168    }
169
170    fn cycle5() -> Graph {
171        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
172    }
173
174    fn star4() -> Graph {
175        Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, Some(4)).unwrap()
176    }
177
178    // --- merrifield_simmons_index ---
179
180    #[test]
181    fn msi_empty() {
182        // σ(empty) = 1 (just the empty set)
183        assert_eq!(merrifield_simmons_index(&empty()).unwrap(), 1);
184    }
185
186    #[test]
187    fn msi_single() {
188        // {}, {0} → 2
189        assert_eq!(merrifield_simmons_index(&single()).unwrap(), 2);
190    }
191
192    #[test]
193    fn msi_no_edges() {
194        // σ(E_n) = 2^n = 8 for n=3
195        assert_eq!(merrifield_simmons_index(&no_edges3()).unwrap(), 8);
196    }
197
198    #[test]
199    fn msi_single_edge() {
200        // {}, {0}, {1} → 3
201        assert_eq!(merrifield_simmons_index(&single_edge()).unwrap(), 3);
202    }
203
204    #[test]
205    fn msi_path3() {
206        // {}, {0}, {1}, {2}, {0,2} → 5
207        assert_eq!(merrifield_simmons_index(&path3()).unwrap(), 5);
208    }
209
210    #[test]
211    fn msi_path4() {
212        // P_4: σ follows Fibonacci-like: σ(P_n) = F(n+2)
213        // σ(P_4) = 8 ({},{0},{1},{2},{3},{0,2},{0,3},{1,3})
214        assert_eq!(merrifield_simmons_index(&path4()).unwrap(), 8);
215    }
216
217    #[test]
218    fn msi_k3() {
219        // K_3: {}, {0}, {1}, {2} → 4
220        assert_eq!(merrifield_simmons_index(&k3()).unwrap(), 4);
221    }
222
223    #[test]
224    fn msi_k4() {
225        // K_4: {}, {0}, {1}, {2}, {3} → 5
226        assert_eq!(merrifield_simmons_index(&k4()).unwrap(), 5);
227    }
228
229    #[test]
230    fn msi_cycle4() {
231        // C_4: {},{0},{1},{2},{3},{0,2},{1,3} → 7
232        assert_eq!(merrifield_simmons_index(&cycle4()).unwrap(), 7);
233    }
234
235    #[test]
236    fn msi_cycle5() {
237        // C_5: 1 + 5 + 5 = 11
238        // i(0)=1, i(1)=5, i(2)=5
239        assert_eq!(merrifield_simmons_index(&cycle5()).unwrap(), 11);
240    }
241
242    #[test]
243    fn msi_star4() {
244        // Star K_{1,3}: {},{0},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3} → 9
245        assert_eq!(merrifield_simmons_index(&star4()).unwrap(), 9);
246    }
247
248    // --- independent_set_count_sequence ---
249
250    #[test]
251    fn iscs_empty() {
252        assert_eq!(independent_set_count_sequence(&empty()).unwrap(), vec![1]);
253    }
254
255    #[test]
256    fn iscs_single() {
257        assert_eq!(
258            independent_set_count_sequence(&single()).unwrap(),
259            vec![1, 1]
260        );
261    }
262
263    #[test]
264    fn iscs_no_edges() {
265        // E_3: i(0)=1, i(1)=3, i(2)=3, i(3)=1
266        assert_eq!(
267            independent_set_count_sequence(&no_edges3()).unwrap(),
268            vec![1, 3, 3, 1]
269        );
270    }
271
272    #[test]
273    fn iscs_single_edge() {
274        assert_eq!(
275            independent_set_count_sequence(&single_edge()).unwrap(),
276            vec![1, 2]
277        );
278    }
279
280    #[test]
281    fn iscs_path3() {
282        // i(0)=1, i(1)=3, i(2)=1
283        assert_eq!(
284            independent_set_count_sequence(&path3()).unwrap(),
285            vec![1, 3, 1]
286        );
287    }
288
289    #[test]
290    fn iscs_k3() {
291        assert_eq!(independent_set_count_sequence(&k3()).unwrap(), vec![1, 3]);
292    }
293
294    #[test]
295    fn iscs_k4() {
296        assert_eq!(independent_set_count_sequence(&k4()).unwrap(), vec![1, 4]);
297    }
298
299    #[test]
300    fn iscs_cycle4() {
301        // i(0)=1, i(1)=4, i(2)=2
302        assert_eq!(
303            independent_set_count_sequence(&cycle4()).unwrap(),
304            vec![1, 4, 2]
305        );
306    }
307
308    #[test]
309    fn iscs_cycle5() {
310        // i(0)=1, i(1)=5, i(2)=5
311        assert_eq!(
312            independent_set_count_sequence(&cycle5()).unwrap(),
313            vec![1, 5, 5]
314        );
315    }
316
317    // --- cross-consistency ---
318
319    #[test]
320    fn msi_equals_sum_of_sequence() {
321        for g in &[path3(), path4(), k3(), k4(), cycle4(), cycle5(), star4()] {
322            let sigma = merrifield_simmons_index(g).unwrap();
323            let seq = independent_set_count_sequence(g).unwrap();
324            let sum: u64 = seq.iter().sum();
325            assert_eq!(sigma, sum);
326        }
327    }
328
329    #[test]
330    fn i0_always_one() {
331        for g in &[empty(), single(), no_edges3(), path4(), k4()] {
332            let seq = independent_set_count_sequence(g).unwrap();
333            assert_eq!(seq[0], 1);
334        }
335    }
336
337    #[test]
338    fn i1_equals_vertex_count() {
339        for g in &[single(), single_edge(), path3(), k3(), k4(), cycle4()] {
340            let seq = independent_set_count_sequence(g).unwrap();
341            if seq.len() > 1 {
342                assert_eq!(seq[1], u64::from(g.vcount()));
343            }
344        }
345    }
346
347    #[test]
348    fn no_edges_is_power_of_two() {
349        for n in 0_u32..=5 {
350            let g = Graph::with_vertices(n);
351            let sigma = merrifield_simmons_index(&g).unwrap();
352            assert_eq!(sigma, 1_u64 << n);
353        }
354    }
355
356    #[test]
357    fn complete_graph_sigma() {
358        // σ(K_n) = n + 1
359        for n in 1_u32..=5 {
360            let edges: Vec<(u32, u32)> = (0..n)
361                .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
362                .collect();
363            let g = if edges.is_empty() {
364                Graph::with_vertices(n)
365            } else {
366                Graph::from_edges(&edges, false, Some(n)).unwrap()
367            };
368            assert_eq!(
369                merrifield_simmons_index(&g).unwrap(),
370                u64::from(n) + 1,
371                "σ(K_{n}) should be {}",
372                n + 1
373            );
374        }
375    }
376
377    #[test]
378    fn path_sigma_fibonacci() {
379        // σ(P_n) = F(n+2) where F(1)=1,F(2)=1,F(3)=2,...
380        let fib = [1, 1, 2, 3, 5, 8, 13, 21];
381        for n in 1_u32..=6 {
382            let edges: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
383            let g = if n == 1 {
384                Graph::with_vertices(1)
385            } else {
386                Graph::from_edges(&edges, false, Some(n)).unwrap()
387            };
388            assert_eq!(
389                merrifield_simmons_index(&g).unwrap(),
390                fib[(n + 1) as usize],
391                "σ(P_{n}) should be F({})",
392                n + 2
393            );
394        }
395    }
396}