vrp_core/construction/features/
minimize_unassigned.rs

1//! Provides the way to control job assignment.
2
3#[cfg(test)]
4#[path = "../../../tests/unit/construction/features/minimize_unassigned_test.rs"]
5mod minimize_unassigned_test;
6
7use super::*;
8use crate::utils::Either;
9use std::iter::empty;
10
11/// Provides a way to build a feature to minimize amount of unassigned jobs.
12pub struct MinimizeUnassignedBuilder {
13    name: String,
14    job_estimator: Option<UnassignedJobEstimator>,
15}
16
17impl MinimizeUnassignedBuilder {
18    /// Creates a new instance of `MinimizeUnassignedBuilder`
19    pub fn new(name: &str) -> Self {
20        Self { name: name.to_string(), job_estimator: None }
21    }
22
23    /// Sets a job estimator function which responsible for cost estimate of unassigned jobs.
24    /// Optional. Default is the implementation which gives 1 as estimate to any unassisgned job.
25    pub fn set_job_estimator<F>(mut self, func: F) -> Self
26    where
27        F: Fn(&SolutionContext, &Job) -> Float + Send + Sync + 'static,
28    {
29        self.job_estimator = Some(Arc::new(func));
30        self
31    }
32
33    /// Builds a feature.
34    pub fn build(mut self) -> GenericResult<Feature> {
35        let unassigned_job_estimator = self.job_estimator.take().unwrap_or_else(|| Arc::new(|_, _| 1.));
36
37        FeatureBuilder::default()
38            .with_name(self.name.as_str())
39            .with_objective(MinimizeUnassignedObjective { unassigned_job_estimator })
40            .build()
41    }
42}
43
44/// A type that allows controlling how a job is estimated in objective fitness.
45type UnassignedJobEstimator = Arc<dyn Fn(&SolutionContext, &Job) -> Float + Send + Sync>;
46
47struct MinimizeUnassignedObjective {
48    unassigned_job_estimator: UnassignedJobEstimator,
49}
50
51impl FeatureObjective for MinimizeUnassignedObjective {
52    fn fitness(&self, solution: &InsertionContext) -> Cost {
53        if solution.solution.routes.is_empty() {
54            // NOTE: when some solution is empty, then it might look better by the number of unassigned jobs
55            //       if we do not estimate ignored jobs.
56            Either::Left(solution.solution.ignored.iter())
57        } else {
58            Either::Right(empty())
59        }
60        .chain(solution.solution.unassigned.keys())
61        .map(|job| (self.unassigned_job_estimator)(&solution.solution, job))
62        .sum::<Float>()
63    }
64
65    fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost {
66        match move_ctx {
67            MoveContext::Route { solution_ctx, job, .. } => -1. * (self.unassigned_job_estimator)(solution_ctx, job),
68            MoveContext::Activity { .. } => Cost::default(),
69        }
70    }
71}