Skip to main content

solverforge_cvrp/
helpers.rs

1use crate::{ProblemData, VrpSolution};
2
3#[inline]
4pub(crate) fn problem_data_for_entity<S: VrpSolution>(
5    plan: &S,
6    entity_idx: usize,
7) -> Option<&ProblemData> {
8    if entity_idx >= plan.vehicle_count() {
9        return None;
10    }
11    let ptr = plan.vehicle_data_ptr(entity_idx);
12    assert!(
13        !ptr.is_null(),
14        "VrpSolution::vehicle_data_ptr({entity_idx}) returned null for a non-empty fleet"
15    );
16    // SAFETY: VrpSolution implementors guarantee valid pointers for the duration
17    // of the solve call; null for a non-empty fleet is rejected above.
18    unsafe { ptr.as_ref() }
19}
20
21pub fn depot_for_entity<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
22    problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.depot)
23}
24
25/// Distance between two element indices for the route owner.
26pub fn route_distance<S: VrpSolution>(plan: &S, entity_idx: usize, from: usize, to: usize) -> i64 {
27    problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.distance_matrix[from][to])
28}
29
30/// Replaces the current route for entity `entity_idx`.
31///
32/// Callers must pass a valid `entity_idx` for the current solution.
33pub fn replace_route<S: VrpSolution>(plan: &mut S, entity_idx: usize, route: Vec<usize>) {
34    *plan.vehicle_visits_mut(entity_idx) = route;
35}
36
37/// Returns a cloned snapshot of the route for entity `entity_idx`.
38///
39/// Callers must pass a valid `entity_idx` for the current solution.
40pub fn get_route<S: VrpSolution>(plan: &S, entity_idx: usize) -> Vec<usize> {
41    plan.vehicle_visits(entity_idx).to_vec()
42}
43
44/// Returns `true` if the route satisfies capacity and time-window constraints.
45pub fn route_feasible<S: VrpSolution>(plan: &S, entity_idx: usize, route: &[usize]) -> bool {
46    if route.is_empty() {
47        return true;
48    }
49    match problem_data_for_entity(plan, entity_idx) {
50        Some(data) => check_capacity_feasible(route, data) && check_time_feasible(route, data),
51        None => true,
52    }
53}
54
55fn check_capacity_feasible(route: &[usize], data: &ProblemData) -> bool {
56    route
57        .iter()
58        .map(|&visit| data.demands[visit] as i64)
59        .sum::<i64>()
60        <= data.capacity
61}
62
63fn check_time_feasible(route: &[usize], data: &ProblemData) -> bool {
64    let mut current_time = data.vehicle_departure_time;
65    let mut prev = data.depot;
66
67    for &visit in route {
68        current_time += data.travel_times[prev][visit];
69
70        let (min_start, max_end) = data.time_windows[visit];
71
72        if current_time < min_start {
73            current_time = min_start;
74        }
75
76        let service_end = current_time + data.service_durations[visit];
77
78        if service_end > max_end {
79            return false;
80        }
81
82        current_time = service_end;
83        prev = visit;
84    }
85
86    true
87}