Skip to main content

rust_igraph/algorithms/properties/
graph_bandwidth.rs

1//! Graph bandwidth (ALGO-TR-033).
2//!
3//! The **bandwidth** of a labeling `f: V → {0..n-1}` is
4//! `max |f(u) - f(v)|` over all edges `(u,v)`.
5//! The **bandwidth of a graph** `B(G)` is the minimum bandwidth
6//! over all labelings.
7//!
8//! Computing `B(G)` is NP-hard; we provide:
9//! - `bandwidth_of_labeling`: compute bandwidth for a given labeling.
10//! - `bandwidth_lower_bound`: lower bound via max degree.
11//! - `bandwidth`: brute-force minimum (small graphs only, ≤ ~10).
12
13#![allow(
14    clippy::cast_possible_truncation,
15    clippy::cast_precision_loss,
16    clippy::many_single_char_names,
17    clippy::needless_range_loop,
18    clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23/// Compute the bandwidth of a specific labeling.
24///
25/// The labeling is given as a permutation: `labeling[i]` is the
26/// label assigned to vertex `i`.
27///
28/// # Examples
29///
30/// ```
31/// use rust_igraph::{Graph, bandwidth_of_labeling};
32///
33/// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, Some(4)).unwrap();
34/// // identity labeling: max |i-j| over edges = max(1,1,1) = 1
35/// assert_eq!(bandwidth_of_labeling(&g, &[0,1,2,3]).unwrap(), 1);
36/// // reversed labeling: max |3-2|,|2-1|,|1-0| = 1
37/// assert_eq!(bandwidth_of_labeling(&g, &[3,2,1,0]).unwrap(), 1);
38/// ```
39pub fn bandwidth_of_labeling(graph: &Graph, labeling: &[u32]) -> IgraphResult<u32> {
40    let n = graph.vcount() as usize;
41    if labeling.len() != n {
42        return Err(crate::core::IgraphError::InvalidArgument(format!(
43            "bandwidth_of_labeling: labeling length {} != vcount {n}",
44            labeling.len()
45        )));
46    }
47
48    let mut bw: u32 = 0;
49    for (u, v) in graph.edges() {
50        let lu = labeling[u as usize];
51        let lv = labeling[v as usize];
52        let diff = lu.abs_diff(lv);
53        if diff > bw {
54            bw = diff;
55        }
56    }
57
58    Ok(bw)
59}
60
61/// Compute a lower bound on graph bandwidth.
62///
63/// Uses the degree-based bound: `B(G) >= ceil(max_degree / 2)`.
64/// Also: for connected graphs, `B(G) >= ceil((n-1) / diameter)`.
65///
66/// Returns the maximum of available lower bounds.
67///
68/// # Examples
69///
70/// ```
71/// use rust_igraph::{Graph, bandwidth_lower_bound};
72///
73/// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, Some(4)).unwrap();
74/// assert!(bandwidth_lower_bound(&g).unwrap() >= 1);
75/// ```
76pub fn bandwidth_lower_bound(graph: &Graph) -> IgraphResult<u32> {
77    let n = graph.vcount() as usize;
78    if n <= 1 {
79        return Ok(0);
80    }
81
82    let mut max_deg: usize = 0;
83    for v in 0..n as u32 {
84        let d = graph.degree(v)?;
85        if d > max_deg {
86            max_deg = d;
87        }
88    }
89
90    let deg_bound = (max_deg.saturating_add(1)) / 2;
91
92    Ok(deg_bound as u32)
93}
94
95/// Compute the exact graph bandwidth `B(G)`.
96///
97/// Tries all permutations of vertices and returns the minimum
98/// bandwidth. Only feasible for very small graphs (≤ ~10 vertices).
99///
100/// # Examples
101///
102/// ```
103/// use rust_igraph::{Graph, bandwidth};
104///
105/// // Path graph: optimal bandwidth is 1
106/// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, Some(4)).unwrap();
107/// assert_eq!(bandwidth(&g).unwrap(), 1);
108/// ```
109pub fn bandwidth(graph: &Graph) -> IgraphResult<u32> {
110    let n = graph.vcount() as usize;
111    if n == 0 {
112        return Ok(0);
113    }
114    if graph.ecount() == 0 {
115        return Ok(0);
116    }
117
118    let edges: Vec<(usize, usize)> = graph
119        .edges()
120        .map(|(u, v)| (u as usize, v as usize))
121        .collect();
122
123    let mut perm: Vec<u32> = (0..n as u32).collect();
124    let mut best = n as u32;
125
126    permute_and_minimize(&edges, &mut perm, 0, &mut best);
127
128    Ok(best)
129}
130
131fn permute_and_minimize(
132    edges: &[(usize, usize)],
133    perm: &mut Vec<u32>,
134    depth: usize,
135    best: &mut u32,
136) {
137    let n = perm.len();
138
139    if depth == n {
140        let bw = compute_bw(edges, perm);
141        if bw < *best {
142            *best = bw;
143        }
144        return;
145    }
146
147    for i in depth..n {
148        perm.swap(depth, i);
149
150        let partial_bw = partial_bandwidth(edges, perm, depth);
151        if partial_bw < *best {
152            permute_and_minimize(edges, perm, depth.saturating_add(1), best);
153        }
154
155        perm.swap(depth, i);
156    }
157}
158
159fn compute_bw(edges: &[(usize, usize)], perm: &[u32]) -> u32 {
160    let mut bw: u32 = 0;
161    for &(u, v) in edges {
162        let lu = perm[u];
163        let lv = perm[v];
164        let diff = lu.abs_diff(lv);
165        if diff > bw {
166            bw = diff;
167        }
168    }
169    bw
170}
171
172fn partial_bandwidth(edges: &[(usize, usize)], perm: &[u32], depth: usize) -> u32 {
173    let mut bw: u32 = 0;
174    for &(u, v) in edges {
175        if u <= depth && v <= depth {
176            let lu = perm[u];
177            let lv = perm[v];
178            let diff = lu.abs_diff(lv);
179            if diff > bw {
180                bw = diff;
181            }
182        }
183    }
184    bw
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    fn path4() -> Graph {
192        Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
193    }
194
195    fn k3() -> Graph {
196        Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
197    }
198
199    fn k4() -> Graph {
200        Graph::from_edges(
201            &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
202            false,
203            Some(4),
204        )
205        .unwrap()
206    }
207
208    fn cycle4() -> Graph {
209        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
210    }
211
212    fn cycle5() -> Graph {
213        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
214    }
215
216    fn star5() -> Graph {
217        Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
218    }
219
220    // --- bandwidth_of_labeling ---
221
222    #[test]
223    fn bol_identity() {
224        let g = path4();
225        assert_eq!(bandwidth_of_labeling(&g, &[0, 1, 2, 3]).unwrap(), 1);
226    }
227
228    #[test]
229    fn bol_reversed() {
230        let g = path4();
231        assert_eq!(bandwidth_of_labeling(&g, &[3, 2, 1, 0]).unwrap(), 1);
232    }
233
234    #[test]
235    fn bol_bad_labeling() {
236        let g = path4();
237        assert_eq!(bandwidth_of_labeling(&g, &[0, 3, 1, 2]).unwrap(), 3);
238    }
239
240    #[test]
241    fn bol_k3() {
242        let g = k3();
243        assert_eq!(bandwidth_of_labeling(&g, &[0, 1, 2]).unwrap(), 2);
244    }
245
246    #[test]
247    fn bol_wrong_length() {
248        let g = path4();
249        assert!(bandwidth_of_labeling(&g, &[0, 1]).is_err());
250    }
251
252    #[test]
253    fn bol_empty() {
254        let g = Graph::with_vertices(0);
255        assert_eq!(bandwidth_of_labeling(&g, &[]).unwrap(), 0);
256    }
257
258    #[test]
259    fn bol_no_edges() {
260        let g = Graph::with_vertices(3);
261        assert_eq!(bandwidth_of_labeling(&g, &[0, 1, 2]).unwrap(), 0);
262    }
263
264    // --- bandwidth_lower_bound ---
265
266    #[test]
267    fn blb_path4() {
268        assert!(bandwidth_lower_bound(&path4()).unwrap() >= 1);
269    }
270
271    #[test]
272    fn blb_k4() {
273        let lb = bandwidth_lower_bound(&k4()).unwrap();
274        assert!(lb >= 2);
275    }
276
277    #[test]
278    fn blb_empty() {
279        let g = Graph::with_vertices(0);
280        assert_eq!(bandwidth_lower_bound(&g).unwrap(), 0);
281    }
282
283    #[test]
284    fn blb_single() {
285        let g = Graph::with_vertices(1);
286        assert_eq!(bandwidth_lower_bound(&g).unwrap(), 0);
287    }
288
289    // --- bandwidth ---
290
291    #[test]
292    fn bw_empty() {
293        let g = Graph::with_vertices(0);
294        assert_eq!(bandwidth(&g).unwrap(), 0);
295    }
296
297    #[test]
298    fn bw_single() {
299        let g = Graph::with_vertices(1);
300        assert_eq!(bandwidth(&g).unwrap(), 0);
301    }
302
303    #[test]
304    fn bw_no_edges() {
305        let g = Graph::with_vertices(3);
306        assert_eq!(bandwidth(&g).unwrap(), 0);
307    }
308
309    #[test]
310    fn bw_single_edge() {
311        let g = Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap();
312        assert_eq!(bandwidth(&g).unwrap(), 1);
313    }
314
315    #[test]
316    fn bw_path4() {
317        assert_eq!(bandwidth(&path4()).unwrap(), 1);
318    }
319
320    #[test]
321    fn bw_k3() {
322        assert_eq!(bandwidth(&k3()).unwrap(), 2);
323    }
324
325    #[test]
326    fn bw_k4() {
327        // B(K_n) = n-1: every pair is an edge, max label diff is n-1
328        assert_eq!(bandwidth(&k4()).unwrap(), 3);
329    }
330
331    #[test]
332    fn bw_cycle4() {
333        assert_eq!(bandwidth(&cycle4()).unwrap(), 2);
334    }
335
336    #[test]
337    fn bw_cycle5() {
338        assert_eq!(bandwidth(&cycle5()).unwrap(), 2);
339    }
340
341    #[test]
342    fn bw_star5() {
343        assert_eq!(bandwidth(&star5()).unwrap(), 2);
344    }
345
346    // --- cross-consistency ---
347
348    #[test]
349    fn bw_geq_lower_bound() {
350        for g in &[path4(), k3(), k4(), cycle4(), cycle5(), star5()] {
351            let bw = bandwidth(g).unwrap();
352            let lb = bandwidth_lower_bound(g).unwrap();
353            assert!(bw >= lb, "bandwidth {bw} < lower bound {lb}");
354        }
355    }
356
357    #[test]
358    fn bw_leq_identity_labeling() {
359        for g in &[path4(), k3(), k4(), cycle4()] {
360            let n = g.vcount() as usize;
361            let identity: Vec<u32> = (0..n as u32).collect();
362            let id_bw = bandwidth_of_labeling(g, &identity).unwrap();
363            let opt_bw = bandwidth(g).unwrap();
364            assert!(opt_bw <= id_bw);
365        }
366    }
367
368    #[test]
369    fn path_bandwidth_is_one() {
370        for n in 2_u32..=6 {
371            let edges: Vec<(u32, u32)> = (0..n - 1).map(|i| (i, i + 1)).collect();
372            let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
373            assert_eq!(bandwidth(&g).unwrap(), 1);
374        }
375    }
376
377    #[test]
378    fn complete_graph_bandwidth() {
379        // B(K_n) = n-1: every pair is an edge
380        let g2 = Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap();
381        assert_eq!(bandwidth(&g2).unwrap(), 1);
382        assert_eq!(bandwidth(&k3()).unwrap(), 2);
383        assert_eq!(bandwidth(&k4()).unwrap(), 3);
384    }
385}