1#[cfg(test)]
2#[path = "../../../tests/unit/construction/heuristics/selectors_test.rs"]
3mod selectors_test;
4
5use crate::construction::heuristics::*;
6use crate::models::problem::Job;
7use crate::models::solution::Leg;
8use crate::utils::*;
9use rand::prelude::*;
10use std::cmp::Ordering;
11use std::ops::ControlFlow;
12use std::sync::Arc;
13
14pub trait RouteSelector: Send + Sync {
17 fn prepare(&self, insertion_ctx: &mut InsertionContext) {
21 insertion_ctx.solution.routes.shuffle(&mut insertion_ctx.environment.random.get_rng());
22 }
23
24 fn select<'a>(
26 &'a self,
27 insertion_ctx: &'a InsertionContext,
28 jobs: &[&'a Job],
29 ) -> Box<dyn Iterator<Item = &'a RouteContext> + 'a>;
30}
31
32#[derive(Default)]
34pub struct AllRouteSelector {}
35
36impl RouteSelector for AllRouteSelector {
37 fn select<'a>(
38 &'a self,
39 insertion_ctx: &'a InsertionContext,
40 _: &[&'a Job],
41 ) -> Box<dyn Iterator<Item = &'a RouteContext> + 'a> {
42 Box::new(insertion_ctx.solution.routes.iter().chain(insertion_ctx.solution.registry.next_route()))
43 }
44}
45
46pub trait JobSelector: Send + Sync {
49 fn prepare(&self, insertion_ctx: &mut InsertionContext) {
53 insertion_ctx.solution.required.shuffle(&mut insertion_ctx.environment.random.get_rng());
54 }
55
56 fn select<'a>(&'a self, insertion_ctx: &'a InsertionContext) -> Box<dyn Iterator<Item = &'a Job> + 'a> {
58 Box::new(insertion_ctx.solution.required.iter())
59 }
60}
61
62#[derive(Default)]
64pub struct AllJobSelector {}
65
66impl JobSelector for AllJobSelector {}
67
68pub trait InsertionEvaluator: Send + Sync {
70 fn evaluate_all(
72 &self,
73 insertion_ctx: &InsertionContext,
74 jobs: &[&Job],
75 routes: &[&RouteContext],
76 leg_selection: &LegSelection,
77 result_selector: &(dyn ResultSelector),
78 ) -> InsertionResult;
79}
80
81pub struct PositionInsertionEvaluator {
83 insertion_position: InsertionPosition,
84}
85
86impl Default for PositionInsertionEvaluator {
87 fn default() -> Self {
88 Self::new(InsertionPosition::Any)
89 }
90}
91
92impl PositionInsertionEvaluator {
93 pub fn new(insertion_position: InsertionPosition) -> Self {
95 Self { insertion_position }
96 }
97
98 pub(crate) fn evaluate_and_collect_all(
100 &self,
101 insertion_ctx: &InsertionContext,
102 jobs: &[&Job],
103 routes: &[&RouteContext],
104 leg_selection: &LegSelection,
105 result_selector: &(dyn ResultSelector),
106 ) -> Vec<InsertionResult> {
107 let is_fold_jobs = insertion_ctx.solution.required.len() > insertion_ctx.solution.routes.len();
108 let goal = &insertion_ctx.problem.goal;
109
110 if is_fold_jobs {
113 parallel_collect(jobs, |job| {
114 let eval_ctx = EvaluationContext { goal, job, leg_selection, result_selector };
115 routes.iter().fold(InsertionResult::make_failure(), |acc, route_ctx| {
116 eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, self.insertion_position, acc)
117 })
118 })
119 } else {
120 parallel_collect(routes, |route_ctx| {
121 jobs.iter().fold(InsertionResult::make_failure(), |acc, job| {
122 let eval_ctx = EvaluationContext { goal, job, leg_selection, result_selector };
123 eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, self.insertion_position, acc)
124 })
125 })
126 }
127 }
128}
129
130impl InsertionEvaluator for PositionInsertionEvaluator {
131 fn evaluate_all(
132 &self,
133 insertion_ctx: &InsertionContext,
134 jobs: &[&Job],
135 routes: &[&RouteContext],
136 leg_selection: &LegSelection,
137 result_selector: &(dyn ResultSelector),
138 ) -> InsertionResult {
139 let goal = &insertion_ctx.problem.goal;
140
141 fold_reduce(
142 cartesian_product(routes, jobs),
143 InsertionResult::make_failure,
144 |acc, (route_ctx, job)| {
145 let eval_ctx = EvaluationContext { goal, job, leg_selection, result_selector };
146 eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, self.insertion_position, acc)
147 },
148 |left, right| result_selector.select_insertion(insertion_ctx, left, right),
149 )
150 }
151}
152
153pub trait ResultSelector: Send + Sync {
155 fn select_insertion(
157 &self,
158 ctx: &InsertionContext,
159 left: InsertionResult,
160 right: InsertionResult,
161 ) -> InsertionResult;
162
163 fn select_cost<'a>(
165 &self,
166 left: &'a InsertionCost,
167 right: &'a InsertionCost,
168 ) -> Either<&'a InsertionCost, &'a InsertionCost> {
169 if left < right {
170 Either::Left(left)
171 } else {
172 Either::Right(right)
173 }
174 }
175}
176
177#[derive(Default)]
179pub struct BestResultSelector {}
180
181impl ResultSelector for BestResultSelector {
182 fn select_insertion(&self, _: &InsertionContext, left: InsertionResult, right: InsertionResult) -> InsertionResult {
183 InsertionResult::choose_best_result(left, right)
184 }
185}
186
187pub struct NoiseResultSelector {
189 noise: Noise,
190}
191
192impl NoiseResultSelector {
193 pub fn new(noise: Noise) -> Self {
195 Self { noise }
196 }
197}
198
199impl ResultSelector for NoiseResultSelector {
200 fn select_insertion(&self, _: &InsertionContext, left: InsertionResult, right: InsertionResult) -> InsertionResult {
201 match (&left, &right) {
202 (InsertionResult::Success(_), InsertionResult::Failure(_)) => left,
203 (InsertionResult::Failure(_), InsertionResult::Success(_)) => right,
204 (InsertionResult::Success(left_success), InsertionResult::Success(right_success)) => {
205 let left_cost: InsertionCost = self.noise.generate_multi(left_success.cost.iter()).collect();
206 let right_cost = self.noise.generate_multi(right_success.cost.iter()).collect();
207
208 match left_cost.cmp(&right_cost) {
209 Ordering::Less => left,
210 Ordering::Greater => right,
211 Ordering::Equal if self.noise.random().is_head_not_tails() => left,
212 _ => right,
213 }
214 }
215 _ => right,
216 }
217 }
218
219 fn select_cost<'a>(
220 &self,
221 left: &'a InsertionCost,
222 right: &'a InsertionCost,
223 ) -> Either<&'a InsertionCost, &'a InsertionCost> {
224 let left_cost: InsertionCost = self.noise.generate_multi(left.iter()).collect();
225 let right_cost: InsertionCost = self.noise.generate_multi(right.iter()).collect();
226
227 match left_cost.cmp(&right_cost) {
228 Ordering::Less => Either::Left(left),
229 Ordering::Greater => Either::Right(right),
230 Ordering::Equal if self.noise.random().is_head_not_tails() => Either::Left(left),
231 _ => Either::Right(right),
232 }
233 }
234}
235
236#[derive(Default)]
238pub struct FarthestResultSelector {}
239
240impl ResultSelector for FarthestResultSelector {
241 fn select_insertion(
242 &self,
243 insertion_ctx: &InsertionContext,
244 left: InsertionResult,
245 right: InsertionResult,
246 ) -> InsertionResult {
247 match (&left, &right) {
248 (InsertionResult::Success(_), InsertionResult::Failure(_)) => left,
249 (InsertionResult::Failure(_), InsertionResult::Success(_)) => right,
250 (InsertionResult::Success(lhs), InsertionResult::Success(rhs)) => {
251 let routes = &insertion_ctx.solution.routes;
252 let lhs_route = routes.iter().find(|route_ctx| route_ctx.route().actor == lhs.actor);
253 let rhs_route = routes.iter().find(|route_ctx| route_ctx.route().actor == rhs.actor);
254
255 let insert_right = match (lhs_route.is_some(), rhs_route.is_some()) {
256 (false, false) => lhs.cost < rhs.cost,
257 (true, false) => false,
258 (false, true) => true,
259 (true, true) => lhs.cost > rhs.cost,
260 };
261
262 if insert_right {
263 right
264 } else {
265 left
266 }
267 }
268 _ => right,
269 }
270 }
271}
272
273pub struct BlinkResultSelector {
276 random: Arc<dyn Random>,
277 ratio: Float,
278}
279
280impl BlinkResultSelector {
281 pub fn new(ratio: Float, random: Arc<dyn Random>) -> Self {
283 Self { random, ratio }
284 }
285
286 pub fn new_with_defaults(random: Arc<dyn Random>) -> Self {
288 Self::new(0.01, random)
289 }
290}
291
292impl ResultSelector for BlinkResultSelector {
293 fn select_insertion(&self, _: &InsertionContext, left: InsertionResult, right: InsertionResult) -> InsertionResult {
294 let is_blink = self.random.is_hit(self.ratio);
295
296 if is_blink {
297 return if self.random.is_head_not_tails() { left } else { right };
298 }
299
300 InsertionResult::choose_best_result(left, right)
301 }
302
303 fn select_cost<'a>(
304 &self,
305 left: &'a InsertionCost,
306 right: &'a InsertionCost,
307 ) -> Either<&'a InsertionCost, &'a InsertionCost> {
308 let is_blink = self.random.is_hit(self.ratio);
309
310 if is_blink {
311 return if self.random.is_head_not_tails() { Either::Left(left) } else { Either::Right(right) };
312 }
313
314 match left.cmp(right) {
315 Ordering::Less => Either::Left(left),
316 Ordering::Greater => Either::Right(right),
317 Ordering::Equal if self.random.is_head_not_tails() => Either::Left(left),
318 _ => Either::Right(right),
319 }
320 }
321}
322
323pub enum ResultSelection {
325 Stochastic(ResultSelectorProvider),
327
328 Concrete(Box<dyn ResultSelector>),
330}
331
332pub struct ResultSelectorProvider {
334 inners: Vec<Box<dyn ResultSelector>>,
335 weights: Vec<usize>,
336 random: Arc<dyn Random>,
337}
338
339impl ResultSelectorProvider {
340 pub fn new_default(random: Arc<dyn Random>) -> Self {
342 Self {
343 inners: vec![
344 Box::<BestResultSelector>::default(),
345 Box::new(NoiseResultSelector::new(Noise::new_with_addition(0.05, (-0.25, 0.25), random.clone()))),
346 Box::new(BlinkResultSelector::new_with_defaults(random.clone())),
347 Box::<FarthestResultSelector>::default(),
348 ],
349 weights: vec![60, 10, 10, 20],
350 random,
351 }
352 }
353
354 pub fn pick(&self) -> &(dyn ResultSelector) {
356 self.inners[self.random.weighted(self.weights.as_slice())].as_ref()
357 }
358}
359
360#[derive(Clone)]
362pub enum LegSelection {
363 Stochastic(Arc<dyn Random>),
365 Exhaustive,
367}
368
369impl LegSelection {
370 pub(crate) fn sample_best<R, FM, FC>(
372 &self,
373 route_ctx: &RouteContext,
374 job: &Job,
375 skip: usize,
376 init: R,
377 mut map_fn: FM,
378 compare_fn: FC,
379 ) -> R
380 where
381 R: Default,
382 FM: FnMut(Leg, R) -> ControlFlow<R, R>,
383 FC: Fn(&R, &R) -> bool,
384 {
385 if let Some((sample_size, random)) = self.get_sample_data(route_ctx, job, skip) {
386 route_ctx
387 .route()
388 .tour
389 .legs()
390 .skip(skip)
391 .sample_search(
392 sample_size,
393 random.clone(),
394 &mut |leg: Leg<'_>| map_fn(leg, R::default()).unwrap_value(),
395 |leg: &Leg<'_>| leg.1 - skip,
396 &compare_fn,
397 )
398 .unwrap_or(init)
399 } else {
400 route_ctx.route().tour.legs().skip(skip).try_fold(init, |acc, leg| map_fn(leg, acc)).unwrap_value()
401 }
402 }
403
404 fn get_sample_data(&self, route_ctx: &RouteContext, job: &Job, skip: usize) -> Option<(usize, Arc<dyn Random>)> {
406 match self {
407 Self::Stochastic(random) => {
408 let gen_usize = |min: i32, max: i32| random.uniform_int(min, max) as usize;
409 let greedy_threshold = match job {
410 Job::Single(_) => gen_usize(32, 48),
411 Job::Multi(_) => gen_usize(16, 32),
412 };
413
414 let total_legs = route_ctx.route().tour.legs().size_hint().0;
415 let visit_legs = if total_legs > skip { total_legs - skip } else { 0 };
416
417 if visit_legs < greedy_threshold {
418 None
419 } else {
420 Some((
421 match job {
422 Job::Single(_) => 8,
423 Job::Multi(_) => 4,
424 },
425 random.clone(),
426 ))
427 }
428 }
429 Self::Exhaustive => None,
430 }
431 }
432}