1#[cfg(test)]
2#[path = "../../../tests/unit/construction/heuristics/evaluators_test.rs"]
3mod evaluators_test;
4
5use rosomaxa::prelude::UnwrapValue;
6use std::ops::ControlFlow;
7use std::sync::Arc;
8
9use crate::construction::heuristics::*;
10use crate::models::common::Timestamp;
11use crate::models::problem::{Job, Multi, Single};
12use crate::models::solution::{Activity, Leg, Place};
13use crate::models::{ConstraintViolation, GoalContext, ViolationCode};
14use crate::utils::Either;
15
16pub struct EvaluationContext<'a> {
18 pub goal: &'a GoalContext,
20 pub job: &'a Job,
22 pub leg_selection: &'a LegSelection,
24 pub result_selector: &'a (dyn ResultSelector),
26}
27
28#[derive(Copy, Clone)]
30pub enum InsertionPosition {
31 Any,
33 Concrete(usize),
35 Last,
37}
38
39pub fn eval_job_insertion_in_route(
42 insertion_ctx: &InsertionContext,
43 eval_ctx: &EvaluationContext,
44 route_ctx: &RouteContext,
45 position: InsertionPosition,
46 alternative: InsertionResult,
47) -> InsertionResult {
48 match (route_ctx.is_stale(), insertion_ctx.solution.unassigned.get(eval_ctx.job)) {
50 (false, Some(UnassignmentInfo::Simple(_))) | (false, Some(UnassignmentInfo::Detailed(_))) => {
51 return alternative
52 }
53 _ => {}
54 }
55
56 let goal = &insertion_ctx.problem.goal;
57
58 if let Some(violation) = goal.evaluate(&MoveContext::route(&insertion_ctx.solution, route_ctx, eval_ctx.job)) {
59 return eval_ctx.result_selector.select_insertion(
60 insertion_ctx,
61 alternative,
62 InsertionResult::make_failure_with_code(violation.code, true, Some(eval_ctx.job.clone())),
63 );
64 }
65
66 let route_costs = goal.estimate(&MoveContext::route(&insertion_ctx.solution, route_ctx, eval_ctx.job));
67
68 let (route_costs, best_known_cost) = if let Some(success) = alternative.as_success() {
70 match eval_ctx.result_selector.select_cost(&success.cost, &route_costs) {
71 Either::Left(_) => return alternative,
72 Either::Right(_) => (route_costs, Some(success.cost.clone())),
73 }
74 } else {
75 (route_costs, None)
76 };
77
78 eval_ctx.result_selector.select_insertion(
79 insertion_ctx,
80 alternative,
81 eval_job_constraint_in_route(eval_ctx, route_ctx, position, route_costs, best_known_cost),
82 )
83}
84
85pub fn eval_job_constraint_in_route(
88 eval_ctx: &EvaluationContext,
89 route_ctx: &RouteContext,
90 position: InsertionPosition,
91 route_costs: InsertionCost,
92 best_known_cost: Option<InsertionCost>,
93) -> InsertionResult {
94 match eval_ctx.job {
95 Job::Single(single) => eval_single(eval_ctx, route_ctx, single, position, route_costs, best_known_cost),
96 Job::Multi(multi) => eval_multi(eval_ctx, route_ctx, multi, position, route_costs, best_known_cost),
97 }
98}
99
100pub(crate) fn eval_single_constraint_in_route(
101 insertion_ctx: &InsertionContext,
102 eval_ctx: &EvaluationContext,
103 route_ctx: &RouteContext,
104 single: &Arc<Single>,
105 position: InsertionPosition,
106 route_costs: InsertionCost,
107 best_known_cost: Option<InsertionCost>,
108) -> InsertionResult {
109 if let Some(violation) =
110 eval_ctx.goal.evaluate(&MoveContext::route(&insertion_ctx.solution, route_ctx, eval_ctx.job))
111 {
112 InsertionResult::Failure(InsertionFailure {
113 constraint: violation.code,
114 stopped: true,
115 job: Some(eval_ctx.job.clone()),
116 })
117 } else {
118 eval_single(eval_ctx, route_ctx, single, position, route_costs, best_known_cost)
119 }
120}
121
122fn eval_single(
123 eval_ctx: &EvaluationContext,
124 route_ctx: &RouteContext,
125 single: &Arc<Single>,
126 position: InsertionPosition,
127 route_costs: InsertionCost,
128 best_known_cost: Option<InsertionCost>,
129) -> InsertionResult {
130 let insertion_idx = get_insertion_index(route_ctx, position);
131 let mut activity = Activity::new_with_job(single.clone());
132
133 let result = analyze_insertion_in_route(
134 eval_ctx,
135 route_ctx,
136 insertion_idx,
137 single,
138 &mut activity,
139 route_costs,
140 SingleContext::new(best_known_cost, 0),
141 );
142
143 let job = eval_ctx.job.clone();
144 if let Some(place) = result.place {
145 activity.place = place;
146 let activities = vec![(activity, result.index)];
147 InsertionResult::make_success(result.cost.unwrap_or_default(), job, activities, route_ctx)
148 } else {
149 let (code, stopped) = result.violation.map_or((ViolationCode::unknown(), false), |v| (v.code, v.stopped));
150 InsertionResult::make_failure_with_code(code, stopped, Some(job))
151 }
152}
153
154fn eval_multi(
155 eval_ctx: &EvaluationContext,
156 route_ctx: &RouteContext,
157 multi: &Arc<Multi>,
158 position: InsertionPosition,
159 route_costs: InsertionCost,
160 best_known_cost: Option<InsertionCost>,
161) -> InsertionResult {
162 let insertion_idx = get_insertion_index(route_ctx, position).unwrap_or(0);
163 let result = multi
165 .permutations()
166 .into_iter()
167 .try_fold(MultiContext::new(best_known_cost, insertion_idx), |acc_res, services| {
168 let mut shadow = ShadowContext::new(eval_ctx.goal, route_ctx);
169 let perm_res = (0..)
170 .try_fold(MultiContext::new(None, insertion_idx), |out, _| {
171 if out.is_failure(route_ctx.route().tour.job_activity_count()) {
172 return ControlFlow::Break(out);
173 }
174 shadow.restore(route_ctx);
175
176 let sq_res = services
178 .iter()
179 .try_fold(out.next(), |in1, service| {
180 if in1.violation.is_some() {
181 return ControlFlow::Break(in1);
182 }
183 let mut activity = Activity::new_with_job(service.clone());
184 let srv_res = analyze_insertion_in_route(
186 eval_ctx,
187 shadow.route_ctx(),
188 None,
189 service,
190 &mut activity,
191 Default::default(),
192 SingleContext::new(None, in1.next_index),
193 );
194
195 if let Some(place) = srv_res.place {
196 activity.place = place;
197 let activity = shadow.insert(activity, srv_res.index);
198 let activities = concat_activities(in1.activities, (activity, srv_res.index));
199 return MultiContext::success(
200 in1.cost.unwrap_or_else(|| route_costs.clone()) + srv_res.cost.unwrap_or_default(),
201 activities,
202 );
203 }
204
205 MultiContext::fail(srv_res, in1)
206 })
207 .unwrap_value();
208
209 MultiContext::promote(sq_res, out)
210 })
211 .unwrap_value();
212
213 MultiContext::promote(perm_res, acc_res)
214 })
215 .unwrap_value();
216
217 let job = eval_ctx.job.clone();
218 if result.is_success() {
219 let activities = result.activities.unwrap_or_default();
220 InsertionResult::make_success(result.cost.unwrap_or_default(), job, activities, route_ctx)
221 } else {
222 let (code, stopped) = result.violation.map_or((ViolationCode::unknown(), false), |v| (v.code, v.stopped));
223 InsertionResult::make_failure_with_code(code, stopped, Some(job))
224 }
225}
226
227fn analyze_insertion_in_route(
228 eval_ctx: &EvaluationContext,
229 route_ctx: &RouteContext,
230 insertion_idx: Option<usize>,
231 single: &Single,
232 target: &mut Activity,
233 route_costs: InsertionCost,
234 init: SingleContext,
235) -> SingleContext {
236 let mut analyze_leg_insertion = |leg: Leg<'_>, init| {
237 analyze_insertion_in_route_leg(eval_ctx, route_ctx, leg, single, target, route_costs.clone(), init)
238 };
239
240 match insertion_idx {
241 Some(idx) => {
242 if let Some(leg) = route_ctx.route().tour.legs().nth(idx) {
243 analyze_leg_insertion(leg, init).unwrap_value()
244 } else {
245 init
246 }
247 }
248 None => eval_ctx.leg_selection.sample_best(
249 route_ctx,
250 eval_ctx.job,
251 init.index,
252 init,
253 &mut |leg: Leg<'_>, init| analyze_leg_insertion(leg, init),
254 {
255 let max_value = InsertionCost::max_value();
256 move |lhs: &SingleContext, rhs: &SingleContext| {
257 eval_ctx
258 .result_selector
259 .select_cost(lhs.cost.as_ref().unwrap_or(max_value), rhs.cost.as_ref().unwrap_or(max_value))
260 .is_left()
261 }
262 },
263 ),
264 }
265}
266
267fn analyze_insertion_in_route_leg(
268 eval_ctx: &EvaluationContext,
269 route_ctx: &RouteContext,
270 leg: Leg,
271 single: &Single,
272 target: &mut Activity,
273 route_costs: InsertionCost,
274 mut single_ctx: SingleContext,
275) -> ControlFlow<SingleContext, SingleContext> {
276 let (items, index) = leg;
277 let (prev, next) = match items {
278 [prev] => (prev, None),
279 [prev, next] => (prev, Some(next)),
280 _ => return ControlFlow::Break(single_ctx),
281 };
282 let start_time = route_ctx.route().tour.start().map_or(Timestamp::default(), |act| act.schedule.departure);
283
284 for (place_idx, place) in single.places.iter().enumerate() {
286 target.place.idx = place_idx;
287 target.place.location = place.location.unwrap_or(prev.place.location);
288 target.place.duration = place.duration;
289
290 for time in place.times.iter() {
292 target.place.time = time.to_time_window(start_time);
293
294 let activity_ctx = ActivityContext { index, prev, target, next };
295 let move_ctx = MoveContext::activity(route_ctx, &activity_ctx);
296
297 if let Some(violation) = eval_ctx.goal.evaluate(&move_ctx) {
298 let is_stopped = violation.stopped;
299 single_ctx.violation = Some(violation);
300 if is_stopped {
301 return ControlFlow::Break(single_ctx);
303 } else {
304 continue;
306 }
307 }
308
309 let costs = eval_ctx.goal.estimate(&move_ctx) + &route_costs;
310 let other_costs = single_ctx.cost.as_ref().unwrap_or(InsertionCost::max_value());
311
312 match eval_ctx.result_selector.select_cost(&costs, other_costs) {
313 Either::Left(_) => {
315 single_ctx.violation = None;
316 single_ctx.index = index;
317 single_ctx.cost = Some(costs);
318 single_ctx.place = Some(target.place.clone());
319 }
320 Either::Right(_) => continue,
321 }
322 }
323 }
324
325 ControlFlow::Continue(single_ctx)
326}
327
328fn get_insertion_index(route_ctx: &RouteContext, position: InsertionPosition) -> Option<usize> {
329 match position {
330 InsertionPosition::Any => None,
331 InsertionPosition::Concrete(idx) => Some(idx),
332 InsertionPosition::Last => Some(route_ctx.route().tour.legs().count().max(1) - 1),
333 }
334}
335
336#[derive(Clone, Debug, Default)]
338struct SingleContext {
339 pub violation: Option<ConstraintViolation>,
341 pub index: usize,
343 pub cost: Option<InsertionCost>,
345 pub place: Option<Place>,
347}
348
349impl SingleContext {
350 fn new(cost: Option<InsertionCost>, index: usize) -> Self {
352 Self { violation: None, index, cost, place: None }
353 }
354}
355
356struct MultiContext {
358 pub violation: Option<ConstraintViolation>,
360 pub start_index: usize,
362 pub next_index: usize,
364 pub cost: Option<InsertionCost>,
366 pub activities: Option<Vec<(Activity, usize)>>,
368}
369
370impl MultiContext {
371 fn new(cost: Option<InsertionCost>, index: usize) -> Self {
373 Self { violation: None, start_index: index, next_index: index, cost, activities: None }
374 }
375
376 fn promote(left: Self, right: Self) -> ControlFlow<Self, Self> {
378 let index = left.start_index.max(right.start_index) + 1;
379 let best = match (&left.cost, &right.cost) {
380 (Some(left_cost), Some(right_cost)) => {
381 if left_cost < right_cost {
382 left
383 } else {
384 right
385 }
386 }
387 (Some(_), None) => left,
388 (None, Some(_)) => right,
389 (None, None) => {
391 if left.violation.is_some() {
392 left
393 } else {
394 right
395 }
396 }
397 };
398
399 let result = Self {
400 violation: best.violation,
401 start_index: index,
402 next_index: index,
403 cost: best.cost,
404 activities: best.activities,
405 };
406
407 if result.violation.as_ref().map_or(false, |v| v.stopped) {
408 ControlFlow::Break(result)
409 } else {
410 ControlFlow::Continue(result)
411 }
412 }
413
414 #[inline]
416 fn fail(err_ctx: SingleContext, other_ctx: MultiContext) -> ControlFlow<Self, Self> {
417 let (code, stopped) = err_ctx
418 .violation
419 .map_or((ViolationCode::unknown(), false), |v| (v.code, v.stopped && other_ctx.activities.is_none()));
420
421 ControlFlow::Break(Self {
422 violation: Some(ConstraintViolation { code, stopped }),
423 start_index: other_ctx.start_index,
424 next_index: other_ctx.start_index,
425 cost: None,
426 activities: None,
427 })
428 }
429
430 #[inline]
432 fn success(cost: InsertionCost, activities: Vec<(Activity, usize)>) -> ControlFlow<Self, Self> {
433 let start_index = activities.first().map_or(0, |act| act.1);
435 let next_index = activities.last().map_or(0, |act| act.1) + 1;
436
437 ControlFlow::Continue(Self {
438 violation: None,
439 start_index,
440 next_index,
441 cost: Some(cost),
442 activities: Some(activities),
443 })
444 }
445
446 #[inline]
448 fn next(&self) -> Self {
449 Self {
450 violation: None,
451 start_index: self.start_index,
452 next_index: self.start_index,
453 cost: None,
454 activities: None,
455 }
456 }
457
458 #[inline]
460 fn is_success(&self) -> bool {
461 self.violation.is_none() && self.cost.is_some() && self.activities.is_some()
462 }
463
464 #[inline]
466 fn is_failure(&self, index: usize) -> bool {
467 self.violation.as_ref().map_or(false, |v| v.stopped) || (self.start_index > index)
468 }
469}
470
471struct ShadowContext<'a> {
473 goal: &'a GoalContext,
474 ctx: Either<&'a RouteContext, RouteContext>,
478}
479
480impl<'a> ShadowContext<'a> {
481 fn new(goal: &'a GoalContext, ctx: &'a RouteContext) -> Self {
482 Self { goal, ctx: Either::Left(ctx) }
483 }
484
485 fn route_ctx(&self) -> &'_ RouteContext {
486 match &self.ctx {
487 Either::Left(route_ctx) => route_ctx,
488 Either::Right(route_ctx) => route_ctx,
489 }
490 }
491
492 fn insert(&mut self, activity: Activity, index: usize) -> Activity {
493 if let Either::Left(route_ctx) = &self.ctx {
494 self.ctx = Either::Right(route_ctx.deep_copy());
495 }
496
497 if let Either::Right(ref mut route_ctx) = self.ctx {
498 route_ctx.route_mut().tour.insert_at(activity.deep_copy(), index + 1);
499 self.goal.accept_route_state(route_ctx);
500 }
501
502 activity
503 }
504
505 fn restore(&mut self, original: &'a RouteContext) {
506 if let Either::Right(_) = &self.ctx {
507 self.ctx = Either::Left(original)
508 }
509 }
510}
511
512fn concat_activities(
513 activities: Option<Vec<(Activity, usize)>>,
514 activity: (Activity, usize),
515) -> Vec<(Activity, usize)> {
516 let mut activities = activities.unwrap_or_default();
517 activities.push((activity.0, activity.1));
518
519 activities
520}