#[cfg(test)]
#[path = "../../../tests/unit/models/problem/jobs_test.rs"]
mod jobs_test;
use crate::models::common::*;
use crate::models::problem::{Costs, Fleet, TransportCost};
use crate::utils::short_type_name;
use hashbrown::HashMap;
use rosomaxa::utils::compare_floats_f32;
use std::cmp::Ordering::Less;
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Weak};
#[derive(Clone)]
pub enum Job {
Single(Arc<Single>),
Multi(Arc<Multi>),
}
impl Job {
pub fn as_single(&self) -> Option<&Arc<Single>> {
match &self {
Job::Single(job) => Some(job),
_ => None,
}
}
pub fn to_single(&self) -> &Arc<Single> {
self.as_single().expect("Unexpected job type: multi")
}
pub fn as_multi(&self) -> Option<&Arc<Multi>> {
match &self {
Job::Multi(job) => Some(job),
_ => None,
}
}
pub fn to_multi(&self) -> &Arc<Multi> {
self.as_multi().expect("Unexpected job type: single")
}
pub fn dimens(&self) -> &Dimensions {
match &self {
Job::Single(single) => &single.dimens,
Job::Multi(multi) => &multi.dimens,
}
}
pub fn places(&self) -> Box<dyn Iterator<Item = &Place> + '_> {
match &self {
Job::Single(single) => Box::new(single.places.iter()),
Job::Multi(multi) => Box::new(multi.jobs.iter().flat_map(|single| single.places.iter())),
}
}
}
impl Debug for Job {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Job::Single(single) => single.fmt(f),
Job::Multi(multi) => multi.fmt(f),
}
}
}
#[derive(Clone)]
pub struct Place {
pub location: Option<Location>,
pub duration: Duration,
pub times: Vec<TimeSpan>,
}
pub struct Single {
pub places: Vec<Place>,
pub dimens: Dimensions,
}
impl Debug for Single {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(short_type_name::<Single>())
.field("id", &self.dimens.get_id().map(|id| id.as_str()).unwrap_or("undef"))
.finish_non_exhaustive()
}
}
pub struct Multi {
pub jobs: Vec<Arc<Single>>,
pub dimens: Dimensions,
permutator: Box<dyn JobPermutation + Send + Sync>,
}
impl Debug for Multi {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(short_type_name::<Multi>())
.field("id", &self.dimens.get_id().map(|id| id.as_str()).unwrap_or("undef"))
.field("jobs", &self.jobs.len())
.finish_non_exhaustive()
}
}
pub trait JobPermutation {
fn get(&self) -> Vec<Vec<usize>>;
fn validate(&self, permutation: &[usize]) -> bool;
}
pub struct FixedJobPermutation {
permutations: Vec<Vec<usize>>,
}
impl FixedJobPermutation {
pub fn new(permutations: Vec<Vec<usize>>) -> Self {
Self { permutations }
}
}
impl JobPermutation for FixedJobPermutation {
fn get(&self) -> Vec<Vec<usize>> {
self.permutations.clone()
}
fn validate(&self, permutation: &[usize]) -> bool {
self.permutations
.iter()
.any(|prm| prm.len() == permutation.len() && prm.iter().zip(permutation.iter()).all(|(&a, &b)| a == b))
}
}
impl Multi {
pub fn new_shared(jobs: Vec<Arc<Single>>, dimens: Dimensions) -> Arc<Self> {
let permutations = vec![(0..jobs.len()).collect()];
Self::bind(Self { jobs, dimens, permutator: Box::new(FixedJobPermutation::new(permutations)) })
}
pub fn new_shared_with_permutator(
jobs: Vec<Arc<Single>>,
dimens: Dimensions,
permutator: Box<dyn JobPermutation + Send + Sync>,
) -> Arc<Self> {
Self::bind(Self { jobs, dimens, permutator })
}
pub fn permutations(&self) -> Vec<Vec<Arc<Single>>> {
self.permutator
.get()
.iter()
.map(|perm| perm.iter().map(|&i| self.jobs.get(i).unwrap().clone()).collect())
.collect()
}
pub fn validate(&self, permutations: &[usize]) -> bool {
self.permutator.validate(permutations)
}
pub fn roots(single: &Single) -> Option<Arc<Multi>> {
single.dimens.get_value::<Weak<Multi>>("rf").and_then(|w| w.upgrade())
}
fn bind(mut multi: Self) -> Arc<Self> {
Arc::new_cyclic(|weak_multi| {
multi.jobs.iter_mut().for_each(|single| {
Arc::get_mut(single)
.expect("Single from Multi should not be shared before binding")
.dimens
.set_value("rf", weak_multi.clone());
});
multi
})
}
}
type LowPrecisionCost = f32;
type JobIndex = HashMap<Job, (Vec<(Job, LowPrecisionCost)>, LowPrecisionCost)>;
const DEFAULT_COST: LowPrecisionCost = 0.;
const UNREACHABLE_COST: LowPrecisionCost = f32::MAX;
const MAX_NEIGHBOURS: usize = 5000;
pub struct Jobs {
jobs: Vec<Job>,
index: HashMap<usize, JobIndex>,
}
impl Jobs {
pub fn new(fleet: &Fleet, jobs: Vec<Job>, transport: &(dyn TransportCost + Send + Sync)) -> Jobs {
Jobs { jobs: jobs.clone(), index: create_index(fleet, jobs, transport) }
}
pub fn all(&'_ self) -> impl Iterator<Item = Job> + '_ {
self.jobs.iter().cloned()
}
pub fn neighbors(&self, profile: &Profile, job: &Job, _: Timestamp) -> impl Iterator<Item = (&Job, Cost)> {
let index = self.index.get(&profile.index).expect("no profile index");
let (neighbours_info, _) = index.get(job).expect("no job in profile index");
neighbours_info.iter().map(|(job, cost)| (job, *cost as f64))
}
pub fn rank(&self, profile: &Profile, job: &Job) -> Cost {
let index = self.index.get(&profile.index).expect("no profile index");
let &(_, cost) = index.get(job).expect("no job in profile index");
cost as f64
}
pub fn size(&self) -> usize {
self.jobs.len()
}
}
impl PartialEq<Job> for Job {
fn eq(&self, other: &Job) -> bool {
match (&self, other) {
(Job::Single(_), Job::Multi(_)) => false,
(Job::Multi(_), Job::Single(_)) => false,
(Job::Single(lhs), Job::Single(rhs)) => std::ptr::eq(lhs.as_ref(), rhs.as_ref()),
(Job::Multi(lhs), Job::Multi(rhs)) => std::ptr::eq(lhs.as_ref(), rhs.as_ref()),
}
}
}
impl Eq for Job {}
impl Hash for Job {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Job::Single(single) => {
let address = single.as_ref() as *const Single;
address.hash(state);
}
Job::Multi(multi) => {
let address = multi.as_ref() as *const Multi;
address.hash(state);
}
}
}
}
pub fn get_job_locations<'a>(job: &'a Job) -> Box<dyn Iterator<Item = Option<Location>> + 'a> {
match job {
Job::Single(single) => Box::new(single.places.iter().map(|p| p.location)),
Job::Multi(multi) => Box::new(multi.jobs.iter().flat_map(|j| j.places.iter().map(|p| p.location))),
}
}
fn create_index(
fleet: &Fleet,
jobs: Vec<Job>,
transport: &(dyn TransportCost + Send + Sync),
) -> HashMap<usize, JobIndex> {
let avg_profile_costs = get_avg_profile_costs(fleet);
fleet.profiles.iter().fold(HashMap::new(), |mut acc, profile| {
let avg_costs = avg_profile_costs.get(&profile.index).unwrap();
let starts: Vec<Location> = fleet
.vehicles
.iter()
.filter(|v| v.profile.index == profile.index)
.flat_map(|v| v.details.iter().map(|d| d.start.as_ref().map(|s| s.location)))
.flatten()
.collect();
let item = jobs.iter().cloned().fold(HashMap::new(), |mut acc, job| {
let mut sorted_job_costs: Vec<(Job, LowPrecisionCost)> = jobs
.iter()
.filter(|j| **j != job)
.map(|j| (j.clone(), get_cost_between_jobs(profile, avg_costs, transport, &job, j)))
.take(MAX_NEIGHBOURS)
.collect();
sorted_job_costs.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Less));
let fleet_costs = starts
.iter()
.cloned()
.map(|s| get_cost_between_job_and_location(profile, avg_costs, transport, &job, s))
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
.unwrap_or(DEFAULT_COST);
acc.insert(job, (sorted_job_costs, fleet_costs));
acc
});
acc.insert(profile.index, item);
acc
})
}
fn get_cost_between_locations(
profile: &Profile,
costs: &Costs,
transport: &(dyn TransportCost + Send + Sync),
from: Location,
to: Location,
) -> LowPrecisionCost {
let distance = transport.distance_approx(profile, from, to);
let duration = transport.duration_approx(profile, from, to);
if distance < 0. || duration < 0. {
UNREACHABLE_COST
} else {
(distance * costs.per_distance + duration * costs.per_driving_time) as f32
}
}
fn get_cost_between_job_and_location(
profile: &Profile,
costs: &Costs,
transport: &(dyn TransportCost + Send + Sync),
job: &Job,
to: Location,
) -> LowPrecisionCost {
get_job_locations(job)
.map(|from| match from {
Some(from) => get_cost_between_locations(profile, costs, transport, from, to),
_ => DEFAULT_COST,
})
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
.unwrap_or(DEFAULT_COST)
}
fn get_cost_between_jobs(
profile: &Profile,
costs: &Costs,
transport: &(dyn TransportCost + Send + Sync),
lhs: &Job,
rhs: &Job,
) -> LowPrecisionCost {
let outer: Vec<Option<Location>> = get_job_locations(lhs).collect();
let inner: Vec<Option<Location>> = get_job_locations(rhs).collect();
let (location_cost, duration) = outer
.iter()
.flat_map(|o| inner.iter().map(move |i| (*o, *i)))
.map(|pair| match pair {
(Some(from), Some(to)) => {
let total = get_cost_between_locations(profile, costs, transport, from, to);
let duration = transport.duration_approx(profile, from, to);
(total, duration)
}
_ => (DEFAULT_COST, 0.),
})
.min_by(|(a, _), (b, _)| compare_floats_f32(*a, *b))
.unwrap_or((DEFAULT_COST, 0.));
let time_cost = lhs
.places()
.flat_map(|place| place.times.iter())
.flat_map(|left| {
rhs.places().flat_map(|place| place.times.iter()).map(move |right| match (left, right) {
(TimeSpan::Window(left), TimeSpan::Window(right)) => {
(left.distance(right) - duration).max(0.)
}
_ => 0.,
})
})
.map(|cost| cost as LowPrecisionCost)
.min_by(|a, b| compare_floats_f32(*a, *b))
.unwrap_or(0.)
* costs.per_waiting_time as LowPrecisionCost;
location_cost + time_cost
}
fn get_avg_profile_costs(fleet: &Fleet) -> HashMap<usize, Costs> {
let get_avg_by = |costs: &Vec<Costs>, map_cost_fn: fn(&Costs) -> f64| -> f64 {
costs.iter().map(map_cost_fn).sum::<f64>() / (costs.len() as f64)
};
fleet
.vehicles
.iter()
.fold(HashMap::<_, Vec<_>>::new(), |mut acc, vehicle| {
acc.entry(vehicle.profile.index).or_default().push(vehicle.costs.clone());
acc
})
.iter()
.map(|(&profile_idx, costs)| {
(
profile_idx,
Costs {
fixed: get_avg_by(costs, |c| c.fixed),
per_distance: get_avg_by(costs, |c| c.per_distance),
per_driving_time: get_avg_by(costs, |c| c.per_driving_time),
per_waiting_time: get_avg_by(costs, |c| c.per_waiting_time),
per_service_time: get_avg_by(costs, |c| c.per_service_time),
},
)
})
.collect()
}