vrp_core/solver/search/local/
exchange_sequence.rs1#[cfg(test)]
2#[path = "../../../../tests/unit/solver/search/local/exchange_sequence_test.rs"]
3mod exchange_sequence_test;
4
5use crate::construction::heuristics::*;
6use crate::models::problem::Job;
7use crate::solver::search::LocalOperator;
8use crate::solver::RefinementContext;
9use rand::prelude::SliceRandom;
10use rosomaxa::prelude::*;
11use std::collections::HashSet;
12use std::ops::ControlFlow;
13
14const MIN_JOBS: usize = 2;
15
16pub struct ExchangeSequence {
18 max_sequence_size: usize,
19 reverse_prob: Float,
20 shuffle_prob: Float,
21}
22
23impl ExchangeSequence {
24 pub fn new(max_sequence_size: usize, reverse_prob: Float, shuffle_prob: Float) -> Self {
26 assert!(max_sequence_size >= MIN_JOBS);
27
28 Self { max_sequence_size, reverse_prob, shuffle_prob }
29 }
30}
31
32impl Default for ExchangeSequence {
33 fn default() -> Self {
34 Self::new(6, 0.5, 0.01)
35 }
36}
37
38impl LocalOperator for ExchangeSequence {
39 fn explore(&self, _: &RefinementContext, insertion_ctx: &InsertionContext) -> Option<InsertionContext> {
40 let route_indices = get_route_indices(insertion_ctx);
41
42 if route_indices.is_empty() {
43 return None;
44 }
45
46 let mut insertion_ctx = insertion_ctx.deep_copy();
47
48 exchange_jobs(
49 &mut insertion_ctx,
50 route_indices.as_slice(),
51 self.max_sequence_size,
52 self.reverse_prob,
53 self.shuffle_prob,
54 );
55
56 Some(insertion_ctx)
57 }
58}
59
60fn get_route_indices(insertion_ctx: &InsertionContext) -> Vec<usize> {
61 insertion_ctx
62 .solution
63 .routes
64 .iter()
65 .enumerate()
66 .filter_map(|(idx, route_ctx)| {
67 let locked_jobs =
68 route_ctx.route().tour.jobs().filter(|job| insertion_ctx.solution.locked.contains(*job)).count();
69 let has_enough_jobs = (route_ctx.route().tour.job_count() - locked_jobs) >= MIN_JOBS;
70
71 if has_enough_jobs {
72 Some(idx)
73 } else {
74 None
75 }
76 })
77 .collect()
78}
79
80fn exchange_jobs(
81 insertion_ctx: &mut InsertionContext,
82 route_indices: &[usize],
83 max_sequence_size: usize,
84 reverse_prob: Float,
85 shuffle_prob: Float,
86) {
87 let get_route_idx = |insertion_ctx: &InsertionContext| {
88 let idx = insertion_ctx.environment.random.uniform_int(0, route_indices.len() as i32 - 1) as usize;
89 route_indices.get(idx).cloned().unwrap()
90 };
91
92 let get_sequence_size = |insertion_ctx: &InsertionContext, route_idx: usize| {
93 let job_count = get_route_ctx(insertion_ctx, route_idx).route().tour.job_count().min(max_sequence_size);
94 insertion_ctx.environment.random.uniform_int(MIN_JOBS as i32, job_count as i32) as usize
95 };
96
97 let first_route_idx = get_route_idx(insertion_ctx);
98 let first_sequence_size = get_sequence_size(insertion_ctx, first_route_idx);
99 let first_jobs = extract_jobs(insertion_ctx, first_route_idx, first_sequence_size);
100
101 let second_route_idx = get_route_idx(insertion_ctx);
102
103 if first_route_idx != second_route_idx {
104 let second_sequence_size = get_sequence_size(insertion_ctx, second_route_idx);
105 let second_jobs = extract_jobs(insertion_ctx, second_route_idx, second_sequence_size);
106
107 insert_jobs(insertion_ctx, first_route_idx, second_jobs, reverse_prob, shuffle_prob);
108 insert_jobs(insertion_ctx, second_route_idx, first_jobs, reverse_prob, shuffle_prob);
109 } else {
110 insert_jobs(insertion_ctx, first_route_idx, first_jobs, reverse_prob, shuffle_prob);
111 }
112
113 finalize_insertion_ctx(insertion_ctx);
114}
115
116fn extract_jobs(insertion_ctx: &mut InsertionContext, route_idx: usize, sequence_size: usize) -> Vec<Job> {
117 let locked = &insertion_ctx.solution.locked;
118 let route_ctx = insertion_ctx.solution.routes.get_mut(route_idx).unwrap();
119 let job_count = route_ctx.route().tour.job_count();
120
121 assert!(job_count >= sequence_size);
122
123 let (_, jobs) = route_ctx.route().tour.all_activities().filter_map(|activity| activity.retrieve_job()).fold(
125 (HashSet::<Job>::default(), Vec::with_capacity(job_count)),
126 |(mut set, mut vec), job| {
127 if !set.contains(&job) && !locked.contains(&job) {
128 vec.push(job.clone());
129 set.insert(job);
130 }
131
132 (set, vec)
133 },
134 );
135
136 let sequence_size = sequence_size.min(jobs.len());
137 let last_index = jobs.len() - sequence_size;
138 let start_index = insertion_ctx.environment.random.uniform_int(0, last_index as i32) as usize;
139
140 let removed =
141 (start_index..(start_index + sequence_size)).fold(Vec::with_capacity(sequence_size), |mut acc, index| {
142 let job = jobs.get(index).unwrap();
143 assert!(route_ctx.route_mut().tour.remove(job));
144 acc.push(job.clone());
145
146 acc
147 });
148
149 insertion_ctx.problem.goal.accept_route_state(route_ctx);
150
151 removed
152}
153
154fn insert_jobs(
155 insertion_ctx: &mut InsertionContext,
156 route_idx: usize,
157 jobs: Vec<Job>,
158 reverse_prob: Float,
159 shuffle_prob: Float,
160) {
161 let random = &insertion_ctx.environment.random;
162 let leg_selection = LegSelection::Stochastic(random.clone());
163 let result_selector = BestResultSelector::default();
164
165 let mut jobs = jobs;
166 match (random.is_hit(reverse_prob), random.is_hit(shuffle_prob)) {
167 (true, _) => {
168 jobs.reverse();
169 }
170 (_, true) => {
171 jobs.shuffle(&mut random.get_rng());
172 }
173 _ => {}
174 };
175
176 let start_index = random
177 .uniform_int(0, get_route_ctx(insertion_ctx, route_idx).route().tour.job_activity_count() as i32)
178 as usize;
179
180 let (failures, _) = jobs.into_iter().fold((Vec::new(), start_index), |(mut unassigned, start_index), job| {
181 let eval_ctx = EvaluationContext {
182 goal: &insertion_ctx.problem.goal,
183 job: &job,
184 leg_selection: &leg_selection,
185 result_selector: &result_selector,
186 };
187
188 let last_index = get_route_ctx(insertion_ctx, route_idx).route().tour.job_activity_count();
190 let (result, start_index) = (start_index..=last_index)
192 .try_fold((InsertionResult::make_failure(), start_index), |_, insertion_idx| {
193 let insertion = eval_job_insertion_in_route(
194 insertion_ctx,
195 &eval_ctx,
196 get_route_ctx(insertion_ctx, route_idx),
197 InsertionPosition::Concrete(insertion_idx),
198 InsertionResult::make_failure(),
200 );
201
202 match &insertion {
203 InsertionResult::Failure(_) => ControlFlow::Continue((insertion, insertion_idx)),
204 InsertionResult::Success(_) => ControlFlow::Break((insertion, insertion_idx)),
205 }
206 })
207 .unwrap_value();
208
209 match result {
210 InsertionResult::Success(success) => {
211 apply_insertion_success(insertion_ctx, success);
212 }
213 InsertionResult::Failure(failure) => unassigned.push((job, failure)),
214 }
215
216 (unassigned, start_index + 1)
217 });
218
219 insertion_ctx.solution.unassigned.extend(failures.into_iter().map(|(job, failure)| {
220 let code = UnassignmentInfo::Simple(failure.constraint);
221 let job = failure.job.unwrap_or(job);
222 (job, code)
223 }));
224}
225
226fn get_route_ctx(insertion_ctx: &InsertionContext, route_idx: usize) -> &RouteContext {
227 insertion_ctx.solution.routes.get(route_idx).unwrap()
228}