use std::collections::VecDeque;
use super::adjacency::AdjacencyGraph;
use crate::error::{SparseError, SparseResult};
#[derive(Debug, Clone)]
pub struct CuthillMcKeeResult {
pub perm: Vec<usize>,
pub inv_perm: Vec<usize>,
pub bandwidth_before: usize,
pub bandwidth_after: usize,
pub profile_before: usize,
pub profile_after: usize,
}
pub fn bandwidth(graph: &AdjacencyGraph, perm: &[usize]) -> SparseResult<usize> {
let n = graph.num_nodes();
if !perm.is_empty() && perm.len() != n {
return Err(SparseError::ValueError(format!(
"permutation length {} does not match graph size {}",
perm.len(),
n
)));
}
let inv_perm = if perm.is_empty() {
(0..n).collect::<Vec<_>>()
} else {
let mut inv = vec![0usize; n];
for (new_i, &old_i) in perm.iter().enumerate() {
if old_i >= n {
return Err(SparseError::ValueError(format!(
"permutation contains out-of-range index {}",
old_i
)));
}
inv[old_i] = new_i;
}
inv
};
let mut bw = 0usize;
for u in 0..n {
for &v in graph.neighbors(u) {
let diff = inv_perm[u].abs_diff(inv_perm[v]);
if diff > bw {
bw = diff;
}
}
}
Ok(bw)
}
pub fn profile(graph: &AdjacencyGraph, perm: &[usize]) -> SparseResult<usize> {
let n = graph.num_nodes();
if !perm.is_empty() && perm.len() != n {
return Err(SparseError::ValueError(format!(
"permutation length {} does not match graph size {}",
perm.len(),
n
)));
}
let inv_perm = if perm.is_empty() {
(0..n).collect::<Vec<_>>()
} else {
let mut inv = vec![0usize; n];
for (new_i, &old_i) in perm.iter().enumerate() {
inv[old_i] = new_i;
}
inv
};
let mut prof = 0usize;
for old_u in 0..n {
let new_u = inv_perm[old_u];
let mut min_col = new_u;
for &old_v in graph.neighbors(old_u) {
let new_v = inv_perm[old_v];
if new_v < min_col {
min_col = new_v;
}
}
prof += new_u - min_col;
}
Ok(prof)
}
pub fn cuthill_mckee(graph: &AdjacencyGraph) -> SparseResult<Vec<usize>> {
let n = graph.num_nodes();
if n == 0 {
return Ok(Vec::new());
}
let start = find_peripheral_node(graph);
let order = bfs_cm_order(graph, n, start);
debug_assert_eq!(order.len(), n);
Ok(order)
}
pub fn reverse_cuthill_mckee(graph: &AdjacencyGraph) -> SparseResult<Vec<usize>> {
let mut perm = cuthill_mckee(graph)?;
perm.reverse();
Ok(perm)
}
pub fn cuthill_mckee_full(graph: &AdjacencyGraph) -> SparseResult<CuthillMcKeeResult> {
let perm = cuthill_mckee(graph)?;
build_result(graph, perm)
}
pub fn reverse_cuthill_mckee_full(graph: &AdjacencyGraph) -> SparseResult<CuthillMcKeeResult> {
let perm = reverse_cuthill_mckee(graph)?;
build_result(graph, perm)
}
fn build_result(graph: &AdjacencyGraph, perm: Vec<usize>) -> SparseResult<CuthillMcKeeResult> {
let n = graph.num_nodes();
let bw_before = bandwidth(graph, &[])?;
let prof_before = profile(graph, &[])?;
let bw_after = bandwidth(graph, &perm)?;
let prof_after = profile(graph, &perm)?;
let mut inv_perm = vec![0usize; n];
for (new_i, &old_i) in perm.iter().enumerate() {
inv_perm[old_i] = new_i;
}
Ok(CuthillMcKeeResult {
perm,
inv_perm,
bandwidth_before: bw_before,
bandwidth_after: bw_after,
profile_before: prof_before,
profile_after: prof_after,
})
}
fn find_peripheral_node(graph: &AdjacencyGraph) -> usize {
let n = graph.num_nodes();
if n == 0 {
return 0;
}
let start = (0..n).min_by_key(|&v| graph.degree(v)).unwrap_or(0);
let levels = bfs_levels(graph, n, start);
let max_level = levels.iter().copied().max().unwrap_or(0);
let candidate = (0..n)
.filter(|&v| levels[v] == max_level)
.min_by_key(|&v| graph.degree(v))
.unwrap_or(start);
let levels2 = bfs_levels(graph, n, candidate);
let max_level2 = levels2.iter().copied().max().unwrap_or(0);
(0..n)
.filter(|&v| levels2[v] == max_level2)
.min_by_key(|&v| graph.degree(v))
.unwrap_or(candidate)
}
fn bfs_levels(graph: &AdjacencyGraph, n: usize, start: usize) -> Vec<usize> {
let mut level = vec![usize::MAX; n];
let mut queue = VecDeque::new();
level[start] = 0;
queue.push_back(start);
while let Some(node) = queue.pop_front() {
let l = level[node];
for &nbr in graph.neighbors(node) {
if level[nbr] == usize::MAX {
level[nbr] = l + 1;
queue.push_back(nbr);
}
}
}
level
}
fn bfs_cm_order(graph: &AdjacencyGraph, n: usize, start: usize) -> Vec<usize> {
let mut visited = vec![false; n];
let mut order = Vec::with_capacity(n);
let mut queue = VecDeque::new();
visited[start] = true;
queue.push_back(start);
bfs_cm_component(graph, &mut visited, &mut order, &mut queue);
for i in 0..n {
if !visited[i] {
visited[i] = true;
queue.push_back(i);
bfs_cm_component(graph, &mut visited, &mut order, &mut queue);
}
}
order
}
fn bfs_cm_component(
graph: &AdjacencyGraph,
visited: &mut [bool],
order: &mut Vec<usize>,
queue: &mut VecDeque<usize>,
) {
while let Some(node) = queue.pop_front() {
order.push(node);
let mut neighbors: Vec<usize> = graph
.neighbors(node)
.iter()
.copied()
.filter(|&nbr| !visited[nbr])
.collect();
neighbors.sort_unstable_by_key(|&v| graph.degree(v));
for nbr in neighbors {
if !visited[nbr] {
visited[nbr] = true;
queue.push_back(nbr);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn path_graph(n: usize) -> AdjacencyGraph {
let mut adj = vec![Vec::new(); n];
for i in 0..n.saturating_sub(1) {
adj[i].push(i + 1);
adj[i + 1].push(i);
}
AdjacencyGraph::from_adjacency_list(adj)
}
fn high_bandwidth_graph(n: usize) -> AdjacencyGraph {
let mut adj = vec![Vec::new(); n];
for i in 0..n.saturating_sub(1) {
adj[i].push(i + 1);
adj[i + 1].push(i);
}
for i in 0..n / 2 {
let j = n - 1 - i;
if i != j && !adj[i].contains(&j) {
adj[i].push(j);
adj[j].push(i);
}
}
for nbrs in adj.iter_mut() {
nbrs.sort_unstable();
nbrs.dedup();
}
AdjacencyGraph::from_adjacency_list(adj)
}
fn star_graph(n: usize) -> AdjacencyGraph {
let mut adj = vec![Vec::new(); n];
for i in 1..n {
adj[0].push(i);
adj[i].push(0);
}
AdjacencyGraph::from_adjacency_list(adj)
}
#[test]
fn test_cm_path_graph_bandwidth() {
let graph = path_graph(10);
let perm = cuthill_mckee(&graph).expect("CM should succeed");
let bw = bandwidth(&graph, &perm).expect("bandwidth");
assert_eq!(bw, 1, "path graph bandwidth should be 1 under CM");
}
#[test]
fn test_rcm_path_graph_bandwidth() {
let graph = path_graph(10);
let perm = reverse_cuthill_mckee(&graph).expect("RCM should succeed");
let bw = bandwidth(&graph, &perm).expect("bandwidth");
assert_eq!(bw, 1, "path graph bandwidth should be 1 under RCM");
}
#[test]
fn test_rcm_reduces_bandwidth_high_bw() {
let graph = high_bandwidth_graph(12);
let natural_bw = bandwidth(&graph, &[]).expect("natural bw");
let perm = reverse_cuthill_mckee(&graph).expect("RCM");
let rcm_bw = bandwidth(&graph, &perm).expect("rcm bw");
assert!(
rcm_bw <= natural_bw,
"RCM bandwidth {} should be <= natural bandwidth {}",
rcm_bw,
natural_bw
);
}
#[test]
fn test_cm_valid_permutation() {
let graph = star_graph(8);
let perm = cuthill_mckee(&graph).expect("CM");
assert_eq!(perm.len(), 8);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, (0..8).collect::<Vec<_>>());
}
#[test]
fn test_rcm_full_metrics() {
let graph = high_bandwidth_graph(10);
let result = reverse_cuthill_mckee_full(&graph).expect("RCM full");
assert!(result.bandwidth_after <= result.bandwidth_before);
assert!(result.profile_after <= result.profile_before || result.profile_after > 0);
assert_eq!(result.perm.len(), 10);
assert_eq!(result.inv_perm.len(), 10);
for (new_i, &old_i) in result.perm.iter().enumerate() {
assert_eq!(result.inv_perm[old_i], new_i);
}
}
#[test]
fn test_cm_empty_graph() {
let graph = AdjacencyGraph::from_adjacency_list(Vec::new());
let perm = cuthill_mckee(&graph).expect("CM empty");
assert!(perm.is_empty());
}
#[test]
fn test_cm_single_node() {
let graph = AdjacencyGraph::from_adjacency_list(vec![Vec::new()]);
let perm = cuthill_mckee(&graph).expect("CM single");
assert_eq!(perm, vec![0]);
}
#[test]
fn test_rcm_disconnected_graph() {
let mut adj = vec![Vec::new(); 6];
adj[0].push(1);
adj[1].push(0);
adj[1].push(2);
adj[2].push(1);
adj[3].push(4);
adj[4].push(3);
adj[4].push(5);
adj[5].push(4);
let graph = AdjacencyGraph::from_adjacency_list(adj);
let perm = reverse_cuthill_mckee(&graph).expect("RCM disconnected");
assert_eq!(perm.len(), 6);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, (0..6).collect::<Vec<_>>());
}
#[test]
fn test_bandwidth_profile_computation() {
let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
let graph = AdjacencyGraph::from_adjacency_list(adj);
let bw = bandwidth(&graph, &[]).expect("bandwidth");
assert_eq!(bw, 2); let prof = profile(&graph, &[]).expect("profile");
assert!(prof > 0);
}
}