Skip to main content

rust_igraph/algorithms/properties/
is_strongly_chordal.rs

1//! Strongly chordal graph predicate (ALGO-PR-113).
2//!
3//! A graph is strongly chordal if it is chordal and every even cycle
4//! of length ≥ 6 has an odd chord. Equivalently, a chordal graph
5//! that admits a strong elimination ordering (SEO): a perfect
6//! elimination ordering where for each vertex v, the later neighbors
7//! of v have their neighborhoods (restricted to later vertices)
8//! totally ordered by inclusion.
9//!
10//! Directed graphs are treated as undirected.
11
12use crate::algorithms::chordality::{is_chordal, maximum_cardinality_search};
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is strongly chordal.
16///
17/// A strongly chordal graph is chordal with every even cycle having
18/// an odd chord. Uses maximum cardinality search to find a PEO, then
19/// verifies the strong elimination property.
20///
21/// Directed graphs are treated as undirected.
22///
23/// # Examples
24///
25/// ```
26/// use rust_igraph::{Graph, is_strongly_chordal};
27///
28/// // Complete graph is strongly chordal
29/// let mut g = Graph::with_vertices(4);
30/// for i in 0..4u32 {
31///     for j in (i+1)..4 {
32///         g.add_edge(i, j).unwrap();
33///     }
34/// }
35/// assert!(is_strongly_chordal(&g).unwrap());
36///
37/// // Sun graph `S_3` is chordal but NOT strongly chordal
38/// let mut g = Graph::with_vertices(6);
39/// // Inner triangle: 0-1-2
40/// g.add_edge(0, 1).unwrap();
41/// g.add_edge(1, 2).unwrap();
42/// g.add_edge(2, 0).unwrap();
43/// // Outer vertices 3,4,5 each adjacent to one edge of triangle
44/// g.add_edge(3, 0).unwrap();
45/// g.add_edge(3, 1).unwrap();
46/// g.add_edge(4, 1).unwrap();
47/// g.add_edge(4, 2).unwrap();
48/// g.add_edge(5, 2).unwrap();
49/// g.add_edge(5, 0).unwrap();
50/// assert!(!is_strongly_chordal(&g).unwrap());
51/// ```
52pub fn is_strongly_chordal(graph: &Graph) -> IgraphResult<bool> {
53    let n = graph.vcount();
54    if n <= 3 {
55        let chordal = is_chordal(graph, None)?;
56        return Ok(chordal.chordal);
57    }
58
59    let chordal = is_chordal(graph, None)?;
60    if !chordal.chordal {
61        return Ok(false);
62    }
63
64    let n_usize = n as usize;
65    let mut adj = vec![vec![false; n_usize]; n_usize];
66    for v in 0..n {
67        let nbrs = graph.neighbors(v)?;
68        for &w in &nbrs {
69            adj[v as usize][w as usize] = true;
70            adj[w as usize][v as usize] = true;
71        }
72    }
73
74    // Get PEO from maximum cardinality search
75    let mcs = maximum_cardinality_search(graph)?;
76    // alpham1[i] is the vertex with rank i; visiting in reverse gives PEO
77    let order = &mcs.alpham1;
78
79    // Build position map: position[v] = index in elimination order
80    let mut position = vec![0usize; n_usize];
81    for (i, &v) in order.iter().enumerate() {
82        position[v as usize] = i;
83    }
84
85    // Check strong elimination property: for each vertex v at position i,
86    // the later neighbors of v (those with position > i) must have their
87    // neighborhoods (restricted to vertices with position > i) totally
88    // ordered by inclusion.
89    for (i, &vi) in order.iter().enumerate() {
90        let v = vi as usize;
91        let later_nbrs: Vec<usize> = (0..n_usize)
92            .filter(|&u| adj[v][u] && position[u] > i)
93            .collect();
94
95        if later_nbrs.len() <= 1 {
96            continue;
97        }
98
99        // For each pair of later neighbors, check inclusion of their
100        // later neighborhoods
101        for (a_idx, &a) in later_nbrs.iter().enumerate() {
102            for &b in &later_nbrs[(a_idx + 1)..] {
103                let nbrs_a: Vec<usize> = (0..n_usize)
104                    .filter(|&x| adj[a][x] && position[x] > i)
105                    .collect();
106                let nbrs_b: Vec<usize> = (0..n_usize)
107                    .filter(|&x| adj[b][x] && position[x] > i)
108                    .collect();
109
110                // Check if nbrs_a ⊆ nbrs_b or nbrs_b ⊆ nbrs_a
111                let a_sub_b = nbrs_a.iter().all(|x| adj[b][*x] || *x == b);
112                let b_sub_a = nbrs_b.iter().all(|x| adj[a][*x] || *x == a);
113
114                if !a_sub_b && !b_sub_a {
115                    return Ok(false);
116                }
117            }
118        }
119    }
120
121    Ok(true)
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn empty_graph() {
130        let g = Graph::with_vertices(0);
131        assert!(is_strongly_chordal(&g).unwrap());
132    }
133
134    #[test]
135    fn single_vertex() {
136        let g = Graph::with_vertices(1);
137        assert!(is_strongly_chordal(&g).unwrap());
138    }
139
140    #[test]
141    fn single_edge() {
142        let mut g = Graph::with_vertices(2);
143        g.add_edge(0, 1).unwrap();
144        assert!(is_strongly_chordal(&g).unwrap());
145    }
146
147    #[test]
148    fn triangle() {
149        let mut g = Graph::with_vertices(3);
150        g.add_edge(0, 1).unwrap();
151        g.add_edge(1, 2).unwrap();
152        g.add_edge(2, 0).unwrap();
153        assert!(is_strongly_chordal(&g).unwrap());
154    }
155
156    #[test]
157    fn complete_k4() {
158        let mut g = Graph::with_vertices(4);
159        for i in 0..4u32 {
160            for j in (i + 1)..4 {
161                g.add_edge(i, j).unwrap();
162            }
163        }
164        assert!(is_strongly_chordal(&g).unwrap());
165    }
166
167    #[test]
168    fn complete_k5() {
169        let mut g = Graph::with_vertices(5);
170        for i in 0..5u32 {
171            for j in (i + 1)..5 {
172                g.add_edge(i, j).unwrap();
173            }
174        }
175        assert!(is_strongly_chordal(&g).unwrap());
176    }
177
178    #[test]
179    fn tree() {
180        // Trees are strongly chordal (chordal + no cycles at all)
181        let mut g = Graph::with_vertices(5);
182        g.add_edge(0, 1).unwrap();
183        g.add_edge(1, 2).unwrap();
184        g.add_edge(1, 3).unwrap();
185        g.add_edge(3, 4).unwrap();
186        assert!(is_strongly_chordal(&g).unwrap());
187    }
188
189    #[test]
190    fn sun_3_not_strongly_chordal() {
191        // Sun graph S_3: inner triangle 0-1-2, outer vertices 3,4,5
192        // 3 adj to 0,1; 4 adj to 1,2; 5 adj to 2,0
193        // This is chordal but NOT strongly chordal
194        let mut g = Graph::with_vertices(6);
195        g.add_edge(0, 1).unwrap();
196        g.add_edge(1, 2).unwrap();
197        g.add_edge(2, 0).unwrap();
198        g.add_edge(3, 0).unwrap();
199        g.add_edge(3, 1).unwrap();
200        g.add_edge(4, 1).unwrap();
201        g.add_edge(4, 2).unwrap();
202        g.add_edge(5, 2).unwrap();
203        g.add_edge(5, 0).unwrap();
204        assert!(!is_strongly_chordal(&g).unwrap());
205    }
206
207    #[test]
208    fn c4_not_chordal() {
209        // C_4 is not chordal → not strongly chordal
210        let mut g = Graph::with_vertices(4);
211        g.add_edge(0, 1).unwrap();
212        g.add_edge(1, 2).unwrap();
213        g.add_edge(2, 3).unwrap();
214        g.add_edge(3, 0).unwrap();
215        assert!(!is_strongly_chordal(&g).unwrap());
216    }
217
218    #[test]
219    fn star() {
220        // Star is a tree → strongly chordal
221        let mut g = Graph::with_vertices(5);
222        g.add_edge(0, 1).unwrap();
223        g.add_edge(0, 2).unwrap();
224        g.add_edge(0, 3).unwrap();
225        g.add_edge(0, 4).unwrap();
226        assert!(is_strongly_chordal(&g).unwrap());
227    }
228
229    #[test]
230    fn path() {
231        let mut g = Graph::with_vertices(5);
232        g.add_edge(0, 1).unwrap();
233        g.add_edge(1, 2).unwrap();
234        g.add_edge(2, 3).unwrap();
235        g.add_edge(3, 4).unwrap();
236        assert!(is_strongly_chordal(&g).unwrap());
237    }
238
239    #[test]
240    fn diamond_strongly_chordal() {
241        // Diamond = K_4 minus one edge. It's chordal and strongly chordal.
242        let mut g = Graph::with_vertices(4);
243        g.add_edge(0, 1).unwrap();
244        g.add_edge(0, 2).unwrap();
245        g.add_edge(0, 3).unwrap();
246        g.add_edge(1, 2).unwrap();
247        g.add_edge(1, 3).unwrap();
248        // missing 2-3
249        assert!(is_strongly_chordal(&g).unwrap());
250    }
251
252    #[test]
253    fn edgeless() {
254        let g = Graph::with_vertices(4);
255        assert!(is_strongly_chordal(&g).unwrap());
256    }
257
258    #[test]
259    fn directed_treated_as_undirected() {
260        let mut g = Graph::new(3, true).unwrap();
261        g.add_edge(0, 1).unwrap();
262        g.add_edge(1, 2).unwrap();
263        g.add_edge(2, 0).unwrap();
264        assert!(is_strongly_chordal(&g).unwrap());
265    }
266}