#![allow(clippy::disallowed_methods)]
use aprender::metaheuristics::{AntColony, Budget, ConstructiveMetaheuristic, SearchSpace};
fn main() {
println!("=== Ant Colony Optimization: Traveling Salesman Problem ===\n");
let city_names = [
"Atlanta",
"Boston",
"Chicago",
"Denver",
"El Paso",
"Fresno",
"Green Bay",
"Houston",
"Indianapolis",
"Jacksonville",
];
let distances: Vec<Vec<f64>> = vec![
vec![
0.0, 1100.0, 720.0, 1400.0, 1400.0, 2200.0, 900.0, 800.0, 530.0, 350.0,
], vec![
1100.0, 0.0, 980.0, 1960.0, 2300.0, 3100.0, 1100.0, 1850.0, 940.0, 1150.0,
], vec![
720.0, 980.0, 0.0, 1000.0, 1700.0, 2100.0, 210.0, 1090.0, 180.0, 900.0,
], vec![
1400.0, 1960.0, 1000.0, 0.0, 680.0, 1200.0, 1300.0, 1030.0, 1060.0, 1900.0,
], vec![
1400.0, 2300.0, 1700.0, 680.0, 0.0, 750.0, 1900.0, 750.0, 1500.0, 1700.0,
], vec![
2200.0, 3100.0, 2100.0, 1200.0, 750.0, 0.0, 2100.0, 1700.0, 2100.0, 2600.0,
], vec![
900.0, 1100.0, 210.0, 1300.0, 1900.0, 2100.0, 0.0, 1200.0, 400.0, 1100.0,
], vec![
800.0, 1850.0, 1090.0, 1030.0, 750.0, 1700.0, 1200.0, 0.0, 1000.0, 850.0,
], vec![
530.0, 940.0, 180.0, 1060.0, 1500.0, 2100.0, 400.0, 1000.0, 0.0, 800.0,
], vec![
350.0, 1150.0, 900.0, 1900.0, 1700.0, 2600.0, 1100.0, 850.0, 800.0, 0.0,
], ];
let n = distances.len();
let adjacency: Vec<Vec<(usize, f64)>> = distances
.iter()
.enumerate()
.map(|(i, row)| {
row.iter()
.enumerate()
.filter(|&(j, _)| i != j)
.map(|(j, &d)| (j, d))
.collect()
})
.collect();
let space = SearchSpace::Graph {
num_nodes: n,
adjacency,
heuristic: None, };
let objective = |tour: &Vec<usize>| -> f64 {
let mut total = 0.0;
for i in 0..tour.len() {
let from = tour[i];
let to = tour[(i + 1) % tour.len()];
total += distances[from][to];
}
total
};
println!("Problem: Visit all {} cities and return to start", n);
println!("Optimization: Minimize total distance\n");
let mut aco = AntColony::new(20) .with_alpha(1.0) .with_beta(2.5) .with_rho(0.1) .with_seed(42);
println!("ACO Parameters:");
println!(" Ants: 20");
println!(" Alpha (pheromone weight): 1.0");
println!(" Beta (heuristic weight): 2.5");
println!(" Rho (evaporation): 0.1");
println!(" Iterations: 100\n");
let result = aco.optimize(&objective, &space, Budget::Iterations(100));
println!("=== Results ===\n");
println!("Best tour found:");
print!(" ");
for (i, &city) in result.solution.iter().enumerate() {
if i > 0 {
print!(" -> ");
}
print!("{}", city_names[city]);
}
println!(" -> {}", city_names[result.solution[0]]);
println!("\nTotal distance: {:.0} miles", result.objective_value);
println!("Iterations: {}", result.iterations);
println!("Termination: {:?}", result.termination);
println!("\nConvergence (every 10 iterations):");
for (i, &val) in result.history.iter().enumerate() {
if i % 10 == 0 || i == result.history.len() - 1 {
println!(" Iter {:3}: {:.0} miles", i, val);
}
}
println!("\n=== Comparison with Greedy Nearest Neighbor ===");
let greedy_tour = greedy_tsp(&distances);
let greedy_dist = objective(&greedy_tour);
print!("Greedy tour: ");
for (i, &city) in greedy_tour.iter().enumerate() {
if i > 0 {
print!(" -> ");
}
print!("{}", city_names[city]);
}
println!(" -> {}", city_names[greedy_tour[0]]);
println!("Greedy distance: {:.0} miles", greedy_dist);
let improvement = (greedy_dist - result.objective_value) / greedy_dist * 100.0;
if improvement > 0.0 {
println!(
"\nACO improved over greedy by {:.1}% ({:.0} miles saved)",
improvement,
greedy_dist - result.objective_value
);
}
}
fn greedy_tsp(distances: &[Vec<f64>]) -> Vec<usize> {
let n = distances.len();
let mut tour = vec![0]; let mut visited = vec![false; n];
visited[0] = true;
while tour.len() < n {
let current = *tour.last().expect("tour should never be empty");
let mut best_next = 0;
let mut best_dist = f64::INFINITY;
for j in 0..n {
if !visited[j] && distances[current][j] < best_dist {
best_dist = distances[current][j];
best_next = j;
}
}
tour.push(best_next);
visited[best_next] = true;
}
tour
}