use nalgebra::DMatrix;
use rayon::prelude::*;
use crate::{
computation_mode::*,
datastructures::{AdjacencyMatrix, Edge, Graph, NAMatrix, Solution},
mst::prim,
solvers::{approximate::matching::approx_min_cost_matching, is_hamiltonian_cycle},
};
pub fn christofides<const MODE: usize>(graph: &NAMatrix) -> Solution {
christofides_generic::<MODE>(graph, compute_approx_matching::<MODE>)
}
pub fn christofides_generic<const MODE: usize>(
graph: &NAMatrix,
matching_computer: fn(&Graph, &NAMatrix) -> Vec<[usize; 2]>,
) -> Solution {
let graph_matr = graph;
debug_assert!(
graph_matr.is_metric(),
"The given Graph is not metric, but Christofides Algorithm
// requires the triangle inequality"
);
let mst = prim::<MODE>(graph_matr);
let matching = matching_computer(&mst, graph_matr);
let multigraph = fill_multigraph_with_mst_and_matching::<MODE>(graph_matr, &mst, matching);
let mut euler_cycle = eulerian_cycle_from_multigraph(multigraph);
hamiltonian_from_eulerian_cycle(graph_matr.dim(), &mut euler_cycle);
let hamilton_cycle = euler_cycle;
let sum_cost: f64 = hamilton_cycle
.windows(2)
.map(|window| graph_matr[(window[0], window[1])])
.sum();
debug_assert!(is_hamiltonian_cycle(hamilton_cycle.as_slice(), graph_matr));
(sum_cost, hamilton_cycle)
}
#[inline]
fn compute_approx_matching<const MODE: usize>(mst: &Graph, graph: &NAMatrix) -> Vec<[usize; 2]> {
let subgraph: Vec<_> = mst
.iter()
.enumerate()
.filter_map(|(i, vertex)| {
if vertex.degree() % 2 == 1 {
Some(i)
} else {
None
}
})
.collect();
let tries = subgraph.len().pow(2);
approx_min_cost_matching::<MODE>(graph, subgraph, tries)
}
fn eulerian_cycle_from_multigraph(mut multigraph: DMatrix<(f64, usize)>) -> Vec<usize> {
let dim = multigraph.shape().0;
let mut euler_cycle = Vec::with_capacity(2 * dim + 1);
let mut degree: Vec<usize> = multigraph
.column_iter()
.map(|col| col.into_iter().map(|&(_, times)| times).sum())
.collect();
euler_cycle.push(0);
while let Some((vertex_idx_in_cycle, &vertex)) = euler_cycle
.iter()
.enumerate()
.find(|(_idx, &vertex)| degree[vertex] != 0)
{
let cycle = find_cycle(vertex, &mut multigraph, &mut degree);
let tail = euler_cycle.split_off(vertex_idx_in_cycle + 1);
euler_cycle.extend_from_slice(&cycle[1..]);
euler_cycle.extend_from_slice(&tail);
}
euler_cycle
}
fn find_cycle(
start_vertex: usize,
multigraph: &mut DMatrix<(f64, usize)>,
degree: &mut [usize],
) -> Vec<usize> {
let mut cycle = Vec::new();
let mut vertex = start_vertex;
cycle.push(vertex);
while let Some((idx, neighbour)) = multigraph
.row_mut(vertex)
.iter_mut()
.enumerate()
.find(|(_idx, &mut (_cost, times))| times != 0)
{
cycle.push(idx);
debug_assert!(
neighbour.1 > 0,
"There should exist an edge from vertex {} to {}",
vertex,
idx
);
neighbour.1 -= 1;
debug_assert!(
multigraph[(idx, vertex)].1 > 0,
"There should exist an edge from vertex {} to {}",
vertex,
idx
);
multigraph[(idx, vertex)].1 -= 1;
debug_assert!(
degree[vertex] > 0,
"The degree of the vertex {} should be more than 0",
vertex
);
degree[vertex] -= 1;
debug_assert!(
degree[idx] > 0,
"The degree of the vertex {} should be more than 0",
idx
);
degree[idx] -= 1;
vertex = idx;
}
cycle
}
fn hamiltonian_from_eulerian_cycle(dim: usize, euler_cycle: &mut Vec<usize>) {
debug_assert!(
dim >= *euler_cycle
.iter()
.max()
.expect("The eulerian cycle shall not be empty")
);
let mut visited = vec![false; dim];
let mut is_in_ham_cycle: Vec<(usize, bool)> = euler_cycle.iter().map(|&i| (i, false)).collect();
for pair in is_in_ham_cycle.iter_mut() {
let i = pair.0;
let in_cycle = &mut pair.1;
if !visited[i] {
visited[i] = true;
*in_cycle = true;
}
}
let mut is_in_cycle_iter = is_in_ham_cycle.into_iter();
euler_cycle.retain(|&_| {
let (_, in_cycle) = is_in_cycle_iter.next().unwrap();
in_cycle
});
euler_cycle.push(
*euler_cycle
.first()
.expect("the hamiltonian cycle should not be empty"),
);
}
#[inline]
fn fill_multigraph_with_mst_and_matching<const MODE: usize>(
base_graph: &NAMatrix,
mst: &Graph,
matching: Vec<[usize; 2]>,
) -> DMatrix<(f64, usize)> {
#[cfg(feature = "mpi")]
if MODE == MPI_COMPUTATION {
return fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(base_graph, mst, matching);
}
let mut multigraph = base_graph.map(|cost| (cost, 0));
match MODE {
SEQ_COMPUTATION => {
mst.iter()
.zip(multigraph.row_iter_mut())
.for_each(|(vertex, mut neighbours_vec)| {
vertex
.iter()
.for_each(|&Edge { to, cost }| neighbours_vec[(0, to)] = (cost, 1))
})
}
PAR_COMPUTATION => {
mst.par_iter()
.zip(multigraph.par_column_iter_mut())
.for_each(|(vertex, mut neighbours_vec)| {
vertex
.iter()
.for_each(|&Edge { to, cost }| neighbours_vec[(to, 0)] = (cost, 1))
});
}
#[cfg(feature = "mpi")]
MPI_COMPUTATION => unreachable!("On MPI the SEQ_COMPUTATION variant is used"),
_ => panic_on_invaid_mode::<MODE>(),
}
match MODE {
SEQ_COMPUTATION => matching.into_iter().for_each(|[i, j]| {
multigraph[(i, j)].1 += 1;
multigraph[(j, i)].1 += 1;
}),
PAR_COMPUTATION => {
let dim = multigraph.shape().0;
let multigraph_slice = multigraph.as_slice();
unsafe {
matching.into_par_iter().for_each(|[i, j]| {
let multigraph_prt = multigraph_slice.as_ptr() as *mut (f64, usize);
(*multigraph_prt.add(j * dim + i)).1 += 1;
(*multigraph_prt.add(i * dim + j)).1 += 1;
})
}
}
#[cfg(feature = "mpi")]
MPI_COMPUTATION => unreachable!("On MPI the SEQ_COMPUTATION variant is used"),
_ => panic_on_invaid_mode::<MODE>(),
}
multigraph
}
#[cfg(test)]
mod test {
use nalgebra::DMatrix;
use crate::{
computation_mode::*,
datastructures::{Edge, Graph, NAMatrix},
mst::prim,
solvers::{
self,
approximate::christofides::{
christofides, eulerian_cycle_from_multigraph,
fill_multigraph_with_mst_and_matching, find_cycle, hamiltonian_from_eulerian_cycle,
},
},
};
#[test]
fn multigraph_can_represent_the_mst_with_empty_matching() {
let matching = vec![];
let graph = NAMatrix(DMatrix::from_row_slice(
3,
3,
&[0., 1., 2., 1., 0., 3., 2., 3., 0.],
));
let mst = prim::<SEQ_COMPUTATION>(&graph);
let expected = DMatrix::from_row_slice(
3,
3,
&[
(0., 0),
(1., 1),
(2., 1),
(1., 1),
(0., 0),
(3., 0),
(2., 1),
(3., 0),
(0., 0),
],
);
assert_eq!(
expected,
fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(&graph, &mst, matching)
);
}
#[test]
fn multigraph_can_represent_empty_mst_with_matching() {
let matching = vec![[1, 0], [2, 0]];
let graph = NAMatrix(DMatrix::from_row_slice(
3,
3,
&[0., 1., 2., 1., 0., 3., 2., 3., 0.],
));
let mst = Graph::from(vec![]);
let expected = DMatrix::from_row_slice(
3,
3,
&[
(0., 0),
(1., 1),
(2., 1),
(1., 1),
(0., 0),
(3., 0),
(2., 1),
(3., 0),
(0., 0),
],
);
assert_eq!(
expected,
fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(&graph, &mst, matching)
);
}
#[test]
fn multigraph_can_represent_the_mst_with_matching() {
let matching = vec![[1, 0], [2, 0]];
let graph = NAMatrix(DMatrix::from_row_slice(
3,
3,
&[0., 1., 2., 1., 0., 3., 2., 3., 0.],
));
let mst = prim::<SEQ_COMPUTATION>(&graph);
let expected = DMatrix::from_row_slice(
3,
3,
&[
(0., 0),
(1., 2),
(2., 2),
(1., 2),
(0., 0),
(3., 0),
(2., 2),
(3., 0),
(0., 0),
],
);
assert_eq!(
expected,
fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(&graph, &mst, matching)
);
}
#[test]
fn fill_multigraph_with_mst_and_matching_seq_and_par_agree() {
let matching = vec![[1, 0], [2, 0]];
let graph = NAMatrix(DMatrix::from_row_slice(
3,
3,
&[0., 1., 2., 1., 0., 3., 2., 3., 0.],
));
let mst = prim::<SEQ_COMPUTATION>(&graph);
let expected = DMatrix::from_row_slice(
3,
3,
&[
(0., 0),
(1., 2),
(2., 2),
(1., 2),
(0., 0),
(3., 0),
(2., 2),
(3., 0),
(0., 0),
],
);
assert_eq!(
fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(
&graph,
&mst,
matching.clone()
),
fill_multigraph_with_mst_and_matching::<PAR_COMPUTATION>(
&graph,
&mst,
matching.clone()
)
);
assert_eq!(
expected,
fill_multigraph_with_mst_and_matching::<SEQ_COMPUTATION>(&graph, &mst, matching,),
);
}
#[test]
fn test_find_cycle() {
let mut multigraph = DMatrix::from_row_slice(
4,
4,
&[
(0., 0),
(1., 1),
(0., 0),
(1., 1),
(1., 1),
(0., 0),
(1., 1),
(0., 0),
(0., 0),
(1., 1),
(0., 0),
(1., 1),
(1., 1),
(0., 0),
(1., 1),
(0., 0),
],
);
let mut degree = vec![2; 4];
let expected = vec![0, 1, 2, 3, 0];
assert_eq!(expected, find_cycle(0, &mut multigraph, &mut degree))
}
#[test]
fn test_find_eulerian_cycle() {
let multigraph = DMatrix::from_row_slice(
4,
4,
&[
(0., 0),
(1., 2),
(1., 1),
(1., 1),
(1., 2),
(0., 0),
(1., 1),
(1., 1),
(1., 1),
(1., 1),
(0., 0),
(1., 2),
(1., 1),
(1., 1),
(1., 2),
(0., 0),
],
);
let expected = 9;
assert_eq!(expected, eulerian_cycle_from_multigraph(multigraph).len());
}
#[test]
fn test_hamiltonian_from_eulerian_cycle() {
let mut euler_cycle = vec![0, 1, 2, 3, 0, 1, 3, 2, 0];
let expected = vec![0, 1, 2, 3, 0];
hamiltonian_from_eulerian_cycle(4, &mut euler_cycle);
assert_eq!(expected, euler_cycle);
}
#[test]
fn test_christofides_against_exact_solver() {
let graph: Graph = vec![
vec![
Edge { to: 0, cost: 0. },
Edge { to: 1, cost: 1. },
Edge { to: 2, cost: 2. },
Edge { to: 3, cost: 1. },
],
vec![
Edge { to: 0, cost: 1. },
Edge { to: 1, cost: 0. },
Edge { to: 2, cost: 1. },
Edge { to: 3, cost: 2. },
],
vec![
Edge { to: 0, cost: 2. },
Edge { to: 1, cost: 1. },
Edge { to: 2, cost: 0. },
Edge { to: 3, cost: 1. },
],
vec![
Edge { to: 0, cost: 1. },
Edge { to: 1, cost: 2. },
Edge { to: 2, cost: 1. },
Edge { to: 3, cost: 0. },
],
]
.into();
let exact_solution = solvers::exact::first_improved_solver::<NAMatrix>(&(&graph).into());
let result = christofides::<SEQ_COMPUTATION>(&(&graph).into());
assert!(
exact_solution.0 <= result.0,
"Christofides algorithm cannot outperfrom the exact solution"
);
assert!(
1.5 * exact_solution.0 >= result.0,
"Christofides algorithm is at most 50% worse than the exact solution"
);
}
}