#[cfg(test)]
#[path = "../../../tests/unit/models/matrix/decipher_test.rs"]
mod decipher_test;
use super::inserter::ActivityInfoInserter;
use super::AdjacencyMatrix;
use crate::construction::heuristics::{InsertionContext, RouteContext, SolutionContext};
use crate::models::problem::{Actor, ActorDetail, Job, Place, Single};
use crate::models::solution::TourActivity;
use crate::models::Problem;
use crate::utils::DefaultRandom;
use hashbrown::{HashMap, HashSet};
use std::hash::Hash;
use std::sync::Arc;
pub struct AdjacencyMatrixDecipher {
problem: Arc<Problem>,
activity_direct_index: HashMap<ActivityInfo, usize>,
activity_reverse_index: HashMap<usize, ActivityInfo>,
actor_direct_index: HashMap<Arc<Actor>, usize>,
actor_reverse_index: HashMap<usize, Arc<Actor>>,
}
#[derive(Hash, Eq, PartialEq, Clone)]
pub enum ActivityInfo {
Job(ActivityWithJob),
Terminal(ActivityWithActor),
}
pub type ActivityWithJob = (Job, usize, usize, usize);
pub type ActivityWithActor = (ActorDetail, usize);
impl AdjacencyMatrixDecipher {
pub fn new(problem: Arc<Problem>) -> Self {
let mut decipher = Self {
problem: problem.clone(),
activity_direct_index: Default::default(),
activity_reverse_index: Default::default(),
actor_direct_index: problem.fleet.actors.iter().cloned().zip(1..).collect(),
actor_reverse_index: (1..).zip(problem.fleet.actors.iter().cloned()).collect(),
};
get_unique_actor_details(&problem.fleet.actors).into_iter().for_each(|adk| match (adk.start, adk.end) {
(Some(_), Some(_)) => {
decipher.add(ActivityInfo::Terminal((adk.clone(), 0)));
decipher.add(ActivityInfo::Terminal((adk, 1)));
}
(None, Some(end)) => decipher.add(ActivityInfo::Terminal((adk, end))),
(Some(start), None) => decipher.add(ActivityInfo::Terminal((adk, start))),
_ => {}
});
problem.jobs.all().for_each(|job| {
match &job {
Job::Single(single) => vec![(0, single.places.iter().collect::<Vec<_>>())],
Job::Multi(multi) => (0..)
.zip(multi.jobs.iter())
.map(|(idx, j)| (idx, j.places.iter().collect::<Vec<_>>()))
.collect::<Vec<_>>(),
}
.iter()
.for_each(|(single_idx, places)| {
(0..).zip(places.iter()).for_each(|(place_idx, place)| {
(0..).zip(place.times.iter()).for_each(|(tw_idx, _)| {
decipher.add(ActivityInfo::Job((job.clone(), *single_idx, place_idx, tw_idx)));
})
})
});
});
decipher
}
pub fn encode<T: AdjacencyMatrix>(&self, solution_ctx: &SolutionContext) -> T {
let mut matrix = T::new(self.dimensions());
solution_ctx.routes.iter().for_each(|rc| {
let actor = &rc.route.actor;
let actor_idx = *self.actor_direct_index.get(actor).unwrap() as f64;
rc.route.tour.legs().for_each(|(items, leg_idx)| {
match items {
[prev, next] => {
let from =
*self.activity_direct_index.get(&create_activity_info(actor, prev, leg_idx)).unwrap();
let to = *self.activity_direct_index.get(&create_activity_info(actor, next, leg_idx)).unwrap();
matrix.set_cell(from, to, actor_idx);
}
[_] => {}
_ => panic!("Unexpected route leg configuration."),
};
});
});
matrix
}
pub fn decode<T: AdjacencyMatrix>(&self, matrix: &T) -> SolutionContext {
let mut ctx = InsertionContext::new(self.problem.clone(), Arc::new(DefaultRandom::default()));
ctx.problem.constraint.accept_solution_state(&mut ctx.solution);
let mut unprocessed =
ctx.solution.ignored.iter().chain(ctx.solution.required.iter()).cloned().collect::<HashSet<_>>();
let mut unassigned: HashSet<Job> = Default::default();
let mut routes = self.get_routes(&mut ctx.solution, matrix);
routes.iter_mut().for_each(|mut rc| {
let actor = &rc.route.actor;
let actor_idx = *self.actor_direct_index.get(actor).unwrap();
let start_info = create_activity_info(actor, rc.route.tour.start().unwrap(), 0);
let start_row_idx = *self.activity_direct_index.get(&start_info).unwrap();
let activity_infos = self.get_activity_infos(matrix, actor_idx, start_row_idx);
ActivityInfoInserter::new(&mut ctx, &mut rc, &mut unprocessed, &mut unassigned, activity_infos).insert();
});
ctx.solution.required = unprocessed
.into_iter()
.chain(unassigned.into_iter())
.chain(ctx.solution.required.into_iter())
.collect::<HashSet<_>>()
.into_iter()
.collect();
ctx.solution.routes = routes;
ctx.restore();
ctx.solution
}
fn add(&mut self, activity_info: ActivityInfo) {
assert_eq!(self.activity_direct_index.len(), self.activity_reverse_index.len());
self.activity_direct_index.insert(activity_info.clone(), self.activity_direct_index.len());
self.activity_reverse_index.insert(self.activity_reverse_index.len(), activity_info);
}
fn dimensions(&self) -> usize {
self.activity_direct_index.len()
}
fn get_routes<T: AdjacencyMatrix>(&self, solution: &mut SolutionContext, matrix: &T) -> Vec<RouteContext> {
let used_actors = solution.routes.iter().map(|r| r.route.actor.clone()).collect::<HashSet<_>>();
let mut routes = solution.routes.clone();
routes.extend(
matrix
.values()
.map(|i| self.actor_reverse_index.get(&(i as usize)).cloned().unwrap())
.filter(|a| used_actors.get(a).is_none())
.map(|a| {
solution.registry.use_actor(&a);
RouteContext::new(a)
}),
);
routes
}
fn get_activity_infos<T: AdjacencyMatrix>(
&self,
matrix: &T,
actor_idx: usize,
start_row_idx: usize,
) -> Vec<&ActivityInfo> {
let mut next_row_idx = start_row_idx;
let mut activity_infos = vec![];
let mut processed: HashSet<usize> = Default::default();
loop {
if let Some(activity_info_idx) = matrix.scan_row(next_row_idx, |v| v.round() as usize == actor_idx) {
if processed.contains(&activity_info_idx) {
break;
}
processed.insert(activity_info_idx);
activity_infos.push(self.activity_reverse_index.get(&activity_info_idx).unwrap());
next_row_idx = activity_info_idx;
continue;
}
break;
}
activity_infos
}
}
fn get_unique_actor_details(actors: &[Arc<Actor>]) -> Vec<ActorDetail> {
let mut unique: HashSet<ActorDetail> = Default::default();
let mut details = actors.iter().map(|a| a.detail.clone()).collect::<Vec<_>>();
details.retain(|d| unique.insert(d.clone()));
details
}
fn create_activity_info(actor: &Arc<Actor>, a: &TourActivity, leg_idx: usize) -> ActivityInfo {
match a.retrieve_job() {
Some(job) => {
let (single_idx, single) = match &job {
Job::Multi(multi) => {
let job = a.job.as_ref().unwrap();
let position = multi
.jobs
.iter()
.position(|j| &*j.as_ref() as *const Single == &*job.as_ref() as *const Single)
.unwrap();
(position, multi.jobs.get(position).unwrap().clone())
}
Job::Single(single) => (0, single.clone()),
};
let (place_idx, tw_idx) = try_match_activity_place(a, &single.places).unwrap();
ActivityInfo::Job((job, single_idx, place_idx, tw_idx))
}
None => ActivityInfo::Terminal((actor.detail.clone(), if leg_idx > 0 { 1 } else { 0 })),
}
}
fn try_match_activity_place(activity: &TourActivity, places: &[Place]) -> Option<(usize, usize)> {
places.iter().enumerate().fold(None, |acc, (place_idx, place)| {
if acc.is_none()
&& place.location.map_or(true, |location| location == activity.place.location)
&& (activity.place.duration - place.duration).abs() < std::f64::EPSILON
{
for (tw_idx, tw) in place.times.iter().enumerate() {
let is_correct = tw.as_time_window().map_or(true, |tw| activity.place.time == tw);
if is_correct {
return Some((place_idx, tw_idx));
}
}
}
acc
})
}