#[cfg(test)]
#[path = "../../../tests/unit/models/solution/tour_test.rs"]
mod tour_test;
use crate::models::common::{IdDimension, Schedule};
use crate::models::problem::{Actor, Job};
use crate::models::solution::{Activity, Place};
use crate::models::OP_START_MSG;
use crate::utils::{short_type_name, Either};
use hashbrown::HashSet;
use rustc_hash::FxHasher;
use std::fmt::{Debug, Formatter};
use std::hash::BuildHasherDefault;
use std::iter::once;
use std::slice::{Iter, IterMut};
pub type Leg<'a> = (&'a [Activity], usize);
#[derive(Default)]
pub struct Tour {
activities: Vec<Activity>,
jobs: HashSet<Job, BuildHasherDefault<FxHasher>>,
is_closed: bool,
}
impl Tour {
pub fn new(actor: &Actor) -> Self {
let mut tour = Self::default();
tour.set_start(create_start_activity(actor));
create_end_activity(actor).map(|end| tour.set_end(end));
tour
}
pub fn set_start(&mut self, activity: Activity) -> &mut Tour {
assert!(activity.job.is_none());
assert!(self.activities.is_empty());
self.activities.push(activity);
self
}
pub fn set_end(&mut self, activity: Activity) -> &mut Tour {
assert!(activity.job.is_none());
assert!(!self.activities.is_empty());
self.activities.push(activity);
self.is_closed = true;
self
}
pub fn insert_last(&mut self, activity: Activity) -> &mut Tour {
self.insert_at(activity, self.job_activity_count() + 1);
self
}
pub fn insert_at(&mut self, activity: Activity, index: usize) -> &mut Tour {
assert!(activity.job.is_some());
assert!(!self.activities.is_empty());
self.jobs.insert(activity.retrieve_job().unwrap());
self.activities.insert(index, activity);
self
}
pub fn remove(&mut self, job: &Job) -> bool {
self.activities.retain(|a| !a.has_same_job(job));
self.jobs.remove(job)
}
pub fn remove_activity_at(&mut self, idx: usize) -> Job {
let job = self
.activities
.get(idx)
.and_then(|a| a.retrieve_job())
.expect("Attempt to remove activity without job from the tour!");
self.remove(&job);
job
}
pub fn all_activities(&self) -> Iter<Activity> {
self.activities.iter()
}
pub fn activities_slice(&self, start: usize, end: usize) -> &[Activity] {
&self.activities[start..=end]
}
pub fn all_activities_mut(&mut self) -> IterMut<Activity> {
self.activities.iter_mut()
}
pub fn job_activities<'a>(&'a self, job: &'a Job) -> impl Iterator<Item = &Activity> + 'a {
self.activities.iter().filter(move |a| a.has_same_job(job))
}
pub fn legs(&self) -> impl Iterator<Item = Leg<'_>> + '_ + Clone {
let last_index = if self.activities.is_empty() { 0 } else { self.activities.len() - 1 };
let window_size = if self.activities.len() == 1 { 1 } else { 2 };
let legs = self.activities.windows(window_size).zip(0_usize..);
let is_open_tour_with_jobs = !self.is_closed && last_index > 0;
if is_open_tour_with_jobs {
Either::Left(legs.chain(once((&self.activities[last_index..], last_index))))
} else {
Either::Right(legs)
}
}
pub fn jobs(&'_ self) -> impl Iterator<Item = Job> + '_ {
self.jobs.iter().cloned()
}
pub fn get(&self, index: usize) -> Option<&Activity> {
self.activities.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Activity> {
self.activities.get_mut(index)
}
pub fn start(&self) -> Option<&Activity> {
self.activities.first()
}
pub fn end(&self) -> Option<&Activity> {
self.activities.last()
}
pub fn contains(&self, job: &Job) -> bool {
self.jobs.contains(job)
}
pub fn index(&self, job: &Job) -> Option<usize> {
self.activities.iter().position(move |a| a.has_same_job(job))
}
pub fn has_job(&self, job: &Job) -> bool {
self.jobs.contains(job)
}
pub fn has_jobs(&self) -> bool {
!self.jobs.is_empty()
}
pub fn job_activity_count(&self) -> usize {
if self.activities.is_empty() {
0
} else {
self.activities.len() - (if self.is_closed { 2 } else { 1 })
}
}
pub fn total(&self) -> usize {
self.activities.len()
}
pub fn job_count(&self) -> usize {
self.jobs.len()
}
pub fn deep_copy(&self) -> Tour {
Tour {
activities: self.activities.iter().map(|a| a.deep_copy()).collect(),
jobs: self.jobs.clone(),
is_closed: self.is_closed,
}
}
}
impl Debug for Tour {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(short_type_name::<Self>())
.field("is_closed", &self.is_closed)
.field("jobs", &self.jobs.len())
.field(
"activities",
&self
.activities
.iter()
.enumerate()
.map(|(idx, activity)| match idx {
0 => "departure".to_string(),
idx if self.is_closed && idx == self.activities.len() - 1 => "arrival".to_string(),
_ => activity
.retrieve_job()
.and_then(|job| job.dimens().get_id().cloned())
.unwrap_or("undef".to_string()),
})
.collect::<Vec<_>>(),
)
.finish()
}
}
fn create_start_activity(actor: &Actor) -> Activity {
let start = &actor.detail.start.as_ref().unwrap_or_else(|| unimplemented!("{}", OP_START_MSG));
let time = start.time.to_time_window();
Activity {
schedule: Schedule { arrival: time.start, departure: time.start },
place: Place { location: start.location, duration: 0.0, time },
job: None,
commute: None,
}
}
fn create_end_activity(actor: &Actor) -> Option<Activity> {
actor.detail.end.as_ref().map(|place| {
let time = place.time.to_time_window();
Activity {
schedule: Schedule { arrival: time.start, departure: time.start },
place: Place { location: place.location, duration: 0.0, time },
job: None,
commute: None,
}
})
}