Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_insertion_lns.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <memory>
20#include <utility>
21#include <vector>
22
23#include "absl/log/check.h"
30#include "ortools/util/bitset.h"
31
32namespace operations_research {
33
34// FilteredHeuristicLocalSearchOperator
35
37 std::unique_ptr<RoutingFilteredHeuristic> heuristic,
38 bool keep_inverse_values)
39 : IntVarLocalSearchOperator(heuristic->model()->Nexts(),
40 keep_inverse_values),
41 model_(heuristic->model()),
42 removed_nodes_(model_->Size()),
43 heuristic_(std::move(heuristic)),
44 consider_vehicle_vars_(!model_->CostsAreHomogeneousAcrossVehicles()) {
45 if (consider_vehicle_vars_) {
46 AddVars(model_->VehicleVars());
47 }
48}
49
50bool FilteredHeuristicLocalSearchOperator::MakeOneNeighbor() {
51 while (IncrementPosition()) {
52 if (model_->CheckLimit()) {
53 // NOTE: Even though the limit is checked in the BuildSolutionFromRoutes()
54 // method of the heuristics, we still check it here to avoid calling
55 // IncrementPosition() and building a solution for every possible position
56 // if the time limit is reached.
57 return false;
58 }
59 // NOTE: No need to call RevertChanges() here as MakeChangeAndInsertNodes()
60 // will always return true if any change was made.
61 if (MakeChangesAndInsertNodes()) {
62 return true;
63 }
64 }
65 return false;
66}
67
68bool FilteredHeuristicLocalSearchOperator::MakeChangesAndInsertNodes() {
70
71 const std::function<int64_t(int64_t)> next_accessor =
73 if (next_accessor == nullptr) {
74 return false;
75 }
76 const Assignment* const result_assignment =
77 heuristic_->BuildSolutionFromRoutes(next_accessor);
78
79 if (result_assignment == nullptr) {
80 return false;
81 }
82
83 bool has_change = false;
84 const std::vector<IntVarElement>& elements =
85 result_assignment->IntVarContainer().elements();
86 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
87 int64_t node_index = model_->Start(vehicle);
88 while (!model_->IsEnd(node_index)) {
89 // NOTE: When building the solution in the heuristic, Next vars are added
90 // to the assignment at the position corresponding to their index.
91 const IntVarElement& node_element = elements[node_index];
92 DCHECK_EQ(node_element.Var(), model_->NextVar(node_index));
93
94 const int64_t new_node_value = node_element.Value();
95 DCHECK_NE(new_node_value, node_index);
96
97 const int64_t vehicle_var_index = VehicleVarIndex(node_index);
98 if (OldValue(node_index) != new_node_value ||
99 (consider_vehicle_vars_ && OldValue(vehicle_var_index) != vehicle)) {
100 has_change = true;
101 SetValue(node_index, new_node_value);
102 if (consider_vehicle_vars_) {
103 SetValue(vehicle_var_index, vehicle);
104 }
105 }
106 node_index = new_node_value;
107 }
108 }
109 // Check for newly unperformed nodes among the ones removed for insertion by
110 // the heuristic.
111 for (int64_t node : removed_nodes_.PositionsSetAtLeastOnce()) {
112 const IntVarElement& node_element = elements[node];
113 DCHECK_EQ(node_element.Var(), model_->NextVar(node));
114 if (node_element.Value() == node) {
115 DCHECK_NE(OldValue(node), node);
116 has_change = true;
117 SetValue(node, node);
118 if (consider_vehicle_vars_) {
119 const int64_t vehicle_var_index = VehicleVarIndex(node);
120 DCHECK_NE(OldValue(vehicle_var_index), -1);
121 SetValue(vehicle_var_index, -1);
122 }
123 }
124 }
125 return has_change;
126}
127
128// FilteredHeuristicPathLNSOperator
129
131 std::unique_ptr<RoutingFilteredHeuristic> heuristic)
132 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
133 current_route_(0),
134 last_route_(0),
135 just_started_(false) {}
136
137void FilteredHeuristicPathLNSOperator::OnStart() {
138 // NOTE: We set last_route_ to current_route_ here to make sure all routes
139 // are scanned in IncrementCurrentRouteToNextNonEmpty().
140 last_route_ = current_route_;
141 if (CurrentRouteIsEmpty()) {
142 IncrementCurrentRouteToNextNonEmpty();
143 }
144 just_started_ = true;
145}
146
147bool FilteredHeuristicPathLNSOperator::IncrementPosition() {
148 if (just_started_) {
149 just_started_ = false;
150 return !CurrentRouteIsEmpty();
151 }
152 IncrementCurrentRouteToNextNonEmpty();
153 return current_route_ != last_route_;
154}
155
156bool FilteredHeuristicPathLNSOperator::CurrentRouteIsEmpty() const {
157 return model_->IsEnd(OldValue(model_->Start(current_route_)));
158}
159
160void FilteredHeuristicPathLNSOperator::IncrementCurrentRouteToNextNonEmpty() {
161 const int num_routes = model_->vehicles();
162 do {
163 ++current_route_ %= num_routes;
164 if (current_route_ == last_route_) {
165 // All routes have been scanned.
166 return;
167 }
168 } while (CurrentRouteIsEmpty());
169}
170
171std::function<int64_t(int64_t)>
172FilteredHeuristicPathLNSOperator::SetupNextAccessorForNeighbor() {
173 const int64_t start_node = model_->Start(current_route_);
174 const int64_t end_node = model_->End(current_route_);
175
176 int64_t node = Value(start_node);
177 while (node != end_node) {
178 removed_nodes_.Set(node);
179 node = Value(node);
180 }
181
182 return [this, start_node, end_node](int64_t node) {
183 if (node == start_node) return end_node;
184 return Value(node);
185 };
186}
187
188// RelocatePathAndHeuristicInsertUnperformedOperator
189
192 std::unique_ptr<RoutingFilteredHeuristic> heuristic)
193 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
194 route_to_relocate_index_(0),
195 empty_route_index_(0),
196 just_started_(false) {}
197
198void RelocatePathAndHeuristicInsertUnperformedOperator::OnStart() {
199 has_unperformed_nodes_ = false;
200 last_node_on_route_.resize(model_->vehicles());
201 routes_to_relocate_.clear();
202 empty_routes_.clear();
203 std::vector<bool> empty_vehicle_of_vehicle_class_added(
204 model_->GetVehicleClassesCount(), false);
205 for (int64_t node = 0; node < model_->Size(); node++) {
206 const int64_t next = OldValue(node);
207 if (next == node) {
208 has_unperformed_nodes_ = true;
209 continue;
210 }
211 if (model_->IsEnd(next)) {
212 last_node_on_route_[model_->VehicleIndex(next)] = node;
213 }
214 }
215
216 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
217 const int64_t next = OldValue(model_->Start(vehicle));
218 if (!model_->IsEnd(next)) {
219 routes_to_relocate_.push_back(vehicle);
220 continue;
221 }
222 const int vehicle_class =
223 model_->GetVehicleClassIndexOfVehicle(vehicle).value();
224 if (!empty_vehicle_of_vehicle_class_added[vehicle_class]) {
225 empty_routes_.push_back(vehicle);
226 empty_vehicle_of_vehicle_class_added[vehicle_class] = true;
227 }
228 }
229
230 if (empty_route_index_ >= empty_routes_.size()) {
231 empty_route_index_ = 0;
232 }
233 if (route_to_relocate_index_ >= routes_to_relocate_.size()) {
234 route_to_relocate_index_ = 0;
235 }
236 last_empty_route_index_ = empty_route_index_;
237 last_route_to_relocate_index_ = route_to_relocate_index_;
238
239 just_started_ = true;
240}
241
242bool RelocatePathAndHeuristicInsertUnperformedOperator::IncrementPosition() {
243 if (!has_unperformed_nodes_ || empty_routes_.empty() ||
244 routes_to_relocate_.empty()) {
245 return false;
246 }
247 if (just_started_) {
248 just_started_ = false;
249 return true;
250 }
251 return IncrementRoutes();
252}
253
254bool RelocatePathAndHeuristicInsertUnperformedOperator::IncrementRoutes() {
255 ++empty_route_index_ %= empty_routes_.size();
256 if (empty_route_index_ != last_empty_route_index_) {
257 return true;
258 }
259 ++route_to_relocate_index_ %= routes_to_relocate_.size();
260 return route_to_relocate_index_ != last_route_to_relocate_index_;
261}
262
263std::function<int64_t(int64_t)>
264RelocatePathAndHeuristicInsertUnperformedOperator::
265 SetupNextAccessorForNeighbor() {
266 const int empty_route = empty_routes_[empty_route_index_];
267 const int relocated_route = routes_to_relocate_[route_to_relocate_index_];
268 if (model_->GetVehicleClassIndexOfVehicle(empty_route) ==
269 model_->GetVehicleClassIndexOfVehicle(relocated_route)) {
270 // Don't try to relocate the route to an empty vehicle of the same class.
271 return nullptr;
272 }
273
274 const int64_t empty_start_node = model_->Start(empty_route);
275 const int64_t empty_end_node = model_->End(empty_route);
276
277 const int64_t relocated_route_start = model_->Start(relocated_route);
278 const int64_t first_relocated_node = OldValue(relocated_route_start);
279 const int64_t last_relocated_node = last_node_on_route_[relocated_route];
280 const int64_t relocated_route_end = model_->End(relocated_route);
281
282 return [this, empty_start_node, empty_end_node, first_relocated_node,
283 last_relocated_node, relocated_route_start,
284 relocated_route_end](int64_t node) {
285 if (node == relocated_route_start) return relocated_route_end;
286 if (node == empty_start_node) return first_relocated_node;
287 if (node == last_relocated_node) return empty_end_node;
288 return Value(node);
289 };
290}
291
292// FilteredHeuristicCloseNodesLNSOperator
293
295 std::unique_ptr<RoutingFilteredHeuristic> heuristic, int num_close_nodes)
296 : FilteredHeuristicLocalSearchOperator(std::move(heuristic),
297 /*keep_inverse_values*/ true),
298 pickup_delivery_pairs_(model_->GetPickupAndDeliveryPairs()),
299 current_node_(0),
300 last_node_(0),
301 just_started_(false),
302 initialized_(false),
303 close_nodes_(model_->Size()),
304 num_close_nodes_(num_close_nodes),
305 new_nexts_(model_->Size()),
306 changed_nexts_(model_->Size()),
307 new_prevs_(model_->Size()),
308 changed_prevs_(model_->Size()) {}
309
310void FilteredHeuristicCloseNodesLNSOperator::Initialize() {
311 if (initialized_) return;
312 initialized_ = true;
313 const int64_t size = model_->Size();
314 const int64_t max_num_neighbors =
315 std::max<int64_t>(0, size - 1 - model_->vehicles());
316 const int64_t num_closest_neighbors =
317 std::min<int64_t>(num_close_nodes_, max_num_neighbors);
318 DCHECK_GE(num_closest_neighbors, 0);
319
320 if (num_closest_neighbors == 0) return;
321
322 const int64_t num_cost_classes = model_->GetCostClassesCount();
323
324 for (int64_t node = 0; node < size; node++) {
325 if (model_->IsStart(node) || model_->IsEnd(node)) continue;
326
327 std::vector<std::pair</*cost*/ double, /*node*/ int64_t>>
328 costed_after_nodes;
329 costed_after_nodes.reserve(size);
330 for (int64_t after_node = 0; after_node < size; after_node++) {
331 if (model_->IsStart(after_node) || model_->IsEnd(after_node) ||
332 after_node == node) {
333 continue;
334 }
335 double total_cost = 0.0;
336 // NOTE: We don't consider the 'always-zero' cost class when searching for
337 // closest neighbors.
338 for (int cost_class = 1; cost_class < num_cost_classes; cost_class++) {
339 total_cost += model_->GetArcCostForClass(node, after_node, cost_class);
340 }
341 costed_after_nodes.emplace_back(total_cost, after_node);
342 }
343
344 std::nth_element(costed_after_nodes.begin(),
345 costed_after_nodes.begin() + num_closest_neighbors - 1,
346 costed_after_nodes.end());
347 std::vector<int64_t>& neighbors = close_nodes_[node];
348 neighbors.reserve(num_closest_neighbors);
349 for (int index = 0; index < num_closest_neighbors; index++) {
350 neighbors.push_back(costed_after_nodes[index].second);
351 }
352 }
353}
354
355void FilteredHeuristicCloseNodesLNSOperator::OnStart() {
356 Initialize();
357 last_node_ = current_node_;
358 just_started_ = true;
359}
360
361bool FilteredHeuristicCloseNodesLNSOperator::IncrementPosition() {
362 DCHECK(initialized_);
363 if (just_started_) {
364 just_started_ = false;
365 return true;
366 }
367 ++current_node_ %= model_->Size();
368 return current_node_ != last_node_;
369}
370
371void FilteredHeuristicCloseNodesLNSOperator::RemoveNode(int64_t node) {
372 DCHECK(!model_->IsEnd(node) && !model_->IsStart(node));
373 DCHECK_NE(Value(node), node);
374 DCHECK(IsActive(node));
375
376 removed_nodes_.Set(node);
377 const int64_t prev = Prev(node);
378 const int64_t next = Next(node);
379 changed_nexts_.Set(prev);
380 new_nexts_[prev] = next;
381 if (next < model_->Size()) {
382 changed_prevs_.Set(next);
383 new_prevs_[next] = prev;
384 }
385}
386
387void FilteredHeuristicCloseNodesLNSOperator::RemoveNodeAndActiveSibling(
388 int64_t node) {
389 if (!IsActive(node)) return;
390 RemoveNode(node);
391
392 for (int64_t sibling_node : GetActiveSiblings(node)) {
393 if (!model_->IsStart(sibling_node) && !model_->IsEnd(sibling_node)) {
394 RemoveNode(sibling_node);
395 }
396 }
397}
398
399std::vector<int64_t> FilteredHeuristicCloseNodesLNSOperator::GetActiveSiblings(
400 int64_t node) const {
401 // NOTE: In most use-cases, where each node is a pickup or delivery in a
402 // single index pair, this function is in O(k) where k is the number of
403 // alternative deliveries or pickups for this index pair.
404 std::vector<int64_t> active_siblings;
405 for (const auto& [pair_index, unused] : model_->GetPickupPositions(node)) {
406 for (int64_t sibling_delivery :
407 pickup_delivery_pairs_[pair_index].delivery_alternatives) {
408 if (IsActive(sibling_delivery)) {
409 active_siblings.push_back(sibling_delivery);
410 break;
411 }
412 }
413 }
414 for (const auto& [pair_index, unused] : model_->GetDeliveryPositions(node)) {
415 for (int64_t sibling_pickup :
416 pickup_delivery_pairs_[pair_index].pickup_alternatives) {
417 if (IsActive(sibling_pickup)) {
418 active_siblings.push_back(sibling_pickup);
419 break;
420 }
421 }
422 }
423 return active_siblings;
424}
425
426std::function<int64_t(int64_t)>
427FilteredHeuristicCloseNodesLNSOperator::SetupNextAccessorForNeighbor() {
428 DCHECK(initialized_);
429 if (model_->IsStart(current_node_)) {
430 return nullptr;
431 }
432 DCHECK(!model_->IsEnd(current_node_));
433
434 changed_nexts_.SparseClearAll();
435 changed_prevs_.SparseClearAll();
436
437 RemoveNodeAndActiveSibling(current_node_);
438
439 for (int64_t neighbor : close_nodes_[current_node_]) {
440 RemoveNodeAndActiveSibling(neighbor);
441 }
442
443 return [this](int64_t node) { return Next(node); };
444}
445
446// FilteredHeuristicExpensiveChainLNSOperator
447
450 std::unique_ptr<RoutingFilteredHeuristic> heuristic,
451 int num_arcs_to_consider,
452 std::function<int64_t(int64_t, int64_t, int64_t)>
453 arc_cost_for_route_start)
454 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
455 current_route_(0),
456 last_route_(0),
457 num_arcs_to_consider_(num_arcs_to_consider),
458 current_expensive_arc_indices_({-1, -1}),
459 arc_cost_for_route_start_(std::move(arc_cost_for_route_start)),
460 just_started_(false) {
461 DCHECK_GE(num_arcs_to_consider_, 2);
462}
463
464void FilteredHeuristicExpensiveChainLNSOperator::OnStart() {
465 last_route_ = current_route_;
466 just_started_ = true;
467}
468
469bool FilteredHeuristicExpensiveChainLNSOperator::IncrementPosition() {
470 if (just_started_) {
471 just_started_ = false;
472 return FindMostExpensiveChainsOnRemainingRoutes();
473 }
474
475 if (IncrementCurrentArcIndices()) return true;
476
477 return IncrementRoute() && FindMostExpensiveChainsOnRemainingRoutes();
478}
479
480std::function<int64_t(int64_t)>
481FilteredHeuristicExpensiveChainLNSOperator::SetupNextAccessorForNeighbor() {
482 const int first_arc_index = current_expensive_arc_indices_.first;
483 const int second_arc_index = current_expensive_arc_indices_.second;
484 DCHECK_LE(0, first_arc_index);
485 DCHECK_LT(first_arc_index, second_arc_index);
486 DCHECK_LT(second_arc_index, most_expensive_arc_starts_and_ranks_.size());
487
488 const std::pair<int, int>& first_start_and_rank =
489 most_expensive_arc_starts_and_ranks_[first_arc_index];
490 const std::pair<int, int>& second_start_and_rank =
491 most_expensive_arc_starts_and_ranks_[second_arc_index];
492 int64_t before_chain, after_chain;
493 if (first_start_and_rank.second < second_start_and_rank.second) {
494 before_chain = first_start_and_rank.first;
495 after_chain = OldValue(second_start_and_rank.first);
496 } else {
497 before_chain = second_start_and_rank.first;
498 after_chain = OldValue(first_start_and_rank.first);
499 }
500
501 int node = Value(before_chain);
502 while (node != after_chain) {
503 removed_nodes_.Set(node);
504 node = Value(node);
505 }
506
507 return [this, before_chain, after_chain](int64_t node) {
508 if (node == before_chain) return after_chain;
509 return OldValue(node);
510 };
511}
512
513bool FilteredHeuristicExpensiveChainLNSOperator::IncrementRoute() {
514 ++current_route_ %= model_->vehicles();
515 return current_route_ != last_route_;
516}
517
518bool FilteredHeuristicExpensiveChainLNSOperator::IncrementCurrentArcIndices() {
519 int& second_index = current_expensive_arc_indices_.second;
520 if (++second_index < most_expensive_arc_starts_and_ranks_.size()) {
521 return true;
522 }
523 int& first_index = current_expensive_arc_indices_.first;
524 if (first_index + 2 < most_expensive_arc_starts_and_ranks_.size()) {
525 first_index++;
526 second_index = first_index + 1;
527 return true;
528 }
529 return false;
530}
531
532bool FilteredHeuristicExpensiveChainLNSOperator::
533 FindMostExpensiveChainsOnRemainingRoutes() {
534 do {
536 num_arcs_to_consider_, model_->Start(current_route_),
537 [this](int64_t i) { return OldValue(i); },
538 [this](int64_t node) { return model_->IsEnd(node); },
539 arc_cost_for_route_start_, &most_expensive_arc_starts_and_ranks_,
540 &current_expensive_arc_indices_)) {
541 return true;
542 }
543 } while (IncrementRoute());
544
545 return false;
546}
547
548} // namespace operations_research
IntegerValue size
FilteredHeuristicCloseNodesLNSOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic, int num_close_nodes)
FilteredHeuristicCloseNodesLNSOperator.
FilteredHeuristicExpensiveChainLNSOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic, int num_arcs_to_consider, std::function< int64_t(int64_t, int64_t, int64_t)> arc_cost_for_route_start)
FilteredHeuristicExpensiveChainLNSOperator.
SparseBitset removed_nodes_
Keeps track of removed nodes when making a neighbor.
virtual std::function< int64_t(int64_t)> SetupNextAccessorForNeighbor()=0
FilteredHeuristicLocalSearchOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic, bool keep_inverse_values=false)
FilteredHeuristicLocalSearchOperator.
FilteredHeuristicPathLNSOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic)
FilteredHeuristicPathLNSOperator.
void SetValue(int64_t index, int64_t value)
void AddVars(const std::vector< IntVar * > &vars)
RelocatePathAndHeuristicInsertUnperformedOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic)
RelocatePathAndHeuristicInsertUnperformedOperator.
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
void Set(IntegerType index)
Definition bitset.h:864
Block * next
GRBmodel * model
int index
In SWIG mode, we don't want anything besides these top-level includes.
bool FindMostExpensiveArcsOnRoute(int num_arcs, int64_t start, const std::function< int64_t(int64_t)> &next_accessor, const std::function< bool(int64_t)> &is_end, const std::function< int64_t(int64_t, int64_t, int64_t)> &arc_cost_for_route_start, std::vector< std::pair< int64_t, int > > *most_expensive_arc_starts_and_ranks, std::pair< int, int > *first_expensive_arc_indices)
STL namespace.
bool Next()
int vehicle_class