vrp_core/models/solution/
tour.rs1#[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
18pub type Leg<'a> = (&'a [Activity], usize);
20
21#[derive(Default)]
23pub struct Tour {
24 activities: Vec<Activity>,
26
27 jobs: HashSet<Job, BuildHasherDefault<FxHasher>>,
29
30 is_closed: bool,
32}
33
34impl Tour {
35 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 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 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 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 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 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 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 pub fn all_activities(&self) -> Iter<Activity> {
100 self.activities.iter()
101 }
102
103 pub fn activities_slice(&self, start: usize, end: usize) -> &[Activity] {
105 &self.activities[start..=end]
106 }
107
108 pub fn all_activities_mut(&mut self) -> IterMut<Activity> {
110 self.activities.iter_mut()
111 }
112
113 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 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 pub fn jobs(&'_ self) -> impl Iterator<Item = &Job> + '_ {
136 self.jobs.iter()
137 }
138
139 pub fn get(&self, index: usize) -> Option<&Activity> {
141 self.activities.get(index)
142 }
143
144 pub fn get_mut(&mut self, index: usize) -> Option<&mut Activity> {
146 self.activities.get_mut(index)
147 }
148
149 pub fn start(&self) -> Option<&Activity> {
151 self.activities.first()
152 }
153
154 pub fn end(&self) -> Option<&Activity> {
156 self.activities.last()
157 }
158
159 pub fn end_idx(&self) -> Option<usize> {
161 self.activities.len().checked_sub(1)
162 }
163
164 pub fn contains(&self, job: &Job) -> bool {
166 self.jobs.contains(job)
167 }
168
169 pub fn index(&self, job: &Job) -> Option<usize> {
171 self.activities.iter().position(move |a| a.has_same_job(job))
172 }
173
174 pub fn index_last(&self, job: &Job) -> Option<usize> {
176 self.activities.iter().rposition(move |a| a.has_same_job(job))
177 }
178
179 pub fn has_job(&self, job: &Job) -> bool {
181 self.jobs.contains(job)
182 }
183
184 pub fn has_jobs(&self) -> bool {
186 !self.jobs.is_empty()
187 }
188
189 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 pub fn total(&self) -> usize {
200 self.activities.len()
201 }
202
203 pub fn job_count(&self) -> usize {
205 self.jobs.len()
206 }
207
208 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
251fn 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
264fn 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}