use crate::indexing::SpIndex;
use crate::sparse::permutation::PermOwnedI;
use crate::sparse::symmetric::is_symmetric;
use crate::sparse::CsMatViewI;
use std::collections::vec_deque::VecDeque;
pub struct Ordering<I> {
pub perm: PermOwnedI<I>,
pub connected_parts: Vec<usize>,
}
pub mod start {
use crate::indexing::SpIndex;
use crate::sparse::CsMatViewI;
pub trait Strategy<N, I, Iptr>
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
fn find_start_vertex(
&mut self,
visited: &[bool],
degrees: &[usize],
mat: &CsMatViewI<N, I, Iptr>,
) -> usize;
}
pub struct Next();
impl<N, I, Iptr> Strategy<N, I, Iptr> for Next
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
fn find_start_vertex(
&mut self,
visited: &[bool],
_degrees: &[usize],
_mat: &CsMatViewI<N, I, Iptr>,
) -> usize {
visited
.iter()
.enumerate()
.find_map(|(i, &a)| if a { None } else { Some(i) })
.expect(
"There should always be a unvisited vertex left to choose",
)
}
}
pub struct MinimumDegree();
impl<N, I, Iptr> Strategy<N, I, Iptr> for MinimumDegree
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
fn find_start_vertex(
&mut self,
visited: &[bool],
degrees: &[usize],
_mat: &CsMatViewI<N, I, Iptr>,
) -> usize {
visited
.iter()
.enumerate()
.filter(|(_i, &a)| !a)
.min_by_key(|(i, _a)| degrees[*i])
.map(|(i, _a)| i)
.expect(
"There should always be a unvisited vertex left to choose",
)
}
}
#[derive(Default)]
pub struct PseudoPeripheral();
impl PseudoPeripheral {
#[inline]
pub fn new() -> Self {
Self::default()
}
fn rls_contender_and_height<N, I, Iptr>(
&mut self,
root: usize,
degrees: &[usize],
mat: &CsMatViewI<N, I, Iptr>,
) -> (usize, usize)
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
let nb_vertices = degrees.len();
let mut visited = vec![false; nb_vertices];
let mut rls = Vec::with_capacity(nb_vertices);
visited[root] = true;
rls.push(root);
let mut rls_index = 0;
let mut height = 0;
let mut current_level_countdown = 1;
let mut next_level_countup = 0;
let mut last_level_len = 1;
while rls_index < rls.len() {
let parent = rls[rls_index];
current_level_countdown -= 1;
let outer = mat.outer_view(parent.index()).unwrap();
for &neighbor in outer.indices() {
if !visited[neighbor.index()] {
visited[neighbor.index()] = true;
next_level_countup += 1;
rls.push(neighbor.index());
}
}
if current_level_countdown == 0 {
if next_level_countup > 0 {
last_level_len = next_level_countup;
}
current_level_countdown = next_level_countup;
next_level_countup = 0;
height += 1;
}
rls_index += 1;
}
let rls_len = rls.len();
let last_level_start_index = rls_len - last_level_len;
let contender = rls[last_level_start_index..rls_len]
.iter()
.min_by_key(|i| degrees[i.index()])
.copied()
.unwrap();
(contender, height)
}
}
impl<N, I, Iptr> Strategy<N, I, Iptr> for PseudoPeripheral
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
fn find_start_vertex(
&mut self,
visited: &[bool],
degrees: &[usize],
mat: &CsMatViewI<N, I, Iptr>,
) -> usize {
let mut current = visited
.iter()
.enumerate()
.find_map(|(i, &a)| if a { None } else { Some(i) })
.expect(
"There should always be a unvisited vertex left to choose",
);
if degrees[current] == 0 {
return current;
}
let (mut contender, mut current_height) =
self.rls_contender_and_height(current, degrees, mat);
loop {
let (contender_contender, contender_height) =
self.rls_contender_and_height(contender, degrees, mat);
if contender_height > current_height {
current_height = contender_height;
current = contender;
contender = contender_contender;
} else {
return current;
}
}
}
}
}
pub mod order {
use super::Ordering;
use crate::indexing::SpIndex;
use crate::sparse::permutation::PermOwnedI;
pub trait DirectedOrdering<I: SpIndex> {
fn prepare(&mut self, nb_vertices: usize);
fn add_transposition(&mut self, vertex_index: usize);
fn add_component_delimeter(&mut self, index: usize);
fn into_ordering(self) -> Ordering<I>;
}
#[derive(Default)]
pub struct Forward<I: SpIndex> {
perm: Vec<I>,
connected_parts: Vec<usize>,
}
impl<I: SpIndex> Forward<I> {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<I: SpIndex> DirectedOrdering<I> for Forward<I> {
#[inline]
fn prepare(&mut self, nb_vertices: usize) {
self.perm.reserve(nb_vertices);
self.connected_parts.reserve(nb_vertices / 16 + 1);
}
#[inline]
fn add_transposition(&mut self, vertex_index: usize) {
self.perm.push(I::from_usize(vertex_index));
}
#[inline]
fn add_component_delimeter(&mut self, index: usize) {
self.connected_parts.push(index);
}
#[inline]
fn into_ordering(self) -> Ordering<I> {
debug_assert!(crate::perm_is_valid(&self.perm));
Ordering {
perm: PermOwnedI::new_trusted(self.perm),
connected_parts: self.connected_parts,
}
}
}
#[derive(Default)]
pub struct Reversed<I: SpIndex> {
perm: Vec<I>,
connected_parts: Vec<usize>,
nb_vertices: usize,
count: usize,
}
impl<I: SpIndex> Reversed<I> {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<I: SpIndex> DirectedOrdering<I> for Reversed<I> {
#[inline]
fn prepare(&mut self, nb_vertices: usize) {
self.perm = vec![I::default(); nb_vertices];
self.connected_parts = Vec::with_capacity(nb_vertices / 16 + 1);
self.nb_vertices = nb_vertices;
}
#[inline]
fn add_transposition(&mut self, vertex_index: usize) {
self.perm[self.nb_vertices - self.count - 1] =
I::from_usize(vertex_index);
self.count += 1;
}
#[inline]
fn add_component_delimeter(&mut self, index: usize) {
self.connected_parts.push(index);
}
#[inline]
fn into_ordering(self) -> Ordering<I> {
let nb_vertices = self.nb_vertices;
let mut connected_parts = self.connected_parts;
connected_parts
.iter_mut()
.for_each(|i| *i = nb_vertices - *i);
connected_parts.reverse();
debug_assert!(crate::perm_is_valid(&self.perm));
Ordering {
perm: PermOwnedI::new_trusted(self.perm),
connected_parts,
}
}
}
}
pub fn cuthill_mckee_custom<N, I, Iptr, S, D>(
mat: CsMatViewI<N, I, Iptr>,
mut starting_strategy: S,
mut directed_ordering: D,
) -> Ordering<I>
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
S: start::Strategy<N, I, Iptr>,
D: order::DirectedOrdering<I>,
{
debug_assert!(is_symmetric(&mat));
assert_eq!(mat.cols(), mat.rows());
let nb_vertices = mat.cols();
let degrees = mat.degrees();
let max_neighbors = degrees.iter().max().copied().unwrap_or(0);
directed_ordering.prepare(nb_vertices);
let mut deque = VecDeque::with_capacity(nb_vertices);
let mut neighbors = Vec::with_capacity(max_neighbors);
let mut visited = vec![false; nb_vertices];
for perm_index in 0..nb_vertices {
let current_vertex = deque.pop_front().unwrap_or_else(|| {
directed_ordering.add_component_delimeter(perm_index);
let new_start_vertex = starting_strategy.find_start_vertex(
&visited, °rees, &mat
);
assert!(
!visited[new_start_vertex],
"Vertex returned by starting strategy should always be unvisited"
);
new_start_vertex
});
directed_ordering.add_transposition(current_vertex);
visited[current_vertex.index()] = true;
let outer = mat.outer_view(current_vertex.index()).unwrap();
neighbors.clear();
for &neighbor in outer.indices() {
if !visited[neighbor.index()] {
neighbors.push((degrees[neighbor.index()], neighbor));
visited[neighbor.index()] = true;
}
}
neighbors.sort_unstable_by_key(|&(deg, _)| deg);
for (_deg, neighbor) in &neighbors {
deque.push_back(neighbor.index());
}
}
directed_ordering.add_component_delimeter(nb_vertices);
directed_ordering.into_ordering()
}
pub fn reverse_cuthill_mckee<N, I, Iptr>(
mat: CsMatViewI<N, I, Iptr>,
) -> Ordering<I>
where
N: PartialEq,
I: SpIndex,
Iptr: SpIndex,
{
cuthill_mckee_custom(
mat,
start::PseudoPeripheral::default(),
order::Reversed::new(),
)
}
#[cfg(test)]
mod test {
use super::{cuthill_mckee_custom, order, reverse_cuthill_mckee, start};
use crate::sparse::permutation::Permutation;
use crate::sparse::CsMat;
fn unconnected_graph_lap() -> CsMat<f64> {
let x = -1.;
#[rustfmt::skip]
let lap_mat = CsMat::new(
(12, 12),
vec![0, 4, 8, 12, 16, 20, 29, 31, 33, 37, 40, 44, 48],
vec![0, 4, 5, 8,
1, 5, 8, 10,
2, 3, 4, 5,
2, 3, 5, 11,
0, 2, 4, 5,
0, 1, 2, 3, 4, 5, 8, 10, 11,
6, 9,
7, 9,
0, 1, 5, 8,
6, 7, 9,
1, 5, 10, 11,
3, 5, 10, 11],
vec![3., x, x, x,
3., x, x, x,
3., x, x, x,
x, 3., x, x,
x, x, 3., x,
x, x, x, x, x, 8., x, x, x,
1., x,
1., x,
x, x, x, 3.,
x, x, 2.,
x, x, 3., x,
x, x, x, 3.],
);
lap_mat
}
#[test]
fn reverse_cuthill_mckee_unconnected_graph_lap_components() {
let lap_mat = unconnected_graph_lap();
let ordering = reverse_cuthill_mckee(lap_mat.view());
assert_eq!(&ordering.connected_parts, &[0, 3, 12],);
}
#[test]
fn reverse_cuthill_mckee_unconnected_graph_lap_perm() {
let lap_mat = unconnected_graph_lap();
let ordering = reverse_cuthill_mckee(lap_mat.view());
let correct_perm =
Permutation::new(vec![7, 9, 6, 11, 10, 3, 1, 2, 5, 8, 4, 0]);
assert_eq!(&ordering.perm.vec(), &correct_perm.vec());
}
#[test]
fn reverse_cuthill_mckee_eye() {
let mat = CsMat::<f64>::eye(3);
let ordering = reverse_cuthill_mckee(mat.view());
let correct_perm = Permutation::new(vec![2, 1, 0]);
assert_eq!(&ordering.perm.vec(), &correct_perm.vec());
}
#[test]
fn cuthill_mckee_eye() {
let mat = CsMat::<f64>::eye(3);
let ordering = cuthill_mckee_custom(
mat.view(),
start::PseudoPeripheral::new(),
order::Forward::new(),
);
let correct_perm = Permutation::new(vec![0, 1, 2]);
assert_eq!(&ordering.perm.vec(), &correct_perm.vec());
}
}