@@ -161,98 +161,180 @@ void LocalSearch<Route,
161
161
SwapStar,
162
162
RouteSplit,
163
163
PriorityReplace,
164
- TSPFix>::recreate(const std::vector<Index>& routes
164
+ TSPFix>::try_job_additions(const std::vector<Index>& routes,
165
+ double regret_coeff
165
166
#ifdef LOG_LS
166
- ,
167
- bool log_addition_step
167
+ ,
168
+ bool log_addition_step
168
169
#endif
169
170
) {
170
- std::vector<Index> ordered_jobs (_sol_state.unassigned .begin (),
171
- _sol_state.unassigned .end ());
172
- std::ranges::sort (ordered_jobs, [this ](const Index lhs, const Index rhs) {
173
- return _input.jobs [lhs] < _input.jobs [rhs];
174
- });
171
+ bool job_added;
175
172
176
- std::unordered_set<Index> modified_vehicles;
173
+ std::vector<std::vector<RouteInsertion>> route_job_insertions;
174
+ route_job_insertions.reserve (routes.size ());
177
175
178
- for (const auto j : ordered_jobs) {
179
- const auto & current_job = _input.jobs [j];
180
- if (current_job.type == JOB_TYPE::DELIVERY) {
181
- continue ;
176
+ for (std::size_t i = 0 ; i < routes.size (); ++i) {
177
+ route_job_insertions.emplace_back (_input.jobs .size (),
178
+ RouteInsertion (_input.get_amount_size ()));
179
+
180
+ const auto v = routes[i];
181
+ const auto fixed_cost =
182
+ _sol[v].empty () ? _input.vehicles [v].fixed_cost () : 0 ;
183
+
184
+ for (const auto j : _sol_state.unassigned ) {
185
+ if (const auto & current_job = _input.jobs [j];
186
+ current_job.type == JOB_TYPE::DELIVERY) {
187
+ continue ;
188
+ }
189
+ route_job_insertions[i][j] =
190
+ compute_best_insertion (_input, _sol_state, j, v, _sol[v]);
191
+
192
+ if (route_job_insertions[i][j].eval != NO_EVAL) {
193
+ route_job_insertions[i][j].eval .cost += fixed_cost;
194
+ }
182
195
}
196
+ }
197
+
198
+ std::unordered_set<Index> modified_vehicles;
183
199
200
+ do {
201
+ Priority best_priority = 0 ;
184
202
RouteInsertion best_insertion (_input.get_amount_size ());
185
- Index best_v = 0 ;
203
+ double best_cost = std::numeric_limits<double >::max ();
204
+ Index best_job_rank = 0 ;
205
+ Index best_route = 0 ;
206
+ std::size_t best_route_idx = 0 ;
186
207
187
- for (const auto v : routes) {
188
- if (const unsigned added_tasks =
189
- (current_job.type == JOB_TYPE::PICKUP) ? 2 : 1 ;
190
- _sol[v].size () + added_tasks > _input.vehicles [v].max_tasks ) {
208
+ for (const auto j : _sol_state.unassigned ) {
209
+ const auto & current_job = _input.jobs [j];
210
+ if (current_job.type == JOB_TYPE::DELIVERY) {
191
211
continue ;
192
212
}
193
213
194
- auto current_best_insertion =
195
- compute_best_insertion (_input, _sol_state, j, v, _sol[v]);
214
+ const auto job_priority = current_job.priority ;
196
215
197
- if (current_best_insertion.eval != NO_EVAL && _sol[v].empty ()) {
198
- current_best_insertion.eval .cost += _input.vehicles [v].fixed_cost ();
216
+ if (job_priority < best_priority) {
217
+ // Insert higher priority jobs first.
218
+ continue ;
199
219
}
200
220
201
- if (current_best_insertion.eval < best_insertion.eval ) {
202
- best_insertion = current_best_insertion;
203
- best_v = v;
221
+ auto smallest = _input.get_cost_upper_bound ();
222
+ auto second_smallest = _input.get_cost_upper_bound ();
223
+ std::size_t smallest_idx = std::numeric_limits<std::size_t >::max ();
224
+
225
+ for (std::size_t i = 0 ; i < routes.size (); ++i) {
226
+ if (route_job_insertions[i][j].eval .cost < smallest) {
227
+ smallest_idx = i;
228
+ second_smallest = smallest;
229
+ smallest = route_job_insertions[i][j].eval .cost ;
230
+ } else if (route_job_insertions[i][j].eval .cost < second_smallest) {
231
+ second_smallest = route_job_insertions[i][j].eval .cost ;
232
+ }
204
233
}
205
- }
206
234
207
- if (best_insertion.eval == NO_EVAL) {
208
- // Current job cannot be inserted.
209
- continue ;
210
- }
235
+ // Find best route for current job based on cost of addition and
236
+ // regret cost of not adding.
237
+ for (std::size_t i = 0 ; i < routes.size (); ++i) {
238
+ if (route_job_insertions[i][j].eval == NO_EVAL) {
239
+ continue ;
240
+ }
211
241
212
- // Found a valid insertion spot for current job.
213
- _sol_state. unassigned . erase (j) ;
242
+ const auto & current_r = _sol[routes[i]];
243
+ const auto & vehicle = _input. vehicles [routes[i]] ;
214
244
215
- if (current_job. type == JOB_TYPE::SINGLE) {
216
- _sol[best_v]. add (_input, j, best_insertion. single_rank );
217
- } else {
218
- assert (current_job. type == JOB_TYPE::PICKUP);
245
+ if (const bool is_pickup = (_input. jobs [j]. type == JOB_TYPE::PICKUP);
246
+ current_r. size () + (is_pickup ? 2 : 1 ) > vehicle. max_tasks ) {
247
+ continue ;
248
+ }
219
249
220
- std::vector<Index> modified_with_pd;
221
- modified_with_pd.reserve (best_insertion.delivery_rank -
222
- best_insertion.pickup_rank + 2 );
223
- modified_with_pd.push_back (j);
224
-
225
- std::copy (_sol[best_v].route .begin () + best_insertion.pickup_rank ,
226
- _sol[best_v].route .begin () + best_insertion.delivery_rank ,
227
- std::back_inserter (modified_with_pd));
228
- modified_with_pd.push_back (j + 1 );
229
-
230
- _sol[best_v].replace (_input,
231
- best_insertion.delivery ,
232
- modified_with_pd.begin (),
233
- modified_with_pd.end (),
234
- best_insertion.pickup_rank ,
235
- best_insertion.delivery_rank );
236
-
237
- assert (_sol_state.unassigned .find (j + 1 ) != _sol_state.unassigned .end ());
238
- _sol_state.unassigned .erase (j + 1 );
250
+ const auto regret_cost =
251
+ (i == smallest_idx) ? second_smallest : smallest;
252
+
253
+ const double current_cost =
254
+ static_cast <double >(route_job_insertions[i][j].eval .cost ) -
255
+ regret_coeff * static_cast <double >(regret_cost);
256
+
257
+ if ((job_priority > best_priority) ||
258
+ (job_priority == best_priority && current_cost < best_cost)) {
259
+ best_priority = job_priority;
260
+ best_job_rank = j;
261
+ best_route = routes[i];
262
+ best_insertion = route_job_insertions[i][j];
263
+ best_cost = current_cost;
264
+ best_route_idx = i;
265
+ }
266
+ }
239
267
}
240
268
241
- // Update best route data, required for consistency.
242
- modified_vehicles.insert (best_v);
243
- _sol_state.update_route_eval (_sol[best_v].route , best_v);
244
- _sol_state.set_insertion_ranks (_sol[best_v], best_v);
269
+ job_added = (best_cost < std::numeric_limits<double >::max ());
270
+
271
+ if (job_added) {
272
+ _sol_state.unassigned .erase (best_job_rank);
273
+
274
+ if (const auto & best_job = _input.jobs [best_job_rank];
275
+ best_job.type == JOB_TYPE::SINGLE) {
276
+ _sol[best_route].add (_input, best_job_rank, best_insertion.single_rank );
277
+ } else {
278
+ assert (best_job.type == JOB_TYPE::PICKUP);
279
+
280
+ std::vector<Index> modified_with_pd;
281
+ modified_with_pd.reserve (best_insertion.delivery_rank -
282
+ best_insertion.pickup_rank + 2 );
283
+ modified_with_pd.push_back (best_job_rank);
284
+
285
+ std::copy (_sol[best_route].route .begin () + best_insertion.pickup_rank ,
286
+ _sol[best_route].route .begin () + best_insertion.delivery_rank ,
287
+ std::back_inserter (modified_with_pd));
288
+ modified_with_pd.push_back (best_job_rank + 1 );
289
+
290
+ _sol[best_route].replace (_input,
291
+ best_insertion.delivery ,
292
+ modified_with_pd.begin (),
293
+ modified_with_pd.end (),
294
+ best_insertion.pickup_rank ,
295
+ best_insertion.delivery_rank );
296
+
297
+ assert (_sol_state.unassigned .find (best_job_rank + 1 ) !=
298
+ _sol_state.unassigned .end ());
299
+ _sol_state.unassigned .erase (best_job_rank + 1 );
300
+ }
301
+
302
+ // Update best_route data required for consistency.
303
+ modified_vehicles.insert (best_route);
304
+ _sol_state.update_route_eval (_sol[best_route].route , best_route);
305
+ _sol_state.set_insertion_ranks (_sol[best_route], best_route);
306
+
307
+ const auto fixed_cost =
308
+ _sol[best_route].empty () ? _input.vehicles [best_route].fixed_cost () : 0 ;
309
+
310
+ for (const auto j : _sol_state.unassigned ) {
311
+ if (const auto & current_job = _input.jobs [j];
312
+ current_job.type == JOB_TYPE::DELIVERY) {
313
+ continue ;
314
+ }
315
+ route_job_insertions[best_route_idx][j] =
316
+ compute_best_insertion (_input,
317
+ _sol_state,
318
+ j,
319
+ best_route,
320
+ _sol[best_route]);
321
+
322
+ if (route_job_insertions[best_route_idx][j].eval != NO_EVAL) {
323
+ route_job_insertions[best_route_idx][j].eval .cost += fixed_cost;
324
+ }
325
+ }
245
326
246
327
#ifdef LOG_LS
247
- if (log_addition_step) {
248
- steps.emplace_back (utils::now (),
249
- log::EVENT::JOB_ADDITION,
250
- OperatorName::MAX,
251
- utils::SolutionIndicators (_input, _sol),
252
- std::nullopt);
253
- }
328
+ if (log_addition_step) {
329
+ steps.emplace_back (utils::now (),
330
+ log::EVENT::JOB_ADDITION,
331
+ OperatorName::MAX,
332
+ utils::SolutionIndicators (_input, _sol),
333
+ std::nullopt);
334
+ }
254
335
#endif
255
- }
336
+ }
337
+ } while (job_added);
256
338
257
339
for (const auto v : modified_vehicles) {
258
340
_sol_state.update_route_bbox (_sol[v].route , v);
@@ -1805,14 +1887,16 @@ void LocalSearch<Route,
1805
1887
1806
1888
for (auto v_rank : update_candidates) {
1807
1889
// Only this update (and update_route_eval done above) are
1808
- // actually required for consistency inside recreate step .
1890
+ // actually required for consistency inside try_job_additions .
1809
1891
_sol_state.set_insertion_ranks (_sol[v_rank], v_rank);
1810
1892
}
1811
1893
1812
- recreate (best_ops[best_source][best_target]->addition_candidates ()
1894
+ try_job_additions (best_ops[best_source][best_target]
1895
+ ->addition_candidates (),
1896
+ 0
1813
1897
#ifdef LOG_LS
1814
- ,
1815
- true
1898
+ ,
1899
+ true
1816
1900
#endif
1817
1901
);
1818
1902
@@ -1859,8 +1943,8 @@ void LocalSearch<Route,
1859
1943
for (auto req_u : best_ops[v][v]->required_unassigned ()) {
1860
1944
if (!_sol_state.unassigned .contains (req_u)) {
1861
1945
// This move should be invalidated because a required
1862
- // unassigned job has been added by recreate step in the
1863
- // meantime.
1946
+ // unassigned job has been added by try_job_additions in
1947
+ // the meantime.
1864
1948
invalidate_move = true ;
1865
1949
break ;
1866
1950
}
@@ -2003,11 +2087,12 @@ void LocalSearch<Route,
2003
2087
_sol_state.set_insertion_ranks (_sol[v], v);
2004
2088
}
2005
2089
2006
- // Recreate solution
2007
- recreate (_all_routes);
2090
+ // Refill jobs.
2091
+ constexpr double refill_regret = 1.5 ;
2092
+ try_job_additions (_all_routes, refill_regret);
2008
2093
2009
2094
// Update everything except what has already been updated in
2010
- // recreate step .
2095
+ // try_job_additions .
2011
2096
for (std::size_t v = 0 ; v < _sol.size (); ++v) {
2012
2097
_sol_state.update_costs (_sol[v].route , v);
2013
2098
_sol_state.update_skills (_sol[v].route , v);
0 commit comments