vrp_core/construction/features/
tour_order.rs1#[cfg(test)]
4#[path = "../../../tests/unit/construction/features/tour_order_test.rs"]
5mod tour_order_test;
6
7use super::*;
8use crate::models::problem::{Actor, Single};
9use crate::models::solution::Activity;
10use crate::utils::Either;
11use std::cmp::Ordering;
12use std::ops::ControlFlow;
13
14custom_solution_state!(TourOrderViolations typeof usize);
15
16pub fn create_tour_order_hard_feature(
18 name: &str,
19 code: ViolationCode,
20 order_fn: TourOrderFn,
21) -> Result<Feature, GenericError> {
22 FeatureBuilder::default().with_name(name).with_constraint(TourOrderConstraint { code, order_fn }).build()
23}
24
25pub fn create_tour_order_soft_feature(name: &str, order_fn: TourOrderFn) -> Result<Feature, GenericError> {
27 FeatureBuilder::default()
28 .with_name(name)
29 .with_objective(TourOrderObjective { order_fn: order_fn.clone() })
30 .with_state(TourOrderState { order_fn })
31 .build()
32}
33
34#[derive(Copy, Clone)]
36pub enum OrderResult {
37 Value(Float),
39 Default,
41 Ignored,
43}
44
45pub type ActorTourOrderFn = Arc<dyn Fn(&Actor, &Single) -> OrderResult + Send + Sync>;
47
48pub type SingleTourOrderFn = Arc<dyn Fn(&Single) -> OrderResult + Send + Sync>;
50
51pub type TourOrderFn = Either<SingleTourOrderFn, ActorTourOrderFn>;
53
54struct TourOrderConstraint {
55 code: ViolationCode,
56 order_fn: TourOrderFn,
57}
58
59impl FeatureConstraint for TourOrderConstraint {
60 fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option<ConstraintViolation> {
61 match move_ctx {
62 MoveContext::Activity { route_ctx, activity_ctx } => {
63 evaluate_result(route_ctx, activity_ctx, &self.order_fn, &|first, second, stopped| {
64 if compare_order_results(first, second) == Ordering::Greater {
65 Some(ConstraintViolation { code: self.code, stopped })
66 } else {
67 None
68 }
69 })
70 }
71 MoveContext::Route { .. } => None,
72 }
73 }
74
75 fn merge(&self, source: Job, candidate: Job) -> Result<Job, ViolationCode> {
76 match &self.order_fn {
77 Either::Left(order_fn) => {
78 let order_fn_cmp = |source: &Single, candidate: &Single| {
79 let source = (order_fn)(source);
80 let candidate = (order_fn)(candidate);
81 match (source, candidate) {
82 (OrderResult::Value(s), OrderResult::Value(c)) => s == c,
83 (OrderResult::Default, OrderResult::Default) | (OrderResult::Ignored, OrderResult::Ignored) => {
84 true
85 }
86 _ => false,
87 }
88 };
89
90 match (&source, &candidate) {
91 (Job::Single(s_source), Job::Single(s_candidate)) if order_fn_cmp(s_source, s_candidate) => {
92 Ok(source)
93 }
94 _ => Err(self.code),
95 }
96 }
97 Either::Right(_) => Err(self.code),
98 }
99 }
100}
101
102struct TourOrderObjective {
103 order_fn: TourOrderFn,
104}
105
106impl FeatureObjective for TourOrderObjective {
107 fn fitness(&self, solution: &InsertionContext) -> Cost {
108 let solution = &solution.solution;
109
110 solution
111 .state
112 .get_tour_order_violations()
113 .copied()
114 .unwrap_or_else(|| get_violations(solution.routes.as_slice(), &self.order_fn)) as Float
115 }
116
117 fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost {
118 match move_ctx {
119 MoveContext::Activity { route_ctx, activity_ctx } => {
120 evaluate_result(route_ctx, activity_ctx, &self.order_fn, &|first, second, _| {
121 if compare_order_results(first, second) == Ordering::Greater {
122 let value = match (first, second) {
123 (OrderResult::Value(first), OrderResult::Value(second)) => first - second,
124 (OrderResult::Default, OrderResult::Value(value)) => -value,
125 (OrderResult::Value(value), OrderResult::Default) => value,
126 _ => Cost::default(),
127 };
128
129 Some(value)
130 } else {
131 None
132 }
133 })
134 .unwrap_or_default()
135 }
136 MoveContext::Route { .. } => Cost::default(),
137 }
138 }
139}
140
141struct TourOrderState {
142 order_fn: TourOrderFn,
143}
144
145impl FeatureState for TourOrderState {
146 fn accept_insertion(&self, _: &mut SolutionContext, _: usize, _: &Job) {}
147
148 fn accept_route_state(&self, _: &mut RouteContext) {}
149
150 fn accept_solution_state(&self, solution_ctx: &mut SolutionContext) {
151 let violations = get_violations(solution_ctx.routes.as_slice(), &self.order_fn);
152 solution_ctx.state.set_tour_order_violations(violations);
153 }
154}
155
156fn evaluate_result<T>(
157 route_ctx: &RouteContext,
158 activity_ctx: &ActivityContext,
159 order_fn: &TourOrderFn,
160 check_order: &(dyn Fn(OrderResult, OrderResult, bool) -> Option<T>),
161) -> Option<T> {
162 let get_order = |single: &Single| match &order_fn {
163 Either::Left(left) => (left)(single),
164 Either::Right(right) => (right)(route_ctx.route().actor.as_ref(), single),
165 };
166
167 let target_order = activity_ctx.target.job.as_ref().map(|single| get_order(single)).unwrap_or(OrderResult::Ignored);
168
169 let get_order_by_idx = |idx: usize| {
170 route_ctx.route().tour.get(idx).and_then(get_single).map(get_order).unwrap_or(OrderResult::Ignored)
171 };
172
173 (0..=activity_ctx.index)
174 .map(get_order_by_idx)
175 .map(|early| (early, target_order, true))
176 .chain(
177 (activity_ctx.index + 1..route_ctx.route().tour.total())
178 .map(get_order_by_idx)
179 .map(|late| (target_order, late, false)),
180 )
181 .try_fold(None, |_, (left, right, stopped)| {
182 let result = (check_order)(left, right, stopped);
183 if result.is_some() {
184 ControlFlow::Break(result)
185 } else {
186 ControlFlow::Continue(None)
187 }
188 })
189 .unwrap_value()
190}
191
192fn get_violations(routes: &[RouteContext], order_fn: &TourOrderFn) -> usize {
193 routes
194 .iter()
195 .map(|route_ctx| {
196 let orders = route_ctx
197 .route()
198 .tour
199 .all_activities()
200 .filter_map(|activity| activity.job.as_ref())
201 .map(|single| match order_fn {
202 Either::Left(left) => (left)(single.as_ref()),
203 Either::Right(right) => (right)(route_ctx.route().actor.as_ref(), single.as_ref()),
204 })
205 .filter(|order| !matches!(order, OrderResult::Ignored))
206 .collect::<Vec<OrderResult>>();
207
208 orders.windows(2).fold(0_usize, |acc, pair| {
209 let value = match *pair {
210 [prev, next] => match compare_order_results(prev, next) {
211 Ordering::Greater => 1,
212 _ => 0,
213 },
214 _ => unreachable!(),
215 };
216
217 acc + value
218 })
219 })
220 .sum::<usize>()
221}
222
223fn get_single(activity: &Activity) -> Option<&Single> {
224 activity.job.as_ref().map(|single| single.as_ref())
225}
226
227fn compare_order_results(left: OrderResult, right: OrderResult) -> Ordering {
228 match (left, right) {
229 (OrderResult::Value(left), OrderResult::Value(right)) => left.total_cmp(&right),
230 (OrderResult::Value(_), OrderResult::Default) => Ordering::Less,
231 (OrderResult::Default, OrderResult::Value(_)) => Ordering::Greater,
232 _ => Ordering::Equal,
233 }
234}