Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_ils.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 <cmath>
18#include <cstddef>
19#include <cstdint>
20#include <functional>
21#include <memory>
22#include <optional>
23#include <random>
24#include <utility>
25#include <vector>
26
27#include "absl/functional/bind_front.h"
28#include "absl/log/check.h"
29#include "absl/time/time.h"
30#include "absl/types/span.h"
31#include "google/protobuf/repeated_ptr_field.h"
36#include "ortools/constraint_solver/routing_ils.pb.h"
37#include "ortools/constraint_solver/routing_parameters.pb.h"
41
42namespace operations_research {
43namespace {
44
45// Returns global cheapest insertion parameters based on the given search
46// parameters.
47// TODO(user): consider having an ILS specific set of parameters.
49MakeGlobalCheapestInsertionParameters(
50 const RoutingSearchParameters& search_parameters, bool is_sequential) {
52 gci_parameters;
53 gci_parameters.is_sequential = is_sequential;
54 gci_parameters.farthest_seeds_ratio =
55 search_parameters.cheapest_insertion_farthest_seeds_ratio();
56 gci_parameters.neighbors_ratio =
57 search_parameters.cheapest_insertion_first_solution_neighbors_ratio();
58 gci_parameters.min_neighbors =
59 search_parameters.cheapest_insertion_first_solution_min_neighbors();
60 gci_parameters.use_neighbors_ratio_for_initialization =
61 search_parameters
62 .cheapest_insertion_first_solution_use_neighbors_ratio_for_initialization(); // NOLINT
63 gci_parameters.add_unperformed_entries =
64 search_parameters.cheapest_insertion_add_unperformed_entries();
65 return gci_parameters;
66}
67
68// Returns savings parameters based on the given search parameters.
69// TODO(user): consider having an ILS specific set of parameters.
71 const RoutingSearchParameters& search_parameters) {
73 savings_parameters.neighbors_ratio =
74 search_parameters.savings_neighbors_ratio();
75 savings_parameters.max_memory_usage_bytes =
76 search_parameters.savings_max_memory_usage_bytes();
77 savings_parameters.add_reverse_arcs =
78 search_parameters.savings_add_reverse_arcs();
79 savings_parameters.arc_coefficient =
80 search_parameters.savings_arc_coefficient();
81 return savings_parameters;
82}
83
84// Returns a ruin procedure based on the given ruin strategy.
85std::unique_ptr<RuinProcedure> MakeRuinProcedure(
86 RoutingModel* model, std::mt19937* rnd, RuinStrategy ruin,
87 int num_neighbors_for_route_selection) {
88 switch (ruin.strategy_case()) {
89 case RuinStrategy::kSpatiallyCloseRoutes:
90 return std::make_unique<CloseRoutesRemovalRuinProcedure>(
91 model, rnd, ruin.spatially_close_routes().num_ruined_routes(),
92 num_neighbors_for_route_selection);
93 break;
94 case RuinStrategy::kRandomWalk:
95 return std::make_unique<RandomWalkRemovalRuinProcedure>(
96 model, rnd, ruin.random_walk().num_removed_visits(),
97 num_neighbors_for_route_selection);
98 case RuinStrategy::kSisr:
99 return std::make_unique<SISRRuinProcedure>(
100 model, rnd, ruin.sisr().max_removed_sequence_size(),
101 ruin.sisr().avg_num_removed_visits(), ruin.sisr().bypass_factor(),
102 num_neighbors_for_route_selection);
103 default:
104 LOG(DFATAL) << "Unsupported ruin procedure.";
105 return nullptr;
106 }
107}
108
109// Returns the ruin procedures associated with the given ruin strategies.
110std::vector<std::unique_ptr<RuinProcedure>> MakeRuinProcedures(
111 RoutingModel* model, std::mt19937* rnd,
112 const google::protobuf::RepeatedPtrField<
113 ::operations_research::RuinStrategy>& ruin_strategies,
114 int num_neighbors_for_route_selection) {
115 std::vector<std::unique_ptr<RuinProcedure>> ruin_procedures;
116 for (const RuinStrategy& ruin : ruin_strategies) {
117 ruin_procedures.push_back(
118 MakeRuinProcedure(model, rnd, ruin, num_neighbors_for_route_selection));
119 }
120 return ruin_procedures;
121}
122
123class SequentialCompositionStrategy
125 public:
126 explicit SequentialCompositionStrategy(
127 std::vector<RuinProcedure*> ruin_procedures)
128 : CompositeRuinProcedure::CompositionStrategy(
129 std::move(ruin_procedures)) {}
130 const std::vector<RuinProcedure*>& Select() override { return ruins_; }
131};
132
133class SequentialRandomizedCompositionStrategy
135 public:
136 SequentialRandomizedCompositionStrategy(
137 std::vector<RuinProcedure*> ruin_procedures, std::mt19937* rnd)
138 : CompositeRuinProcedure::CompositionStrategy(std::move(ruin_procedures)),
139 rnd_(*rnd) {}
140 const std::vector<RuinProcedure*>& Select() override {
141 std::shuffle(ruins_.begin(), ruins_.end(), rnd_);
142 return ruins_;
143 }
144
145 private:
146 std::mt19937& rnd_;
147};
148
149class SingleRandomCompositionStrategy
151 public:
152 SingleRandomCompositionStrategy(std::vector<RuinProcedure*> ruin_procedures,
153 std::mt19937* rnd)
154 : CompositeRuinProcedure::CompositionStrategy(std::move(ruin_procedures)),
155 rnd_(*rnd) {
156 single_ruin_.resize(1);
157 }
158 const std::vector<RuinProcedure*>& Select() override {
159 single_ruin_[0] = ruins_[rnd_() % ruins_.size()];
160 return single_ruin_;
161 }
162
163 private:
164 std::mt19937& rnd_;
165
166 // Stores the single ruin that will be returned.
167 std::vector<RuinProcedure*> single_ruin_;
168};
169
170// Returns a composition strategy based on the input parameters.
171std::unique_ptr<CompositeRuinProcedure::CompositionStrategy>
172MakeRuinCompositionStrategy(
173 absl::Span<const std::unique_ptr<RuinProcedure>> ruins,
174 RuinCompositionStrategy::Value composition_strategy, std::mt19937* rnd) {
175 std::vector<RuinProcedure*> ruin_ptrs;
176 ruin_ptrs.reserve(ruins.size());
177 for (const auto& ruin : ruins) {
178 ruin_ptrs.push_back(ruin.get());
179 }
180
181 switch (composition_strategy) {
182 case RuinCompositionStrategy::RUN_ALL_SEQUENTIALLY:
183 return std::make_unique<SequentialCompositionStrategy>(
184 std::move(ruin_ptrs));
185 case RuinCompositionStrategy::RUN_ALL_RANDOMLY:
186 return std::make_unique<SequentialRandomizedCompositionStrategy>(
187 std::move(ruin_ptrs), rnd);
188 case RuinCompositionStrategy::RUN_ONE_RANDOMLY:
189 return std::make_unique<SingleRandomCompositionStrategy>(
190 std::move(ruin_ptrs), rnd);
191 default:
192 LOG(DFATAL) << "Unsupported composition strategy.";
193 return nullptr;
194 }
195}
196
197// Returns a ruin procedure based on the given ruin and recreate parameters.
198std::unique_ptr<RuinProcedure> MakeRuinProcedure(
199 const RuinRecreateParameters& parameters, RoutingModel* model,
200 std::mt19937* rnd) {
201 const int num_non_start_end_nodes = model->Size() - model->vehicles();
202 const uint32_t preferred_num_neighbors =
203 parameters.route_selection_neighbors_ratio() * num_non_start_end_nodes;
204
205 // TODO(user): rename parameters.route_selection_max_neighbors to something
206 // more general that can be used by multiple ruin procedures.
207 const uint32_t num_neighbors_for_route_selection =
208 std::min(parameters.route_selection_max_neighbors(),
209 std::max(parameters.route_selection_min_neighbors(),
210 preferred_num_neighbors));
211
212 if (parameters.ruin_strategies().size() == 1) {
213 return MakeRuinProcedure(model, rnd, *parameters.ruin_strategies().begin(),
214 num_neighbors_for_route_selection);
215 }
216
217 return std::make_unique<CompositeRuinProcedure>(
218 model,
219 MakeRuinProcedures(model, rnd, parameters.ruin_strategies(),
220 num_neighbors_for_route_selection),
221 parameters.ruin_composition_strategy(), rnd);
222}
223
224// Returns a recreate procedure based on the given parameters.
225std::unique_ptr<RoutingFilteredHeuristic> MakeRecreateProcedure(
226 const RoutingSearchParameters& parameters, RoutingModel* model,
227 std::function<bool()> stop_search,
228 LocalSearchFilterManager* filter_manager) {
229 switch (parameters.iterated_local_search_parameters()
230 .ruin_recreate_parameters()
231 .recreate_strategy()) {
232 case FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION:
233 return std::make_unique<LocalCheapestInsertionFilteredHeuristic>(
234 model, std::move(stop_search),
235 absl::bind_front(&RoutingModel::GetArcCostForVehicle, model),
236 parameters.local_cheapest_cost_insertion_pickup_delivery_strategy(),
238 parameters.local_cheapest_insertion_sorting_properties()),
239 filter_manager, model->GetBinCapacities());
240 case FirstSolutionStrategy::LOCAL_CHEAPEST_COST_INSERTION:
241 return std::make_unique<LocalCheapestInsertionFilteredHeuristic>(
242 model, std::move(stop_search),
243 /*evaluator=*/nullptr,
244 parameters.local_cheapest_cost_insertion_pickup_delivery_strategy(),
246 parameters.local_cheapest_insertion_sorting_properties()),
247 filter_manager, model->GetBinCapacities());
248 case FirstSolutionStrategy::SEQUENTIAL_CHEAPEST_INSERTION: {
249 GlobalCheapestInsertionFilteredHeuristic::
250 GlobalCheapestInsertionParameters gci_parameters =
251 MakeGlobalCheapestInsertionParameters(parameters,
252 /*is_sequential=*/true);
253 return std::make_unique<GlobalCheapestInsertionFilteredHeuristic>(
254 model, std::move(stop_search),
255 absl::bind_front(&RoutingModel::GetArcCostForVehicle, model),
256 [model](int64_t i) { return model->UnperformedPenaltyOrValue(0, i); },
257 filter_manager, gci_parameters);
258 }
259 case FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION: {
260 GlobalCheapestInsertionFilteredHeuristic::
261 GlobalCheapestInsertionParameters gci_parameters =
262 MakeGlobalCheapestInsertionParameters(parameters,
263 /*is_sequential=*/false);
264 return std::make_unique<GlobalCheapestInsertionFilteredHeuristic>(
265 model, std::move(stop_search),
266 absl::bind_front(&RoutingModel::GetArcCostForVehicle, model),
267 [model](int64_t i) { return model->UnperformedPenaltyOrValue(0, i); },
268 filter_manager, gci_parameters);
269 }
270 case FirstSolutionStrategy::SAVINGS: {
271 return std::make_unique<SequentialSavingsFilteredHeuristic>(
272 model, std::move(stop_search), MakeSavingsParameters(parameters),
273 filter_manager);
274 }
275 case FirstSolutionStrategy::PARALLEL_SAVINGS: {
276 return std::make_unique<ParallelSavingsFilteredHeuristic>(
277 model, std::move(stop_search), MakeSavingsParameters(parameters),
278 filter_manager);
279 }
280 default:
281 LOG(DFATAL) << "Unsupported recreate procedure.";
282 return nullptr;
283 }
284
285 return nullptr;
286}
287
288// Greedy criterion in which the reference assignment is only replaced by an
289// improving candidate assignment.
290class GreedyDescentAcceptanceCriterion : public NeighborAcceptanceCriterion {
291 public:
292 bool Accept([[maybe_unused]] const SearchState& search_state,
293 const Assignment* candidate,
294 const Assignment* reference) override {
295 return candidate->ObjectiveValue() < reference->ObjectiveValue();
296 }
297};
298
299// Simulated annealing cooling schedule interface.
300class CoolingSchedule {
301 public:
302 CoolingSchedule(NeighborAcceptanceCriterion::SearchState final_search_state,
303 double initial_temperature, double final_temperature)
304 : final_search_state_(std::move(final_search_state)),
305 initial_temperature_(initial_temperature),
306 final_temperature_(final_temperature) {
307 DCHECK_GE(initial_temperature_, final_temperature_);
308 }
309 virtual ~CoolingSchedule() = default;
310
311 // Returns the temperature according to given search state.
312 virtual double GetTemperature(
313 const NeighborAcceptanceCriterion::SearchState& search_state) const = 0;
314
315 protected:
316 // Returns the progress of the given search state with respect to the final
317 // search state.
318 double GetProgress(
319 const NeighborAcceptanceCriterion::SearchState& search_state) const {
320 const double duration_progress =
321 absl::FDivDuration(search_state.duration, final_search_state_.duration);
322 const double solutions_progress =
323 static_cast<double>(search_state.solutions) /
324 final_search_state_.solutions;
325 const double progress = std::max(duration_progress, solutions_progress);
326 // We take the min with 1 as at the end of the search we may go a bit above
327 // 1 with duration_progress depending on when we check the time limit.
328 return std::min(1.0, progress);
329 }
330
331 const NeighborAcceptanceCriterion::SearchState final_search_state_;
332 const double initial_temperature_;
333 const double final_temperature_;
334};
335
336// A cooling schedule that lowers the temperature in an exponential way.
337class ExponentialCoolingSchedule : public CoolingSchedule {
338 public:
339 ExponentialCoolingSchedule(
340 NeighborAcceptanceCriterion::SearchState final_search_state,
341 double initial_temperature, double final_temperature)
342 : CoolingSchedule(std::move(final_search_state), initial_temperature,
343 final_temperature),
344 temperature_ratio_(final_temperature / initial_temperature) {}
345
346 double GetTemperature(const NeighborAcceptanceCriterion::SearchState&
347 search_state) const override {
348 const double progress = GetProgress(search_state);
349
350 return initial_temperature_ * std::pow(temperature_ratio_, progress);
351 }
352
353 private:
354 const double temperature_ratio_;
355};
356
357// A cooling schedule that lowers the temperature in a linear way.
358class LinearCoolingSchedule : public CoolingSchedule {
359 public:
360 LinearCoolingSchedule(
361 NeighborAcceptanceCriterion::SearchState final_search_state,
362 double initial_temperature, double final_temperature)
363 : CoolingSchedule(std::move(final_search_state), initial_temperature,
364 final_temperature) {}
365
366 double GetTemperature(const NeighborAcceptanceCriterion::SearchState&
367 search_state) const override {
368 const double progress = GetProgress(search_state);
369 return initial_temperature_ -
370 progress * (initial_temperature_ - final_temperature_);
371 }
372};
373
374// Returns a cooling schedule based on the given input parameters.
375std::unique_ptr<CoolingSchedule> MakeCoolingSchedule(
376 const RoutingModel& model, const RoutingSearchParameters& parameters,
377 std::mt19937* rnd) {
378 const absl::Duration final_duration =
379 !parameters.has_time_limit()
380 ? absl::InfiniteDuration()
381 : util_time::DecodeGoogleApiProto(parameters.time_limit()).value();
382
383 const SimulatedAnnealingParameters& sa_params =
384 parameters.iterated_local_search_parameters()
385 .simulated_annealing_parameters();
386
388 final_duration, parameters.solution_limit()};
389
390 const auto [initial_temperature, final_temperature] =
391 GetSimulatedAnnealingTemperatures(model, sa_params, rnd);
392
393 switch (sa_params.cooling_schedule_strategy()) {
394 case CoolingScheduleStrategy::EXPONENTIAL:
395 return std::make_unique<ExponentialCoolingSchedule>(
397 parameters.solution_limit()},
398 initial_temperature, final_temperature);
399 case CoolingScheduleStrategy::LINEAR:
400 return std::make_unique<LinearCoolingSchedule>(
401 std::move(final_search_state), initial_temperature,
402 final_temperature);
403 default:
404 LOG(DFATAL) << "Unsupported cooling schedule strategy.";
405 return nullptr;
406 }
407}
408
409// Simulated annealing acceptance criterion in which the reference assignment is
410// replaced with a probability given by the quality of the candidate solution,
411// the current search state and the chosen cooling schedule.
412class SimulatedAnnealingAcceptanceCriterion
414 public:
415 explicit SimulatedAnnealingAcceptanceCriterion(
416 std::unique_ptr<CoolingSchedule> cooling_schedule, std::mt19937* rnd)
417 : cooling_schedule_(std::move(cooling_schedule)),
418 rnd_(*rnd),
419 probability_distribution_(0.0, 1.0) {}
420
421 bool Accept(const SearchState& search_state, const Assignment* candidate,
422 const Assignment* reference) override {
423 double temperature = cooling_schedule_->GetTemperature(search_state);
424 return candidate->ObjectiveValue() +
425 temperature * std::log(probability_distribution_(rnd_)) <
426 reference->ObjectiveValue();
427 }
428
429 private:
430 std::unique_ptr<CoolingSchedule> cooling_schedule_;
431 std::mt19937 rnd_;
432 std::uniform_real_distribution<double> probability_distribution_;
433};
434
435// Returns whether the given assignment has at least one performed node.
436bool HasPerformedNodes(const RoutingModel& model,
437 const Assignment& assignment) {
438 for (int v = 0; v < model.vehicles(); ++v) {
439 if (model.Next(assignment, model.Start(v)) != model.End(v)) {
440 return true;
441 }
442 }
443 return false;
444}
445
446// Returns the number of used vehicles.
447int CountUsedVehicles(const RoutingModel& model, const Assignment& assignment) {
448 int count = 0;
449 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
450 count += model.Next(assignment, model.Start(vehicle)) != model.End(vehicle);
451 }
452 return count;
453}
454
455// Returns the average route size of non empty routes.
456double ComputeAverageNonEmptyRouteSize(const RoutingModel& model,
457 const Assignment& assignment) {
458 const int num_used_vehicles = CountUsedVehicles(model, assignment);
459 if (num_used_vehicles == 0) return 0;
460
461 const double num_visits = model.Size() - model.vehicles();
462 return num_visits / num_used_vehicles;
463}
464
465// Returns a random performed visit for the given assignment. The procedure
466// requires a distribution including all visits. Returns -1 if there are no
467// performed visits.
468int64_t PickRandomPerformedVisit(
469 const RoutingModel& model, const Assignment& assignment, std::mt19937& rnd,
470 std::uniform_int_distribution<int64_t>& customer_dist) {
471 DCHECK_EQ(customer_dist.min(), 0);
472 DCHECK_EQ(customer_dist.max(), model.Size() - model.vehicles());
473
474 if (!HasPerformedNodes(model, assignment)) {
475 return -1;
476 }
477
478 int64_t customer;
479 do {
480 customer = customer_dist(rnd);
481 } while (model.IsStart(customer) ||
482 assignment.Value(model.VehicleVar(customer)) == -1);
483 DCHECK(!model.IsEnd(customer));
484 return customer;
485}
486} // namespace
487
488RoutingSolution::RoutingSolution(const RoutingModel& model) : model_(model) {
489 const int all_nodes = model.Size() + model.vehicles();
490
491 nexts_.resize(all_nodes, -1);
492 prevs_.resize(all_nodes, -1);
493 route_sizes_.resize(model.vehicles(), 0);
494}
495
496void RoutingSolution::Reset(const Assignment* assignment) {
497 assignment_ = assignment;
498
499 // TODO(user): consider resetting only previously set values.
500 nexts_.assign(nexts_.size(), -1);
501 // TODO(user): consider removing the resets below, and only rely on
502 // nexts_.
503 prevs_.assign(prevs_.size(), -1);
504 route_sizes_.assign(model_.vehicles(), -1);
505}
506
508 const int64_t start = model_.Start(vehicle);
509
510 if (BelongsToInitializedRoute(start)) {
511 return;
512 }
513
514 const int64_t end = model_.End(vehicle);
515
516 int64_t prev = end;
517 int64_t curr = start;
518
519 // Setup the start and inner nodes.
520 route_sizes_[vehicle] = -1;
521 while (curr != end) {
522 const int64_t next = assignment_->Value(model_.NextVar(curr));
523
524 nexts_[curr] = next;
525 prevs_[curr] = prev;
526 ++route_sizes_[vehicle];
527
528 prev = curr;
529 curr = next;
530 }
531
532 // Setup the end node.
533 nexts_[end] = start;
534 prevs_[end] = prev;
535}
536
537bool RoutingSolution::BelongsToInitializedRoute(int64_t node_index) const {
538 DCHECK_EQ(nexts_[node_index] != -1, prevs_[node_index] != -1);
539 return nexts_[node_index] != -1;
540}
541
542int64_t RoutingSolution::GetNextNodeIndex(int64_t node_index) const {
543 return BelongsToInitializedRoute(node_index)
544 ? nexts_[node_index]
545 : assignment_->Value(model_.NextVar(node_index));
546}
547
548int64_t RoutingSolution::GetInitializedPrevNodeIndex(int64_t node_index) const {
549 DCHECK(BelongsToInitializedRoute(node_index));
550 return prevs_[node_index];
551}
552
553int RoutingSolution::GetRouteSize(int vehicle) const {
554 DCHECK(BelongsToInitializedRoute(model_.Start(vehicle)));
555 return route_sizes_[vehicle];
556}
557
558bool RoutingSolution::CanBeRemoved(int64_t node_index) const {
559 return !model_.IsStart(node_index) && !model_.IsEnd(node_index) &&
560 GetNextNodeIndex(node_index) != node_index;
561}
562
563void RoutingSolution::RemoveNode(int64_t node_index) {
564 DCHECK(BelongsToInitializedRoute(node_index));
565
566 DCHECK_NE(nexts_[node_index], node_index);
567 DCHECK_NE(prevs_[node_index], node_index);
568
569 const int64_t next = nexts_[node_index];
570 const int64_t prev = prevs_[node_index];
571
572 const int vehicle = assignment_->Value(model_.VehicleVar(node_index));
573 --route_sizes_[vehicle];
574 DCHECK_GE(route_sizes_[vehicle], 0);
575
576 nexts_[prev] = next;
577 prevs_[next] = prev;
578
579 nexts_[node_index] = node_index;
580 prevs_[node_index] = node_index;
581}
582
584 DCHECK(!model_.IsStart(customer));
585 DCHECK(!model_.IsEnd(customer));
586 if (const std::optional<int64_t> sibling_node =
587 model_.GetFirstMatchingPickupDeliverySibling(
588 customer, [this](int64_t node) { return CanBeRemoved(node); });
589 sibling_node.has_value()) {
590 const int sibling_vehicle =
591 assignment_->Value(model_.VehicleVar(sibling_node.value()));
592 DCHECK_NE(sibling_vehicle, -1);
593
594 InitializeRouteInfoIfNeeded(sibling_vehicle);
595 RemoveNode(sibling_node.value());
596 }
597}
598
600 int64_t visit, std::mt19937& rnd,
601 std::bernoulli_distribution& boolean_dist) const {
602 DCHECK(BelongsToInitializedRoute(visit));
603 DCHECK(!model_.IsStart(visit));
604 DCHECK(!model_.IsEnd(visit));
605 // The visit is performed.
606 DCHECK(CanBeRemoved(visit));
607
608 const int vehicle = assignment_->Value(model_.VehicleVar(visit));
609 if (GetRouteSize(vehicle) <= 1) {
610 return -1;
611 }
612
613 const bool move_forward = boolean_dist(rnd);
614 int64_t next_node = move_forward ? GetNextNodeIndex(visit)
616 if (model_.IsStart(next_node) || model_.IsEnd(next_node)) {
617 next_node = move_forward ? GetInitializedPrevNodeIndex(visit)
618 : GetNextNodeIndex(visit);
619 }
620 DCHECK(!model_.IsStart(next_node));
621 DCHECK(!model_.IsEnd(next_node));
622 return next_node;
623}
624
626 int64_t seed_visit, std::mt19937& rnd,
627 std::bernoulli_distribution& boolean_dist, int size) const {
628 DCHECK(BelongsToInitializedRoute(seed_visit));
629 DCHECK(!model_.IsStart(seed_visit));
630 DCHECK(!model_.IsEnd(seed_visit));
631 // The seed visit is actually performed.
632 DCHECK(CanBeRemoved(seed_visit));
633
634 // The seed visit is always included.
635 --size;
636
637 // Sequence's excluded boundaries.
638 int64_t left = GetInitializedPrevNodeIndex(seed_visit);
639 int64_t right = GetNextNodeIndex(seed_visit);
640
641 while (size-- > 0) {
642 if (model_.IsStart(left) && model_.IsEnd(right)) {
643 // We can no longer extend the sequence either way.
644 break;
645 }
646
647 // When left is at the start (resp. right is at the end), we can
648 // only extend right (resp. left), and if both ends are free to
649 // move we decide the direction at random.
650 if (model_.IsStart(left)) {
651 right = GetNextNodeIndex(right);
652 } else if (model_.IsEnd(right)) {
653 left = GetInitializedPrevNodeIndex(left);
654 } else {
655 const bool move_forward = boolean_dist(rnd);
656 if (move_forward) {
657 right = GetNextNodeIndex(right);
658 } else {
659 left = GetInitializedPrevNodeIndex(left);
660 }
661 }
662 }
663
664 // TODO(user): consider taking the container in input to avoid multiple
665 // memory allocations.
666 std::vector<int64_t> sequence;
667 int64_t curr = GetNextNodeIndex(left);
668 while (curr != right) {
669 sequence.push_back(curr);
670 curr = GetNextNodeIndex(curr);
671 }
672 return sequence;
673}
674
676 std::vector<RuinProcedure*> ruin_procedures)
677 : ruins_(std::move(ruin_procedures)) {}
678
680 RoutingModel* model,
681 std::vector<std::unique_ptr<RuinProcedure>> ruin_procedures,
682 RuinCompositionStrategy::Value composition_strategy, std::mt19937* rnd)
683 : model_(*model),
684 ruin_procedures_(std::move(ruin_procedures)),
685 composition_strategy_(MakeRuinCompositionStrategy(
686 ruin_procedures_, composition_strategy, rnd)),
687 ruined_assignment_(model_.solver()->MakeAssignment()),
688 next_assignment_(model_.solver()->MakeAssignment()) {}
689
690std::function<int64_t(int64_t)> CompositeRuinProcedure::Ruin(
691 const Assignment* assignment) {
692 const std::vector<RuinProcedure*>& ruins = composition_strategy_->Select();
693
694 auto next_accessors = ruins[0]->Ruin(assignment);
695 for (int i = 1; i < ruins.size(); ++i) {
696 const Assignment* next_assignment =
697 BuildAssignmentFromNextAccessor(next_accessors);
698 ruined_assignment_->Copy(next_assignment);
699 next_accessors = ruins[i]->Ruin(ruined_assignment_);
700 }
701
702 return next_accessors;
703}
704
705const Assignment* CompositeRuinProcedure::BuildAssignmentFromNextAccessor(
706 const std::function<int64_t(int64_t)>& next_accessors) {
707 next_assignment_->Clear();
708
709 // Setup next variables for nodes and vehicle variables for unperformed nodes.
710 for (int node = 0; node < model_.Size(); node++) {
711 const int64_t next = next_accessors(node);
712 next_assignment_->Add(model_.NextVar(node))->SetValue(next);
713 if (next == node) {
714 // Node is unperformed, we set its vehicle var accordingly.
715 next_assignment_->Add(model_.VehicleVar(node))->SetValue(-1);
716 }
717 }
718
719 // Setup vehicle variables for performed nodes.
720 for (int vehicle = 0; vehicle < model_.vehicles(); ++vehicle) {
721 int64_t node = model_.Start(vehicle);
722 while (!model_.IsEnd(node)) {
723 next_assignment_->Add(model_.VehicleVar(node))->SetValue(vehicle);
724 node = next_accessors(node);
725 }
726 // Also set the vehicle var for the vehicle end.
727 next_assignment_->Add(model_.VehicleVar(node))->SetValue(vehicle);
728 }
729
730 return next_assignment_;
731}
732
734 RoutingModel* model, std::mt19937* rnd, size_t num_routes,
735 int num_neighbors_for_route_selection)
736 : model_(*model),
737 neighbors_manager_(model->GetOrCreateNodeNeighborsByCostClass(
738 {num_neighbors_for_route_selection,
739 /*add_vehicle_starts_to_neighbors=*/false,
740 /*add_vehicle_ends_to_neighbors=*/false,
741 /*only_sort_neighbors_for_partial_neighborhoods=*/false})),
742 num_routes_(num_routes),
743 rnd_(*rnd),
744 customer_dist_(0, model->Size() - model->vehicles()),
745 removed_routes_(model->vehicles()) {}
746
747std::function<int64_t(int64_t)> CloseRoutesRemovalRuinProcedure::Ruin(
748 const Assignment* assignment) {
749 if (num_routes_ == 0) {
750 return [this, assignment](int64_t node) {
751 return assignment->Value(model_.NextVar(node));
752 };
753 }
754
755 const int64_t seed_node =
756 PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_);
757 if (seed_node == -1) {
758 return [this, assignment](int64_t node) {
759 return assignment->Value(model_.NextVar(node));
760 };
761 }
762
763 removed_routes_.SparseClearAll();
764
765 const int seed_route = assignment->Value(model_.VehicleVar(seed_node));
766 DCHECK_GE(seed_route, 0);
767
768 removed_routes_.Set(seed_route);
769
770 const RoutingCostClassIndex cost_class_index =
771 model_.GetCostClassIndexOfVehicle(seed_route);
772
773 const std::vector<int>& neighbors =
774 neighbors_manager_->GetOutgoingNeighborsOfNodeForCostClass(
775 cost_class_index.value(), seed_node);
776
777 for (int neighbor : neighbors) {
778 if (removed_routes_.NumberOfSetCallsWithDifferentArguments() ==
779 num_routes_) {
780 break;
781 }
782 const int64_t route = assignment->Value(model_.VehicleVar(neighbor));
783 if (route < 0 || removed_routes_[route]) {
784 continue;
785 }
786 removed_routes_.Set(route);
787 }
788
789 return [this, assignment](int64_t node) {
790 // Shortcut removed routes to remove associated customers.
791 if (model_.IsStart(node)) {
792 const int route = assignment->Value(model_.VehicleVar(node));
793 if (removed_routes_[route]) {
794 return model_.End(route);
795 }
796 }
797 return assignment->Value(model_.NextVar(node));
798 };
799}
800
802 RoutingModel* model, std::mt19937* rnd, int walk_length,
803 int num_neighbors_for_route_selection)
804 : model_(*model),
805 routing_solution_(*model),
806 neighbors_manager_(model->GetOrCreateNodeNeighborsByCostClass(
807 {num_neighbors_for_route_selection,
808 /*add_vehicle_starts_to_neighbors=*/false,
809 /*add_vehicle_ends_to_neighbors=*/false,
810 /*only_sort_neighbors_for_partial_neighborhoods=*/false})),
811 rnd_(*rnd),
812 walk_length_(walk_length),
813 customer_dist_(0, model->Size() - model->vehicles()) {}
814
815std::function<int64_t(int64_t)> RandomWalkRemovalRuinProcedure::Ruin(
816 const Assignment* assignment) {
817 if (walk_length_ == 0) {
818 return [this, assignment](int64_t node) {
819 return assignment->Value(model_.NextVar(node));
820 };
821 }
822
823 int64_t curr_node =
824 PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_);
825 if (curr_node == -1) {
826 return [this, assignment](int64_t node) {
827 return assignment->Value(model_.NextVar(node));
828 };
829 }
830
831 routing_solution_.Reset(assignment);
832
833 int walk_length = walk_length_;
834
835 while (walk_length-- > 0) {
836 // Remove the active siblings node of curr before selecting next, so that
837 // we do not accidentally end up with next being one of these sibling
838 // nodes.
839 routing_solution_.RemovePerformedPickupDeliverySibling(curr_node);
840
841 const int64_t next_node = GetNextNodeToRemove(assignment, curr_node);
842
843 routing_solution_.RemoveNode(curr_node);
844
845 if (next_node == -1) {
846 // We were not able to find a vertex where to move next.
847 // We thus prematurely abort the ruin.
848 break;
849 }
850
851 curr_node = next_node;
852 }
853
854 return
855 [this](int64_t node) { return routing_solution_.GetNextNodeIndex(node); };
856}
857
858int64_t RandomWalkRemovalRuinProcedure::GetNextNodeToRemove(
859 const Assignment* assignment, int node) {
860 const int curr_vehicle = assignment->Value(model_.VehicleVar(node));
861 routing_solution_.InitializeRouteInfoIfNeeded(curr_vehicle);
862
863 if (boolean_dist_(rnd_)) {
864 const int64_t next_node =
865 routing_solution_.GetRandomAdjacentVisit(node, rnd_, boolean_dist_);
866 if (next_node != -1) {
867 return next_node;
868 }
869 }
870
871 // Pick the next node by jumping to a neighboring (non empty) route,
872 // otherwise.
873 const RoutingCostClassIndex cost_class_index =
874 model_.GetCostClassIndexOfVehicle(curr_vehicle);
875
876 const std::vector<int>& neighbors =
877 neighbors_manager_->GetOutgoingNeighborsOfNodeForCostClass(
878 cost_class_index.value(), node);
879
880 int64_t same_route_closest_neighbor = -1;
881
882 for (int neighbor : neighbors) {
883 const int neighbor_vehicle = assignment->Value(model_.VehicleVar(neighbor));
884
885 if (!routing_solution_.CanBeRemoved(neighbor)) {
886 continue;
887 }
888
889 if (neighbor_vehicle == curr_vehicle) {
890 if (same_route_closest_neighbor == -1) {
891 same_route_closest_neighbor = neighbor;
892 }
893 continue;
894 }
895
896 return neighbor;
897 }
898
899 // If we are not able to find a customer in another route, we are ok
900 // with taking a customer from the current one.
901 // Note that it can be -1 if no removable neighbor was found for the input
902 // node.
903 return same_route_closest_neighbor;
904}
905
906SISRRuinProcedure::SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd,
907 int max_removed_sequence_size,
908 int avg_num_removed_visits,
909 double bypass_factor, int num_neighbors)
910 : model_(*model),
911 rnd_(*rnd),
912 max_removed_sequence_size_(max_removed_sequence_size),
913 avg_num_removed_visits_(avg_num_removed_visits),
914 bypass_factor_(bypass_factor),
915 neighbors_manager_(model->GetOrCreateNodeNeighborsByCostClass(
916 {num_neighbors,
917 /*add_vehicle_starts_to_neighbors=*/false,
918 /*add_vehicle_ends_to_neighbors=*/false,
919 /*only_sort_neighbors_for_partial_neighborhoods=*/false})),
920 customer_dist_(0, model->Size() - model->vehicles()),
921 probability_dist_(0.0, 1.0),
922 ruined_routes_(model->vehicles()),
923 routing_solution_(*model) {}
924
925std::function<int64_t(int64_t)> SISRRuinProcedure::Ruin(
926 const Assignment* assignment) {
927 const int64_t seed_node =
928 PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_);
929 if (seed_node == -1) {
930 return [this, assignment](int64_t node) {
931 return assignment->Value(model_.NextVar(node));
932 };
933 }
934
935 routing_solution_.Reset(assignment);
936 ruined_routes_.SparseClearAll();
937
938 const double max_sequence_size =
939 std::min<double>(max_removed_sequence_size_,
940 ComputeAverageNonEmptyRouteSize(model_, *assignment));
941
942 const double max_num_removed_sequences =
943 (4 * avg_num_removed_visits_) / (1 + max_sequence_size) - 1;
944 DCHECK_GE(max_num_removed_sequences, 1);
945
946 const int num_sequences_to_remove =
947 std::floor(std::uniform_real_distribution<double>(
948 1.0, max_num_removed_sequences + 1)(rnd_));
949
950 // We start by disrupting the route where the seed visit is served.
951 const int seed_route = RuinRoute(*assignment, seed_node, max_sequence_size);
952 DCHECK_NE(seed_route, -1);
953
954 const RoutingCostClassIndex cost_class_index =
955 model_.GetCostClassIndexOfVehicle(seed_route);
956
957 for (const int neighbor :
958 neighbors_manager_->GetOutgoingNeighborsOfNodeForCostClass(
959 cost_class_index.value(), seed_node)) {
960 if (ruined_routes_.NumberOfSetCallsWithDifferentArguments() ==
961 num_sequences_to_remove) {
962 break;
963 }
964
965 if (!routing_solution_.CanBeRemoved(neighbor)) {
966 continue;
967 }
968
969 RuinRoute(*assignment, neighbor, max_sequence_size);
970 }
971
972 return
973 [this](int64_t node) { return routing_solution_.GetNextNodeIndex(node); };
974}
975
976int SISRRuinProcedure::RuinRoute(const Assignment& assignment,
977 int64_t seed_visit,
978 double global_max_sequence_size) {
979 const int route = assignment.Value(model_.VehicleVar(seed_visit));
980 DCHECK_GE(route, 0);
981 if (ruined_routes_[route]) return -1;
982
983 routing_solution_.InitializeRouteInfoIfNeeded(route);
984 ruined_routes_.Set(route);
985
986 const double max_sequence_size = std::min<double>(
987 routing_solution_.GetRouteSize(route), global_max_sequence_size);
988
989 int sequence_size = std::floor(
990 std::uniform_real_distribution<double>(1.0, max_sequence_size + 1)(rnd_));
991
992 if (sequence_size == 1 || sequence_size == max_sequence_size ||
993 boolean_dist_(rnd_)) {
994 RuinRouteWithSequenceProcedure(seed_visit, sequence_size);
995 } else {
996 RuinRouteWithSplitSequenceProcedure(route, seed_visit, sequence_size);
997 }
998
999 return route;
1000}
1001
1002void SISRRuinProcedure::RuinRouteWithSequenceProcedure(int64_t seed_visit,
1003 int sequence_size) {
1004 const std::vector<int64_t> sequence =
1005 routing_solution_.GetRandomSequenceOfVisits(seed_visit, rnd_,
1006 boolean_dist_, sequence_size);
1007
1008 // Remove the selected visits.
1009 for (const int64_t visit : sequence) {
1010 routing_solution_.RemoveNode(visit);
1011 }
1012
1013 // Remove any still performed pickup or delivery siblings.
1014 for (const int64_t visit : sequence) {
1015 routing_solution_.RemovePerformedPickupDeliverySibling(visit);
1016 }
1017}
1018
1019void SISRRuinProcedure::RuinRouteWithSplitSequenceProcedure(int64_t route,
1020 int64_t seed_visit,
1021 int sequence_size) {
1022 const int max_num_bypassed_visits =
1023 routing_solution_.GetRouteSize(route) - sequence_size;
1024 int num_bypassed_visits = 1;
1025 while (num_bypassed_visits < max_num_bypassed_visits &&
1026 probability_dist_(rnd_) >= bypass_factor_ * probability_dist_(rnd_)) {
1027 ++num_bypassed_visits;
1028 }
1029
1030 const std::vector<int64_t> sequence =
1031 routing_solution_.GetRandomSequenceOfVisits(
1032 seed_visit, rnd_, boolean_dist_, sequence_size + num_bypassed_visits);
1033
1034 const int start_bypassed_visits = rnd_() % (sequence_size + 1);
1035 const int end_bypassed_visits = start_bypassed_visits + num_bypassed_visits;
1036
1037 // Remove the selected visits.
1038 for (int i = 0; i < start_bypassed_visits; ++i) {
1039 routing_solution_.RemoveNode(sequence[i]);
1040 }
1041 for (int i = end_bypassed_visits; i < sequence.size(); ++i) {
1042 routing_solution_.RemoveNode(sequence[i]);
1043 }
1044
1045 // Remove any still performed pickup or delivery siblings.
1046 for (int i = 0; i < start_bypassed_visits; ++i) {
1047 routing_solution_.RemovePerformedPickupDeliverySibling(sequence[i]);
1048 }
1049 for (int i = end_bypassed_visits; i < sequence.size(); ++i) {
1050 routing_solution_.RemovePerformedPickupDeliverySibling(sequence[i]);
1051 }
1052}
1053
1055 public:
1057 const Assignment* assignment, std::unique_ptr<RuinProcedure> ruin,
1058 std::unique_ptr<RoutingFilteredHeuristic> recreate)
1059 : assignment_(*assignment),
1060 ruin_(std::move(ruin)),
1061 recreate_(std::move(recreate)) {}
1062
1063 Decision* Next(Solver* const solver) override {
1064 Assignment* const new_assignment = Recreate(ruin_->Ruin(&assignment_));
1065 if (new_assignment) {
1066 new_assignment->Restore();
1067 } else {
1068 solver->Fail();
1069 }
1070 return nullptr;
1071 }
1072
1073 private:
1074 Assignment* Recreate(const std::function<int64_t(int64_t)>& next_accessor) {
1075 return recreate_->BuildSolutionFromRoutes(next_accessor);
1076 }
1077
1078 const Assignment& assignment_;
1079 std::unique_ptr<RuinProcedure> ruin_;
1080 std::unique_ptr<RoutingFilteredHeuristic> recreate_;
1081};
1082
1084 const RoutingSearchParameters& parameters, RoutingModel* model,
1085 std::mt19937* rnd, const Assignment* assignment,
1086 std::function<bool()> stop_search,
1087 LocalSearchFilterManager* filter_manager) {
1088 std::unique_ptr<RuinProcedure> ruin = MakeRuinProcedure(
1089 parameters.iterated_local_search_parameters().ruin_recreate_parameters(),
1090 model, rnd);
1091
1092 std::unique_ptr<RoutingFilteredHeuristic> recreate = MakeRecreateProcedure(
1093 parameters, model, std::move(stop_search), filter_manager);
1094
1095 return model->solver()->RevAlloc(new RuinAndRecreateDecisionBuilder(
1096 assignment, std::move(ruin), std::move(recreate)));
1097}
1098
1100 const RoutingSearchParameters& parameters, RoutingModel* model,
1101 std::mt19937* rnd, const Assignment* assignment,
1102 std::function<bool()> stop_search,
1103 LocalSearchFilterManager* filter_manager) {
1104 switch (
1105 parameters.iterated_local_search_parameters().perturbation_strategy()) {
1106 case PerturbationStrategy::RUIN_AND_RECREATE:
1108 parameters, model, rnd, assignment, std::move(stop_search),
1109 filter_manager);
1110 default:
1111 LOG(DFATAL) << "Unsupported perturbation strategy.";
1112 return nullptr;
1113 }
1114}
1115
1116std::unique_ptr<NeighborAcceptanceCriterion> MakeNeighborAcceptanceCriterion(
1117 const RoutingModel& model, const RoutingSearchParameters& parameters,
1118 std::mt19937* rnd) {
1119 CHECK(parameters.has_iterated_local_search_parameters());
1120 switch (parameters.iterated_local_search_parameters().acceptance_strategy()) {
1121 case AcceptanceStrategy::GREEDY_DESCENT:
1122 return std::make_unique<GreedyDescentAcceptanceCriterion>();
1123 case AcceptanceStrategy::SIMULATED_ANNEALING:
1124 return std::make_unique<SimulatedAnnealingAcceptanceCriterion>(
1125 MakeCoolingSchedule(model, parameters, rnd), rnd);
1126 default:
1127 LOG(DFATAL) << "Unsupported acceptance strategy.";
1128 return nullptr;
1129 }
1130}
1131
1132std::pair<double, double> GetSimulatedAnnealingTemperatures(
1133 const RoutingModel& model, const SimulatedAnnealingParameters& sa_params,
1134 std::mt19937* rnd) {
1135 if (!sa_params.automatic_temperatures()) {
1136 return {sa_params.initial_temperature(), sa_params.final_temperature()};
1137 }
1138
1139 // In the unlikely case there are no vehicles (i.e., we will end up with an
1140 // "all unperformed" solution), we simply return 0.0 as initial and final
1141 // temperatures.
1142 if (model.vehicles() == 0) {
1143 return {0.0, 0.0};
1144 }
1145
1146 std::vector<int> num_vehicles_of_class(model.GetCostClassesCount(), 0);
1147 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
1148 const RoutingCostClassIndex cost_class =
1149 model.GetCostClassIndexOfVehicle(vehicle);
1150 ++num_vehicles_of_class[cost_class.value()];
1151 }
1152
1153 std::uniform_int_distribution<int64_t> node_dist(0, model.nodes() - 1);
1154
1155 const int sample_size = model.nodes();
1156 DCHECK_GT(sample_size, 0);
1157
1158 std::vector<double> mean_arc_cost_for_class(model.GetCostClassesCount(), 0.0);
1159 for (int cost_class = 0; cost_class < model.GetCostClassesCount();
1160 ++cost_class) {
1161 if (num_vehicles_of_class[cost_class] == 0) {
1162 continue;
1163 }
1164
1165 for (int i = 0; i < sample_size; ++i) {
1166 mean_arc_cost_for_class[cost_class] += model.GetArcCostForClass(
1167 node_dist(*rnd), node_dist(*rnd), cost_class);
1168 }
1169 mean_arc_cost_for_class[cost_class] /= sample_size;
1170 }
1171
1172 double reference_temperature = 0;
1173 DCHECK_GT(model.vehicles(), 0);
1174 for (int cost_class = 0; cost_class < model.GetCostClassesCount();
1175 ++cost_class) {
1176 reference_temperature += mean_arc_cost_for_class[cost_class] *
1177 num_vehicles_of_class[cost_class] /
1178 model.vehicles();
1179 }
1180
1181 return {reference_temperature * 0.1, reference_temperature * 0.001};
1182}
1183
1184} // namespace operations_research
int64_t Value(const IntVar *var) const
IntVarElement * Add(IntVar *var)
CloseRoutesRemovalRuinProcedure(RoutingModel *model, std::mt19937 *rnd, size_t num_routes, int num_neighbors_for_route_selection)
std::function< int64_t(int64_t)> Ruin(const Assignment *assignment) override
std::vector< RuinProcedure * > ruins_
Contains ptrs to the available ruins.
CompositionStrategy(std::vector< RuinProcedure * > ruin_procedures)
CompositeRuinProcedure(RoutingModel *model, std::vector< std::unique_ptr< RuinProcedure > > ruin_procedures, RuinCompositionStrategy::Value composition_strategy, std::mt19937 *rnd)
std::function< int64_t(int64_t)> Ruin(const Assignment *assignment) override
Returns next accessors describing the ruined solution.
Neighbor acceptance criterion interface.
RandomWalkRemovalRuinProcedure(RoutingModel *model, std::mt19937 *rnd, int walk_length, int num_neighbors_for_route_selection)
std::function< int64_t(int64_t)> Ruin(const Assignment *assignment) override
Returns next accessors describing the ruined solution.
int64_t GetInitializedPrevNodeIndex(int64_t node_index) const
bool CanBeRemoved(int64_t node_index) const
void RemoveNode(int64_t node_index)
void RemovePerformedPickupDeliverySibling(int64_t customer)
Removes the performed sibling pickup or delivery of customer, if any.
void Reset(const Assignment *assignment)
Initializes the routing solution for the given assignment.
int64_t GetRandomAdjacentVisit(int64_t visit, std::mt19937 &rnd, std::bernoulli_distribution &boolean_dist) const
void InitializeRouteInfoIfNeeded(int vehicle)
std::vector< int64_t > GetRandomSequenceOfVisits(int64_t seed_visit, std::mt19937 &rnd, std::bernoulli_distribution &boolean_dist, int size) const
RoutingSolution(const RoutingModel &model)
int64_t GetNextNodeIndex(int64_t node_index) const
Returns the next node index of the given node_index.
int GetRouteSize(int vehicle) const
bool BelongsToInitializedRoute(int64_t node_index) const
Returns whether node_index belongs to a route that has been initialized.
Decision * Next(Solver *const solver) override
RuinAndRecreateDecisionBuilder(const Assignment *assignment, std::unique_ptr< RuinProcedure > ruin, std::unique_ptr< RoutingFilteredHeuristic > recreate)
SISRRuinProcedure(RoutingModel *model, std::mt19937 *rnd, int max_removed_sequence_size, int avg_num_removed_visits, double bypass_factor, int num_neighbors)
std::function< int64_t(int64_t)> Ruin(const Assignment *assignment) override
Returns next accessors describing the ruined solution.
For the time being, Solver is neither MT_SAFE nor MT_HOT.
void Fail()
Abandon the current branch in the search tree. A backtrack will follow.
void Set(IntegerType index)
Definition bitset.h:885
time_limit
Definition solve.cc:22
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< RoutingSearchParameters::InsertionSortingProperty > GetLocalCheapestInsertionSortingProperties(absl::Span< const int > lci_insertion_sorting_properties)
DecisionBuilder * MakeRuinAndRecreateDecisionBuilder(const RoutingSearchParameters &parameters, RoutingModel *model, std::mt19937 *rnd, const Assignment *assignment, std::function< bool()> stop_search, LocalSearchFilterManager *filter_manager)
std::pair< double, double > GetSimulatedAnnealingTemperatures(const RoutingModel &model, const SimulatedAnnealingParameters &sa_params, std::mt19937 *rnd)
DecisionBuilder * MakePerturbationDecisionBuilder(const RoutingSearchParameters &parameters, RoutingModel *model, std::mt19937 *rnd, const Assignment *assignment, std::function< bool()> stop_search, LocalSearchFilterManager *filter_manager)
std::unique_ptr< NeighborAcceptanceCriterion > MakeNeighborAcceptanceCriterion(const RoutingModel &model, const RoutingSearchParameters &parameters, std::mt19937 *rnd)
Returns a neighbor acceptance criterion based on the given parameters.
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition protoutil.h:42
bool is_sequential
Whether the routes are constructed sequentially or in parallel.
Representation of the search process state.