use std::collections::VecDeque;
use rustc_hash::FxHashSet;
use super::{TdBag, TreeDecomposition};
use crate::Error;
use crate::graph::index_by_vertex;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RootedForest {
order: Vec<usize>,
parent: Vec<Option<usize>>,
depth: Vec<usize>,
component_roots: Vec<usize>,
}
impl RootedForest {
pub fn order(&self) -> &[usize] {
&self.order
}
pub fn parents(&self) -> &[Option<usize>] {
&self.parent
}
pub fn depths(&self) -> &[usize] {
&self.depth
}
pub fn component_roots(&self) -> &[usize] {
&self.component_roots
}
}
fn rooted_forest_from_adjacency(
adj: &[Vec<usize>],
roots: impl IntoIterator<Item = usize>,
) -> RootedForest {
let n = adj.len();
let mut parent = vec![None; n];
let mut depth = vec![0usize; n];
let mut order = Vec::with_capacity(n);
let mut visited = vec![false; n];
let mut component_roots = Vec::new();
let mut queue = VecDeque::new();
for start in roots {
if visited[start] {
continue;
}
component_roots.push(start);
visited[start] = true;
queue.push_back(start);
while let Some(t) = queue.pop_front() {
order.push(t);
for &nb in &adj[t] {
if !visited[nb] {
visited[nb] = true;
parent[nb] = Some(t);
depth[nb] = depth[t] + 1;
queue.push_back(nb);
}
}
}
}
RootedForest {
order,
parent,
depth,
component_roots,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Projection {
decomposition: TreeDecomposition,
local_to_original: Vec<u32>,
}
impl Projection {
pub fn decomposition(&self) -> &TreeDecomposition {
&self.decomposition
}
pub fn local_to_original(&self) -> &[u32] {
&self.local_to_original
}
pub fn into_parts(self) -> (TreeDecomposition, Vec<u32>) {
(self.decomposition, self.local_to_original)
}
}
pub(super) fn project_td_keeping_global_ids(
td: &TreeDecomposition,
keep: &[u32],
) -> Option<TreeDecomposition> {
let n = td.bags.len();
if n == 0 {
return None;
}
let keep: FxHashSet<u32> = keep.iter().copied().collect();
let projected: Vec<Vec<u32>> = td
.bags
.iter()
.map(|bag| {
bag.vertices
.iter()
.copied()
.filter(|v| keep.contains(v))
.collect()
})
.collect();
let non_empty: Vec<usize> = (0..n).filter(|&i| !projected[i].is_empty()).collect();
if non_empty.is_empty() {
return None;
}
let mut old_to_new = vec![None; n];
for (new_i, &old_i) in non_empty.iter().enumerate() {
old_to_new[old_i] = Some(new_i);
}
let new_count = non_empty.len();
let mut new_adj: Vec<Vec<usize>> = vec![Vec::new(); new_count];
let parent_in_td = rooted_forest_from_adjacency(&td.adj, 0..n).parent;
for &old_i in &non_empty {
let new_i = old_to_new[old_i].unwrap();
let mut ancestor = parent_in_td[old_i];
while let Some(old_ancestor) = ancestor {
if let Some(new_j) = old_to_new[old_ancestor] {
new_adj[new_i].push(new_j);
new_adj[new_j].push(new_i);
break;
}
ancestor = parent_in_td[old_ancestor];
}
}
let new_bags: Vec<TdBag> = non_empty
.iter()
.map(|&old_id| TdBag::new(projected[old_id].clone()))
.collect();
Some(TreeDecomposition::from_parts(
td.num_vertices,
new_bags,
new_adj,
))
}
fn project(td: &TreeDecomposition, keep: &[u32]) -> Result<Projection, Error> {
let mut sorted: Vec<u32> = keep.to_vec();
sorted.sort_unstable();
sorted.dedup();
if let Some(&vertex) = sorted.iter().find(|&&vertex| vertex >= td.num_vertices) {
return Err(Error::InvalidInput(format!(
"projected vertex {vertex} is outside 0..{}",
td.num_vertices
)));
}
let global_to_local = index_by_vertex(&sorted);
if sorted.is_empty() {
return Ok(Projection {
decomposition: TreeDecomposition::from_parts(0, Vec::new(), Vec::new()),
local_to_original: Vec::new(),
});
}
let Some(mut projected) = project_td_keeping_global_ids(td, &sorted) else {
return Err(Error::InvalidDecomposition(
"none of the projected vertices occurs in a bag".into(),
));
};
let represented: FxHashSet<u32> = projected
.bags
.iter()
.flat_map(|bag| bag.vertices.iter().copied())
.collect();
if let Some(&missing) = sorted
.iter()
.find(|&&vertex| !represented.contains(&vertex))
{
return Err(Error::InvalidDecomposition(format!(
"projected vertex {missing} occurs in no bag"
)));
}
for bag in &mut projected.bags {
for v in &mut bag.vertices {
*v = global_to_local[&*v];
}
}
projected.num_vertices = sorted.len() as u32;
Ok(Projection {
decomposition: projected,
local_to_original: sorted,
})
}
impl TreeDecomposition {
pub fn rooted_forest(
&self,
roots: impl IntoIterator<Item = usize>,
) -> Result<RootedForest, Error> {
let roots: Vec<usize> = roots.into_iter().collect();
if let Some(&root) = roots.iter().find(|&&root| root >= self.bags.len()) {
return Err(Error::InvalidInput(format!(
"root bag {root} is outside 0..{}",
self.bags.len()
)));
}
Ok(rooted_forest_from_adjacency(
&self.adj,
roots.into_iter().chain(0..self.bags.len()),
))
}
pub fn project(&self, keep: &[u32]) -> Result<Projection, Error> {
project(self, keep)
}
}
fn augment_for_separator(td: &mut TreeDecomposition, sep: &[u32]) -> Option<usize> {
if td.bags.is_empty() {
return None;
}
let sep_set: FxHashSet<u32> = sep.iter().copied().collect();
let anchor = (0..td.bags.len()).max_by_key(|&i| {
td.bags[i]
.vertices
.iter()
.filter(|v| sep_set.contains(v))
.count()
})?;
for &v in sep {
if td.bags[anchor].vertices.contains(&v) {
continue;
}
let src = (0..td.bags.len()).find(|&i| td.bags[i].vertices.contains(&v))?;
if src != anchor {
match bag_path_bfs(&td.adj, src, anchor) {
Some(path) => {
for &b in &path {
if !td.bags[b].vertices.contains(&v) {
td.bags[b].vertices.push(v);
}
}
}
None => {
td.adj[src].push(anchor);
td.adj[anchor].push(src);
td.bags[anchor].vertices.push(v);
}
}
}
}
for bag in td.bags.iter_mut() {
bag.vertices.sort_unstable();
bag.vertices.dedup();
}
Some(anchor)
}
fn bag_path_bfs(adj: &[Vec<usize>], src: usize, dst: usize) -> Option<Vec<usize>> {
if src == dst {
return Some(vec![src]);
}
let parent = rooted_forest_from_adjacency(adj, [src]).parent;
let mut path = vec![dst];
let mut x = dst;
while x != src {
x = parent[x]?;
path.push(x);
}
path.reverse();
Some(path)
}
pub(super) fn glue_at_separator(
mut td_a: TreeDecomposition,
mut td_b: TreeDecomposition,
sep: &[u32],
) -> Option<TreeDecomposition> {
if td_a.num_vertices != td_b.num_vertices {
return None;
}
let num_vertices = td_a.num_vertices;
let anchor_a = augment_for_separator(&mut td_a, sep)?;
let anchor_b = augment_for_separator(&mut td_b, sep)?;
let mut sep_sorted: Vec<u32> = sep.to_vec();
sep_sorted.sort_unstable();
sep_sorted.dedup();
let sep_bag = TdBag::new(sep_sorted);
let a_len = td_a.bags.len();
let b_len = td_b.bags.len();
let mut bags: Vec<TdBag> = Vec::with_capacity(1 + a_len + b_len);
bags.push(sep_bag);
bags.extend(td_a.bags);
bags.extend(td_b.bags);
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); bags.len()];
let a_offset = 1;
for (i, nbs) in td_a.adj.into_iter().enumerate() {
for nb in nbs {
if nb > i {
adj[a_offset + i].push(a_offset + nb);
adj[a_offset + nb].push(a_offset + i);
}
}
}
let b_offset = a_offset + a_len;
for (i, nbs) in td_b.adj.into_iter().enumerate() {
for nb in nbs {
if nb > i {
adj[b_offset + i].push(b_offset + nb);
adj[b_offset + nb].push(b_offset + i);
}
}
}
adj[0].push(a_offset + anchor_a);
adj[a_offset + anchor_a].push(0);
adj[0].push(b_offset + anchor_b);
adj[b_offset + anchor_b].push(0);
Some(TreeDecomposition::from_parts(num_vertices, bags, adj))
}
#[cfg(test)]
mod tests;