Google OR-Tools v9.12
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-2025 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 <optional>
21#include <utility>
22#include <vector>
23
24#include "absl/log/check.h"
31#include "ortools/util/bitset.h"
32
33namespace operations_research {
34
35// FilteredHeuristicLocalSearchOperator
36
38 std::unique_ptr<RoutingFilteredHeuristic> heuristic,
39 bool keep_inverse_values)
40 : IntVarLocalSearchOperator(heuristic->model()->Nexts(),
41 keep_inverse_values),
42 model_(heuristic->model()),
44 heuristic_(std::move(heuristic)),
45 consider_vehicle_vars_(!model_->CostsAreHomogeneousAcrossVehicles()) {
46 if (consider_vehicle_vars_) {
47 AddVars(model_->VehicleVars());
48 }
49}
50
51bool FilteredHeuristicLocalSearchOperator::MakeOneNeighbor() {
52 while (IncrementPosition()) {
53 if (model_->CheckLimit()) {
54 // NOTE: Even though the limit is checked in the BuildSolutionFromRoutes()
55 // method of the heuristics, we still check it here to avoid calling
56 // IncrementPosition() and building a solution for every possible position
57 // if the time limit is reached.
58 return false;
59 }
60 // NOTE: No need to call RevertChanges() here as MakeChangeAndInsertNodes()
61 // will always return true if any change was made.
62 if (MakeChangesAndInsertNodes()) {
63 return true;
64 }
65 }
66 return false;
67}
68
69bool FilteredHeuristicLocalSearchOperator::MakeChangesAndInsertNodes() {
70 removed_nodes_.SparseClearAll();
71
72 const std::function<int64_t(int64_t)> next_accessor =
74 if (next_accessor == nullptr) {
75 return false;
76 }
77 model_->solver()->set_context(DebugString());
78 const Assignment* const result_assignment =
79 heuristic_->BuildSolutionFromRoutes(next_accessor);
80 model_->solver()->set_context("");
81
82 if (result_assignment == nullptr) {
83 return false;
84 }
85
86 bool has_change = false;
87 const std::vector<IntVarElement>& elements =
88 result_assignment->IntVarContainer().elements();
89 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
90 int64_t node_index = model_->Start(vehicle);
91 while (!model_->IsEnd(node_index)) {
92 // NOTE: When building the solution in the heuristic, Next vars are added
93 // to the assignment at the position corresponding to their index.
94 const IntVarElement& node_element = elements[node_index];
95 DCHECK_EQ(node_element.Var(), model_->NextVar(node_index));
96
97 const int64_t new_node_value = node_element.Value();
98 DCHECK_NE(new_node_value, node_index);
99
100 const int64_t vehicle_var_index = VehicleVarIndex(node_index);
101 if (OldValue(node_index) != new_node_value ||
102 (consider_vehicle_vars_ && OldValue(vehicle_var_index) != vehicle)) {
103 has_change = true;
104 SetValue(node_index, new_node_value);
105 if (consider_vehicle_vars_) {
106 SetValue(vehicle_var_index, vehicle);
107 }
108 }
109 node_index = new_node_value;
110 }
111 }
112 // Check for newly unperformed nodes among the ones removed for insertion by
113 // the heuristic.
114 for (int64_t node : removed_nodes_.PositionsSetAtLeastOnce()) {
115 const IntVarElement& node_element = elements[node];
116 DCHECK_EQ(node_element.Var(), model_->NextVar(node));
117 if (node_element.Value() == node) {
118 DCHECK_NE(OldValue(node), node);
119 has_change = true;
120 SetValue(node, node);
121 if (consider_vehicle_vars_) {
122 const int64_t vehicle_var_index = VehicleVarIndex(node);
123 DCHECK_NE(OldValue(vehicle_var_index), -1);
124 SetValue(vehicle_var_index, -1);
125 }
126 }
127 }
128 return has_change;
129}
130
131// FilteredHeuristicPathLNSOperator
132
134 std::unique_ptr<RoutingFilteredHeuristic> heuristic)
135 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
136 num_routes_(model_->vehicles()),
137 current_route_(0),
138 last_route_(0),
139 just_started_(false) {}
140
141void FilteredHeuristicPathLNSOperator::OnStart() {
142 // NOTE: We set last_route_ to current_route_ here to make sure all routes
143 // are scanned in GetFirstNonEmptyRouteAfterCurrentRoute().
144 last_route_ = current_route_;
145 if (RouteIsEmpty(current_route_)) {
146 current_route_ = GetFirstNonEmptyRouteAfterCurrentRoute();
147 }
148 just_started_ = true;
149}
150
151bool FilteredHeuristicPathLNSOperator::IncrementPosition() {
152 if (just_started_) {
153 just_started_ = false;
154 // If current_route_ is empty or is the only non-empty route, then we don't
155 // create a new neighbor with this operator as it would result in running
156 // a first solution heuristic with all the nodes.
157 return !RouteIsEmpty(current_route_) &&
158 GetFirstNonEmptyRouteAfterCurrentRoute() != last_route_;
159 }
160 current_route_ = GetFirstNonEmptyRouteAfterCurrentRoute();
161 return current_route_ != last_route_;
162}
163
164bool FilteredHeuristicPathLNSOperator::RouteIsEmpty(int route) const {
165 return model_->IsEnd(OldValue(model_->Start(route)));
166}
167
168int FilteredHeuristicPathLNSOperator::GetFirstNonEmptyRouteAfterCurrentRoute()
169 const {
170 int route = GetNextRoute(current_route_);
171 while (route != last_route_ && RouteIsEmpty(route)) {
172 route = GetNextRoute(route);
173 }
174 return route;
175}
176
177std::function<int64_t(int64_t)>
178FilteredHeuristicPathLNSOperator::SetupNextAccessorForNeighbor() {
179 const int64_t start_node = model_->Start(current_route_);
180 const int64_t end_node = model_->End(current_route_);
181
182 int64_t node = Value(start_node);
183 while (node != end_node) {
184 removed_nodes_.Set(node);
185 node = Value(node);
186 }
187
188 return [this, start_node, end_node](int64_t node) {
189 if (node == start_node) return end_node;
190 return Value(node);
191 };
192}
193
194// RelocatePathAndHeuristicInsertUnperformedOperator
195
198 std::unique_ptr<RoutingFilteredHeuristic> heuristic)
199 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
200 route_to_relocate_index_(0),
201 empty_route_index_(0),
202 just_started_(false) {}
203
204void RelocatePathAndHeuristicInsertUnperformedOperator::OnStart() {
205 has_unperformed_nodes_ = false;
206 last_node_on_route_.resize(model_->vehicles());
207 routes_to_relocate_.clear();
208 empty_routes_.clear();
209 std::vector<bool> empty_vehicle_of_vehicle_class_added(
210 model_->GetVehicleClassesCount(), false);
211 for (int64_t node = 0; node < model_->Size(); node++) {
212 const int64_t next = OldValue(node);
213 if (next == node) {
214 has_unperformed_nodes_ = true;
215 continue;
216 }
217 if (model_->IsEnd(next)) {
218 last_node_on_route_[model_->VehicleIndex(next)] = node;
219 }
220 }
221
222 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
223 const int64_t next = OldValue(model_->Start(vehicle));
224 if (!model_->IsEnd(next)) {
225 routes_to_relocate_.push_back(vehicle);
226 continue;
227 }
228 const int vehicle_class =
229 model_->GetVehicleClassIndexOfVehicle(vehicle).value();
230 if (!empty_vehicle_of_vehicle_class_added[vehicle_class]) {
231 empty_routes_.push_back(vehicle);
232 empty_vehicle_of_vehicle_class_added[vehicle_class] = true;
233 }
234 }
235
236 if (empty_route_index_ >= empty_routes_.size()) {
237 empty_route_index_ = 0;
238 }
239 if (route_to_relocate_index_ >= routes_to_relocate_.size()) {
240 route_to_relocate_index_ = 0;
241 }
242 last_empty_route_index_ = empty_route_index_;
243 last_route_to_relocate_index_ = route_to_relocate_index_;
244
245 just_started_ = true;
246}
247
248bool RelocatePathAndHeuristicInsertUnperformedOperator::IncrementPosition() {
249 if (!has_unperformed_nodes_ || empty_routes_.empty() ||
250 routes_to_relocate_.empty()) {
251 return false;
252 }
253 if (just_started_) {
254 just_started_ = false;
255 return true;
256 }
257 return IncrementRoutes();
258}
259
260bool RelocatePathAndHeuristicInsertUnperformedOperator::IncrementRoutes() {
261 ++empty_route_index_ %= empty_routes_.size();
262 if (empty_route_index_ != last_empty_route_index_) {
263 return true;
264 }
265 ++route_to_relocate_index_ %= routes_to_relocate_.size();
266 return route_to_relocate_index_ != last_route_to_relocate_index_;
267}
268
269std::function<int64_t(int64_t)>
270RelocatePathAndHeuristicInsertUnperformedOperator::
271 SetupNextAccessorForNeighbor() {
272 const int empty_route = empty_routes_[empty_route_index_];
273 const int relocated_route = routes_to_relocate_[route_to_relocate_index_];
274 if (model_->GetVehicleClassIndexOfVehicle(empty_route) ==
275 model_->GetVehicleClassIndexOfVehicle(relocated_route)) {
276 // Don't try to relocate the route to an empty vehicle of the same class.
277 return nullptr;
278 }
279
280 const int64_t empty_start_node = model_->Start(empty_route);
281 const int64_t empty_end_node = model_->End(empty_route);
282
283 const int64_t relocated_route_start = model_->Start(relocated_route);
284 const int64_t first_relocated_node = OldValue(relocated_route_start);
285 const int64_t last_relocated_node = last_node_on_route_[relocated_route];
286 const int64_t relocated_route_end = model_->End(relocated_route);
287
288 return [this, empty_start_node, empty_end_node, first_relocated_node,
289 last_relocated_node, relocated_route_start,
290 relocated_route_end](int64_t node) {
291 if (node == relocated_route_start) return relocated_route_end;
292 if (node == empty_start_node) return first_relocated_node;
293 if (node == last_relocated_node) return empty_end_node;
294 return Value(node);
295 };
296}
297
298// FilteredHeuristicCloseNodesLNSOperator
299
301 std::unique_ptr<RoutingFilteredHeuristic> heuristic, int num_close_nodes)
302 : FilteredHeuristicLocalSearchOperator(std::move(heuristic),
303 /*keep_inverse_values*/ true),
304 pickup_delivery_pairs_(model_->GetPickupAndDeliveryPairs()),
305 current_node_(0),
306 last_node_(0),
307 just_started_(false),
308 initialized_(false),
309 close_nodes_(model_->Size()),
310 num_close_nodes_(num_close_nodes),
311 new_nexts_(model_->Size()),
312 changed_nexts_(model_->Size()),
313 new_prevs_(model_->Size()),
314 changed_prevs_(model_->Size()) {}
315
316void FilteredHeuristicCloseNodesLNSOperator::Initialize() {
317 if (initialized_) return;
318 initialized_ = true;
319 const int64_t size = model_->Size();
320 const int64_t max_num_neighbors =
321 std::max<int64_t>(0, size - 1 - model_->vehicles());
322 const int64_t num_closest_neighbors =
323 std::min<int64_t>(num_close_nodes_, max_num_neighbors);
324 DCHECK_GE(num_closest_neighbors, 0);
325
326 if (num_closest_neighbors == 0) return;
327
328 const int64_t num_cost_classes = model_->GetCostClassesCount();
329
330 for (int64_t node = 0; node < size; node++) {
331 if (model_->IsStart(node) || model_->IsEnd(node)) continue;
332
333 std::vector<std::pair</*cost*/ double, /*node*/ int64_t>>
334 costed_after_nodes;
335 costed_after_nodes.reserve(size);
336 for (int64_t after_node = 0; after_node < size; after_node++) {
337 if (model_->IsStart(after_node) || model_->IsEnd(after_node) ||
338 after_node == node) {
339 continue;
340 }
341 double total_cost = 0.0;
342 // NOTE: We don't consider the 'always-zero' cost class when searching for
343 // closest neighbors.
344 for (int cost_class = 1; cost_class < num_cost_classes; cost_class++) {
345 total_cost += model_->GetArcCostForClass(node, after_node, cost_class);
346 }
347 costed_after_nodes.emplace_back(total_cost, after_node);
348 }
349
350 std::nth_element(costed_after_nodes.begin(),
351 costed_after_nodes.begin() + num_closest_neighbors - 1,
352 costed_after_nodes.end());
353 std::vector<int64_t>& neighbors = close_nodes_[node];
354 neighbors.reserve(num_closest_neighbors);
355 for (int index = 0; index < num_closest_neighbors; index++) {
356 neighbors.push_back(costed_after_nodes[index].second);
357 }
358 }
359}
360
361void FilteredHeuristicCloseNodesLNSOperator::OnStart() {
362 Initialize();
363 last_node_ = current_node_;
364 just_started_ = true;
365}
366
367bool FilteredHeuristicCloseNodesLNSOperator::IncrementPosition() {
368 DCHECK(initialized_);
369 if (just_started_) {
370 just_started_ = false;
371 return true;
372 }
373 ++current_node_ %= model_->Size();
374 return current_node_ != last_node_;
375}
376
377void FilteredHeuristicCloseNodesLNSOperator::RemoveNode(int64_t node) {
378 DCHECK(!model_->IsEnd(node) && !model_->IsStart(node));
379 DCHECK_NE(Value(node), node);
380 DCHECK(IsActive(node));
381
382 removed_nodes_.Set(node);
383 const int64_t prev = Prev(node);
384 const int64_t next = Next(node);
385 changed_nexts_.Set(prev);
386 new_nexts_[prev] = next;
387 if (next < model_->Size()) {
388 changed_prevs_.Set(next);
389 new_prevs_[next] = prev;
390 }
391}
392
393void FilteredHeuristicCloseNodesLNSOperator::RemoveNodeAndActiveSibling(
394 int64_t node) {
395 if (!IsActive(node)) return;
396 RemoveNode(node);
397
398 if (const std::optional<int64_t> sibling_node =
399 model_->GetFirstMatchingPickupDeliverySibling(
400 node,
401 [this](int64_t node) {
402 return IsActive(node) && !model_->IsStart(node) &&
403 !model_->IsEnd(node);
404 });
405 sibling_node.has_value()) {
406 RemoveNode(sibling_node.value());
407 }
408}
409
410std::function<int64_t(int64_t)>
411FilteredHeuristicCloseNodesLNSOperator::SetupNextAccessorForNeighbor() {
412 DCHECK(initialized_);
413 if (model_->IsStart(current_node_)) {
414 return nullptr;
415 }
416 DCHECK(!model_->IsEnd(current_node_));
417
418 changed_nexts_.SparseClearAll();
419 changed_prevs_.SparseClearAll();
420
421 RemoveNodeAndActiveSibling(current_node_);
422
423 for (int64_t neighbor : close_nodes_[current_node_]) {
424 RemoveNodeAndActiveSibling(neighbor);
425 }
426
427 return [this](int64_t node) { return Next(node); };
428}
429
430// FilteredHeuristicExpensiveChainLNSOperator
431
434 std::unique_ptr<RoutingFilteredHeuristic> heuristic,
435 int num_arcs_to_consider,
436 std::function<int64_t(int64_t, int64_t, int64_t)>
437 arc_cost_for_route_start)
438 : FilteredHeuristicLocalSearchOperator(std::move(heuristic)),
439 current_route_(0),
440 last_route_(0),
441 num_arcs_to_consider_(num_arcs_to_consider),
442 current_expensive_arc_indices_({-1, -1}),
443 arc_cost_for_route_start_(std::move(arc_cost_for_route_start)),
444 just_started_(false) {
445 DCHECK_GE(num_arcs_to_consider_, 2);
446}
447
448void FilteredHeuristicExpensiveChainLNSOperator::OnStart() {
449 last_route_ = current_route_;
450 just_started_ = true;
451}
452
453bool FilteredHeuristicExpensiveChainLNSOperator::IncrementPosition() {
454 if (just_started_) {
455 just_started_ = false;
456 return FindMostExpensiveChainsOnRemainingRoutes();
457 }
458
459 if (IncrementCurrentArcIndices()) return true;
460
461 return IncrementRoute() && FindMostExpensiveChainsOnRemainingRoutes();
462}
463
464std::function<int64_t(int64_t)>
465FilteredHeuristicExpensiveChainLNSOperator::SetupNextAccessorForNeighbor() {
466 const int first_arc_index = current_expensive_arc_indices_.first;
467 const int second_arc_index = current_expensive_arc_indices_.second;
468 DCHECK_LE(0, first_arc_index);
469 DCHECK_LT(first_arc_index, second_arc_index);
470 DCHECK_LT(second_arc_index, most_expensive_arc_starts_and_ranks_.size());
471
472 const std::pair<int, int>& first_start_and_rank =
473 most_expensive_arc_starts_and_ranks_[first_arc_index];
474 const std::pair<int, int>& second_start_and_rank =
475 most_expensive_arc_starts_and_ranks_[second_arc_index];
476 int64_t before_chain, after_chain;
477 if (first_start_and_rank.second < second_start_and_rank.second) {
478 before_chain = first_start_and_rank.first;
479 after_chain = OldValue(second_start_and_rank.first);
480 } else {
481 before_chain = second_start_and_rank.first;
482 after_chain = OldValue(first_start_and_rank.first);
483 }
484
485 int node = Value(before_chain);
486 while (node != after_chain) {
487 removed_nodes_.Set(node);
488 node = Value(node);
489 }
490
491 return [this, before_chain, after_chain](int64_t node) {
492 if (node == before_chain) return after_chain;
493 return OldValue(node);
494 };
495}
496
497bool FilteredHeuristicExpensiveChainLNSOperator::IncrementRoute() {
498 ++current_route_ %= model_->vehicles();
499 return current_route_ != last_route_;
500}
501
502bool FilteredHeuristicExpensiveChainLNSOperator::IncrementCurrentArcIndices() {
503 int& second_index = current_expensive_arc_indices_.second;
504 if (++second_index < most_expensive_arc_starts_and_ranks_.size()) {
505 return true;
506 }
507 int& first_index = current_expensive_arc_indices_.first;
508 if (first_index + 2 < most_expensive_arc_starts_and_ranks_.size()) {
509 first_index++;
510 second_index = first_index + 1;
511 return true;
512 }
513 return false;
514}
515
516bool FilteredHeuristicExpensiveChainLNSOperator::
517 FindMostExpensiveChainsOnRemainingRoutes() {
518 do {
520 num_arcs_to_consider_, model_->Start(current_route_),
521 [this](int64_t i) { return OldValue(i); },
522 [this](int64_t node) { return model_->IsEnd(node); },
523 arc_cost_for_route_start_, &most_expensive_arc_starts_and_ranks_,
524 &current_expensive_arc_indices_)) {
525 return true;
526 }
527 } while (IncrementRoute());
528
529 return false;
530}
531
532} // namespace operations_research
virtual std::string DebugString() const
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)
IntVarLocalSearchOperator(const std::vector< IntVar * > &vars, bool keep_inverse_values=false)
RelocatePathAndHeuristicInsertUnperformedOperator(std::unique_ptr< RoutingFilteredHeuristic > heuristic)
RelocatePathAndHeuristicInsertUnperformedOperator.
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()