1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#[cfg(test)]
#[path = "../../tests/unit/checker/limits_test.rs"]
mod limits_test;
use super::*;
use crate::utils::combine_error_results;
pub fn check_limits(context: &CheckerContext) -> Result<(), Vec<String>> {
combine_error_results(&[check_shift_limits(context), check_shift_time(context)])
}
fn check_shift_limits(context: &CheckerContext) -> Result<(), String> {
context.solution.tours.iter().try_for_each::<_, Result<_, String>>(|tour| {
let vehicle = context.get_vehicle(&tour.vehicle_id)?;
if let Some(ref limits) = vehicle.limits {
if let Some(max_distance) = limits.max_distance {
if tour.statistic.distance as f64 > max_distance {
return Err(format!(
"max distance limit violation, expected: not more than {}, got: {}, vehicle id '{}', shift index: {}",
max_distance, tour.statistic.distance, tour.vehicle_id, tour.shift_index
));
}
}
if let Some(shift_time) = limits.shift_time {
if tour.statistic.duration as f64 > shift_time {
return Err(format!(
"shift time limit violation, expected: not more than {}, got: {}, vehicle id '{}', shift index: {}",
shift_time, tour.statistic.duration, tour.vehicle_id, tour.shift_index
));
}
}
if let Some(tour_size_limit) = limits.tour_size {
let shift = context.get_vehicle_shift(tour)?;
let extra_activities = if shift.end.is_some() { 2 } else { 1 };
let tour_activities = tour.stops.iter().flat_map(|stop| stop.activities.iter()).count();
let tour_activities = if tour_activities > extra_activities { tour_activities - extra_activities } else { 0 };
if tour_activities > tour_size_limit {
return Err(format!(
"tour size limit violation, expected: not more than {}, got: {}, vehicle id '{}', shift index: {}",
tour_size_limit, tour_activities, tour.vehicle_id, tour.shift_index
))
}
}
}
Ok(())
})
}
fn check_shift_time(context: &CheckerContext) -> Result<(), String> {
context.solution.tours.iter().try_for_each::<_, Result<_, String>>(|tour| {
let vehicle = context.get_vehicle(&tour.vehicle_id)?;
let (start, end) = tour.stops.first().zip(tour.stops.last()).ok_or("empty tour")?;
let departure = parse_time(&start.time.departure);
let arrival = parse_time(&end.time.arrival);
let has_match = vehicle
.shifts
.iter()
.map(|shift| {
let start = parse_time(&shift.start.earliest);
let end = shift.end.as_ref().map(|end| parse_time(&end.latest)).unwrap_or(f64::MAX);
(start, end)
})
.any(|(start, end)| departure >= start && arrival <= end);
if !has_match {
Err(format!(
"tour time is outside shift time, vehicle id '{}', shift index: {}",
tour.vehicle_id, tour.shift_index
))
} else {
Ok(())
}
})
}