vrp_core/construction/heuristics/
metrics.rs1#[cfg(test)]
2#[path = "../../../tests/unit/construction/heuristics/metrics_test.rs"]
3mod metrics_test;
4
5use crate::construction::enablers::{TotalDistanceTourState, TotalDurationTourState, WaitingTimeActivityState};
6use crate::construction::features::MaxVehicleLoadTourState;
7use crate::construction::heuristics::{InsertionContext, RouteState};
8use crate::models::common::Distance;
9use crate::models::problem::TravelTime;
10use rosomaxa::algorithms::math::*;
11use rosomaxa::prelude::*;
12use rosomaxa::utils::{parallel_collect, SelectionSamplingIterator};
13use std::cmp::Ordering;
14
15pub fn get_max_load_variance(insertion_ctx: &InsertionContext) -> Float {
17 get_variance(
18 get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load()).collect::<Vec<_>>().as_slice(),
19 )
20}
21
22pub fn get_max_load_mean(insertion_ctx: &InsertionContext) -> Float {
24 get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load()))
25}
26
27pub fn get_full_load_ratio(insertion_ctx: &InsertionContext) -> Float {
29 let total = insertion_ctx.solution.routes.len();
30 if total == 0 {
31 0.
32 } else {
33 let full_capacity = get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load())
34 .filter(|&max_load| max_load > 0.9)
35 .count();
36
37 full_capacity as Float / total as Float
38 }
39}
40
41pub fn get_customers_deviation(insertion_ctx: &InsertionContext) -> Float {
43 let values = insertion_ctx
44 .solution
45 .routes
46 .iter()
47 .map(|route_ctx| route_ctx.route().tour.job_count() as Float)
48 .collect::<Vec<_>>();
49
50 get_stdev(values.as_slice())
51}
52
53pub fn get_duration_mean(insertion_ctx: &InsertionContext) -> Float {
55 get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_total_duration()))
56}
57
58pub fn get_distance_mean(insertion_ctx: &InsertionContext) -> Float {
60 get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_total_distance()))
61}
62
63pub fn get_waiting_mean(insertion_ctx: &InsertionContext) -> Float {
65 get_mean_iter(
66 insertion_ctx
67 .solution
68 .routes
69 .iter()
70 .filter(|route_ctx| route_ctx.route().tour.get(1).is_some())
71 .map(|route_ctx| route_ctx.state().get_waiting_time_at(1).copied().unwrap_or(0.)),
72 )
73}
74pub fn get_longest_distance_between_customers_mean(insertion_ctx: &InsertionContext) -> Float {
76 let transport = insertion_ctx.problem.transport.as_ref();
77 get_mean_iter(insertion_ctx.solution.routes.iter().map(|route_ctx| {
78 route_ctx.route().tour.legs().fold(0., |acc, (activities, _)| match activities {
79 [_] => acc,
80 [prev, next] => transport
81 .distance(
82 route_ctx.route(),
83 prev.place.location,
84 next.place.location,
85 TravelTime::Departure(prev.schedule.departure),
86 )
87 .max(acc),
88 _ => panic!("Unexpected route leg configuration."),
89 })
90 }))
91}
92
93pub fn get_average_distance_between_depot_customer_mean(insertion_ctx: &InsertionContext) -> Float {
95 let transport = insertion_ctx.problem.transport.as_ref();
96
97 get_mean_iter(insertion_ctx.solution.routes.iter().map(|route_ctx| {
98 let depot = route_ctx.route().tour.start().expect("empty tour");
99
100 get_mean_iter(route_ctx.route().tour.all_activities().skip(1).map(|activity| {
101 transport.distance(
102 route_ctx.route(),
103 depot.place.location,
104 activity.place.location,
105 TravelTime::Departure(depot.schedule.departure),
106 )
107 }))
108 }))
109}
110
111pub fn get_longest_distance_between_depot_customer_mean(insertion_ctx: &InsertionContext) -> Float {
113 let transport = insertion_ctx.problem.transport.as_ref();
114
115 get_mean_iter(insertion_ctx.solution.routes.iter().filter_map(|route_ctx| {
116 let depot = route_ctx.route().tour.start().expect("empty tour");
117
118 route_ctx
119 .route()
120 .tour
121 .all_activities()
122 .skip(1)
123 .map(|activity| {
124 transport.distance(
125 route_ctx.route(),
126 depot.place.location,
127 activity.place.location,
128 TravelTime::Departure(depot.schedule.departure),
129 )
130 })
131 .max_by(|a, b| a.total_cmp(b))
132 }))
133}
134
135pub fn get_first_distance_customer_mean(insertion_ctx: &InsertionContext) -> Float {
137 let transport = insertion_ctx.problem.transport.as_ref();
138
139 get_mean_iter(insertion_ctx.solution.routes.iter().filter_map(|route_ctx| {
140 let route = route_ctx.route();
141 route.tour.get(1).zip(route.tour.start()).map(|(activity, depot)| {
142 transport.distance(
143 route,
144 depot.place.location,
145 activity.place.location,
146 TravelTime::Departure(depot.schedule.departure),
147 )
148 })
149 }))
150}
151
152pub fn get_last_distance_customer_mean(insertion_ctx: &InsertionContext) -> Float {
154 let transport = insertion_ctx.problem.transport.as_ref();
155
156 let distances = insertion_ctx.solution.routes.iter().filter_map(|route_ctx| {
157 let tour = &route_ctx.route().tour;
158 if tour.total() < 2 {
159 return None;
160 }
161
162 let last_idx = tour.total() - 1;
164 let before_last_idx = last_idx - 1;
165
166 tour.get(before_last_idx).zip(tour.get(last_idx)).map(|(activity, depot)| {
167 transport.distance(
168 route_ctx.route(),
169 activity.place.location,
170 depot.place.location,
171 TravelTime::Departure(depot.schedule.departure),
172 )
173 })
174 });
175
176 get_mean_iter(distances)
177}
178
179pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> Vec<Vec<usize>> {
182 const LOCATION_SAMPLE_SIZE: usize = 4;
183
184 let routes = &insertion_ctx.solution.routes;
185 let transport = insertion_ctx.problem.transport.as_ref();
186 let random = &insertion_ctx.environment.random;
187
188 let indexed_route_clusters = routes
190 .iter()
191 .map(|route_ctx| {
192 SelectionSamplingIterator::new(
193 route_ctx.route().tour.all_activities(),
194 LOCATION_SAMPLE_SIZE,
195 random.clone(),
196 )
197 .map(|activity| activity.place.location)
198 .collect::<Vec<_>>()
199 })
200 .enumerate()
201 .collect::<Vec<_>>();
202
203 parallel_collect(&indexed_route_clusters, |(outer_idx, outer_clusters)| {
204 let mut route_distances = indexed_route_clusters
205 .iter()
206 .filter(move |(inner_idx, _)| *outer_idx != *inner_idx)
207 .map(move |(inner_idx, inner_clusters)| {
208 let pair_distance = outer_clusters
210 .iter()
211 .flat_map(|outer| inner_clusters.iter().map(move |inner| (inner, outer)))
212 .map(|(&o, &i)| {
213 let inner_profile = &routes[*inner_idx].route().actor.vehicle.profile;
215 let outer_profile = &routes[*outer_idx].route().actor.vehicle.profile;
216 transport.distance_approx(inner_profile, o, i).max(0.)
217 + transport.distance_approx(outer_profile, o, i).max(0.)
218 })
219 .sum::<Distance>()
220 / 2.;
221
222 let total_pairs = outer_clusters.len() * inner_clusters.len();
223 let distance = if total_pairs == 0 {
224 None
225 } else {
226 Some(pair_distance / total_pairs as Float)
228 };
229
230 (*inner_idx, distance)
231 })
232 .collect::<Vec<_>>();
233
234 route_distances.sort_unstable_by(|(_, a_distance), (_, b_distance)| match (a_distance, b_distance) {
235 (Some(a_distance), Some(b_distance)) => a_distance.total_cmp(b_distance),
236 (Some(_), None) => Ordering::Less,
237 _ => Ordering::Greater,
238 });
239
240 let (indices, _): (Vec<_>, Vec<_>) = route_distances.into_iter().unzip();
241
242 indices
243 })
244}
245
246fn get_values_from_route_state<'a>(
247 insertion_ctx: &'a InsertionContext,
248 state_value_fn: impl Fn(&'a RouteState) -> Option<&Float> + 'a,
249) -> impl Iterator<Item = Float> + 'a {
250 insertion_ctx
251 .solution
252 .routes
253 .iter()
254 .map(move |route_ctx| state_value_fn(route_ctx.state()).copied().unwrap_or_default())
255}