1#[cfg(test)]
2#[path = "../../../tests/unit/models/problem/jobs_test.rs"]
3mod jobs_test;
4
5use crate::construction::clustering::dbscan::create_job_clusters;
6use crate::models::common::*;
7use crate::models::problem::{Costs, Fleet, TransportCost};
8use crate::utils::{short_type_name, Either};
9use rosomaxa::prelude::{Float, GenericResult, InfoLogger};
10use rosomaxa::utils::{parallel_collect, Timer};
11use std::cmp::Ordering::Less;
12use std::collections::{HashMap, HashSet};
13use std::fmt::{Debug, Formatter};
14use std::hash::{Hash, Hasher};
15use std::sync::{Arc, Weak};
16
17custom_dimension!(JobId typeof String);
18
19#[derive(Clone)]
21pub enum Job {
22 Single(Arc<Single>),
24 Multi(Arc<Multi>),
26}
27
28impl Job {
29 pub fn as_single(&self) -> Option<&Arc<Single>> {
31 match &self {
32 Job::Single(job) => Some(job),
33 _ => None,
34 }
35 }
36
37 pub fn to_single(&self) -> &Arc<Single> {
39 self.as_single().expect("Unexpected job type: multi")
40 }
41
42 pub fn as_multi(&self) -> Option<&Arc<Multi>> {
44 match &self {
45 Job::Multi(job) => Some(job),
46 _ => None,
47 }
48 }
49
50 pub fn to_multi(&self) -> &Arc<Multi> {
52 self.as_multi().expect("Unexpected job type: single")
53 }
54
55 pub fn dimens(&self) -> &Dimensions {
57 match &self {
58 Job::Single(single) => &single.dimens,
59 Job::Multi(multi) => &multi.dimens,
60 }
61 }
62
63 pub fn places(&self) -> impl Iterator<Item = &Place> + '_ {
65 match &self {
66 Job::Single(single) => Either::Left(single.places.iter()),
67 Job::Multi(multi) => Either::Right(multi.jobs.iter().flat_map(|single| single.places.iter())),
68 }
69 }
70}
71
72impl Debug for Job {
73 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74 match self {
75 Job::Single(single) => single.fmt(f),
76 Job::Multi(multi) => multi.fmt(f),
77 }
78 }
79}
80
81#[derive(Clone)]
83pub struct Place {
84 pub location: Option<Location>,
86 pub duration: Duration,
88 pub times: Vec<TimeSpan>,
90}
91
92pub struct Single {
94 pub places: Vec<Place>,
96 pub dimens: Dimensions,
98}
99
100impl Debug for Single {
101 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102 f.debug_struct(short_type_name::<Single>())
103 .field("id", &self.dimens.get_job_id().map(|id| id.as_str()).unwrap_or("undef"))
104 .finish_non_exhaustive()
105 }
106}
107
108pub struct Multi {
112 pub jobs: Vec<Arc<Single>>,
114 pub dimens: Dimensions,
116 permutator: Box<dyn JobPermutation>,
118}
119
120impl Debug for Multi {
121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct(short_type_name::<Multi>())
123 .field("id", &self.dimens.get_job_id().map(|id| id.as_str()).unwrap_or("undef"))
124 .field("jobs", &self.jobs.len())
125 .finish_non_exhaustive()
126 }
127}
128
129pub trait JobPermutation: Send + Sync {
132 fn get(&self) -> Vec<Vec<usize>>;
135
136 fn validate(&self, permutation: &[usize]) -> bool;
138}
139
140pub struct FixedJobPermutation {
142 permutations: Vec<Vec<usize>>,
143}
144
145impl FixedJobPermutation {
146 pub fn new(permutations: Vec<Vec<usize>>) -> Self {
148 Self { permutations }
149 }
150}
151
152impl JobPermutation for FixedJobPermutation {
153 fn get(&self) -> Vec<Vec<usize>> {
154 self.permutations.clone()
155 }
156
157 fn validate(&self, permutation: &[usize]) -> bool {
158 self.permutations
159 .iter()
160 .any(|prm| prm.len() == permutation.len() && prm.iter().zip(permutation.iter()).all(|(&a, &b)| a == b))
161 }
162}
163
164impl Multi {
165 pub fn new_shared(jobs: Vec<Arc<Single>>, dimens: Dimensions) -> Arc<Self> {
168 let permutations = vec![(0..jobs.len()).collect()];
169 Self::bind(Self { jobs, dimens, permutator: Box::new(FixedJobPermutation::new(permutations)) })
170 }
171
172 pub fn new_shared_with_permutator(
174 jobs: Vec<Arc<Single>>,
175 dimens: Dimensions,
176 permutator: Box<dyn JobPermutation>,
177 ) -> Arc<Self> {
178 Self::bind(Self { jobs, dimens, permutator })
179 }
180
181 pub fn permutations(&self) -> Vec<Vec<Arc<Single>>> {
183 self.permutator
184 .get()
185 .iter()
186 .map(|perm| perm.iter().map(|&i| self.jobs.get(i).unwrap().clone()).collect())
187 .collect()
188 }
189
190 pub fn validate(&self, permutations: &[usize]) -> bool {
192 self.permutator.validate(permutations)
193 }
194
195 pub fn roots(single: &Single) -> Option<Arc<Multi>> {
197 single.dimens.get_value::<JobLink, Weak<Multi>>().and_then(|w| w.upgrade())
198 }
199
200 fn bind(mut multi: Self) -> Arc<Self> {
202 Arc::new_cyclic(|weak_multi| {
203 multi.jobs.iter_mut().for_each(|single| {
204 Arc::get_mut(single)
205 .expect("Single from Multi should not be shared before binding")
206 .dimens
207 .set_value::<JobLink, _>(weak_multi.clone());
208 });
209
210 multi
211 })
212 }
213}
214
215struct JobLink;
217
218type LowPrecisionCost = f32;
220type JobIndex = HashMap<Job, (Vec<(Job, LowPrecisionCost)>, LowPrecisionCost)>;
221
222const DEFAULT_COST: LowPrecisionCost = 0.;
224
225const UNREACHABLE_COST: LowPrecisionCost = f32::MAX;
227
228const MAX_NEIGHBOURS: usize = 256;
232
233pub struct Jobs {
235 jobs: Vec<Job>,
236 index: HashMap<usize, JobIndex>,
237 clusters: Vec<HashSet<Job>>,
238}
239
240impl Jobs {
241 pub fn new(
243 fleet: &Fleet,
244 jobs: Vec<Job>,
245 transport: &(dyn TransportCost),
246 logger: &InfoLogger,
247 ) -> GenericResult<Jobs> {
248 let index = create_index(fleet, jobs.clone(), transport, logger);
249 let clusters =
250 create_job_clusters(&jobs, fleet, Some(3), None, |profile, job| neighbors(&index, profile, job))?;
251
252 Ok(Jobs { jobs, index, clusters })
253 }
254
255 pub fn all(&self) -> &[Job] {
257 &self.jobs
258 }
259
260 pub fn neighbors(&self, profile: &Profile, job: &Job, _: Timestamp) -> impl Iterator<Item = (&Job, Cost)> {
263 neighbors(&self.index, profile, job)
264 }
265
266 pub fn clusters(&self) -> &[HashSet<Job>] {
268 &self.clusters
269 }
270
271 pub fn rank(&self, profile: &Profile, job: &Job) -> Option<Cost> {
274 self.index.get(&profile.index).and_then(|index| index.get(job)).map(|(_, cost)| *cost as Cost)
275 }
276
277 pub fn size(&self) -> usize {
279 self.jobs.len()
280 }
281}
282
283impl PartialEq<Job> for Job {
284 fn eq(&self, other: &Job) -> bool {
285 match (&self, other) {
286 (Job::Single(_), Job::Multi(_)) | (Job::Multi(_), Job::Single(_)) => false,
287 (Job::Single(lhs), Job::Single(rhs)) => Arc::ptr_eq(lhs, rhs),
288 (Job::Multi(lhs), Job::Multi(rhs)) => Arc::ptr_eq(lhs, rhs),
289 }
290 }
291}
292
293impl Eq for Job {}
294
295impl Hash for Job {
296 fn hash<H: Hasher>(&self, state: &mut H) {
297 match self {
298 Job::Single(single) => {
299 Arc::as_ptr(single).hash(state);
300 }
301 Job::Multi(multi) => {
302 Arc::as_ptr(multi).hash(state);
303 }
304 }
305 }
306}
307
308fn neighbors<'a>(
309 index: &'a HashMap<usize, JobIndex>,
310 profile: &Profile,
311 job: &Job,
312) -> impl Iterator<Item = (&'a Job, Cost)> {
313 index
314 .get(&profile.index)
315 .and_then(|index| index.get(job))
316 .into_iter()
317 .flat_map(|(info, _)| info.iter().map(|(job, cost)| (job, *cost as Float)))
318}
319
320pub fn get_job_locations(job: &Job) -> impl Iterator<Item = Option<Location>> + '_ {
322 match job {
323 Job::Single(single) => Either::Left(single.places.iter().map(|p| p.location)),
324 Job::Multi(multi) => Either::Right(multi.jobs.iter().flat_map(|j| j.places.iter().map(|p| p.location))),
325 }
326}
327
328fn create_index(
330 fleet: &Fleet,
331 jobs: Vec<Job>,
332 transport: &(dyn TransportCost),
333 logger: &InfoLogger,
334) -> HashMap<usize, JobIndex> {
335 let avg_profile_costs = get_avg_profile_costs(fleet);
336
337 Timer::measure_duration_with_callback(
338 || {
339 fleet.profiles.iter().fold(HashMap::new(), |mut acc, profile| {
340 let avg_costs = avg_profile_costs.get(&profile.index).unwrap();
341 let starts: Vec<Location> = fleet
343 .vehicles
344 .iter()
345 .filter(|v| v.profile.index == profile.index)
346 .flat_map(|v| v.details.iter().map(|d| d.start.as_ref().map(|s| s.location)))
347 .flatten()
348 .collect();
349
350 let item = parallel_collect(&jobs, |job| {
352 let mut sorted_job_costs: Vec<(Job, LowPrecisionCost)> = jobs
353 .iter()
354 .filter(|j| **j != *job)
355 .map(|j| (j.clone(), get_cost_between_jobs(profile, avg_costs, transport, job, j)))
356 .collect();
357 sorted_job_costs.sort_unstable_by(|(_, a), (_, b)| a.total_cmp(b));
358
359 sorted_job_costs.truncate(MAX_NEIGHBOURS);
360 sorted_job_costs.shrink_to_fit();
361
362 let fleet_costs = starts
363 .iter()
364 .cloned()
365 .map(|s| get_cost_between_job_and_location(profile, avg_costs, transport, job, s))
366 .min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
367 .unwrap_or(DEFAULT_COST);
368
369 (job.clone(), (sorted_job_costs, fleet_costs))
370 })
371 .into_iter()
372 .collect::<HashMap<_, _>>();
373
374 acc.insert(profile.index, item);
375 acc
376 })
377 },
378 |duration| (logger)(format!("job index created in {}ms", duration.as_millis()).as_str()),
379 )
380}
381
382fn get_cost_between_locations(
383 profile: &Profile,
384 costs: &Costs,
385 transport: &(dyn TransportCost),
386 from: Location,
387 to: Location,
388) -> LowPrecisionCost {
389 let distance = transport.distance_approx(profile, from, to);
390 let duration = transport.duration_approx(profile, from, to);
391
392 if distance < 0. || duration < 0. {
393 UNREACHABLE_COST
395 } else {
396 (distance * costs.per_distance + duration * costs.per_driving_time) as LowPrecisionCost
397 }
398}
399
400fn get_cost_between_job_and_location(
402 profile: &Profile,
403 costs: &Costs,
404 transport: &(dyn TransportCost),
405 job: &Job,
406 to: Location,
407) -> LowPrecisionCost {
408 get_job_locations(job)
409 .flatten()
410 .map(|from| get_cost_between_locations(profile, costs, transport, from, to))
411 .min_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
412 .unwrap_or(UNREACHABLE_COST)
413}
414
415fn get_cost_between_jobs(
417 profile: &Profile,
418 costs: &Costs,
419 transport: &(dyn TransportCost),
420 lhs: &Job,
421 rhs: &Job,
422) -> LowPrecisionCost {
423 let outer: Vec<Option<Location>> = get_job_locations(lhs).collect();
424 let inner: Vec<Option<Location>> = get_job_locations(rhs).collect();
425
426 let routing_cost = outer
427 .iter()
428 .flat_map(|o| inner.iter().map(move |i| (*o, *i)))
429 .map(|pair| match pair {
430 (Some(from), Some(to)) => get_cost_between_locations(profile, costs, transport, from, to),
431 _ => DEFAULT_COST,
432 })
433 .min_by(|a, b| a.total_cmp(b))
434 .unwrap_or(DEFAULT_COST);
435
436 routing_cost
439}
440
441fn get_avg_profile_costs(fleet: &Fleet) -> HashMap<usize, Costs> {
442 let get_avg_by = |costs: &Vec<Costs>, map_cost_fn: fn(&Costs) -> Float| -> Float {
443 costs.iter().map(map_cost_fn).sum::<Float>() / (costs.len() as Float)
444 };
445 fleet
446 .vehicles
447 .iter()
448 .fold(HashMap::<_, Vec<_>>::new(), |mut acc, vehicle| {
449 acc.entry(vehicle.profile.index).or_default().push(vehicle.costs.clone());
450 acc
451 })
452 .iter()
453 .map(|(&profile_idx, costs)| {
454 (
455 profile_idx,
456 Costs {
457 fixed: get_avg_by(costs, |c| c.fixed),
458 per_distance: get_avg_by(costs, |c| c.per_distance),
459 per_driving_time: get_avg_by(costs, |c| c.per_driving_time),
460 per_waiting_time: get_avg_by(costs, |c| c.per_waiting_time),
461 per_service_time: get_avg_by(costs, |c| c.per_service_time),
462 },
463 )
464 })
465 .collect()
466}