vrp_core/construction/clustering/vicinity/
mod.rs

1//! Provides functionality to group jobs in some vicinity radius.
2
3#[cfg(test)]
4#[path = "../../../../tests/unit/construction/clustering/vicinity/vicinity_test.rs"]
5mod vicinity_test;
6
7use crate::construction::heuristics::*;
8use crate::models::common::Dimensions;
9use crate::models::common::*;
10use crate::models::problem::{Actor, Job};
11use crate::models::Problem;
12use rosomaxa::prelude::*;
13use std::cmp::Ordering;
14use std::collections::HashSet;
15use std::ops::ControlFlow;
16use std::sync::Arc;
17
18mod estimations;
19use self::estimations::*;
20use crate::models::solution::Commute;
21use crate::prelude::ViolationCode;
22
23custom_dimension!(ClusterInfo typeof Vec<ClusterInfo>);
24
25/// Holds center job and its neighbor jobs.
26pub type ClusterCandidate<'a> = (&'a Job, &'a HashSet<Job>);
27
28type CheckInsertionFn = (dyn Fn(&Job) -> Result<(), ViolationCode> + Send + Sync);
29
30/// Specifies clustering algorithm configuration.
31#[derive(Clone)]
32pub struct ClusterConfig {
33    /// A matrix profile used to calculate traveling durations and distances.
34    pub profile: Profile,
35    /// A thresholds for job clustering.
36    pub threshold: ThresholdPolicy,
37    /// Job visiting policy
38    pub visiting: VisitPolicy,
39    /// Job service time policy.
40    pub serving: ServingPolicy,
41    /// Specifies filtering policy.
42    pub filtering: FilterPolicy,
43    /// Specifies building policy.
44    pub building: BuilderPolicy,
45}
46
47/// Defines a various thresholds to control cluster size.
48#[derive(Clone)]
49pub struct ThresholdPolicy {
50    /// Moving duration limit.
51    pub moving_duration: Duration,
52    /// Moving distance limit.
53    pub moving_distance: Distance,
54    /// Minimum shared time for jobs (non-inclusive).
55    pub min_shared_time: Option<Duration>,
56    /// The smallest time window of the cluster after service time shrinking.
57    pub smallest_time_window: Option<Duration>,
58    /// The maximum amount of jobs per cluster.
59    pub max_jobs_per_cluster: Option<usize>,
60}
61
62/// Specifies cluster visiting policy.
63#[derive(Clone)]
64pub enum VisitPolicy {
65    /// It is required to return to the first job's location (cluster center) before visiting a next job.
66    Return,
67    /// Clustered jobs are visited one by one from the cluster center finishing in the end at the
68    /// first job's location.
69    ClosedContinuation,
70    /// Clustered jobs are visited one by one starting from the cluster center and finishing in the
71    /// end at the last job's location.
72    /// NOTE: this might be useful for use clustering algorithm to split problem into sub-problems.
73    /// TODO: make sure that it can be used with other non-clustered activities in the same stop.
74    OpenContinuation,
75}
76
77/// Specifies filtering policy.
78#[derive(Clone)]
79pub struct FilterPolicy {
80    /// Job filter.
81    pub job_filter: Arc<dyn Fn(&Job) -> bool + Send + Sync>,
82    /// Actor filter.
83    pub actor_filter: Arc<dyn Fn(&Actor) -> bool + Send + Sync>,
84}
85
86/// Specifies service time policy.
87#[derive(Clone)]
88pub enum ServingPolicy {
89    /// Keep original service time.
90    Original {
91        /// Parking time.
92        parking: Duration,
93    },
94    /// Correct service time by some multiplier.
95    Multiplier {
96        /// Multiplier value applied to original job's duration.
97        multiplier: Float,
98        /// Parking time.
99        parking: Duration,
100    },
101    /// Use fixed value for all clustered jobs.
102    Fixed {
103        /// Fixed value used for all jobs in the cluster.
104        value: Duration,
105        /// Parking time.
106        parking: Duration,
107    },
108}
109
110/// A function type which orders visiting clusters based on their estimated size.
111pub type OrderingGlobalFn = Arc<dyn Fn(ClusterCandidate, ClusterCandidate) -> Ordering + Send + Sync>;
112/// A function type which orders visiting jobs in a cluster based on their visit info.
113pub type OrderingLocalFn = Arc<dyn Fn(&ClusterInfo, &ClusterInfo) -> Ordering + Send + Sync>;
114
115/// Allows to control how clusters are built.
116#[derive(Clone)]
117pub struct BuilderPolicy {
118    /// Orders visiting clusters based on their estimated size.
119    pub ordering_global_fn: OrderingGlobalFn,
120    /// Orders visiting jobs in a cluster based on their visit info.
121    pub ordering_local_fn: OrderingLocalFn,
122}
123
124/// Keeps track of information specific for job in the cluster.
125#[derive(Clone)]
126pub struct ClusterInfo {
127    /// An original job.
128    pub job: Job,
129    /// An activity's service time.
130    pub service_time: Duration,
131    /// An used place index.
132    pub place_idx: usize,
133    /// Commute information.
134    pub commute: Commute,
135}
136
137/// Creates clusters of jobs grouping them together best on vicinity properties.
138/// Limitations:
139/// - only single jobs are clustered
140/// - time offset in job times is not supported
141pub fn create_job_clusters(
142    problem: Arc<Problem>,
143    environment: Arc<Environment>,
144    config: &ClusterConfig,
145) -> Vec<(Job, Vec<Job>)> {
146    let insertion_ctx = InsertionContext::new_empty(problem.clone(), environment);
147    let constraint = insertion_ctx.problem.goal.clone();
148    let check_insertion = get_check_insertion_fn(insertion_ctx, config.filtering.actor_filter.clone());
149    let transport = problem.transport.as_ref();
150    let jobs = problem
151        .jobs
152        .all()
153        .iter()
154        .filter(|job| (config.filtering.job_filter)(job))
155        // NOTE multi-job is not supported
156        .filter(|job| job.as_single().is_some())
157        .cloned()
158        .collect::<Vec<_>>();
159
160    let estimates = get_jobs_dissimilarities(jobs.as_slice(), transport, config);
161
162    get_clusters(&constraint, estimates, config, &check_insertion)
163}
164
165/// Gets function which checks possibility of cluster insertion.
166fn get_check_insertion_fn(
167    insertion_ctx: InsertionContext,
168    actor_filter: Arc<dyn Fn(&Actor) -> bool + Send + Sync>,
169) -> impl Fn(&Job) -> Result<(), ViolationCode> {
170    move |job: &Job| -> Result<(), ViolationCode> {
171        let eval_ctx = EvaluationContext {
172            goal: &insertion_ctx.problem.goal,
173            job,
174            leg_selection: &LegSelection::Exhaustive,
175            result_selector: &BestResultSelector::default(),
176        };
177
178        insertion_ctx
179            .solution
180            .registry
181            .next_route()
182            .filter(|route_ctx| (actor_filter)(&route_ctx.route().actor))
183            .try_fold(Err(ViolationCode::unknown()), |_, route_ctx| {
184                let result = eval_job_insertion_in_route(
185                    &insertion_ctx,
186                    &eval_ctx,
187                    route_ctx,
188                    InsertionPosition::Any,
189                    InsertionResult::make_failure(),
190                );
191
192                match result {
193                    InsertionResult::Success(_) => ControlFlow::Break(Ok(())),
194                    InsertionResult::Failure(failure) => ControlFlow::Continue(Err(failure.constraint)),
195                }
196            })
197            .unwrap_value()
198    }
199}
200
201impl ServingPolicy {
202    /// Gets parking time.
203    pub fn get_parking(&self) -> Duration {
204        match &self {
205            Self::Original { parking } => *parking,
206            Self::Multiplier { parking, .. } => *parking,
207            Self::Fixed { parking, .. } => *parking,
208        }
209    }
210}