mod aco;
mod ga;
mod hybrid;
mod tabu;
pub use aco::AcoSolver;
pub use ga::GaSolver;
pub use hybrid::HybridSolver;
pub use tabu::TabuSolver;
use crate::error::TspResult;
use crate::instance::TspInstance;
#[derive(Debug, Clone, Copy)]
pub enum Budget {
Iterations(usize),
Evaluations(usize),
}
impl Budget {
pub fn limit(&self) -> usize {
match self {
Self::Iterations(n) | Self::Evaluations(n) => *n,
}
}
}
#[derive(Debug, Clone)]
pub struct TspSolution {
pub tour: Vec<usize>,
pub length: f64,
pub evaluations: usize,
pub history: Vec<f64>,
}
impl TspSolution {
pub fn new(tour: Vec<usize>, length: f64) -> Self {
Self {
tour,
length,
evaluations: 0,
history: vec![length],
}
}
pub fn is_better_than(&self, other: &Self) -> bool {
self.length < other.length
}
}
pub trait TspSolver: Send + Sync {
fn solve(&mut self, instance: &TspInstance, budget: Budget) -> TspResult<TspSolution>;
fn name(&self) -> &'static str;
fn reset(&mut self);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TspAlgorithm {
Aco,
Tabu,
Ga,
Hybrid,
}
impl TspAlgorithm {
pub fn as_str(&self) -> &'static str {
match self {
Self::Aco => "aco",
Self::Tabu => "tabu",
Self::Ga => "ga",
Self::Hybrid => "hybrid",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"aco" | "ant" | "antcolony" => Some(Self::Aco),
"tabu" | "tabusearch" => Some(Self::Tabu),
"ga" | "genetic" => Some(Self::Ga),
"hybrid" | "auto" => Some(Self::Hybrid),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolutionTier {
Optimal,
Excellent,
Good,
Acceptable,
Poor,
}
impl SolutionTier {
pub fn from_gap(gap_percent: f64) -> Self {
if gap_percent < 0.1 {
Self::Optimal
} else if gap_percent < 1.0 {
Self::Excellent
} else if gap_percent < 2.0 {
Self::Good
} else if gap_percent < 5.0 {
Self::Acceptable
} else {
Self::Poor
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Optimal => "Optimal",
Self::Excellent => "Excellent",
Self::Good => "Good",
Self::Acceptable => "Acceptable",
Self::Poor => "Poor",
}
}
}
pub fn optimality_gap(solution_length: f64, optimal_length: f64) -> f64 {
((solution_length - optimal_length) / optimal_length) * 100.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_solution_new() {
let solution = TspSolution::new(vec![0, 1, 2], 10.5);
assert_eq!(solution.tour, vec![0, 1, 2]);
assert!((solution.length - 10.5).abs() < 1e-10);
assert_eq!(solution.evaluations, 0);
assert_eq!(solution.history.len(), 1);
}
#[test]
fn test_solution_comparison() {
let better = TspSolution::new(vec![0, 1, 2], 10.0);
let worse = TspSolution::new(vec![0, 2, 1], 15.0);
assert!(better.is_better_than(&worse));
assert!(!worse.is_better_than(&better));
}
#[test]
fn test_algorithm_parsing() {
assert_eq!(TspAlgorithm::parse("aco"), Some(TspAlgorithm::Aco));
assert_eq!(TspAlgorithm::parse("ACO"), Some(TspAlgorithm::Aco));
assert_eq!(TspAlgorithm::parse("tabu"), Some(TspAlgorithm::Tabu));
assert_eq!(TspAlgorithm::parse("ga"), Some(TspAlgorithm::Ga));
assert_eq!(TspAlgorithm::parse("hybrid"), Some(TspAlgorithm::Hybrid));
assert_eq!(TspAlgorithm::parse("auto"), Some(TspAlgorithm::Hybrid));
assert_eq!(TspAlgorithm::parse("unknown"), None);
}
#[test]
fn test_algorithm_as_str() {
assert_eq!(TspAlgorithm::Aco.as_str(), "aco");
assert_eq!(TspAlgorithm::Tabu.as_str(), "tabu");
assert_eq!(TspAlgorithm::Ga.as_str(), "ga");
assert_eq!(TspAlgorithm::Hybrid.as_str(), "hybrid");
}
#[test]
fn test_solution_tier_from_gap() {
assert_eq!(SolutionTier::from_gap(0.05), SolutionTier::Optimal);
assert_eq!(SolutionTier::from_gap(0.5), SolutionTier::Excellent);
assert_eq!(SolutionTier::from_gap(1.5), SolutionTier::Good);
assert_eq!(SolutionTier::from_gap(3.0), SolutionTier::Acceptable);
assert_eq!(SolutionTier::from_gap(10.0), SolutionTier::Poor);
}
#[test]
fn test_optimality_gap() {
let gap = optimality_gap(102.0, 100.0);
assert!((gap - 2.0).abs() < 1e-10);
let gap = optimality_gap(100.0, 100.0);
assert!((gap - 0.0).abs() < 1e-10);
}
#[test]
fn test_budget_limit() {
assert_eq!(Budget::Iterations(100).limit(), 100);
assert_eq!(Budget::Evaluations(500).limit(), 500);
}
}