use crate::instance::SolomonInstance;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Route {
pub nodes: Vec<usize>,
}
impl Route {
#[inline]
pub fn new() -> Self {
Self { nodes: Vec::new() }
}
#[inline]
pub fn from_nodes(nodes: Vec<usize>) -> Self {
Self { nodes }
}
#[inline]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn distance(&self, instance: &SolomonInstance) -> f32 {
if self.nodes.is_empty() {
return 0.0;
}
let mut total_dist = instance.distance(0, self.nodes[0]);
for window in self.nodes.windows(2) {
total_dist += instance.distance(window[0], window[1]);
}
total_dist += instance.distance(*self.nodes.last().unwrap(), 0);
total_dist
}
pub fn total_demand(&self, instance: &SolomonInstance) -> f32 {
self.nodes.iter().map(|&node| instance.demand(node)).sum()
}
#[inline]
pub fn is_capacity_feasible(&self, instance: &SolomonInstance) -> bool {
self.total_demand(instance) <= instance.vehicle.capacity
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Solution {
pub routes: Vec<Route>,
}
impl Solution {
#[inline]
pub fn new(routes: Vec<Route>) -> Self {
Self { routes }
}
#[inline]
pub fn empty() -> Self {
Self { routes: Vec::new() }
}
pub fn total_distance(&self, instance: &SolomonInstance) -> f32 {
self.routes
.iter()
.map(|route| route.distance(instance))
.sum()
}
pub fn total_customers_visited(&self) -> usize {
self.routes.iter().map(Route::len).sum()
}
pub fn is_feasible(&self, instance: &SolomonInstance) -> bool {
if instance.validate().is_err() {
return false;
}
let active_routes = self.routes.iter().filter(|r| !r.is_empty());
if active_routes.clone().count() > instance.vehicle.num_vehicles {
return false;
}
let mut visited = vec![false; instance.num_nodes];
for route in active_routes {
for &node in &route.nodes {
if node == 0 || node >= instance.num_nodes {
return false;
}
if visited[node] {
return false;
}
visited[node] = true;
}
if !route.is_capacity_feasible(instance) {
return false;
}
}
visited[1..].iter().all(|&v| v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instance::VehicleConfig;
#[test]
fn test_solution_rejects_invalid_ids_without_panicking() {
let instance = create_mock_instance();
for node in [instance.num_nodes, usize::MAX] {
let solution = Solution::new(vec![Route::from_nodes(vec![node])]);
assert!(!solution.is_feasible(&instance));
}
}
#[test]
fn test_solution_rejects_malformed_instance() {
let mut instance = create_mock_instance();
instance.demands.clear();
let solution = Solution::new(vec![Route::from_nodes(vec![1, 2, 3])]);
assert!(!solution.is_feasible(&instance));
}
#[test]
fn test_solution_ignores_empty_routes_in_fleet_count() {
let instance = create_mock_instance();
let solution = Solution::new(vec![
Route::new(),
Route::from_nodes(vec![1, 2]),
Route::from_nodes(vec![3]),
]);
assert!(solution.is_feasible(&instance));
}
fn create_mock_instance() -> SolomonInstance {
let xs = vec![0.0, 3.0, 3.0, 0.0];
let ys = vec![0.0, 0.0, 4.0, 4.0];
let demands = vec![0.0, 10.0, 20.0, 15.0];
let ready_times = vec![0.0, 0.0, 0.0, 0.0];
let due_times = vec![1000.0, 1000.0, 1000.0, 1000.0];
let service_times = vec![0.0, 10.0, 10.0, 10.0];
let distance_matrix = crate::instance::compute_distance_matrix(&xs, &ys);
SolomonInstance {
name: "MockInstance".into(),
vehicle: VehicleConfig {
num_vehicles: 2,
capacity: 40.0,
},
num_nodes: 4,
xs,
ys,
demands,
ready_times,
due_times,
service_times,
distance_matrix,
}
}
#[test]
fn test_empty_route_and_solution() {
let instance = create_mock_instance();
let route = Route::new();
assert!(route.is_empty());
assert_eq!(route.len(), 0);
assert_eq!(route.distance(&instance), 0.0);
assert_eq!(route.total_demand(&instance), 0.0);
assert!(route.is_capacity_feasible(&instance));
let solution = Solution::empty();
assert_eq!(solution.total_distance(&instance), 0.0);
assert_eq!(solution.total_customers_visited(), 0);
}
#[test]
fn test_route_distance_and_demand() {
let instance = create_mock_instance();
let route = Route::from_nodes(vec![1, 2]);
assert_eq!(route.len(), 2);
assert!(!route.is_empty());
assert_eq!(route.total_demand(&instance), 30.0);
assert!(route.is_capacity_feasible(&instance));
let dist = route.distance(&instance);
assert!((dist - 12.0).abs() < 1e-5);
}
#[test]
fn test_solution_feasible_valid() {
let instance = create_mock_instance();
let r1 = Route::from_nodes(vec![1, 2]);
let r2 = Route::from_nodes(vec![3]);
let solution = Solution::new(vec![r1, r2]);
assert_eq!(solution.total_customers_visited(), 3);
assert!(solution.is_feasible(&instance));
}
#[test]
fn test_solution_infeasible_exceeds_capacity() {
let instance = create_mock_instance();
let r = Route::from_nodes(vec![1, 2, 3]);
let solution = Solution::new(vec![r]);
assert!(!solution.is_feasible(&instance));
}
#[test]
fn test_solution_infeasible_duplicate_customer() {
let instance = create_mock_instance();
let r1 = Route::from_nodes(vec![1, 2]);
let r2 = Route::from_nodes(vec![1, 3]);
let solution = Solution::new(vec![r1, r2]);
assert!(!solution.is_feasible(&instance));
}
#[test]
fn test_solution_infeasible_missing_customer() {
let instance = create_mock_instance();
let r1 = Route::from_nodes(vec![1, 2]);
let solution = Solution::new(vec![r1]);
assert!(!solution.is_feasible(&instance));
}
#[test]
fn test_solution_infeasible_too_many_vehicles() {
let instance = create_mock_instance();
let r1 = Route::from_nodes(vec![1]);
let r2 = Route::from_nodes(vec![2]);
let r3 = Route::from_nodes(vec![3]);
let solution = Solution::new(vec![r1, r2, r3]);
assert!(!solution.is_feasible(&instance));
}
#[test]
fn test_solution_infeasible_depot_in_route() {
let instance = create_mock_instance();
let r1 = Route::from_nodes(vec![0, 1, 2]);
let r2 = Route::from_nodes(vec![3]);
let solution = Solution::new(vec![r1, r2]);
assert!(!solution.is_feasible(&instance));
}
}