1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19
20pub fn treewidth_upper_bound(graph: &Graph) -> IgraphResult<u32> {
36 let (tw, _) = elimination_order_internal(graph, EliminationHeuristic::MinDegree);
37 Ok(tw)
38}
39
40pub fn treewidth_min_fill(graph: &Graph) -> IgraphResult<u32> {
54 let (tw, _) = elimination_order_internal(graph, EliminationHeuristic::MinFill);
55 Ok(tw)
56}
57
58pub fn elimination_ordering(graph: &Graph) -> IgraphResult<Vec<u32>> {
72 let (_, order) = elimination_order_internal(graph, EliminationHeuristic::MinDegree);
73 Ok(order)
74}
75
76#[derive(Clone, Copy)]
77enum EliminationHeuristic {
78 MinDegree,
79 MinFill,
80}
81
82fn elimination_order_internal(graph: &Graph, heuristic: EliminationHeuristic) -> (u32, Vec<u32>) {
83 let n = graph.vcount() as usize;
84 if n == 0 {
85 return (0, Vec::new());
86 }
87
88 let mut adj: Vec<Vec<bool>> = vec![vec![false; n]; n];
89 for (u, v) in graph.edges() {
90 let ui = u as usize;
91 let vi = v as usize;
92 if ui != vi {
93 adj[ui][vi] = true;
94 if !graph.is_directed() {
95 adj[vi][ui] = true;
96 }
97 }
98 }
99
100 let mut eliminated = vec![false; n];
101 let mut order = Vec::with_capacity(n);
102 let mut tw: u32 = 0;
103
104 for _ in 0..n {
105 let v = match heuristic {
106 EliminationHeuristic::MinDegree => pick_min_degree(&adj, &eliminated, n),
107 EliminationHeuristic::MinFill => pick_min_fill(&adj, &eliminated, n),
108 };
109
110 let nbrs: Vec<usize> = (0..n)
111 .filter(|&u| !eliminated[u] && u != v && adj[v][u])
112 .collect();
113
114 let deg = nbrs.len() as u32;
115 if deg > tw {
116 tw = deg;
117 }
118
119 for i in 0..nbrs.len() {
120 for j in (i + 1)..nbrs.len() {
121 let a = nbrs[i];
122 let b = nbrs[j];
123 adj[a][b] = true;
124 adj[b][a] = true;
125 }
126 }
127
128 eliminated[v] = true;
129 order.push(v as u32);
130 }
131
132 (tw, order)
133}
134
135fn pick_min_degree(adj: &[Vec<bool>], eliminated: &[bool], n: usize) -> usize {
136 let mut best = usize::MAX;
137 let mut best_v = 0;
138
139 for v in 0..n {
140 if eliminated[v] {
141 continue;
142 }
143 let deg = (0..n)
144 .filter(|&u| !eliminated[u] && u != v && adj[v][u])
145 .count();
146 if deg < best {
147 best = deg;
148 best_v = v;
149 }
150 }
151
152 best_v
153}
154
155fn pick_min_fill(adj: &[Vec<bool>], eliminated: &[bool], n: usize) -> usize {
156 let mut best_fill = usize::MAX;
157 let mut best_deg = usize::MAX;
158 let mut best_v = 0;
159
160 for v in 0..n {
161 if eliminated[v] {
162 continue;
163 }
164 let nbrs: Vec<usize> = (0..n)
165 .filter(|&u| !eliminated[u] && u != v && adj[v][u])
166 .collect();
167
168 let mut fill: usize = 0;
169 for i in 0..nbrs.len() {
170 for j in (i + 1)..nbrs.len() {
171 if !adj[nbrs[i]][nbrs[j]] {
172 fill = fill.saturating_add(1);
173 }
174 }
175 }
176
177 if fill < best_fill || (fill == best_fill && nbrs.len() < best_deg) {
178 best_fill = fill;
179 best_deg = nbrs.len();
180 best_v = v;
181 }
182 }
183
184 best_v
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 fn grid2x3() -> Graph {
221 Graph::from_edges(
225 &[(0, 1), (1, 2), (3, 4), (4, 5), (0, 3), (1, 4), (2, 5)],
226 false,
227 Some(6),
228 )
229 .unwrap()
230 }
231
232 fn petersen() -> Graph {
233 Graph::from_edges(
234 &[
235 (0, 1),
236 (1, 2),
237 (2, 3),
238 (3, 4),
239 (4, 0),
240 (0, 5),
241 (1, 6),
242 (2, 7),
243 (3, 8),
244 (4, 9),
245 (5, 7),
246 (7, 9),
247 (9, 6),
248 (6, 8),
249 (8, 5),
250 ],
251 false,
252 Some(10),
253 )
254 .unwrap()
255 }
256
257 #[test]
260 fn twub_empty() {
261 let g = Graph::with_vertices(0);
262 assert_eq!(treewidth_upper_bound(&g).unwrap(), 0);
263 }
264
265 #[test]
266 fn twub_single() {
267 let g = Graph::with_vertices(1);
268 assert_eq!(treewidth_upper_bound(&g).unwrap(), 0);
269 }
270
271 #[test]
272 fn twub_no_edges() {
273 let g = Graph::with_vertices(5);
274 assert_eq!(treewidth_upper_bound(&g).unwrap(), 0);
275 }
276
277 #[test]
278 fn twub_path4() {
279 assert_eq!(treewidth_upper_bound(&path4()).unwrap(), 1);
281 }
282
283 #[test]
284 fn twub_star5() {
285 assert_eq!(treewidth_upper_bound(&star5()).unwrap(), 1);
287 }
288
289 #[test]
290 fn twub_k3() {
291 assert_eq!(treewidth_upper_bound(&k3()).unwrap(), 2);
293 }
294
295 #[test]
296 fn twub_k4() {
297 assert_eq!(treewidth_upper_bound(&k4()).unwrap(), 3);
299 }
300
301 #[test]
302 fn twub_cycle4() {
303 assert_eq!(treewidth_upper_bound(&cycle4()).unwrap(), 2);
305 }
306
307 #[test]
308 fn twub_cycle5() {
309 assert_eq!(treewidth_upper_bound(&cycle5()).unwrap(), 2);
310 }
311
312 #[test]
313 fn twub_grid2x3() {
314 let tw = treewidth_upper_bound(&grid2x3()).unwrap();
316 assert!(tw >= 2);
317 assert!(tw <= 3);
318 }
319
320 #[test]
321 fn twub_petersen() {
322 let tw = treewidth_upper_bound(&petersen()).unwrap();
324 assert!(tw >= 4);
325 assert!(tw <= 6);
326 }
327
328 #[test]
331 fn twmf_path4() {
332 assert_eq!(treewidth_min_fill(&path4()).unwrap(), 1);
333 }
334
335 #[test]
336 fn twmf_k3() {
337 assert_eq!(treewidth_min_fill(&k3()).unwrap(), 2);
338 }
339
340 #[test]
341 fn twmf_k4() {
342 assert_eq!(treewidth_min_fill(&k4()).unwrap(), 3);
343 }
344
345 #[test]
346 fn twmf_cycle5() {
347 assert_eq!(treewidth_min_fill(&cycle5()).unwrap(), 2);
348 }
349
350 #[test]
351 fn twmf_star5() {
352 assert_eq!(treewidth_min_fill(&star5()).unwrap(), 1);
353 }
354
355 #[test]
358 fn eo_length() {
359 let g = path4();
360 let order = elimination_ordering(&g).unwrap();
361 assert_eq!(order.len(), 4);
362 }
363
364 #[test]
365 fn eo_permutation() {
366 let g = k4();
367 let order = elimination_ordering(&g).unwrap();
368 let mut sorted = order.clone();
369 sorted.sort_unstable();
370 assert_eq!(sorted, vec![0, 1, 2, 3]);
371 }
372
373 #[test]
374 fn eo_empty() {
375 let g = Graph::with_vertices(0);
376 assert!(elimination_ordering(&g).unwrap().is_empty());
377 }
378
379 #[test]
382 fn min_fill_leq_min_degree() {
383 for g in &[path4(), k3(), cycle4(), cycle5(), star5()] {
385 let bound_degree = treewidth_upper_bound(g).unwrap();
386 let bound_fill = treewidth_min_fill(g).unwrap();
387 assert!(bound_fill <= bound_degree.saturating_add(1));
389 }
390 }
391
392 #[test]
393 fn tw_at_least_one_for_edges() {
394 for g in &[path4(), k3(), k4(), cycle4(), cycle5()] {
395 assert!(treewidth_upper_bound(g).unwrap() >= 1);
396 }
397 }
398
399 #[test]
400 fn tw_leq_n_minus_1() {
401 for g in &[path4(), k3(), k4(), cycle5(), star5()] {
402 let n = g.vcount();
403 let tw = treewidth_upper_bound(g).unwrap();
404 assert!(tw < n);
405 }
406 }
407
408 #[test]
409 fn complete_graph_tw_is_n_minus_1() {
410 for n in 2_u32..=5 {
412 let edges: Vec<(u32, u32)> = (0..n)
413 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
414 .collect();
415 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
416 assert_eq!(
417 treewidth_upper_bound(&g).unwrap(),
418 n - 1,
419 "tw(K_{n}) should be {}",
420 n - 1
421 );
422 }
423 }
424
425 #[test]
426 fn tree_tw_is_one() {
427 let trees = vec![
429 path4(),
430 star5(),
431 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (2, 4)], false, Some(5)).unwrap(),
432 ];
433 for g in &trees {
434 assert_eq!(treewidth_upper_bound(g).unwrap(), 1);
435 }
436 }
437}