solverforge_cvrp/
helpers.rs1use 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 unsafe { ptr.as_ref() }
19}
20
21#[inline]
22fn optional_problem_data_for_entity<S: VrpSolution>(
23 plan: &S,
24 entity_idx: usize,
25) -> Option<&ProblemData> {
26 if entity_idx >= plan.vehicle_count() {
27 return None;
28 }
29 let ptr = plan.vehicle_data_ptr(entity_idx);
30 if ptr.is_null() {
31 return None;
32 }
33 unsafe { ptr.as_ref() }
37}
38
39pub fn depot_for_entity<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
40 problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.depot)
41}
42
43pub fn savings_metric_class<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
48 if entity_idx >= plan.vehicle_count() {
49 return entity_idx;
50 }
51
52 let ptr = plan.vehicle_data_ptr(entity_idx);
53 assert!(
54 !ptr.is_null(),
55 "VrpSolution::vehicle_data_ptr({entity_idx}) returned null for a non-empty fleet"
56 );
57 ptr as usize
58}
59
60pub fn savings_depot_for_entity<S: VrpSolution>(plan: &S, entity_idx: usize) -> usize {
62 depot_for_entity(plan, entity_idx)
63}
64
65pub fn savings_distance<S: VrpSolution>(
67 plan: &S,
68 entity_idx: usize,
69 from: usize,
70 to: usize,
71) -> i64 {
72 route_distance(plan, entity_idx, from, to)
73}
74
75pub fn savings_feasible<S: VrpSolution>(plan: &S, entity_idx: usize, route: &[usize]) -> bool {
80 if route.is_empty() {
81 return true;
82 }
83 let Some(data) = optional_problem_data_for_entity(plan, entity_idx) else {
84 return false;
85 };
86 route_is_structurally_valid(route, data)
87}
88
89pub fn route_distance<S: VrpSolution>(plan: &S, entity_idx: usize, from: usize, to: usize) -> i64 {
91 problem_data_for_entity(plan, entity_idx).map_or(0, |data| data.distance_matrix[from][to])
92}
93
94pub fn replace_route<S: VrpSolution>(plan: &mut S, entity_idx: usize, route: Vec<usize>) {
98 *plan.vehicle_visits_mut(entity_idx) = route;
99}
100
101pub fn get_route<S: VrpSolution>(plan: &S, entity_idx: usize) -> Vec<usize> {
105 plan.vehicle_visits(entity_idx).to_vec()
106}
107
108pub fn route_feasible<S: VrpSolution>(plan: &S, entity_idx: usize, route: &[usize]) -> bool {
110 if route.is_empty() {
111 return true;
112 }
113 let Some(data) = optional_problem_data_for_entity(plan, entity_idx) else {
114 return false;
115 };
116 route_is_structurally_valid(route, data)
117 && route_is_capacity_feasible(route, data)
118 && route_is_time_feasible(route, data)
119}
120
121pub mod route_hooks {
123 pub use super::depot_for_entity as depot;
124 pub use super::get_route as get;
125 pub use super::replace_route as set;
126 pub use super::route_distance as distance;
127 pub use super::route_feasible as feasible;
128}
129
130pub mod savings_hooks {
135 pub use super::savings_depot_for_entity as depot;
136 pub use super::savings_distance as distance;
137 pub use super::savings_feasible as feasible;
138}
139
140fn route_is_structurally_valid(route: &[usize], data: &ProblemData) -> bool {
141 let Some(max_visit) = route.iter().copied().max() else {
142 return true;
143 };
144 let max_node = max_visit.max(data.depot);
145
146 if max_visit >= data.demands.len()
147 || max_visit >= data.time_windows.len()
148 || max_visit >= data.service_durations.len()
149 || max_node >= data.distance_matrix.len()
150 || max_node >= data.travel_times.len()
151 {
152 return false;
153 }
154
155 route
156 .iter()
157 .chain(std::iter::once(&data.depot))
158 .all(|&node| {
159 data.distance_matrix
160 .get(node)
161 .is_some_and(|row| row.len() > max_node)
162 && data
163 .travel_times
164 .get(node)
165 .is_some_and(|row| row.len() > max_node)
166 })
167}
168
169fn route_is_capacity_feasible(route: &[usize], data: &ProblemData) -> bool {
170 route
171 .iter()
172 .map(|&visit| i64::from(data.demands[visit]))
173 .sum::<i64>()
174 <= data.capacity
175}
176
177fn route_is_time_feasible(route: &[usize], data: &ProblemData) -> bool {
178 let mut current_time = data.vehicle_departure_time;
179 let mut previous = data.depot;
180
181 for &visit in route {
182 current_time += data.travel_times[previous][visit];
183
184 let (min_start, max_end) = data.time_windows[visit];
185 if current_time < min_start {
186 current_time = min_start;
187 }
188
189 current_time += data.service_durations[visit];
190 if current_time > max_end {
191 return false;
192 }
193
194 previous = visit;
195 }
196
197 true
198}