use crate::{ProblemData, VrpSolution};
#[inline]
pub(crate) fn problem_data_for_entity<S: VrpSolution>(
plan: &S,
entity_idx: usize,
) -> Option<&ProblemData> {
if entity_idx >= plan.vehicle_count() {
return None;
}
let ptr = plan.vehicle_data_ptr(entity_idx);
assert!(
!ptr.is_null(),
"VrpSolution::vehicle_data_ptr({entity_idx}) returned null for a non-empty fleet"
);
unsafe { ptr.as_ref() }
}
#[inline]
fn optional_problem_data_for_entity<S: VrpSolution>(
plan: &S,
entity_idx: usize,
) -> Option<&ProblemData> {
if entity_idx >= plan.vehicle_count() {
return None;
}
let ptr = plan.vehicle_data_ptr(entity_idx);
if ptr.is_null() {
return None;
}
unsafe { ptr.as_ref() }
}
pub fn depot_for_entity<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.depot)
}
pub fn savings_metric_class<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
if entity_idx >= plan.vehicle_count() {
return entity_idx;
}
let ptr = plan.vehicle_data_ptr(entity_idx);
assert!(
!ptr.is_null(),
"VrpSolution::vehicle_data_ptr({entity_idx}) returned null for a non-empty fleet"
);
ptr as usize
}
pub fn savings_depot_for_entity<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
depot_for_entity(plan, entity_idx)
}
pub fn savings_distance<S: VrpSolution>(
plan: &S,
entity_idx: usize,
from: usize,
to: usize,
) -> i64 {
route_distance(plan, entity_idx, from, to)
}
pub fn savings_feasible<S: VrpSolution>(plan: &S, entity_idx: usize, route: &[usize]) -> bool {
if route.is_empty() {
return true;
}
let Some(data) = optional_problem_data_for_entity(plan, entity_idx) else {
return false;
};
route_is_structurally_valid(route, data)
}
pub fn route_distance<S: VrpSolution>(plan: &S, entity_idx: usize, from: usize, to: usize) -> i64 {
problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.distance_cost(from, to))
}
pub fn replace_route<S: VrpSolution>(plan: &mut S, entity_idx: usize, route: Vec<usize>) {
*plan.vehicle_visits_mut(entity_idx) = route;
}
pub fn get_route<S: VrpSolution>(plan: &S, entity_idx: usize) -> Vec<usize> {
plan.vehicle_visits(entity_idx).to_vec()
}
pub fn route_feasible<S: VrpSolution>(plan: &S, entity_idx: usize, route: &[usize]) -> bool {
if route.is_empty() {
return true;
}
let Some(data) = optional_problem_data_for_entity(plan, entity_idx) else {
return false;
};
route_is_structurally_valid(route, data)
&& route_is_capacity_feasible(route, data)
&& route_is_time_feasible(route, data)
}
pub mod route_hooks {
pub use super::depot_for_entity as depot;
pub use super::get_route as get;
pub use super::replace_route as set;
pub use super::route_distance as distance;
pub use super::route_feasible as feasible;
}
pub mod savings_hooks {
pub use super::savings_depot_for_entity as depot;
pub use super::savings_distance as distance;
pub use super::savings_feasible as feasible;
}
fn route_is_structurally_valid(route: &[usize], data: &ProblemData) -> bool {
let Some(max_visit) = route.iter().copied().max() else {
return true;
};
let max_node = max_visit.max(data.depot);
if max_visit >= data.demands.len()
|| max_visit >= data.time_windows.len()
|| max_visit >= data.service_durations.len()
|| max_node >= data.distance_matrix.len()
|| max_node >= data.travel_times.len()
{
return false;
}
route
.iter()
.chain(std::iter::once(&data.depot))
.all(|&node| {
data.distance_matrix
.get(node)
.is_some_and(|row| row.len() > max_node)
&& data
.travel_times
.get(node)
.is_some_and(|row| row.len() > max_node)
})
}
fn route_is_capacity_feasible(route: &[usize], data: &ProblemData) -> bool {
let mut total = 0_i64;
for &visit in route {
let demand = i64::from(data.demands[visit]);
total = match total.checked_add(demand) {
Some(total) => total,
None => return false,
};
}
total <= data.capacity
}
fn route_is_time_feasible(route: &[usize], data: &ProblemData) -> bool {
let mut current_time = data.vehicle_departure_time;
let mut previous = data.depot;
for &visit in route {
let Some(travel_time) = data.travel_time(previous, visit) else {
return false;
};
current_time = match current_time.checked_add(travel_time) {
Some(current_time) => current_time,
None => return false,
};
let (min_start, max_end) = data.time_windows[visit];
if current_time < min_start {
current_time = min_start;
}
let service_duration = data.service_durations[visit];
if service_duration < 0 {
return false;
}
current_time = match current_time.checked_add(service_duration) {
Some(current_time) => current_time,
None => return false,
};
if current_time > max_end {
return false;
}
previous = visit;
}
let Some(return_time) = data.travel_time(previous, data.depot) else {
return false;
};
current_time.checked_add(return_time).is_some()
}