use crate::core::types::{BaseGraph, GraphConstructor, NodeId};
use rustc_hash::{FxHashMap, FxHashSet};
use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn eliminate(adj: &mut FxHashMap<NodeId, FxHashSet<NodeId>>, node: NodeId, neighbors: &[NodeId]) {
for i in 0..neighbors.len() {
for j in (i + 1)..neighbors.len() {
let (a, b) = (neighbors[i], neighbors[j]);
if adj.get_mut(&a).is_some_and(|s| s.insert(b)) {
if let Some(s) = adj.get_mut(&b) {
s.insert(a);
}
}
}
}
adj.remove(&node);
for &v in neighbors {
if let Some(s) = adj.get_mut(&v) {
s.remove(&node);
}
}
}
pub fn treewidth_min_degree<A, Ty>(graph: &BaseGraph<A, f64, Ty>) -> (usize, Vec<NodeId>)
where
Ty: GraphConstructor<A, f64>,
{
let mut order = Vec::new();
let mut adj: FxHashMap<NodeId, FxHashSet<NodeId>> = graph
.nodes()
.map(|(u, _)| (u, graph.neighbors(u).filter(|&v| v != u).collect()))
.collect();
if adj.is_empty() {
return (0, order);
}
let mut treewidth = 0;
let mut heap: BinaryHeap<Reverse<(usize, NodeId)>> = adj
.iter()
.map(|(&u, nbrs)| Reverse((nbrs.len(), u)))
.collect();
while !adj.is_empty() {
let u = loop {
match heap.pop() {
Some(Reverse((deg, node))) => match adj.get(&node) {
Some(nbrs) if nbrs.len() == deg => break node,
_ => {} },
None => {
match adj.keys().next() {
Some(&node) => break node,
None => return (treewidth, order),
}
}
}
};
let neighbors: Vec<NodeId> = adj
.get(&u)
.map(|s| s.iter().copied().collect())
.unwrap_or_default();
if neighbors.len() > treewidth {
treewidth = neighbors.len();
}
eliminate(&mut adj, u, &neighbors);
order.push(u);
for &v in &neighbors {
if let Some(s) = adj.get(&v) {
heap.push(Reverse((s.len(), v)));
}
}
}
(treewidth, order)
}
fn fill_in_of(adj: &FxHashMap<NodeId, FxHashSet<NodeId>>, node: NodeId) -> usize {
let Some(nbrs) = adj.get(&node) else {
return 0;
};
let mut missing = 0;
for &a in nbrs {
let na = &adj[&a];
for &b in nbrs {
if a != b && !na.contains(&b) {
missing += 1;
}
}
}
missing / 2
}
fn add_fill_edge(
adj: &mut FxHashMap<NodeId, FxHashSet<NodeId>>,
fill: &mut FxHashMap<NodeId, usize>,
touched: &mut FxHashSet<NodeId>,
a: NodeId,
b: NodeId,
) {
let (delta_a, delta_b, common) = {
let na = &adj[&a];
let nb = &adj[&b];
let delta_a = na.iter().filter(|x| !nb.contains(x)).count();
let delta_b = nb.iter().filter(|x| !na.contains(x)).count();
let common: Vec<NodeId> = na.iter().copied().filter(|x| nb.contains(x)).collect();
(delta_a, delta_b, common)
};
if let Some(f) = fill.get_mut(&a) {
*f += delta_a;
}
if let Some(f) = fill.get_mut(&b) {
*f += delta_b;
}
for &w in &common {
if let Some(f) = fill.get_mut(&w) {
*f -= 1;
}
touched.insert(w);
}
if let Some(s) = adj.get_mut(&a) {
s.insert(b);
}
if let Some(s) = adj.get_mut(&b) {
s.insert(a);
}
touched.insert(a);
touched.insert(b);
}
fn remove_eliminated(
adj: &mut FxHashMap<NodeId, FxHashSet<NodeId>>,
fill: &mut FxHashMap<NodeId, usize>,
touched: &mut FxHashSet<NodeId>,
u: NodeId,
neighbors: &[NodeId],
) {
for &v in neighbors {
let dec = {
let nv = &adj[&v];
let nu = &adj[&u];
nv.iter().filter(|&&x| x != u && !nu.contains(&x)).count()
};
if let Some(f) = fill.get_mut(&v) {
*f -= dec;
}
touched.insert(v);
}
for &v in neighbors {
if let Some(s) = adj.get_mut(&v) {
s.remove(&u);
}
}
adj.remove(&u);
}
pub fn treewidth_min_fill_in<A, Ty>(graph: &BaseGraph<A, f64, Ty>) -> (usize, Vec<NodeId>)
where
Ty: GraphConstructor<A, f64>,
{
let mut order = Vec::new();
let mut adj: FxHashMap<NodeId, FxHashSet<NodeId>> = graph
.nodes()
.map(|(u, _)| (u, graph.neighbors(u).filter(|&v| v != u).collect()))
.collect();
if adj.is_empty() {
return (0, order);
}
let mut fill: FxHashMap<NodeId, usize> =
adj.keys().map(|&u| (u, fill_in_of(&adj, u))).collect();
let mut heap: BinaryHeap<Reverse<(usize, NodeId)>> =
fill.iter().map(|(&u, &f)| Reverse((f, u))).collect();
let mut treewidth = 0;
while !adj.is_empty() {
let u = loop {
match heap.pop() {
Some(Reverse((f, node))) => match fill.get(&node) {
Some(&cur) if cur == f => break node,
_ => {} },
None => match adj.keys().next() {
Some(&node) => break node,
None => return (treewidth, order),
},
}
};
let neighbors: Vec<NodeId> = adj
.get(&u)
.map(|s| s.iter().copied().collect())
.unwrap_or_default();
if neighbors.len() > treewidth {
treewidth = neighbors.len();
}
let mut touched: FxHashSet<NodeId> = FxHashSet::default();
for i in 0..neighbors.len() {
for j in (i + 1)..neighbors.len() {
let (a, b) = (neighbors[i], neighbors[j]);
if adj.get(&a).is_some_and(|s| !s.contains(&b)) {
add_fill_edge(&mut adj, &mut fill, &mut touched, a, b);
}
}
}
remove_eliminated(&mut adj, &mut fill, &mut touched, u, &neighbors);
fill.remove(&u);
order.push(u);
for v in touched {
if let Some(&f) = fill.get(&v) {
heap.push(Reverse((f, v)));
}
}
}
(treewidth, order)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::Graph;
#[test]
fn test_treewidth_empty_graph() {
let graph: Graph<i32, f64> = Graph::new();
let (tw, order) = treewidth_min_degree(&graph);
assert_eq!(tw, 0);
assert!(order.is_empty());
}
#[test]
fn test_treewidth_single_node() {
let mut graph = Graph::new();
let n1 = graph.add_node(1);
let (tw, order) = treewidth_min_degree(&graph);
assert_eq!(tw, 0);
assert_eq!(order.len(), 1);
assert_eq!(order[0], n1);
}
#[test]
fn test_treewidth_path() {
let mut graph = Graph::new();
let n1 = graph.add_node(1);
let n2 = graph.add_node(2);
let n3 = graph.add_node(3);
graph.add_edge(n1, n2, 1.0);
graph.add_edge(n2, n3, 1.0);
let (tw, order) = treewidth_min_degree(&graph);
assert!(tw <= 1); assert_eq!(order.len(), 3);
}
#[test]
fn test_treewidth_min_fill_in_empty() {
let graph: Graph<i32, f64> = Graph::new();
let (tw, order) = treewidth_min_fill_in(&graph);
assert_eq!(tw, 0);
assert!(order.is_empty());
}
fn build_grid(rows: usize, cols: usize) -> Graph<i32, f64> {
let mut graph = Graph::new();
let ids: Vec<_> = (0..(rows * cols))
.map(|i| graph.add_node(i as i32))
.collect();
let idx = |r: usize, c: usize| r * cols + c;
for r in 0..rows {
for c in 0..cols {
if c + 1 < cols {
graph.add_edge(ids[idx(r, c)], ids[idx(r, c + 1)], 1.0);
}
if r + 1 < rows {
graph.add_edge(ids[idx(r, c)], ids[idx(r + 1, c)], 1.0);
}
}
}
graph
}
#[test]
fn test_treewidth_min_degree_complete_graph() {
let mut graph = Graph::new();
let nodes: Vec<_> = (0..4).map(|i| graph.add_node(i)).collect();
for i in 0..4 {
for j in (i + 1)..4 {
graph.add_edge(nodes[i], nodes[j], 1.0);
}
}
let (tw, order) = treewidth_min_degree(&graph);
assert_eq!(tw, 3);
assert_eq!(order.len(), 4);
}
#[test]
fn test_treewidth_min_degree_grid_requires_fill_in() {
let graph = build_grid(6, 6);
let (tw, _order) = treewidth_min_degree(&graph);
assert!(tw >= 6, "expected treewidth >= 6 for a 6x6 grid, got {tw}");
}
#[test]
fn test_treewidth_min_fill_in_grid_requires_fill_in() {
let graph = build_grid(6, 6);
let (tw, _order) = treewidth_min_fill_in(&graph);
assert!(tw >= 6, "expected treewidth >= 6 for a 6x6 grid, got {tw}");
}
#[test]
fn test_treewidth_min_fill_in_cycle() {
let mut graph = Graph::new();
let nodes: Vec<_> = (0..6).map(|i| graph.add_node(i)).collect();
for i in 0..6 {
graph.add_edge(nodes[i], nodes[(i + 1) % 6], 1.0);
}
let (tw, order) = treewidth_min_fill_in(&graph);
assert_eq!(tw, 2);
assert_eq!(order.len(), 6);
}
}