Skip to main content

vrp_core/models/solution/
tour.rs

1#[cfg(test)]
2#[path = "../../../tests/unit/models/solution/tour_test.rs"]
3mod tour_test;
4
5use crate::models::common::Schedule;
6use crate::models::problem::{Actor, Job, JobIdDimension};
7use crate::models::solution::{Activity, Place};
8use crate::models::OP_START_MSG;
9use crate::utils::{short_type_name, Either};
10use rustc_hash::FxHasher;
11use std::collections::HashSet;
12use std::fmt::{Debug, Formatter};
13use std::hash::BuildHasherDefault;
14use std::iter::once;
15use std::ops::Index;
16use std::slice::{Iter, IterMut};
17
18/// A tour leg.
19pub type Leg<'a> = (&'a [Activity], usize);
20
21/// Represents a tour, a smart container for jobs with their associated activities.
22#[derive(Default)]
23pub struct Tour {
24    /// Stores activities in the order the performed.
25    activities: Vec<Activity>,
26
27    /// Stores jobs in the order of their activities added.
28    jobs: HashSet<Job, BuildHasherDefault<FxHasher>>,
29
30    /// Keeps track whether tour is set as closed.
31    is_closed: bool,
32}
33
34impl Tour {
35    /// Creates a new tour with start and optional end using actor properties.
36    pub fn new(actor: &Actor) -> Self {
37        let mut tour = Self::default();
38        tour.set_start(create_start_activity(actor));
39        create_end_activity(actor).map(|end| tour.set_end(end));
40
41        tour
42    }
43
44    /// Sets tour start.
45    pub fn set_start(&mut self, activity: Activity) -> &mut Tour {
46        assert!(activity.job.is_none());
47        assert!(self.activities.is_empty());
48        self.activities.push(activity);
49
50        self
51    }
52
53    /// Sets tour end.
54    pub fn set_end(&mut self, activity: Activity) -> &mut Tour {
55        assert!(activity.job.is_none());
56        assert!(!self.activities.is_empty());
57        self.activities.push(activity);
58        self.is_closed = true;
59
60        self
61    }
62
63    /// Inserts activity within its job to the end of tour.
64    pub fn insert_last(&mut self, activity: Activity) -> &mut Tour {
65        self.insert_at(activity, self.job_activity_count() + 1);
66        self
67    }
68
69    /// Inserts activity within its job at specified index.
70    pub fn insert_at(&mut self, activity: Activity, index: usize) -> &mut Tour {
71        assert!(activity.job.is_some());
72        assert!(!self.activities.is_empty());
73
74        self.jobs.insert(activity.retrieve_job().unwrap());
75        self.activities.insert(index, activity);
76
77        self
78    }
79
80    /// Removes job within its activities from the tour.
81    pub fn remove(&mut self, job: &Job) -> bool {
82        self.activities.retain(|a| !a.has_same_job(job));
83        self.jobs.remove(job)
84    }
85
86    /// Removes activity and its job from the tour.
87    pub fn remove_activity_at(&mut self, idx: usize) -> Job {
88        let job = self
89            .activities
90            .get(idx)
91            .and_then(|a| a.retrieve_job())
92            .expect("Attempt to remove activity without job from the tour!");
93        self.remove(&job);
94
95        job
96    }
97
98    /// Returns all activities in tour.
99    pub fn all_activities(&self) -> Iter<Activity> {
100        self.activities.iter()
101    }
102
103    /// Returns activities slice in specific range (all inclusive).
104    pub fn activities_slice(&self, start: usize, end: usize) -> &[Activity] {
105        &self.activities[start..=end]
106    }
107
108    /// Returns all activities in tour as mutable.
109    pub fn all_activities_mut(&mut self) -> IterMut<Activity> {
110        self.activities.iter_mut()
111    }
112
113    /// Returns all activities in tour for specific job.
114    pub fn job_activities<'a>(&'a self, job: &'a Job) -> impl Iterator<Item = &Activity> + 'a {
115        self.activities.iter().filter(move |a| a.has_same_job(job))
116    }
117
118    /// Returns counted tour legs.
119    pub fn legs(&self) -> impl Iterator<Item = Leg<'_>> + '_ + Clone {
120        let last_index = if self.activities.is_empty() { 0 } else { self.activities.len() - 1 };
121
122        let window_size = if self.activities.len() == 1 { 1 } else { 2 };
123        let legs = self.activities.windows(window_size).zip(0_usize..);
124
125        let is_open_tour_with_jobs = !self.is_closed && last_index > 0;
126
127        if is_open_tour_with_jobs {
128            Either::Left(legs.chain(once((&self.activities[last_index..], last_index))))
129        } else {
130            Either::Right(legs)
131        }
132    }
133
134    /// Returns all jobs.
135    pub fn jobs(&'_ self) -> impl Iterator<Item = &Job> + '_ {
136        self.jobs.iter()
137    }
138
139    /// Returns activity by its index in tour.
140    pub fn get(&self, index: usize) -> Option<&Activity> {
141        self.activities.get(index)
142    }
143
144    /// Returns mutable activity by its index in tour.
145    pub fn get_mut(&mut self, index: usize) -> Option<&mut Activity> {
146        self.activities.get_mut(index)
147    }
148
149    /// Returns start activity in tour.
150    pub fn start(&self) -> Option<&Activity> {
151        self.activities.first()
152    }
153
154    /// Returns end activity in tour.
155    pub fn end(&self) -> Option<&Activity> {
156        self.activities.last()
157    }
158
159    /// Returns end activity in tour.
160    pub fn end_idx(&self) -> Option<usize> {
161        self.activities.len().checked_sub(1)
162    }
163
164    /// Checks whether job is present in tour
165    pub fn contains(&self, job: &Job) -> bool {
166        self.jobs.contains(job)
167    }
168
169    /// Returns index of first job occurrence in the tour.
170    pub fn index(&self, job: &Job) -> Option<usize> {
171        self.activities.iter().position(move |a| a.has_same_job(job))
172    }
173
174    /// Returns index of last job occurrence in the tour.
175    pub fn index_last(&self, job: &Job) -> Option<usize> {
176        self.activities.iter().rposition(move |a| a.has_same_job(job))
177    }
178
179    /// Checks whether job is present in tour.
180    pub fn has_job(&self, job: &Job) -> bool {
181        self.jobs.contains(job)
182    }
183
184    /// Checks whether tour has jobs.
185    pub fn has_jobs(&self) -> bool {
186        !self.jobs.is_empty()
187    }
188
189    /// Returns total amount of job activities.
190    pub fn job_activity_count(&self) -> usize {
191        if self.activities.is_empty() {
192            0
193        } else {
194            self.activities.len() - (if self.is_closed { 2 } else { 1 })
195        }
196    }
197
198    /// Returns amount of all activities in tour.
199    pub fn total(&self) -> usize {
200        self.activities.len()
201    }
202
203    /// Returns amount of jobs.
204    pub fn job_count(&self) -> usize {
205        self.jobs.len()
206    }
207
208    /// Creates a copy of existing tour deeply copying all activities and jobs.
209    pub fn deep_copy(&self) -> Tour {
210        Tour {
211            activities: self.activities.iter().map(|a| a.deep_copy()).collect(),
212            jobs: self.jobs.clone(),
213            is_closed: self.is_closed,
214        }
215    }
216}
217
218impl Index<usize> for Tour {
219    type Output = Activity;
220
221    fn index(&self, index: usize) -> &Self::Output {
222        &self.activities[index]
223    }
224}
225
226impl Debug for Tour {
227    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
228        f.debug_struct(short_type_name::<Self>())
229            .field("is_closed", &self.is_closed)
230            .field("jobs", &self.jobs.len())
231            .field(
232                "activities",
233                &self
234                    .activities
235                    .iter()
236                    .enumerate()
237                    .map(|(idx, activity)| match idx {
238                        0 => "departure".to_string(),
239                        idx if self.is_closed && idx == self.activities.len() - 1 => "arrival".to_string(),
240                        _ => activity
241                            .retrieve_job()
242                            .and_then(|job| job.dimens().get_job_id().cloned())
243                            .unwrap_or("undef".to_string()),
244                    })
245                    .collect::<Vec<_>>(),
246            )
247            .finish()
248    }
249}
250
251/// Creates start activity.
252fn create_start_activity(actor: &Actor) -> Activity {
253    let start = &actor.detail.start.as_ref().unwrap_or_else(|| unimplemented!("{}", OP_START_MSG));
254    let time = start.time.to_time_window();
255
256    Activity {
257        schedule: Schedule { arrival: time.start, departure: time.start },
258        place: Place { idx: 0, location: start.location, duration: 0., time },
259        job: None,
260        commute: None,
261    }
262}
263
264/// Creates end activity if it is specified for the actor.
265fn create_end_activity(actor: &Actor) -> Option<Activity> {
266    actor.detail.end.as_ref().map(|place| {
267        let time = place.time.to_time_window();
268        Activity {
269            schedule: Schedule { arrival: time.start, departure: time.start },
270            place: Place { idx: 0, location: place.location, duration: 0.0, time },
271            job: None,
272            commute: None,
273        }
274    })
275}