use core::panic;
use std::{
cmp::Reverse,
ops::{Deref, DerefMut},
};
use crate::{
computation_mode::*,
datastructures::{AdjacencyMatrix, Edge, Graph, NAMatrix},
};
use delegate::delegate;
use nalgebra::{Dyn, U1};
use ordered_float::OrderedFloat;
use priority_queue::PriorityQueue;
use rayon::prelude::*;
pub fn prim<const MODE: usize>(graph: &NAMatrix) -> Graph {
match MODE {
SEQ_COMPUTATION => prim_with_excluded_node_single_threaded(graph, &[]),
PAR_COMPUTATION => prim_with_excluded_node_multi_threaded(graph, &[]),
#[cfg(feature = "mpi")]
MPI_COMPUTATION => {
eprintln!("Warning: defaulting to sequential implementation of prims algorithm");
prim::<SEQ_COMPUTATION>(graph)
}
_ => panic_on_invaid_mode::<MODE>(),
}
}
pub fn prim_with_excluded_node_multi_threaded(
graph: &NAMatrix,
excluded_vertices: &[usize],
) -> Graph {
prim_with_excluded_node::<MultiThreadedVecWrapper>(graph, excluded_vertices)
}
pub fn prim_with_excluded_node_single_threaded(
graph: &NAMatrix,
excluded_vertices: &[usize],
) -> Graph {
prim_with_excluded_node::<Vec<(Edge, bool)>>(graph, excluded_vertices)
}
pub fn prim_with_excluded_node_priority_queue(
graph: &NAMatrix,
excluded_vertices: &[usize],
) -> Graph {
prim_with_excluded_node::<VerticesInPriorityQueue>(graph, excluded_vertices)
}
fn prim_with_excluded_node<D: FindMinCostEdge>(
graph: &NAMatrix,
excluded_vertices: &[usize],
) -> Graph {
let num_vertices = graph.dim();
let unconnected_node = num_vertices;
let mut mst_adj_list: Vec<Vec<Edge>> = vec![Vec::new(); num_vertices + 1];
let mut dist_from_mst = D::from_default_value(
Edge {
cost: f64::INFINITY,
to: unconnected_node,
},
num_vertices + 1,
);
let start_index = {
let mut idx = 0;
while excluded_vertices.contains(&idx) {
idx += 1;
}
if idx >= num_vertices {
return vec![].into();
}
idx
};
dist_from_mst.set_cost(
start_index,
Edge {
to: start_index,
cost: 0.,
},
);
for &vertex in excluded_vertices {
dist_from_mst.set_excluded_vertex(vertex)
}
for _ in 0..=num_vertices {
let (next_vertex, next_edge) = dist_from_mst.find_edge_with_minimal_cost();
if next_edge.cost == f64::INFINITY {
break;
}
dist_from_mst.mark_vertex_as_used(next_vertex);
if next_vertex != start_index {
let reverse_edge = Edge {
to: next_vertex,
cost: next_edge.cost,
};
let connection_from = next_edge.to;
let connection_to = next_vertex;
mst_adj_list[connection_to].push(next_edge);
mst_adj_list[connection_from].push(reverse_edge);
}
dist_from_mst.update_minimal_cost(next_vertex, graph.row(next_vertex))
}
mst_adj_list.pop();
Graph::from(mst_adj_list)
}
type NAMatrixRowView<'a> =
nalgebra::Matrix<f64, U1, Dyn, nalgebra::ViewStorage<'a, f64, U1, Dyn, U1, Dyn>>;
trait FindMinCostEdge {
fn from_default_value(default_val: Edge, size: usize) -> Self;
fn find_edge_with_minimal_cost(&self) -> (usize, Edge);
fn update_minimal_cost(&mut self, from: usize, new_neighbours: NAMatrixRowView);
fn set_cost(&mut self, from: usize, edge_to: Edge);
fn set_excluded_vertex(&mut self, excluded_vertex: usize);
fn mark_vertex_as_used(&mut self, used_vertex: usize);
}
#[derive(Clone, Debug, PartialEq)]
struct VerticesInPriorityQueue {
cost_queue: PriorityQueue<usize, Reverse<OrderedFloat<f64>>>,
connection_to_mst: Vec<usize>,
used: Vec<bool>,
}
impl FindMinCostEdge for VerticesInPriorityQueue {
fn from_default_value(default_val: Edge, size: usize) -> Self {
VerticesInPriorityQueue {
cost_queue: PriorityQueue::from(
(0..size)
.map(|i| (i, Reverse(OrderedFloat(default_val.cost))))
.collect::<Vec<(usize, Reverse<OrderedFloat<f64>>)>>(),
),
connection_to_mst: vec![default_val.to; size],
used: vec![false; size],
}
}
fn find_edge_with_minimal_cost(&self) -> (usize, Edge) {
let base_case = Edge {
to: self.connection_to_mst.len(),
cost: f64::INFINITY,
};
let (&next_vertex, &Reverse(OrderedFloat(cost))) = self
.cost_queue
.peek()
.unwrap_or((&base_case.to, &Reverse(OrderedFloat(base_case.cost))));
let to = self.connection_to_mst[next_vertex];
(next_vertex, Edge { to, cost })
}
fn update_minimal_cost(&mut self, from: usize, new_neighbours: NAMatrixRowView) {
for (to, &cost) in new_neighbours.iter().enumerate() {
if self.used[to] {
continue;
}
let Reverse(OrderedFloat(old_cost)) = self.cost_queue
.push_increase(to, Reverse(OrderedFloat(cost)))
.unwrap_or_else(|| panic!("Every unused unused vertex shall be contained in the queue from the beginning. Missing vertex: {}", to));
if cost <= old_cost {
self.connection_to_mst[to] = from;
}
}
}
fn set_cost(&mut self, from: usize, edge_to: Edge) {
self.cost_queue
.change_priority(&from, Reverse(OrderedFloat(edge_to.cost)));
self.connection_to_mst[from] = edge_to.to;
}
fn set_excluded_vertex(&mut self, excluded_vertex: usize) {
self.mark_vertex_as_used(excluded_vertex);
}
fn mark_vertex_as_used(&mut self, used_vertex: usize) {
self.cost_queue.remove(&used_vertex);
self.used[used_vertex] = true;
}
}
impl FindMinCostEdge for Vec<(Edge, bool)> {
fn from_default_value(default_val: Edge, size: usize) -> Self {
vec![(default_val, false); size]
}
fn find_edge_with_minimal_cost(&self) -> (usize, Edge) {
let base_case = Edge {
to: self.len(),
cost: f64::INFINITY,
};
let (next_vertex, reverse_edge) = self
.iter()
.enumerate()
.filter_map(
|(i, &(edge, used_in_mst))| if used_in_mst { None } else { Some((i, edge)) },
)
.min_by(|&(_, edg_i), &(_, edg_j)| {
OrderedFloat(edg_i.cost).cmp(&OrderedFloat(edg_j.cost))
})
.unwrap_or((base_case.to, base_case));
(next_vertex, reverse_edge)
}
fn update_minimal_cost(&mut self, from: usize, new_neighbours: NAMatrixRowView) {
for (to, &cost) in new_neighbours.iter().enumerate() {
if cost < self[to].0.cost {
self[to].0 = Edge { to: from, cost };
}
}
}
fn set_cost(&mut self, from: usize, edge_to: Edge) {
self[from].0 = edge_to;
}
fn mark_vertex_as_used(&mut self, used_vertex: usize) {
self[used_vertex].1 = true;
}
fn set_excluded_vertex(&mut self, excluded_vertex: usize) {
self.mark_vertex_as_used(excluded_vertex);
}
}
#[derive(Debug, PartialEq)]
struct MultiThreadedVecWrapper(Vec<(Edge, bool)>);
impl Deref for MultiThreadedVecWrapper {
type Target = Vec<(Edge, bool)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for MultiThreadedVecWrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl FindMinCostEdge for MultiThreadedVecWrapper {
fn from_default_value(default_val: Edge, size: usize) -> Self {
MultiThreadedVecWrapper(Vec::from_default_value(default_val, size))
}
delegate! {
to self.0 {
fn set_cost(&mut self, from: usize, edge_to: Edge);
fn set_excluded_vertex(&mut self, excluded_vertex: usize);
fn mark_vertex_as_used(&mut self, used_vertex: usize);
}
}
fn update_minimal_cost(&mut self, from: usize, new_neighbours: NAMatrixRowView) {
let dim = new_neighbours.shape().1;
(0..dim).into_par_iter().for_each(|to| {
let neighbour_prt = new_neighbours.as_ptr() as *mut f64;
let cost = unsafe { *neighbour_prt.add(dim * to) };
let to_dist_ptr = self.as_ptr() as *mut (Edge, bool);
if cost < self[to].0.cost {
unsafe {
(*to_dist_ptr.add(to)).0 = Edge { to: from, cost };
}
}
});
}
fn find_edge_with_minimal_cost(&self) -> (usize, Edge) {
let base_case = Edge {
to: self.0.len(),
cost: f64::INFINITY,
};
let (next_vertex, reverse_edge) = self
.0
.par_iter()
.enumerate()
.filter_map(
|(i, &(edge, used_in_mst))| if used_in_mst { None } else { Some((i, edge)) },
)
.min_by(|&(_, edg_i), &(_, edg_j)| {
OrderedFloat(edg_i.cost).cmp(&OrderedFloat(edg_j.cost))
})
.unwrap_or((base_case.to, base_case));
(next_vertex, reverse_edge)
}
}
#[cfg(test)]
mod test {
use std::assert_eq;
use nalgebra::DMatrix;
use super::*;
#[test]
fn easy_prim() {
let graph = Graph::from(vec![
vec![Edge { to: 1, cost: 1.0 }],
vec![Edge { to: 0, cost: 1.0 }],
]);
let mst = prim::<SEQ_COMPUTATION>(&(&graph).into());
assert_eq!(graph, mst);
}
#[test]
fn four_vertices_mst_prim() {
let graph = Graph::from(vec![
vec![
Edge { to: 1, cost: 1.0 },
Edge { to: 2, cost: 0.1 },
Edge { to: 3, cost: 2.0 },
],
vec![
Edge { to: 0, cost: 1.0 },
Edge { to: 2, cost: 5.0 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 0.1 },
Edge { to: 1, cost: 1.1 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 2.0 },
Edge { to: 1, cost: 0.1 },
Edge { to: 2, cost: 0.1 },
],
]);
let expected = Graph::from(vec![
vec![Edge { to: 2, cost: 0.1 }],
vec![Edge { to: 3, cost: 0.1 }],
vec![Edge { to: 0, cost: 0.1 }, Edge { to: 3, cost: 0.1 }],
vec![Edge { to: 2, cost: 0.1 }, Edge { to: 1, cost: 0.1 }],
]);
assert_eq!(expected, prim::<SEQ_COMPUTATION>(&(&graph).into()));
}
#[test]
fn exclude_one_vertex_from_mst() {
let graph = Graph::from(vec![
vec![
Edge { to: 1, cost: 1.0 },
Edge { to: 2, cost: 0.1 },
Edge { to: 3, cost: 2.0 },
],
vec![
Edge { to: 0, cost: 1.0 },
Edge { to: 2, cost: 5.0 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 0.1 },
Edge { to: 1, cost: 1.1 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 2.0 },
Edge { to: 1, cost: 0.1 },
Edge { to: 2, cost: 0.1 },
],
]);
let expected = Graph::from(vec![
vec![],
vec![Edge { to: 3, cost: 0.1 }],
vec![Edge { to: 3, cost: 0.1 }],
vec![Edge { to: 1, cost: 0.1 }, Edge { to: 2, cost: 0.1 }],
]);
assert_eq!(
expected,
prim_with_excluded_node_multi_threaded(&(&graph).into(), &[0])
);
}
#[test]
fn prim_all_versions_agree() {
let graph = Graph::from(vec![
vec![
Edge { to: 1, cost: 1.0 },
Edge { to: 2, cost: 0.1 },
Edge { to: 3, cost: 2.0 },
],
vec![
Edge { to: 0, cost: 1.0 },
Edge { to: 2, cost: 5.0 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 0.1 },
Edge { to: 1, cost: 1.1 },
Edge { to: 3, cost: 0.1 },
],
vec![
Edge { to: 0, cost: 2.0 },
Edge { to: 1, cost: 0.1 },
Edge { to: 2, cost: 0.1 },
],
]);
let excluded_vertex = &[0];
let res_st = prim_with_excluded_node_single_threaded(&(&graph).into(), excluded_vertex);
let res_mt = prim_with_excluded_node_multi_threaded(&(&graph).into(), excluded_vertex);
let res_prio = prim_with_excluded_node_priority_queue(&(&graph).into(), excluded_vertex);
assert_eq!(
res_st, res_mt,
"single_threaded should agree with multi_threaded"
);
assert_eq!(
res_st, res_prio,
"single_threaded should agree with priority queue version"
);
}
#[test]
fn test_vertices_in_priority_queue_from_default_value() {
let default_val = Edge {
to: 3,
cost: f64::INFINITY,
};
let size = 5;
let vert = VerticesInPriorityQueue::from_default_value(default_val, size);
let mut queue = PriorityQueue::new();
for i in 0..size {
queue.push(i, Reverse(OrderedFloat(f64::INFINITY)));
}
assert_eq!(vert.cost_queue, queue);
assert_eq!(vert.cost_queue.into_vec(), vec![0, 1, 2, 3, 4]);
assert_eq!(vert.connection_to_mst, vec![3; 5])
}
#[test]
fn test_vertices_in_priority_queue_increase_priority() {
let default_val = Edge {
to: 4,
cost: f64::INFINITY,
};
let size = 5;
let mut vert = VerticesInPriorityQueue::from_default_value(default_val, size);
let res = vert.cost_queue.push_increase(0, Reverse(OrderedFloat(1.0)));
assert_eq!(res, Some(Reverse(OrderedFloat(f64::INFINITY))));
}
#[test]
fn test_vertices_in_priority_queue_update_priority_does_not_panic() {
let default_val = Edge {
to: 4,
cost: f64::INFINITY,
};
let size = 5;
let mut vert = VerticesInPriorityQueue::from_default_value(default_val, size);
let mat = DMatrix::from_row_slice(1, size, &[1.0; 5]);
vert.update_minimal_cost(0, mat.row(0));
}
#[test]
fn test_vertices_in_priority_queue_update_priority_works() {
let default_val = Edge {
to: 4,
cost: f64::INFINITY,
};
let size = 5;
let mut vert = VerticesInPriorityQueue::from_default_value(default_val, size);
let mat = DMatrix::from_row_slice(1, size, &[0.0, 1.0, 0.0, 0.0, 0.0]);
vert.update_minimal_cost(0, mat.row(0));
assert_eq!(vert.connection_to_mst[1], 0);
assert_eq!(
vert.cost_queue.get_priority(&1),
Some(&Reverse(OrderedFloat(1.0f64)))
);
}
}