vrp_core/construction/features/
capacity.rs1#[cfg(test)]
4#[path = "../../../tests/unit/construction/features/capacity_test.rs"]
5mod capacity_test;
6
7use super::*;
8use crate::construction::enablers::*;
9use crate::models::solution::Activity;
10use std::marker::PhantomData;
11use std::sync::Arc;
12
13custom_activity_state!(CurrentCapacity typeof T: LoadOps);
14
15custom_activity_state!(MaxFutureCapacity typeof T: LoadOps);
16
17custom_activity_state!(MaxPastCapacity typeof T: LoadOps);
18
19custom_tour_state!(MaxVehicleLoad typeof Float);
20
21custom_dimension!(VehicleCapacity typeof T: LoadOps);
22
23pub trait JobDemandDimension {
25 fn set_job_demand<T: LoadOps>(&mut self, demand: Demand<T>) -> &mut Self;
27
28 fn get_job_demand<T: LoadOps>(&self) -> Option<&Demand<T>>;
30}
31
32pub struct CapacityFeatureBuilder<T: LoadOps> {
34 name: String,
35 route_intervals: Option<RouteIntervals>,
36 violation_code: Option<ViolationCode>,
37 phantom_data: PhantomData<T>,
38}
39
40impl<T: LoadOps> CapacityFeatureBuilder<T> {
41 pub fn new(name: &str) -> Self {
43 Self { name: name.to_string(), route_intervals: None, violation_code: None, phantom_data: Default::default() }
44 }
45
46 pub fn set_violation_code(mut self, violation_code: ViolationCode) -> Self {
48 self.violation_code = Some(violation_code);
49 self
50 }
51
52 pub fn set_route_intervals(mut self, route_intervals: RouteIntervals) -> Self {
54 self.route_intervals = Some(route_intervals);
55 self
56 }
57
58 pub fn build(self) -> GenericResult<Feature> {
60 let name = self.name.as_str();
61 let violation_code = self.violation_code.unwrap_or_default();
62
63 if let Some(route_intervals) = self.route_intervals {
64 create_multi_trip_feature(
65 name,
66 violation_code,
67 MarkerInsertionPolicy::Last,
68 Arc::new(CapacitatedMultiTrip::<T> { route_intervals, violation_code, phantom: Default::default() }),
69 )
70 } else {
71 create_multi_trip_feature(
72 name,
73 violation_code,
74 MarkerInsertionPolicy::Last,
75 Arc::new(CapacitatedMultiTrip::<T> {
76 route_intervals: RouteIntervals::Single,
77 violation_code,
78 phantom: Default::default(),
79 }),
80 )
81 }
82 }
83}
84
85impl<T> FeatureConstraint for CapacitatedMultiTrip<T>
86where
87 T: LoadOps,
88{
89 fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option<ConstraintViolation> {
90 match move_ctx {
91 MoveContext::Route { route_ctx, job, .. } => self.evaluate_job(route_ctx, job),
92 MoveContext::Activity { route_ctx, activity_ctx } => self.evaluate_activity(route_ctx, activity_ctx),
93 }
94 }
95
96 fn merge(&self, source: Job, candidate: Job) -> Result<Job, ViolationCode> {
97 match (&source, &candidate) {
98 (Job::Single(s_source), Job::Single(s_candidate)) => {
99 let source_demand: Option<&Demand<T>> = s_source.dimens.get_job_demand();
100 let candidate_demand: Option<&Demand<T>> = s_candidate.dimens.get_job_demand();
101
102 match (source_demand, candidate_demand) {
103 (None, None) | (Some(_), None) => Ok(source),
104 _ => {
105 let source_demand = source_demand.cloned().unwrap_or_default();
106 let candidate_demand = candidate_demand.cloned().unwrap_or_default();
107 let new_demand = source_demand + candidate_demand;
108
109 let mut single = Single { places: s_source.places.clone(), dimens: s_source.dimens.clone() };
110 single.dimens.set_job_demand(new_demand);
111
112 Ok(Job::Single(Arc::new(single)))
113 }
114 }
115 }
116 _ => Err(self.violation_code),
117 }
118 }
119}
120
121struct CapacitatedMultiTrip<T>
122where
123 T: LoadOps,
124{
125 route_intervals: RouteIntervals,
126 violation_code: ViolationCode,
127 phantom: PhantomData<T>,
128}
129
130impl<T> MultiTrip for CapacitatedMultiTrip<T>
131where
132 T: LoadOps,
133{
134 fn get_route_intervals(&self) -> &RouteIntervals {
135 &self.route_intervals
136 }
137
138 fn get_constraint(&self) -> &(dyn FeatureConstraint) {
139 self
140 }
141
142 fn recalculate_states(&self, route_ctx: &mut RouteContext) {
143 let marker_intervals = self
144 .get_route_intervals()
145 .get_marker_intervals(route_ctx)
146 .cloned()
147 .unwrap_or_else(|| vec![(0, route_ctx.route().tour.total() - 1)]);
148
149 let tour_len = route_ctx.route().tour.total();
150
151 let mut current_capacities = vec![T::default(); tour_len];
152 let mut max_past_capacities = vec![T::default(); tour_len];
153 let mut max_future_capacities = vec![T::default(); tour_len];
154
155 let (_, max_load) =
156 marker_intervals.into_iter().fold((T::default(), T::default()), |(acc, max), (start_idx, end_idx)| {
157 let route = route_ctx.route();
158
159 let (start_delivery, end_pickup) = route.tour.activities_slice(start_idx, end_idx).iter().fold(
161 (acc, T::default()),
162 |acc, activity| {
163 self.get_demand(activity)
164 .map(|demand| (acc.0 + demand.delivery.0, acc.1 + demand.pickup.0))
165 .unwrap_or_else(|| acc)
166 },
167 );
168
169 let (current, _) = route.tour.activities_slice(start_idx, end_idx).iter().enumerate().fold(
171 (start_delivery, T::default()),
172 |(current, max), (idx, activity)| {
173 let activity_idx = start_idx + idx;
174 let change = self.get_demand(activity).map(|demand| demand.change()).unwrap_or_default();
175
176 let current = current + change;
177 let max = max.max_load(current);
178
179 current_capacities[activity_idx] = current;
180 max_past_capacities[activity_idx] = max;
181
182 (current, max)
183 },
184 );
185
186 let current_max = (start_idx..=end_idx).rev().fold(current, |max, activity_idx| {
187 let max = max.max_load(current_capacities[activity_idx]);
188 max_future_capacities[activity_idx] = max;
189
190 max
191 });
192
193 (current - end_pickup, current_max.max_load(max))
194 });
195
196 route_ctx.state_mut().set_current_capacity_states(current_capacities);
197 route_ctx.state_mut().set_max_past_capacity_states(max_past_capacities);
198 route_ctx.state_mut().set_max_future_capacity_states(max_future_capacities);
199
200 if let Some(capacity) = route_ctx.route().actor.clone().vehicle.dimens.get_vehicle_capacity::<T>() {
201 route_ctx.state_mut().set_max_vehicle_load(max_load.ratio(capacity));
202 }
203 }
204
205 fn try_recover(&self, _: &mut SolutionContext, _: &[usize], _: &[Job]) -> bool {
206 false
208 }
209}
210
211impl<T> CapacitatedMultiTrip<T>
212where
213 T: LoadOps,
214{
215 fn evaluate_job(&self, route_ctx: &RouteContext, job: &Job) -> Option<ConstraintViolation> {
216 let can_handle = match job {
217 Job::Single(job) => self.can_handle_demand_on_intervals(route_ctx, job.dimens.get_job_demand(), None),
218 Job::Multi(job) => job
219 .jobs
220 .iter()
221 .any(|job| self.can_handle_demand_on_intervals(route_ctx, job.dimens.get_job_demand(), None)),
222 };
223
224 if can_handle {
225 ConstraintViolation::success()
226 } else {
227 ConstraintViolation::fail(self.violation_code)
228 }
229 }
230
231 fn evaluate_activity(
232 &self,
233 route_ctx: &RouteContext,
234 activity_ctx: &ActivityContext,
235 ) -> Option<ConstraintViolation> {
236 let demand = self.get_demand(activity_ctx.target);
237
238 let violation = if activity_ctx.target.retrieve_job().map_or(false, |job| job.as_multi().is_some()) {
239 if self.can_handle_demand_on_intervals(route_ctx, demand, Some(activity_ctx.index)) {
241 None
242 } else {
243 Some(false)
244 }
245 } else {
246 has_demand_violation(route_ctx, activity_ctx.index, demand, !self.has_markers(route_ctx))
247 };
248
249 violation.map(|stopped| ConstraintViolation { code: self.violation_code, stopped })
250 }
251
252 fn has_markers(&self, route_ctx: &RouteContext) -> bool {
253 self.route_intervals.get_marker_intervals(route_ctx).map_or(false, |intervals| intervals.len() > 1)
254 }
255
256 fn can_handle_demand_on_intervals(
257 &self,
258 route_ctx: &RouteContext,
259 demand: Option<&Demand<T>>,
260 insert_idx: Option<usize>,
261 ) -> bool {
262 let has_demand_violation = |activity_idx: usize| has_demand_violation(route_ctx, activity_idx, demand, true);
263
264 let has_demand_violation_on_borders = |start_idx: usize, end_idx: usize| {
265 has_demand_violation(start_idx).is_none() || has_demand_violation(end_idx).is_none()
266 };
267
268 self.route_intervals
269 .get_marker_intervals(route_ctx)
270 .map(|intervals| {
271 if let Some(insert_idx) = insert_idx {
272 intervals
273 .iter()
274 .filter(|(_, end_idx)| insert_idx <= *end_idx)
275 .all(|(start_idx, _)| has_demand_violation(insert_idx.max(*start_idx)).is_none())
276 } else {
277 intervals.iter().any(|(start_idx, end_idx)| has_demand_violation_on_borders(*start_idx, *end_idx))
278 }
279 })
280 .unwrap_or_else(|| {
281 if let Some(insert_idx) = insert_idx {
282 has_demand_violation(insert_idx).is_none()
283 } else {
284 let last_idx = route_ctx.route().tour.end_idx().unwrap_or_default();
285 has_demand_violation_on_borders(0, last_idx)
286 }
287 })
288 }
289
290 fn get_demand<'a>(&self, activity: &'a Activity) -> Option<&'a Demand<T>> {
291 activity.job.as_ref().and_then(|single| single.dimens.get_job_demand())
292 }
293}
294
295fn has_demand_violation<T: LoadOps>(
296 route_ctx: &RouteContext,
297 pivot_idx: usize,
298 demand: Option<&Demand<T>>,
299 stopped: bool,
300) -> Option<bool> {
301 let capacity: Option<&T> = route_ctx.route().actor.vehicle.dimens.get_vehicle_capacity();
302 let demand = demand?;
303
304 let capacity = if let Some(capacity) = capacity {
305 capacity
306 } else {
307 return Some(stopped);
308 };
309
310 let state = route_ctx.state();
311
312 if demand.delivery.0.is_not_empty() {
314 let past: T = state.get_max_past_capacity_at(pivot_idx).copied().unwrap_or_default();
315 if !capacity.can_fit(&(past + demand.delivery.0)) {
316 return Some(stopped);
317 }
318 }
319
320 if demand.pickup.0.is_not_empty() {
322 let future: T = state.get_max_future_capacity_at(pivot_idx).copied().unwrap_or_default();
323 if !capacity.can_fit(&(future + demand.pickup.0)) {
324 return Some(false);
325 }
326 }
327
328 let change = demand.change();
330 if change.is_not_empty() {
331 let future: T = state.get_max_future_capacity_at(pivot_idx).copied().unwrap_or_default();
332 if !capacity.can_fit(&(future + change)) {
333 return Some(false);
334 }
335
336 let current: T = state.get_current_capacity_at(pivot_idx).copied().unwrap_or_default();
337 if !capacity.can_fit(&(current + change)) {
338 return Some(false);
339 }
340 }
341
342 None
343}
344
345struct JobDemandDimenKey;
347impl JobDemandDimension for Dimensions {
348 fn set_job_demand<T: LoadOps>(&mut self, demand: Demand<T>) -> &mut Self {
349 self.set_value::<JobDemandDimenKey, _>(demand);
350 self
351 }
352
353 fn get_job_demand<T: LoadOps>(&self) -> Option<&Demand<T>> {
354 self.get_value::<JobDemandDimenKey, _>()
355 }
356}