Skip to main content

vrp_core/construction/heuristics/
insertions.rs

1#[cfg(test)]
2#[path = "../../../tests/unit/construction/heuristics/insertions_test.rs"]
3mod insertions_test;
4
5use crate::construction::heuristics::*;
6use crate::models::common::Cost;
7use crate::models::problem::{Actor, Job, JobIdDimension};
8use crate::models::solution::Activity;
9use crate::models::ViolationCode;
10use lazy_static::lazy_static;
11use rosomaxa::prelude::*;
12use std::borrow::Borrow;
13use std::cmp::Ordering;
14use std::fmt::{Debug, Formatter};
15use std::ops::{Add, ControlFlow, Index, Sub};
16use std::sync::Arc;
17use tinyvec::{TinyVec, TinyVecIterator};
18
19/// Specifies insertion result variant.
20#[derive(Debug)]
21pub enum InsertionResult {
22    /// Successful insertion result.
23    Success(InsertionSuccess),
24    /// Insertion failure.
25    Failure(InsertionFailure),
26}
27
28/// Specifies insertion success result needed to insert job into tour.
29pub struct InsertionSuccess {
30    /// Specifies delta cost change for the insertion.
31    pub cost: InsertionCost,
32
33    /// Original job to be inserted.
34    pub job: Job,
35
36    /// Specifies activities within index where they have to be inserted.
37    pub activities: Vec<(Activity, usize)>,
38
39    /// Specifies actor to be used.
40    pub actor: Arc<Actor>,
41}
42
43impl Debug for InsertionSuccess {
44    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct(short_type_name::<Self>())
46            .field("cost", &self.cost)
47            .field("job", &self.job)
48            .field(
49                "activities",
50                &self
51                    .activities
52                    .iter()
53                    .map(|(a, idx)| {
54                        (
55                            a.retrieve_job()
56                                .and_then(|job| job.dimens().get_job_id().cloned())
57                                .unwrap_or("undef".to_string()),
58                            *idx,
59                        )
60                    })
61                    .collect::<Vec<_>>(),
62            )
63            .field("actor", self.actor.as_ref())
64            .finish()
65    }
66}
67
68/// Specifies insertion failure.
69#[derive(Debug)]
70pub struct InsertionFailure {
71    /// Failed constraint code.
72    pub constraint: ViolationCode,
73    /// A flag which signalizes that algorithm should stop trying to insert at next positions.
74    pub stopped: bool,
75    /// Original job failed to be inserted.
76    pub job: Option<Job>,
77}
78
79/// Specifies a max size of stack allocated array to be used. If data size exceeds it,
80/// then heap allocated vector is used which leads to performance impact.
81const COST_DIMENSION: usize = 6;
82
83/// A size of a cost array used by `InsertionCost`.
84type CostArray = [Cost; COST_DIMENSION];
85
86lazy_static! {
87    static ref MAX_INSERTION_COST: InsertionCost = InsertionCost::new(&[Cost::MAX]);
88}
89
90/// A lexicographical cost of job's insertion.
91#[derive(Clone, Default)]
92pub struct InsertionCost {
93    data: TinyVec<CostArray>,
94}
95
96impl InsertionCost {
97    /// Creates a new instance of `InsertionCost`.
98    pub fn new(data: &[Cost]) -> Self {
99        Self { data: data.into() }
100    }
101
102    /// Returns iterator over cost values.
103    pub fn iter(&self) -> impl Iterator<Item = Cost> + '_ {
104        self.data.iter().cloned()
105    }
106
107    /// Returns a reference to the highest possible insertion cost.
108    pub fn max_value() -> &'static Self {
109        &MAX_INSERTION_COST
110    }
111}
112
113impl FromIterator<Cost> for InsertionCost {
114    fn from_iter<T: IntoIterator<Item = Cost>>(iter: T) -> Self {
115        Self { data: TinyVec::<CostArray>::from_iter(iter) }
116    }
117}
118
119impl IntoIterator for InsertionCost {
120    type Item = Cost;
121    type IntoIter = TinyVecIterator<CostArray>;
122
123    fn into_iter(self) -> Self::IntoIter {
124        self.data.into_iter()
125    }
126}
127
128impl Eq for InsertionCost {}
129
130impl PartialEq for InsertionCost {
131    fn eq(&self, other: &Self) -> bool {
132        self.cmp(other) == Ordering::Equal
133    }
134}
135
136impl PartialOrd for InsertionCost {
137    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138        Some(self.cmp(other))
139    }
140}
141
142impl Ord for InsertionCost {
143    fn cmp(&self, other: &Self) -> Ordering {
144        let size = self.data.len().max(other.data.len());
145        (0..size)
146            .try_fold(Ordering::Equal, |acc, idx| {
147                let left = self.data.get(idx).cloned().unwrap_or_default();
148                let right = other.data.get(idx).cloned().unwrap_or_default();
149
150                let result = left.total_cmp(&right);
151                match result {
152                    Ordering::Equal => ControlFlow::Continue(acc),
153                    _ => ControlFlow::Break(result),
154                }
155            })
156            .unwrap_value()
157    }
158}
159
160impl<'a, B> Add<B> for &'a InsertionCost
161where
162    B: Borrow<InsertionCost>,
163{
164    type Output = InsertionCost;
165
166    fn add(self, rhs: B) -> Self::Output {
167        let rhs = rhs.borrow();
168        let size = self.data.len().max(rhs.data.len());
169
170        (0..size)
171            .map(|idx| {
172                self.data.get(idx).copied().unwrap_or(Cost::default())
173                    + rhs.data.get(idx).copied().unwrap_or(Cost::default())
174            })
175            .collect()
176    }
177}
178
179impl<B> Add<B> for InsertionCost
180where
181    B: Borrow<InsertionCost>,
182{
183    type Output = InsertionCost;
184
185    fn add(self, rhs: B) -> Self::Output {
186        &self + rhs
187    }
188}
189
190impl<'a, B> Sub<B> for &'a InsertionCost
191where
192    B: Borrow<InsertionCost>,
193{
194    type Output = InsertionCost;
195
196    fn sub(self, rhs: B) -> Self::Output {
197        let rhs = rhs.borrow();
198        let size = self.data.len().max(rhs.data.len());
199
200        (0..size)
201            .map(|idx| {
202                self.data.get(idx).copied().unwrap_or(Cost::default())
203                    - rhs.data.get(idx).copied().unwrap_or(Cost::default())
204            })
205            .collect()
206    }
207}
208
209impl<B> Sub<B> for InsertionCost
210where
211    B: Borrow<InsertionCost>,
212{
213    type Output = InsertionCost;
214
215    fn sub(self, rhs: B) -> Self::Output {
216        &self - rhs
217    }
218}
219
220impl Debug for InsertionCost {
221    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
222        f.debug_list().entries(&self.data).finish()
223    }
224}
225
226impl Index<usize> for InsertionCost {
227    type Output = Cost;
228
229    fn index(&self, index: usize) -> &Self::Output {
230        if index < self.data.len() {
231            &self.data[index]
232        } else {
233            panic!("index out of range: {index}, size is {}", self.data.len())
234        }
235    }
236}
237
238/// Implements generalized insertion heuristic.
239/// Using `JobSelector`, `RouteSelector`, and `ResultSelector` it tries to identify next job to
240/// be inserted until there are no jobs left or it is not possible to insert due to constraint
241/// limitations.
242pub struct InsertionHeuristic {
243    insertion_evaluator: Box<dyn InsertionEvaluator + Send + Sync>,
244}
245
246impl Default for InsertionHeuristic {
247    fn default() -> Self {
248        InsertionHeuristic::new(Box::<PositionInsertionEvaluator>::default())
249    }
250}
251
252impl InsertionHeuristic {
253    /// Creates a new instance of `InsertionHeuristic`.
254    pub fn new(insertion_evaluator: Box<dyn InsertionEvaluator + Send + Sync>) -> Self {
255        Self { insertion_evaluator }
256    }
257}
258
259impl InsertionHeuristic {
260    /// Runs common insertion heuristic algorithm using given selector specializations.
261    pub fn process(
262        &self,
263        mut insertion_ctx: InsertionContext,
264        job_selector: &(dyn JobSelector),
265        route_selector: &(dyn RouteSelector),
266        leg_selection: &LegSelection,
267        result_selector: &(dyn ResultSelector),
268    ) -> InsertionContext {
269        prepare_insertion_ctx(&mut insertion_ctx);
270
271        while !insertion_ctx.solution.required.is_empty()
272            && !insertion_ctx.environment.quota.as_ref().map_or(false, |q| q.is_reached())
273        {
274            job_selector.prepare(&mut insertion_ctx);
275            route_selector.prepare(&mut insertion_ctx);
276
277            let jobs = job_selector.select(&insertion_ctx).collect::<Vec<_>>();
278            let routes = route_selector.select(&insertion_ctx, jobs.as_slice()).collect::<Vec<_>>();
279
280            let result =
281                self.insertion_evaluator.evaluate_all(&insertion_ctx, &jobs, &routes, leg_selection, result_selector);
282
283            match result {
284                InsertionResult::Success(success) => {
285                    apply_insertion_success(&mut insertion_ctx, success);
286                }
287                InsertionResult::Failure(failure) => {
288                    // NOTE copy data to make borrow checker happy
289                    let (route_indices, jobs) = copy_selection_data(&insertion_ctx, routes.as_slice(), jobs.as_slice());
290                    apply_insertion_failure(&mut insertion_ctx, failure, &route_indices, &jobs);
291                }
292            }
293        }
294
295        finalize_insertion_ctx(&mut insertion_ctx);
296
297        insertion_ctx
298    }
299}
300
301impl InsertionResult {
302    /// Creates result which represents insertion success.
303    pub fn make_success(
304        cost: InsertionCost,
305        job: Job,
306        activities: Vec<(Activity, usize)>,
307        route_ctx: &RouteContext,
308    ) -> Self {
309        Self::Success(InsertionSuccess { cost, job, activities, actor: route_ctx.route().actor.clone() })
310    }
311
312    /// Creates result which represents insertion failure.
313    pub fn make_failure() -> Self {
314        Self::make_failure_with_code(ViolationCode::unknown(), false, None)
315    }
316
317    /// Creates result which represents insertion failure with given code.
318    pub fn make_failure_with_code(code: ViolationCode, stopped: bool, job: Option<Job>) -> Self {
319        Self::Failure(InsertionFailure { constraint: code, stopped, job })
320    }
321
322    /// Compares two insertion results and returns the cheapest by cost.
323    pub fn choose_best_result(left: Self, right: Self) -> Self {
324        match (&left, &right) {
325            (Self::Success(_), Self::Failure(_)) => left,
326            (Self::Failure(_), Self::Success(_)) => right,
327            (Self::Success(lhs), Self::Success(rhs)) => {
328                if lhs.cost > rhs.cost {
329                    right
330                } else {
331                    left
332                }
333            }
334            (Self::Failure(_), Self::Failure(rhs)) => {
335                if rhs.constraint == ViolationCode::unknown() {
336                    left
337                } else {
338                    right
339                }
340            }
341        }
342    }
343
344    /// Returns insertion result as success.
345    pub fn as_success(&self) -> Option<&InsertionSuccess> {
346        match self {
347            Self::Success(success) => Some(success),
348            Self::Failure(_) => None,
349        }
350    }
351}
352
353impl TryFrom<InsertionResult> for InsertionSuccess {
354    type Error = InsertionFailure;
355
356    fn try_from(value: InsertionResult) -> Result<Self, Self::Error> {
357        match value {
358            InsertionResult::Success(success) => Ok(success),
359            InsertionResult::Failure(failure) => Err(failure),
360        }
361    }
362}
363
364pub(crate) fn prepare_insertion_ctx(insertion_ctx: &mut InsertionContext) {
365    insertion_ctx.solution.required.extend(insertion_ctx.solution.unassigned.keys().cloned());
366    insertion_ctx.problem.goal.accept_solution_state(&mut insertion_ctx.solution);
367}
368
369pub(crate) fn finalize_insertion_ctx(insertion_ctx: &mut InsertionContext) {
370    finalize_unassigned(insertion_ctx, UnassignmentInfo::Unknown);
371
372    insertion_ctx.problem.goal.accept_solution_state(&mut insertion_ctx.solution);
373}
374
375pub(crate) fn apply_insertion_success(insertion_ctx: &mut InsertionContext, success: InsertionSuccess) {
376    let route_index = if let Some(new_route_ctx) = insertion_ctx.solution.registry.get_route(&success.actor) {
377        insertion_ctx.solution.routes.push(new_route_ctx);
378        insertion_ctx.solution.routes.len() - 1
379    } else {
380        insertion_ctx
381            .solution
382            .routes
383            .iter()
384            .position(|route_ctx| route_ctx.route().actor == success.actor)
385            .expect("registry is out of sync with used routes")
386    };
387
388    let route_ctx = insertion_ctx.solution.routes.get_mut(route_index).unwrap();
389    let route = route_ctx.route_mut();
390    success.activities.into_iter().for_each(|(a, index)| {
391        route.tour.insert_at(a, index + 1);
392    });
393
394    let job = success.job;
395    insertion_ctx.solution.required.retain(|j| *j != job);
396    insertion_ctx.solution.unassigned.remove(&job);
397    insertion_ctx.problem.goal.accept_insertion(&mut insertion_ctx.solution, route_index, &job);
398}
399
400fn apply_insertion_failure(
401    insertion_ctx: &mut InsertionContext,
402    failure: InsertionFailure,
403    route_indices: &[usize],
404    jobs: &[Job],
405) {
406    let selected_routes = route_indices.len();
407    let selected_jobs = jobs.len();
408
409    // NOTE in most of the cases, it is not needed to reevaluate insertion for all other jobs
410    let all_unassignable = selected_jobs == insertion_ctx.solution.required.len()
411        && selected_routes == insertion_ctx.solution.routes.len();
412
413    // give a change to promote special jobs which might unblock assignment for other jobs
414    // TODO move this a bit up to avoid adding failed jobs to unassigned?
415    let failure_handled = insertion_ctx.problem.goal.notify_failure(&mut insertion_ctx.solution, route_indices, jobs);
416    if failure_handled {
417        return;
418    }
419
420    // NOTE this happens when evaluator fails to insert jobs due to lack of routes in registry
421    // TODO remove from required only jobs from selected list
422    let no_routes_available = failure.job.is_none();
423
424    if let Some(job) = failure.job {
425        insertion_ctx.solution.unassigned.insert(job.clone(), UnassignmentInfo::Simple(failure.constraint));
426        insertion_ctx.solution.required.retain(|j| *j != job);
427    }
428
429    if all_unassignable || no_routes_available {
430        let code =
431            if all_unassignable { UnassignmentInfo::Unknown } else { UnassignmentInfo::Simple(failure.constraint) };
432        finalize_unassigned(insertion_ctx, code);
433    }
434}
435
436fn finalize_unassigned(insertion_ctx: &mut InsertionContext, code: UnassignmentInfo) {
437    let unassigned = &insertion_ctx.solution.unassigned;
438    insertion_ctx.solution.required.retain(|job| !unassigned.contains_key(job));
439    insertion_ctx.solution.unassigned.extend(insertion_ctx.solution.required.drain(0..).map(|job| (job, code.clone())));
440}
441
442fn copy_selection_data(
443    insertion_ctx: &InsertionContext,
444    routes: &[&RouteContext],
445    jobs: &[&Job],
446) -> (Vec<usize>, Vec<Job>) {
447    let route_indices = routes
448        .iter()
449        .filter_map(|route| insertion_ctx.solution.routes.iter().position(|r| r == *route))
450        .collect::<Vec<_>>();
451    let jobs = jobs.iter().map(|&job| job.clone()).collect::<Vec<_>>();
452
453    (route_indices, jobs)
454}