use crate::core::error::{GraphinaError, Result};
use crate::core::paths::dijkstra;
use crate::core::types::{BaseGraph, GraphConstructor, NodeId};
use ordered_float::OrderedFloat;
use std::collections::HashSet;
pub fn christofides<A, Ty>(graph: &BaseGraph<A, f64, Ty>) -> Result<(Vec<NodeId>, f64)>
where
A: Clone,
Ty: GraphConstructor<A, f64> + GraphConstructor<A, OrderedFloat<f64>>,
{
let start_node = graph
.nodes()
.next()
.map(|(u, _)| u)
.ok_or_else(|| GraphinaError::invalid_graph("Cannot run TSP on an empty graph."))?;
greedy_tsp(&graph.convert::<OrderedFloat<f64>>(), start_node)
}
pub fn traveling_salesman_problem<A, Ty>(
graph: &BaseGraph<A, f64, Ty>,
) -> Result<(Vec<NodeId>, f64)>
where
A: Clone,
Ty: GraphConstructor<A, f64> + GraphConstructor<A, OrderedFloat<f64>>,
{
let start_node = graph
.nodes()
.next()
.map(|(u, _)| u)
.ok_or_else(|| GraphinaError::invalid_graph("Cannot run TSP on an empty graph."))?;
greedy_tsp(&graph.convert::<OrderedFloat<f64>>(), start_node)
}
pub fn greedy_tsp<A, Ty>(
graph: &BaseGraph<A, OrderedFloat<f64>, Ty>,
start: NodeId,
) -> Result<(Vec<NodeId>, f64)>
where
A: Clone,
Ty: GraphConstructor<A, OrderedFloat<f64>>,
{
if graph.is_empty() {
return Err(GraphinaError::invalid_graph(
"Cannot run TSP on an empty graph.",
));
}
if graph.node_count() == 1 {
return Err(GraphinaError::invalid_graph(
"Cannot run TSP on a single-node graph.",
));
}
let mut tour = Vec::new();
let mut visited: HashSet<NodeId> = HashSet::new();
let mut current = start;
tour.push(current);
visited.insert(current);
let mut unvisited: Vec<NodeId> = graph
.nodes()
.map(|(id, _)| id)
.filter(|id| !visited.contains(id))
.collect();
while !unvisited.is_empty() {
if let Ok(distance_map) = dijkstra(graph, current) {
let mut nearest = None;
let mut min_dist = OrderedFloat(f64::INFINITY);
for &candidate in &unvisited {
if candidate == current {
continue;
}
if let Some(Some(d)) = distance_map.get(&candidate) {
if *d < min_dist {
min_dist = *d;
nearest = Some(candidate);
}
}
}
if let Some(next) = nearest {
tour.push(next);
visited.insert(next);
current = next;
} else {
return Err(GraphinaError::algorithm_error(
"Could not find a path to any unvisited node.",
));
}
} else {
return Err(GraphinaError::algorithm_error(
"Could not compute distances from the current node.",
));
}
unvisited = graph
.nodes()
.map(|(id, _)| id)
.filter(|id| !visited.contains(id))
.collect();
}
tour.push(start);
let total_cost = tour_cost(graph, &tour)?;
if total_cost.is_infinite() {
return Err(GraphinaError::algorithm_error(
"Could not compute finite tour cost (possibly disconnected).",
));
}
Ok((tour, total_cost.into_inner()))
}
pub fn nearest_neighbor<A, Ty>(
graph: &BaseGraph<A, f64, Ty>,
start: NodeId,
) -> Result<(Vec<NodeId>, f64)>
where
A: Clone,
Ty: GraphConstructor<A, f64> + GraphConstructor<A, OrderedFloat<f64>>,
{
greedy_tsp(&graph.convert::<OrderedFloat<f64>>(), start)
}
pub fn greedy_nearest_neighbor<A, Ty>(
graph: &BaseGraph<A, f64, Ty>,
start: NodeId,
) -> Result<(Vec<NodeId>, f64)>
where
A: Clone,
Ty: GraphConstructor<A, f64> + GraphConstructor<A, OrderedFloat<f64>>,
{
greedy_tsp(&graph.convert::<OrderedFloat<f64>>(), start)
}
fn tour_cost<A, Ty>(
graph: &BaseGraph<A, OrderedFloat<f64>, Ty>,
tour: &[NodeId],
) -> Result<OrderedFloat<f64>>
where
A: Clone,
Ty: GraphConstructor<A, OrderedFloat<f64>>,
{
if tour.len() < 2 {
return Ok(OrderedFloat(0.0));
}
let mut total_cost = 0.0;
for i in 0..tour.len() - 1 {
let u = tour[i];
let v = tour[i + 1];
if let Ok(dist_map) = dijkstra(graph, u) {
if let Some(Some(d)) = dist_map.get(&v) {
total_cost += d.into_inner();
} else {
return Ok(OrderedFloat(f64::INFINITY));
}
} else {
return Ok(OrderedFloat(f64::INFINITY));
}
}
Ok(OrderedFloat(total_cost))
}
#[cfg(test)]
mod tests {
use super::{greedy_tsp, tour_cost};
use crate::core::types::Graph;
use ordered_float::OrderedFloat;
#[test]
fn greedy_tsp_on_square_graph() {
let mut g: Graph<i32, f64> = Graph::new();
let n0 = g.add_node(0);
let n1 = g.add_node(1);
let n2 = g.add_node(2);
let n3 = g.add_node(3);
g.add_edge(n0, n1, 1.0);
g.add_edge(n1, n2, 1.0);
g.add_edge(n2, n3, 1.0);
g.add_edge(n3, n0, 1.0);
let g_ord = g.convert::<OrderedFloat<f64>>();
let (tour, cost) = greedy_tsp(&g_ord, n0).expect("greedy tsp should succeed");
assert!(tour.len() >= 5); assert!((cost - 4.0).abs() < 1e-9);
assert_eq!(*tour.first().unwrap(), n0);
assert_eq!(*tour.last().unwrap(), n0);
}
#[test]
fn tour_cost_handles_short_tour() {
let mut g: Graph<i32, f64> = Graph::new();
let n0 = g.add_node(0);
let g_ord = g.convert::<OrderedFloat<f64>>();
assert_eq!(tour_cost(&g_ord, &[]).unwrap(), 0.0);
assert_eq!(tour_cost(&g_ord, &[n0]).unwrap(), 0.0);
}
#[test]
fn greedy_tsp_errors_when_disconnected() {
let mut g: Graph<i32, f64> = Graph::new();
let n0 = g.add_node(0);
let _n1 = g.add_node(1);
let g_ord = g.convert::<OrderedFloat<f64>>();
let err = greedy_tsp(&g_ord, n0).unwrap_err();
assert!(format!("{}", err).contains("Could not find a path"));
}
}