vrp_core/construction/features/
groups.rs

1//! A feature to model group of jobs.
2
3use super::*;
4use std::collections::HashSet;
5
6#[cfg(test)]
7#[path = "../../../tests/unit/construction/features/groups_test.rs"]
8mod groups_test;
9
10custom_dimension!(JobGroup typeof String);
11custom_tour_state!(CurrentGroups typeof HashSet<String>);
12
13/// Creates a job group feature as a hard constraint.
14pub fn create_group_feature(name: &str, total_jobs: usize, code: ViolationCode) -> Result<Feature, GenericError> {
15    FeatureBuilder::default()
16        .with_name(name)
17        .with_constraint(GroupConstraint { total_jobs, code })
18        .with_state(GroupState {})
19        .build()
20}
21
22struct GroupConstraint {
23    total_jobs: usize,
24    code: ViolationCode,
25}
26
27impl FeatureConstraint for GroupConstraint {
28    fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option<ConstraintViolation> {
29        match move_ctx {
30            MoveContext::Route { solution_ctx, route_ctx, job } => job.dimens().get_job_group().and_then(|group| {
31                let is_partial_problem = solution_ctx.get_jobs_amount() != self.total_jobs;
32                if is_partial_problem {
33                    return ConstraintViolation::fail(self.code);
34                }
35
36                let other_route = solution_ctx
37                    .routes
38                    .iter()
39                    .filter(|rc| rc.route().actor != route_ctx.route().actor)
40                    .filter_map(|rc| rc.state().get_current_groups())
41                    .any(|groups| groups.contains(group));
42
43                if other_route {
44                    ConstraintViolation::fail(self.code)
45                } else {
46                    None
47                }
48            }),
49            MoveContext::Activity { .. } => None,
50        }
51    }
52
53    fn merge(&self, source: Job, candidate: Job) -> Result<Job, ViolationCode> {
54        match (source.dimens().get_job_group(), candidate.dimens().get_job_group()) {
55            (None, None) => Ok(source),
56            (Some(s_group), Some(c_group)) if s_group == c_group => Ok(source),
57            _ => Err(self.code),
58        }
59    }
60}
61
62struct GroupState {}
63
64impl FeatureState for GroupState {
65    fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, job: &Job) {
66        if let Some(group) = job.dimens().get_job_group() {
67            let route_ctx = solution_ctx.routes.get_mut(route_index).unwrap();
68
69            let mut groups = get_groups(route_ctx);
70            groups.insert(group.clone());
71
72            route_ctx.state_mut().set_current_groups(groups);
73        }
74    }
75
76    fn accept_route_state(&self, _: &mut RouteContext) {}
77
78    fn accept_solution_state(&self, solution_ctx: &mut SolutionContext) {
79        solution_ctx.routes.iter_mut().for_each(|route_ctx| {
80            let groups = get_groups(route_ctx);
81            route_ctx.state_mut().set_current_groups(groups);
82        });
83    }
84}
85
86fn get_groups(route_ctx: &RouteContext) -> HashSet<String> {
87    route_ctx.route().tour.jobs().filter_map(|job| job.dimens().get_job_group()).cloned().collect()
88}