Skip to main content

rust_igraph/algorithms/properties/
is_proper_interval.rs

1//! Proper interval graph predicate (ALGO-PR-115).
2//!
3//! A graph is a proper interval graph (equivalently, unit interval
4//! graph) if it can be represented as an intersection graph of
5//! intervals on the real line where no interval properly contains
6//! another. This is equivalent to being both an interval graph and
7//! claw-free, or equivalently chordal + claw-free + AT-free.
8//!
9//! A simpler characterization: a graph is a proper interval graph iff
10//! it is chordal and does not contain an induced claw (`K_{1,3}`) and
11//! admits a vertex ordering where the closed neighborhoods are
12//! consecutive (consecutive-1's property in the augmented adjacency
13//! matrix). We use the characterization: proper interval ⟺ chordal +
14//! claw-free (Roberts 1969 / Wegner 1967).
15//!
16//! Returns `false` for directed graphs.
17
18use crate::algorithms::chordality::is_chordal;
19use crate::algorithms::properties::is_claw_free::is_claw_free;
20use crate::core::{Graph, IgraphResult};
21
22/// Check whether a graph is a proper interval graph.
23///
24/// A proper interval graph is chordal and claw-free. Equivalently,
25/// it can be represented by unit-length intervals on the real line.
26///
27/// Returns `false` for directed graphs.
28///
29/// # Examples
30///
31/// ```
32/// use rust_igraph::{Graph, is_proper_interval};
33///
34/// // Path `P_4` is a proper interval graph
35/// let mut g = Graph::with_vertices(4);
36/// g.add_edge(0, 1).unwrap();
37/// g.add_edge(1, 2).unwrap();
38/// g.add_edge(2, 3).unwrap();
39/// assert!(is_proper_interval(&g).unwrap());
40///
41/// // Star `S_3` is NOT proper interval (has a claw)
42/// let mut g = Graph::with_vertices(4);
43/// g.add_edge(0, 1).unwrap();
44/// g.add_edge(0, 2).unwrap();
45/// g.add_edge(0, 3).unwrap();
46/// assert!(!is_proper_interval(&g).unwrap());
47/// ```
48pub fn is_proper_interval(graph: &Graph) -> IgraphResult<bool> {
49    if graph.is_directed() {
50        return Ok(false);
51    }
52
53    let ch = is_chordal(graph, None)?;
54    if !ch.chordal {
55        return Ok(false);
56    }
57
58    is_claw_free(graph)
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn empty_graph() {
67        let g = Graph::with_vertices(0);
68        assert!(is_proper_interval(&g).unwrap());
69    }
70
71    #[test]
72    fn single_vertex() {
73        let g = Graph::with_vertices(1);
74        assert!(is_proper_interval(&g).unwrap());
75    }
76
77    #[test]
78    fn single_edge() {
79        let mut g = Graph::with_vertices(2);
80        g.add_edge(0, 1).unwrap();
81        assert!(is_proper_interval(&g).unwrap());
82    }
83
84    #[test]
85    fn triangle() {
86        let mut g = Graph::with_vertices(3);
87        g.add_edge(0, 1).unwrap();
88        g.add_edge(1, 2).unwrap();
89        g.add_edge(2, 0).unwrap();
90        assert!(is_proper_interval(&g).unwrap());
91    }
92
93    #[test]
94    fn path_p4() {
95        // P_4: 0-1-2-3. Chordal (no induced C_4+), claw-free (max degree 2).
96        let mut g = Graph::with_vertices(4);
97        g.add_edge(0, 1).unwrap();
98        g.add_edge(1, 2).unwrap();
99        g.add_edge(2, 3).unwrap();
100        assert!(is_proper_interval(&g).unwrap());
101    }
102
103    #[test]
104    fn path_p5() {
105        let mut g = Graph::with_vertices(5);
106        g.add_edge(0, 1).unwrap();
107        g.add_edge(1, 2).unwrap();
108        g.add_edge(2, 3).unwrap();
109        g.add_edge(3, 4).unwrap();
110        assert!(is_proper_interval(&g).unwrap());
111    }
112
113    #[test]
114    fn complete_k4() {
115        let mut g = Graph::with_vertices(4);
116        for i in 0..4u32 {
117            for j in (i + 1)..4 {
118                g.add_edge(i, j).unwrap();
119            }
120        }
121        assert!(is_proper_interval(&g).unwrap());
122    }
123
124    #[test]
125    fn star_not_proper_interval() {
126        // Star S_3: has claw K_{1,3} → NOT proper interval
127        let mut g = Graph::with_vertices(4);
128        g.add_edge(0, 1).unwrap();
129        g.add_edge(0, 2).unwrap();
130        g.add_edge(0, 3).unwrap();
131        assert!(!is_proper_interval(&g).unwrap());
132    }
133
134    #[test]
135    fn c4_not_proper_interval() {
136        // C_4: not chordal → NOT proper interval
137        let mut g = Graph::with_vertices(4);
138        g.add_edge(0, 1).unwrap();
139        g.add_edge(1, 2).unwrap();
140        g.add_edge(2, 3).unwrap();
141        g.add_edge(3, 0).unwrap();
142        assert!(!is_proper_interval(&g).unwrap());
143    }
144
145    #[test]
146    fn c5_not_proper_interval() {
147        // C_5: not chordal → NOT proper interval
148        let mut g = Graph::with_vertices(5);
149        g.add_edge(0, 1).unwrap();
150        g.add_edge(1, 2).unwrap();
151        g.add_edge(2, 3).unwrap();
152        g.add_edge(3, 4).unwrap();
153        g.add_edge(4, 0).unwrap();
154        assert!(!is_proper_interval(&g).unwrap());
155    }
156
157    #[test]
158    fn diamond() {
159        // Diamond: K_4 minus one edge. Chordal, max degree 3 but no
160        // induced claw (the degree-3 vertices have two mutual neighbors).
161        let mut g = Graph::with_vertices(4);
162        g.add_edge(0, 1).unwrap();
163        g.add_edge(0, 2).unwrap();
164        g.add_edge(0, 3).unwrap();
165        g.add_edge(1, 2).unwrap();
166        g.add_edge(1, 3).unwrap();
167        // missing 2-3
168        assert!(is_proper_interval(&g).unwrap());
169    }
170
171    #[test]
172    fn edgeless() {
173        let g = Graph::with_vertices(4);
174        assert!(is_proper_interval(&g).unwrap());
175    }
176
177    #[test]
178    fn sun_3_not_proper_interval() {
179        // Sun S_3: chordal but has a claw (outer vertex + 2 inner nbrs +
180        // another outer vertex). Actually let's check: outer vertex 3 has
181        // neighbors 0,1. Degree 2, can't form claw from 3.
182        // Inner vertex 0 has neighbors 1,2,3,5 (degree 4). Check if 0
183        // has 3 independent neighbors: 3 adj to 1 ✓, 5 adj to 2 ✓,
184        // 3 adj to 5? No edge → 3,2,5 independent? 2 adj 5 ✓ → not independent.
185        // Actually: N(0) = {1, 2, 5, 3}. Are {1,5,3} independent?
186        // 1-5? No. 1-3? Yes (edge). So {1,3} not independent.
187        // {2,5,3}: 2-5? Yes. Not independent.
188        // This might not have a claw. Sun_3 is chordal but not strongly
189        // chordal. It IS interval and proper interval depends on claw-free.
190        // Let me use a different example.
191        // Tree with branching: 0-1, 0-2, 0-3, 1-4 → claw at 0 (leaves 2,3
192        // and 1 independent). Chordal (tree). Has claw → NOT proper interval.
193        let mut g = Graph::with_vertices(5);
194        g.add_edge(0, 1).unwrap();
195        g.add_edge(0, 2).unwrap();
196        g.add_edge(0, 3).unwrap();
197        g.add_edge(1, 4).unwrap();
198        assert!(!is_proper_interval(&g).unwrap());
199    }
200
201    #[test]
202    fn caterpillar_with_claw() {
203        // 0-1-2-3, 1-4, 1-5. Vertex 1 has neighbors {0,2,4,5}.
204        // {0,4,5} pairwise non-adjacent → claw at 1 → NOT proper interval.
205        let mut g = Graph::with_vertices(6);
206        g.add_edge(0, 1).unwrap();
207        g.add_edge(1, 2).unwrap();
208        g.add_edge(2, 3).unwrap();
209        g.add_edge(1, 4).unwrap();
210        g.add_edge(1, 5).unwrap();
211        assert!(!is_proper_interval(&g).unwrap());
212    }
213
214    #[test]
215    fn directed_returns_false() {
216        let mut g = Graph::new(3, true).unwrap();
217        g.add_edge(0, 1).unwrap();
218        g.add_edge(1, 2).unwrap();
219        g.add_edge(2, 0).unwrap();
220        assert!(!is_proper_interval(&g).unwrap());
221    }
222}