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    let ptr = plan.vehicle_data_ptr(entity_idx);
9    assert!(
10        !ptr.is_null(),
11        "VrpSolution::vehicle_data_ptr({entity_idx}) returned null for a non-empty fleet"
12    );
13    // SAFETY: VrpSolution implementors guarantee valid pointers for the duration
14    // of the solve call; null for a non-empty fleet is rejected above.
15    unsafe { ptr.as_ref() }
16}
17
18#[inline]
19fn first_problem_data<S: VrpSolution>(plan: &S) -> Option<&ProblemData> {
20    if plan.vehicle_count() == 0 {
21        return None;
22    }
23    problem_data_for_entity(plan, 0)
24}
25
26/// Distance between two element indices using the first vehicle's data pointer.
27pub fn distance<S: VrpSolution>(plan: &S, i: usize, j: usize) -> i64 {
28    first_problem_data(plan).map_or(0, |data| data.distance_matrix[i][j])
29}
30
31pub fn depot_for_entity<S: VrpSolution>(plan: &S, _entity_idx: usize) -> usize {
32    first_problem_data(plan).map_or(0, |data| data.depot)
33}
34
35pub fn depot_for_cw<S: VrpSolution>(plan: &S) -> usize {
36    first_problem_data(plan).map_or(0, |data| data.depot)
37}
38
39pub fn element_load<S: VrpSolution>(plan: &S, elem: usize) -> i64 {
40    first_problem_data(plan).map_or(0, |data| data.demands[elem] as i64)
41}
42
43pub fn capacity<S: VrpSolution>(plan: &S) -> i64 {
44    first_problem_data(plan).map_or(i64::MAX, |data| data.capacity)
45}
46
47/// Replaces the current route for entity `entity_idx`.
48///
49/// Callers must pass a valid `entity_idx` for the current solution.
50pub fn replace_route<S: VrpSolution>(plan: &mut S, entity_idx: usize, route: Vec<usize>) {
51    *plan.vehicle_visits_mut(entity_idx) = route;
52}
53
54/// Returns a cloned snapshot of the route for entity `entity_idx`.
55///
56/// Callers must pass a valid `entity_idx` for the current solution.
57pub fn get_route<S: VrpSolution>(plan: &S, entity_idx: usize) -> Vec<usize> {
58    plan.vehicle_visits(entity_idx).to_vec()
59}
60
61/// Returns `true` if the route satisfies all time-window constraints.
62pub fn is_time_feasible<S: VrpSolution>(plan: &S, route: &[usize]) -> bool {
63    if route.is_empty() {
64        return true;
65    }
66    match first_problem_data(plan) {
67        Some(data) => check_time_feasible(route, data),
68        None => true,
69    }
70}
71
72/// K-opt feasibility gate: returns `true` if the route satisfies all time-window constraints.
73/// The `entity_idx` parameter is ignored — time windows are uniform across vehicles.
74pub fn is_kopt_feasible<S: VrpSolution>(plan: &S, _entity_idx: usize, route: &[usize]) -> bool {
75    is_time_feasible(plan, route)
76}
77
78fn check_time_feasible(route: &[usize], data: &ProblemData) -> bool {
79    let mut current_time = data.vehicle_departure_time;
80    let mut prev = data.depot;
81
82    for &visit in route {
83        current_time += data.travel_times[prev][visit];
84
85        let (min_start, max_end) = data.time_windows[visit];
86
87        if current_time < min_start {
88            current_time = min_start;
89        }
90
91        let service_end = current_time + data.service_durations[visit];
92
93        if service_end > max_end {
94            return false;
95        }
96
97        current_time = service_end;
98        prev = visit;
99    }
100
101    true
102}