Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
local_search.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
14#include <algorithm>
15#include <cmath>
16#include <cstddef>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <memory>
21#include <numeric>
22#include <optional>
23#include <random>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/algorithm/container.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/flags/flag.h"
32#include "absl/log/check.h"
33#include "absl/random/distributions.h"
34#include "absl/random/random.h"
35#include "absl/strings/str_cat.h"
36#include "absl/strings/str_format.h"
37#include "absl/strings/string_view.h"
38#include "absl/time/time.h"
39#include "absl/types/span.h"
45#include "ortools/base/timer.h"
49#include "ortools/util/bitset.h"
51
52ABSL_FLAG(int, cp_local_search_sync_frequency, 16,
53 "Frequency of checks for better solutions in the solution pool.");
54
55ABSL_FLAG(int, cp_local_search_tsp_opt_size, 13,
56 "Size of TSPs solved in the TSPOpt operator.");
57
58ABSL_FLAG(int, cp_local_search_tsp_lns_size, 10,
59 "Size of TSPs solved in the TSPLns operator.");
60
61ABSL_FLAG(bool, cp_use_empty_path_symmetry_breaker, true,
62 "If true, equivalent empty paths are removed from the neighborhood "
63 "of PathOperators");
64
65namespace operations_research {
66
67// Utility methods to ensure the communication between local search and the
68// search.
69
70// Returns true if a local optimum has been reached and cannot be improved.
71bool LocalOptimumReached(Search* search);
72
73// Returns true if the search accepts the delta (actually checking this by
74// calling AcceptDelta on the monitors of the search).
75bool AcceptDelta(Search* search, Assignment* delta, Assignment* deltadelta);
76
77// Notifies the search that a neighbor has been accepted by local search.
78void AcceptNeighbor(Search* search);
79void AcceptUncheckedNeighbor(Search* search);
80
81// ----- Base operator class for operators manipulating IntVars -----
82
84 Assignment* deltadelta) {
85 CHECK(delta != nullptr);
86 VLOG(2) << DebugString() << "::MakeNextNeighbor(delta=("
87 << delta->DebugString() << "), deltadelta=("
88 << (deltadelta ? deltadelta->DebugString() : std::string("nullptr"));
89 while (true) {
90 RevertChanges(true);
91
92 if (!MakeOneNeighbor()) {
93 return false;
94 }
95
96 if (ApplyChanges(delta, deltadelta)) {
97 VLOG(2) << "Delta (" << DebugString() << ") = " << delta->DebugString();
98 return true;
99 }
100 }
101 return false;
102}
103// TODO(user): Make this a pure virtual.
105
106// ----- Base Large Neighborhood Search operator -----
107
108BaseLns::BaseLns(const std::vector<IntVar*>& vars)
110
112
114 fragment_.clear();
115 if (NextFragment()) {
116 for (int candidate : fragment_) {
117 Deactivate(candidate);
118 }
119 return true;
120 }
121 return false;
122}
123
124void BaseLns::OnStart() { InitFragments(); }
125
127
129 if (index >= 0 && index < Size()) {
130 fragment_.push_back(index);
131 }
132}
133
134int BaseLns::FragmentSize() const { return fragment_.size(); }
135
136// ----- Simple Large Neighborhood Search operator -----
137
138// Frees number_of_variables (contiguous in vars) variables.
139
140namespace {
141class SimpleLns : public BaseLns {
142 public:
143 SimpleLns(const std::vector<IntVar*>& vars, int number_of_variables)
144 : BaseLns(vars), index_(0), number_of_variables_(number_of_variables) {
145 CHECK_GT(number_of_variables_, 0);
146 }
147 ~SimpleLns() override {}
148 void InitFragments() override { index_ = 0; }
149 bool NextFragment() override;
150 std::string DebugString() const override { return "SimpleLns"; }
151
152 private:
153 int index_;
154 const int number_of_variables_;
155};
156
157bool SimpleLns::NextFragment() {
158 const int size = Size();
159 if (index_ < size) {
160 for (int i = index_; i < index_ + number_of_variables_; ++i) {
161 AppendToFragment(i % size);
162 }
163 ++index_;
164 return true;
165 }
166 return false;
167}
168
169// ----- Random Large Neighborhood Search operator -----
170
171// Frees up to number_of_variables random variables.
172
173class RandomLns : public BaseLns {
174 public:
175 RandomLns(const std::vector<IntVar*>& vars, int number_of_variables,
176 int32_t seed)
177 : BaseLns(vars), rand_(seed), number_of_variables_(number_of_variables) {
178 CHECK_GT(number_of_variables_, 0);
179 CHECK_LE(number_of_variables_, Size());
180 }
181 ~RandomLns() override {}
182 bool NextFragment() override;
183
184 std::string DebugString() const override { return "RandomLns"; }
185
186 private:
187 std::mt19937 rand_;
188 const int number_of_variables_;
189};
190
191bool RandomLns::NextFragment() {
192 DCHECK_GT(Size(), 0);
193 for (int i = 0; i < number_of_variables_; ++i) {
194 AppendToFragment(absl::Uniform<int>(rand_, 0, Size()));
195 }
196 return true;
197}
198} // namespace
199
201 const std::vector<IntVar*>& vars, int number_of_variables) {
202 return MakeRandomLnsOperator(vars, number_of_variables, CpRandomSeed());
203}
204
206 const std::vector<IntVar*>& vars, int number_of_variables, int32_t seed) {
207 return RevAlloc(new RandomLns(vars, number_of_variables, seed));
208}
209
210// ----- Move Toward Target Local Search operator -----
211
212// A local search operator that compares the current assignment with a target
213// one, and that generates neighbors corresponding to a single variable being
214// changed from its current value to its target value.
215namespace {
216class MoveTowardTargetLS : public IntVarLocalSearchOperator {
217 public:
218 MoveTowardTargetLS(const std::vector<IntVar*>& variables,
219 const std::vector<int64_t>& target_values)
220 : IntVarLocalSearchOperator(variables),
221 target_(target_values),
222 // Initialize variable_index_ at the number of the of variables minus
223 // one, so that the first to be tried (after one increment) is the one
224 // of index 0.
225 variable_index_(Size() - 1) {
226 CHECK_EQ(target_values.size(), variables.size()) << "Illegal arguments.";
227 }
228
229 ~MoveTowardTargetLS() override {}
230
231 std::string DebugString() const override { return "MoveTowardTargetLS"; }
232
233 protected:
234 // Make a neighbor assigning one variable to its target value.
235 bool MakeOneNeighbor() override {
236 while (num_var_since_last_start_ < Size()) {
237 ++num_var_since_last_start_;
238 variable_index_ = (variable_index_ + 1) % Size();
239 const int64_t target_value = target_.at(variable_index_);
240 const int64_t current_value = OldValue(variable_index_);
241 if (current_value != target_value) {
242 SetValue(variable_index_, target_value);
243 return true;
244 }
245 }
246 return false;
247 }
248
249 private:
250 void OnStart() override {
251 // Do not change the value of variable_index_: this way, we keep going from
252 // where we last modified something. This is because we expect that most
253 // often, the variables we have just checked are less likely to be able
254 // to be changed to their target values than the ones we have not yet
255 // checked.
256 //
257 // Consider the case where oddly indexed variables can be assigned to their
258 // target values (no matter in what order they are considered), while even
259 // indexed ones cannot. Restarting at index 0 each time an odd-indexed
260 // variable is modified will cause a total of Theta(n^2) neighbors to be
261 // generated, while not restarting will produce only Theta(n) neighbors.
262 CHECK_GE(variable_index_, 0);
263 CHECK_LT(variable_index_, Size());
264 num_var_since_last_start_ = 0;
265 }
266
267 // Target values
268 const std::vector<int64_t> target_;
269
270 // Index of the next variable to try to restore
271 int64_t variable_index_;
272
273 // Number of variables checked since the last call to OnStart().
274 int64_t num_var_since_last_start_;
275};
276} // namespace
277
279 const Assignment& target) {
280 typedef std::vector<IntVarElement> Elements;
281 const Elements& elements = target.IntVarContainer().elements();
282 // Copy target values and construct the vector of variables
283 std::vector<IntVar*> vars;
284 std::vector<int64_t> values;
285 vars.reserve(target.NumIntVars());
286 values.reserve(target.NumIntVars());
287 for (const auto& it : elements) {
288 vars.push_back(it.Var());
289 values.push_back(it.Value());
290 }
291 return MakeMoveTowardTargetOperator(vars, values);
292}
293
295 const std::vector<IntVar*>& variables,
296 const std::vector<int64_t>& target_values) {
297 return RevAlloc(new MoveTowardTargetLS(variables, target_values));
298}
299
300// ----- ChangeValue Operators -----
301
302ChangeValue::ChangeValue(const std::vector<IntVar*>& vars)
303 : IntVarLocalSearchOperator(vars), index_(0) {}
304
306
308 const int size = Size();
309 while (index_ < size) {
310 const int64_t value = ModifyValue(index_, Value(index_));
311 SetValue(index_, value);
312 ++index_;
313 return true;
314 }
315 return false;
316}
317
318void ChangeValue::OnStart() { index_ = 0; }
319
320// Increments the current value of variables.
321
322namespace {
323class IncrementValue : public ChangeValue {
324 public:
325 explicit IncrementValue(const std::vector<IntVar*>& vars)
326 : ChangeValue(vars) {}
327 ~IncrementValue() override {}
328 int64_t ModifyValue(int64_t, int64_t value) override { return value + 1; }
329
330 std::string DebugString() const override { return "IncrementValue"; }
331};
332
333// Decrements the current value of variables.
334
335class DecrementValue : public ChangeValue {
336 public:
337 explicit DecrementValue(const std::vector<IntVar*>& vars)
338 : ChangeValue(vars) {}
339 ~DecrementValue() override {}
340 int64_t ModifyValue(int64_t, int64_t value) override { return value - 1; }
341
342 std::string DebugString() const override { return "DecrementValue"; }
343};
344} // namespace
345
346// ----- Path-based Operators -----
347
348PathOperator::PathOperator(const std::vector<IntVar*>& next_vars,
349 const std::vector<IntVar*>& path_vars,
350 IterationParameters iteration_parameters)
351 : IntVarLocalSearchOperator(next_vars, true),
352 number_of_nexts_(next_vars.size()),
353 ignore_path_vars_(path_vars.empty()),
354 base_nodes_(iteration_parameters.number_of_base_nodes),
355 base_alternatives_(iteration_parameters.number_of_base_nodes),
356 base_sibling_alternatives_(iteration_parameters.number_of_base_nodes),
357 end_nodes_(iteration_parameters.number_of_base_nodes),
358 base_paths_(iteration_parameters.number_of_base_nodes),
359 node_path_starts_(number_of_nexts_, -1),
360 node_path_ends_(number_of_nexts_, -1),
361 calls_per_base_node_(iteration_parameters.number_of_base_nodes, 0),
362 just_started_(false),
363 first_start_(true),
364 next_base_to_increment_(iteration_parameters.number_of_base_nodes),
365 iteration_parameters_(std::move(iteration_parameters)),
366 optimal_paths_enabled_(false),
367 active_paths_(number_of_nexts_),
368 alternative_index_(next_vars.size(), -1) {
369 DCHECK_GT(iteration_parameters_.number_of_base_nodes, 0);
370 if (!ignore_path_vars_) {
371 AddVars(path_vars);
372 }
373 path_basis_.push_back(0);
374 for (int i = 1; i < base_nodes_.size(); ++i) {
375 if (!OnSamePathAsPreviousBase(i)) path_basis_.push_back(i);
376 }
377 if ((path_basis_.size() > 2) ||
378 (!next_vars.empty() && !next_vars.back()
379 ->solver()
380 ->parameters()
381 .skip_locally_optimal_paths())) {
382 iteration_parameters_.skip_locally_optimal_paths = false;
383 }
384}
385
386void PathOperator::Reset() { active_paths_.Clear(); }
387
388void PathOperator::OnStart() {
389 optimal_paths_enabled_ = false;
390 InitializeBaseNodes();
391 InitializeAlternatives();
393}
394
396 while (IncrementPosition()) {
397 // Need to revert changes here since MakeNeighbor might have returned false
398 // and have done changes in the previous iteration.
399 RevertChanges(true);
400 if (MakeNeighbor()) {
401 return true;
402 }
403 }
404 return false;
405}
406
408 if (ignore_path_vars_) {
409 return true;
410 }
411 if (index < number_of_nexts_) {
412 int path_index = index + number_of_nexts_;
413 return Value(path_index) == OldValue(path_index);
414 }
415 int next_index = index - number_of_nexts_;
416 return Value(next_index) == OldValue(next_index);
417}
418
419bool PathOperator::MoveChain(int64_t before_chain, int64_t chain_end,
420 int64_t destination) {
421 if (destination == before_chain || destination == chain_end) return false;
422 DCHECK(CheckChainValidity(before_chain, chain_end, destination) &&
423 !IsPathEnd(chain_end) && !IsPathEnd(destination));
424 const int64_t destination_path = Path(destination);
425 const int64_t after_chain = Next(chain_end);
426 SetNext(chain_end, Next(destination), destination_path);
427 if (!ignore_path_vars_) {
428 int current = destination;
429 int next = Next(before_chain);
430 while (current != chain_end) {
431 SetNext(current, next, destination_path);
432 current = next;
433 next = Next(next);
434 }
435 } else {
436 SetNext(destination, Next(before_chain), destination_path);
437 }
438 SetNext(before_chain, after_chain, Path(before_chain));
439 return true;
440}
441
442bool PathOperator::ReverseChain(int64_t before_chain, int64_t after_chain,
443 int64_t* chain_last) {
444 if (CheckChainValidity(before_chain, after_chain, -1)) {
445 int64_t path = Path(before_chain);
446 int64_t current = Next(before_chain);
447 if (current == after_chain) {
448 return false;
449 }
450 int64_t current_next = Next(current);
451 SetNext(current, after_chain, path);
452 while (current_next != after_chain) {
453 const int64_t next = Next(current_next);
454 SetNext(current_next, current, path);
455 current = current_next;
456 current_next = next;
457 }
458 SetNext(before_chain, current, path);
459 *chain_last = current;
460 return true;
461 }
462 return false;
463}
464
465bool PathOperator::MakeActive(int64_t node, int64_t destination) {
466 if (!IsPathEnd(destination)) {
467 int64_t destination_path = Path(destination);
468 SetNext(node, Next(destination), destination_path);
469 SetNext(destination, node, destination_path);
470 return true;
471 }
472 return false;
473}
474
475bool PathOperator::MakeChainInactive(int64_t before_chain, int64_t chain_end) {
476 const int64_t kNoPath = -1;
477 if (CheckChainValidity(before_chain, chain_end, -1) &&
478 !IsPathEnd(chain_end)) {
479 const int64_t after_chain = Next(chain_end);
480 int64_t current = Next(before_chain);
481 while (current != after_chain) {
482 const int64_t next = Next(current);
483 SetNext(current, current, kNoPath);
484 current = next;
485 }
486 SetNext(before_chain, after_chain, Path(before_chain));
487 return true;
488 }
489 return false;
490}
491
492bool PathOperator::SwapActiveAndInactive(int64_t active, int64_t inactive) {
493 if (active == inactive) return false;
494 const int64_t prev = Prev(active);
495 return MakeChainInactive(prev, active) && MakeActive(inactive, prev);
496}
497
498bool PathOperator::IncrementPosition() {
499 const int base_node_size = iteration_parameters_.number_of_base_nodes;
500
501 if (just_started_) {
502 just_started_ = false;
503 return true;
504 }
505 const int number_of_paths = path_starts_.size();
506 // Finding next base node positions.
507 // Increment the position of inner base nodes first (higher index nodes);
508 // if a base node is at the end of a path, reposition it at the start
509 // of the path and increment the position of the preceding base node (this
510 // action is called a restart).
511 int last_restarted = base_node_size;
512 for (int i = base_node_size - 1; i >= 0; --i) {
513 if (base_nodes_[i] < number_of_nexts_ && i <= next_base_to_increment_) {
514 if (ConsiderAlternatives(i)) {
515 // Iterate on sibling alternatives.
516 const int sibling_alternative_index =
517 GetSiblingAlternativeIndex(base_nodes_[i]);
518 if (sibling_alternative_index >= 0) {
519 if (base_sibling_alternatives_[i] <
520 alternative_sets_[sibling_alternative_index].size() - 1) {
521 ++base_sibling_alternatives_[i];
522 break;
523 }
524 base_sibling_alternatives_[i] = 0;
525 }
526 // Iterate on base alternatives.
527 const int alternative_index = alternative_index_[base_nodes_[i]];
528 if (alternative_index >= 0) {
529 if (base_alternatives_[i] <
530 alternative_sets_[alternative_index].size() - 1) {
531 ++base_alternatives_[i];
532 break;
533 }
534 base_alternatives_[i] = 0;
535 base_sibling_alternatives_[i] = 0;
536 }
537 }
538 if (iteration_parameters_.get_neighbors != nullptr &&
539 ++calls_per_base_node_[i] <
540 iteration_parameters_.get_neighbors(BaseNode(i), StartNode(i))
541 .size()) {
542 break;
543 }
544 calls_per_base_node_[i] = 0;
545 base_alternatives_[i] = 0;
546 base_sibling_alternatives_[i] = 0;
547 base_nodes_[i] = OldNext(base_nodes_[i]);
548 if (iteration_parameters_.accept_path_end_base ||
549 !IsPathEnd(base_nodes_[i]))
550 break;
551 }
552 calls_per_base_node_[i] = 0;
553 base_alternatives_[i] = 0;
554 base_sibling_alternatives_[i] = 0;
555 base_nodes_[i] = StartNode(i);
556 last_restarted = i;
557 }
558 next_base_to_increment_ = base_node_size;
559 // At the end of the loop, base nodes with indexes in
560 // [last_restarted, base_node_size[ have been restarted.
561 // Restarted base nodes are then repositioned by the virtual
562 // GetBaseNodeRestartPosition to reflect position constraints between
563 // base nodes (by default GetBaseNodeRestartPosition leaves the nodes
564 // at the start of the path).
565 // Base nodes are repositioned in ascending order to ensure that all
566 // base nodes "below" the node being repositioned have their final
567 // position.
568 for (int i = last_restarted; i < base_node_size; ++i) {
569 calls_per_base_node_[i] = 0;
570 base_alternatives_[i] = 0;
571 base_sibling_alternatives_[i] = 0;
572 base_nodes_[i] = GetBaseNodeRestartPosition(i);
573 }
574 if (last_restarted > 0) {
575 return CheckEnds();
576 }
577 // If all base nodes have been restarted, base nodes are moved to new paths.
578 // First we mark the current paths as locally optimal if they have been
579 // completely explored.
580 if (optimal_paths_enabled_ &&
581 iteration_parameters_.skip_locally_optimal_paths) {
582 if (path_basis_.size() > 1) {
583 for (int i = 1; i < path_basis_.size(); ++i) {
584 active_paths_.DeactivatePathPair(StartNode(path_basis_[i - 1]),
585 StartNode(path_basis_[i]));
586 }
587 } else {
588 active_paths_.DeactivatePathPair(StartNode(path_basis_[0]),
589 StartNode(path_basis_[0]));
590 }
591 }
592 std::vector<int> current_starts(base_node_size);
593 for (int i = 0; i < base_node_size; ++i) {
594 current_starts[i] = StartNode(i);
595 }
596 // Exploration of next paths can lead to locally optimal paths since we are
597 // exploring them from scratch.
598 optimal_paths_enabled_ = true;
599 while (true) {
600 for (int i = base_node_size - 1; i >= 0; --i) {
601 const int next_path_index = base_paths_[i] + 1;
602 if (next_path_index < number_of_paths) {
603 base_paths_[i] = next_path_index;
604 calls_per_base_node_[i] = 0;
605 base_alternatives_[i] = 0;
606 base_sibling_alternatives_[i] = 0;
607 base_nodes_[i] = path_starts_[next_path_index];
608 if (i == 0 || !OnSamePathAsPreviousBase(i)) {
609 break;
610 }
611 } else {
612 base_paths_[i] = 0;
613 calls_per_base_node_[i] = 0;
614 base_alternatives_[i] = 0;
615 base_sibling_alternatives_[i] = 0;
616 base_nodes_[i] = path_starts_[0];
617 }
618 }
619 if (!iteration_parameters_.skip_locally_optimal_paths) return CheckEnds();
620 // If the new paths have already been completely explored, we can
621 // skip them from now on.
622 if (path_basis_.size() > 1) {
623 for (int j = 1; j < path_basis_.size(); ++j) {
624 if (active_paths_.IsPathPairActive(StartNode(path_basis_[j - 1]),
625 StartNode(path_basis_[j]))) {
626 return CheckEnds();
627 }
628 }
629 } else {
630 if (active_paths_.IsPathPairActive(StartNode(path_basis_[0]),
631 StartNode(path_basis_[0]))) {
632 return CheckEnds();
633 }
634 }
635 // If we are back to paths we just iterated on or have reached the end
636 // of the neighborhood search space, we can stop.
637 if (!CheckEnds()) return false;
638 bool stop = true;
639 for (int i = 0; i < base_node_size; ++i) {
640 if (StartNode(i) != current_starts[i]) {
641 stop = false;
642 break;
643 }
644 }
645 if (stop) return false;
646 }
647 return CheckEnds();
648}
649
650void PathOperator::InitializePathStarts() {
651 // Detect nodes which do not have any possible predecessor in a path; these
652 // nodes are path starts.
653 int max_next = -1;
654 std::vector<bool> has_prevs(number_of_nexts_, false);
655 for (int i = 0; i < number_of_nexts_; ++i) {
656 const int next = OldNext(i);
657 if (next < number_of_nexts_) {
658 has_prevs[next] = true;
659 }
660 max_next = std::max(max_next, next);
661 }
662 // Update locally optimal paths.
663 if (iteration_parameters_.skip_locally_optimal_paths) {
664 active_paths_.Initialize(
665 /*is_start=*/[&has_prevs](int node) { return !has_prevs[node]; });
666 for (int i = 0; i < number_of_nexts_; ++i) {
667 if (!has_prevs[i]) {
668 int current = i;
669 while (!IsPathEnd(current)) {
670 if ((OldNext(current) != PrevNext(current))) {
671 active_paths_.ActivatePath(i);
672 break;
673 }
674 current = OldNext(current);
675 }
676 }
677 }
678 }
679 // Create a list of path starts, dropping equivalent path starts of
680 // currently empty paths.
681 std::vector<bool> empty_found(number_of_nexts_, false);
682 std::vector<int64_t> new_path_starts;
683 const bool use_empty_path_symmetry_breaker =
684 absl::GetFlag(FLAGS_cp_use_empty_path_symmetry_breaker);
685 for (int i = 0; i < number_of_nexts_; ++i) {
686 if (!has_prevs[i]) {
687 if (use_empty_path_symmetry_breaker && IsPathEnd(OldNext(i))) {
688 if (iteration_parameters_.start_empty_path_class != nullptr) {
689 if (empty_found[iteration_parameters_.start_empty_path_class(i)])
690 continue;
691 empty_found[iteration_parameters_.start_empty_path_class(i)] = true;
692 }
693 }
694 new_path_starts.push_back(i);
695 }
696 }
697 if (!first_start_) {
698 // Synchronizing base_paths_ with base node positions. When the last move
699 // was performed a base node could have been moved to a new route in which
700 // case base_paths_ needs to be updated. This needs to be done on the path
701 // starts before we re-adjust base nodes for new path starts.
702 std::vector<int> node_paths(max_next + 1, -1);
703 for (int i = 0; i < path_starts_.size(); ++i) {
704 int node = path_starts_[i];
705 while (!IsPathEnd(node)) {
706 node_paths[node] = i;
707 node = OldNext(node);
708 }
709 node_paths[node] = i;
710 }
711 for (int j = 0; j < iteration_parameters_.number_of_base_nodes; ++j) {
712 // Always restart from first alternative.
713 calls_per_base_node_[j] = 0;
714 base_alternatives_[j] = 0;
715 base_sibling_alternatives_[j] = 0;
716 if (IsInactive(base_nodes_[j]) || node_paths[base_nodes_[j]] == -1) {
717 // Base node was made inactive or was moved to a new path, reposition
718 // the base node to the start of the path on which it was.
719 base_nodes_[j] = path_starts_[base_paths_[j]];
720 } else {
721 base_paths_[j] = node_paths[base_nodes_[j]];
722 }
723 }
724 // Re-adjust current base_nodes and base_paths to take into account new
725 // path starts (there could be fewer if a new path was made empty, or more
726 // if nodes were added to a formerly empty path).
727 int new_index = 0;
728 absl::flat_hash_set<int> found_bases;
729 for (int i = 0; i < path_starts_.size(); ++i) {
730 int index = new_index;
731 // Note: old and new path starts are sorted by construction.
732 while (index < new_path_starts.size() &&
733 new_path_starts[index] < path_starts_[i]) {
734 ++index;
735 }
736 const bool found = (index < new_path_starts.size() &&
737 new_path_starts[index] == path_starts_[i]);
738 if (found) {
739 new_index = index;
740 }
741 for (int j = 0; j < iteration_parameters_.number_of_base_nodes; ++j) {
742 if (base_paths_[j] == i && !found_bases.contains(j)) {
743 found_bases.insert(j);
744 base_paths_[j] = new_index;
745 // If the current position of the base node is a removed empty path,
746 // readjusting it to the last visited path start.
747 if (!found) {
748 base_nodes_[j] = new_path_starts[new_index];
749 }
750 }
751 }
752 }
753 }
754 path_starts_.swap(new_path_starts);
755 // For every base path, store the end corresponding to the path start.
756 // TODO(user): make this faster, maybe by pairing starts with ends.
757 path_ends_.clear();
758 path_ends_.reserve(path_starts_.size());
759 int64_t max_node_index = number_of_nexts_ - 1;
760 for (const int start_node : path_starts_) {
761 int64_t node = start_node;
762 while (!IsPathEnd(node)) node = OldNext(node);
763 path_ends_.push_back(node);
764 max_node_index = std::max(max_node_index, node);
765 }
766 node_path_starts_.assign(max_node_index + 1, -1);
767 node_path_ends_.assign(max_node_index + 1, -1);
768 for (int i = 0; i < path_starts_.size(); ++i) {
769 const int64_t start_node = path_starts_[i];
770 const int64_t end_node = path_ends_[i];
771 int64_t node = start_node;
772 while (!IsPathEnd(node)) {
773 node_path_starts_[node] = start_node;
774 node_path_ends_[node] = end_node;
775 node = OldNext(node);
776 }
777 node_path_starts_[node] = start_node;
778 node_path_ends_[node] = end_node;
779 }
780}
781
782void PathOperator::InitializeInactives() {
783 inactives_.clear();
784 for (int i = 0; i < number_of_nexts_; ++i) {
785 inactives_.push_back(OldNext(i) == i);
786 }
787}
788
789void PathOperator::InitializeBaseNodes() {
790 // Inactive nodes must be detected before determining new path starts.
791 InitializeInactives();
792 InitializePathStarts();
793 if (first_start_ || InitPosition()) {
794 // Only do this once since the following starts will continue from the
795 // preceding position
796 for (int i = 0; i < iteration_parameters_.number_of_base_nodes; ++i) {
797 base_paths_[i] = 0;
798 base_nodes_[i] = path_starts_[0];
799 }
800 first_start_ = false;
801 }
802 for (int i = 0; i < iteration_parameters_.number_of_base_nodes; ++i) {
803 // If base node has been made inactive, restart from path start.
804 int64_t base_node = base_nodes_[i];
805 if (RestartAtPathStartOnSynchronize() || IsInactive(base_node)) {
806 base_node = path_starts_[base_paths_[i]];
807 base_nodes_[i] = base_node;
808 }
809 end_nodes_[i] = base_node;
810 }
811 // Repair end_nodes_ in case some must be on the same path and are not anymore
812 // (due to other operators moving these nodes).
813 for (int i = 1; i < iteration_parameters_.number_of_base_nodes; ++i) {
815 !OnSamePath(base_nodes_[i - 1], base_nodes_[i])) {
816 const int64_t base_node = base_nodes_[i - 1];
817 base_nodes_[i] = base_node;
818 end_nodes_[i] = base_node;
819 base_paths_[i] = base_paths_[i - 1];
820 }
821 }
822 for (int i = 0; i < iteration_parameters_.number_of_base_nodes; ++i) {
823 base_alternatives_[i] = 0;
824 base_sibling_alternatives_[i] = 0;
825 calls_per_base_node_[i] = 0;
826 }
827 just_started_ = true;
828}
829
830void PathOperator::InitializeAlternatives() {
831 active_in_alternative_set_.resize(alternative_sets_.size(), -1);
832 for (int i = 0; i < alternative_sets_.size(); ++i) {
833 const int64_t current_active = active_in_alternative_set_[i];
834 if (current_active >= 0 && !IsInactive(current_active)) continue;
835 for (int64_t index : alternative_sets_[i]) {
836 if (!IsInactive(index)) {
837 active_in_alternative_set_[i] = index;
838 break;
839 }
840 }
841 }
842}
843
844bool PathOperator::OnSamePath(int64_t node1, int64_t node2) const {
845 if (IsInactive(node1) != IsInactive(node2)) {
846 return false;
847 }
848 for (int node = node1; !IsPathEnd(node); node = OldNext(node)) {
849 if (node == node2) {
850 return true;
851 }
852 }
853 for (int node = node2; !IsPathEnd(node); node = OldNext(node)) {
854 if (node == node1) {
855 return true;
856 }
857 }
858 return false;
859}
860
861// Rejects chain if chain_end is not after before_chain on the path or if
862// the chain contains exclude. Given before_chain is the node before the
863// chain, if before_chain and chain_end are the same the chain is rejected too.
864// Also rejects cycles (cycle detection is detected through chain length
865// overflow).
866bool PathOperator::CheckChainValidity(int64_t before_chain, int64_t chain_end,
867 int64_t exclude) const {
868 if (before_chain == chain_end || before_chain == exclude) return false;
869 int64_t current = before_chain;
870 int chain_size = 0;
871 while (current != chain_end) {
872 if (chain_size > number_of_nexts_) {
873 return false;
874 }
875 if (IsPathEnd(current)) {
876 return false;
877 }
878 current = Next(current);
879 ++chain_size;
880 if (current == exclude) {
881 return false;
882 }
883 }
884 return true;
885}
886
887// ----- 2Opt -----
888
889// Reverses a sub-chain of a path. It is called 2Opt because it breaks
890// 2 arcs on the path; resulting paths are called 2-optimal.
891// Possible neighbors for the path 1 -> 2 -> 3 -> 4 -> 5
892// (where (1, 5) are first and last nodes of the path and can therefore not be
893// moved):
894// 1 -> 3 -> 2 -> 4 -> 5
895// 1 -> 4 -> 3 -> 2 -> 5
896// 1 -> 2 -> 4 -> 3 -> 5
897class TwoOpt : public PathOperator {
898 public:
900 const std::vector<IntVar*>& vars,
901 const std::vector<IntVar*>& secondary_vars,
902 std::function<int(int64_t)> start_empty_path_class,
903 std::function<const std::vector<int>&(int, int)> get_neighbors = nullptr)
904 : PathOperator(
905 vars, secondary_vars, get_neighbors == nullptr ? 2 : 1,
906 /*skip_locally_optimal_paths=*/true, /*accept_path_end_base=*/true,
907 std::move(start_empty_path_class), std::move(get_neighbors)),
908 last_base_(-1),
909 last_(-1) {}
910 ~TwoOpt() override {}
911 bool MakeNeighbor() override;
912 bool IsIncremental() const override { return true; }
913 void Reset() override {
915 // When using metaheuristics, path operators will reactivate optimal
916 // routes and iterating will start at route starts, which can
917 // potentially be out of sync with the last incremental moves. This requires
918 // resetting incrementalism.
919 last_ = -1;
920 }
921
922 std::string DebugString() const override { return "TwoOpt"; }
923
924 protected:
925 bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
926 // Both base nodes have to be on the same path.
927 return true;
928 }
929 int64_t GetBaseNodeRestartPosition(int base_index) override {
930 return (base_index == 0) ? StartNode(0) : BaseNode(0);
931 }
932
933 private:
934 void OnNodeInitialization() override { last_ = -1; }
935
936 int64_t last_base_;
937 int64_t last_;
938};
939
941 const int64_t node0 = BaseNode(0);
942 int64_t node1 = -1;
943 if (HasNeighbors()) {
944 const int64_t neighbor = GetNeighborForBaseNode(0);
945 if (IsInactive(neighbor)) return false;
946 if (CurrentNodePathStart(node0) != CurrentNodePathStart(neighbor)) {
947 return false;
948 }
949 node1 = Next(neighbor);
950 } else {
951 DCHECK_EQ(StartNode(0), StartNode(1));
952 node1 = BaseNode(1);
953 }
954 // Incrementality is disabled with neighbors.
955 if (last_base_ != node0 || last_ == -1 || HasNeighbors()) {
956 RevertChanges(false);
957 if (IsPathEnd(node0)) {
958 last_ = -1;
959 return false;
960 }
961 last_base_ = node0;
962 last_ = Next(node0);
963 int64_t chain_last;
964 if (ReverseChain(node0, node1, &chain_last)
965 // Check there are more than one node in the chain (reversing a
966 // single node is a NOP).
967 && last_ != chain_last) {
968 return true;
969 }
970 last_ = -1;
971 return false;
972 }
973 const int64_t to_move = Next(last_);
974 DCHECK_EQ(Next(to_move), node1);
975 return MoveChain(last_, to_move, node0);
976}
977
978// ----- Relocate -----
979
980// Moves a sub-chain of a path to another position; the specified chain length
981// is the fixed length of the chains being moved. When this length is 1 the
982// operator simply moves a node to another position.
983// Possible neighbors for the path 1 -> 2 -> 3 -> 4 -> 5, for a chain length
984// of 2 (where (1, 5) are first and last nodes of the path and can
985// therefore not be moved):
986// 1 -> 4 -> 2 -> 3 -> 5
987// 1 -> 3 -> 4 -> 2 -> 5
988//
989// Using Relocate with chain lengths of 1, 2 and 3 together is equivalent to
990// the OrOpt operator on a path. The OrOpt operator is a limited version of
991// 3Opt (breaks 3 arcs on a path).
992
993class Relocate : public PathOperator {
994 public:
995 Relocate(const std::vector<IntVar*>& vars,
996 const std::vector<IntVar*>& secondary_vars, const std::string& name,
997 std::function<int(int64_t)> start_empty_path_class,
998 std::function<const std::vector<int>&(int, int)> get_neighbors,
999 int64_t chain_length = 1LL, bool single_path = false)
1000 : PathOperator(
1001 vars, secondary_vars, get_neighbors == nullptr ? 2 : 1,
1002 /*skip_locally_optimal_paths=*/true, /*accept_path_end_base=*/false,
1003 std::move(start_empty_path_class), std::move(get_neighbors)),
1004 chain_length_(chain_length),
1005 single_path_(single_path),
1006 name_(name) {
1007 CHECK_GT(chain_length_, 0);
1008 }
1009 Relocate(const std::vector<IntVar*>& vars,
1010 const std::vector<IntVar*>& secondary_vars,
1011 std::function<int(int64_t)> start_empty_path_class,
1012 std::function<const std::vector<int>&(int, int)> get_neighbors,
1013 int64_t chain_length = 1LL, bool single_path = false)
1014 : Relocate(vars, secondary_vars,
1015 absl::StrCat("Relocate<", chain_length, ">"),
1016 std::move(start_empty_path_class), std::move(get_neighbors),
1017 chain_length, single_path) {}
1018 Relocate(const std::vector<IntVar*>& vars,
1019 const std::vector<IntVar*>& secondary_vars,
1020 std::function<int(int64_t)> start_empty_path_class,
1021 int64_t chain_length = 1LL, bool single_path = false)
1022 : Relocate(vars, secondary_vars,
1023 absl::StrCat("Relocate<", chain_length, ">"),
1024 std::move(start_empty_path_class), nullptr, chain_length,
1025 single_path) {}
1026 ~Relocate() override {}
1027 bool MakeNeighbor() override;
1028
1029 std::string DebugString() const override { return name_; }
1030
1031 protected:
1032 bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
1033 // Both base nodes have to be on the same path when it's the single path
1034 // version.
1035 return single_path_;
1036 }
1037
1038 private:
1039 const int64_t chain_length_;
1040 const bool single_path_;
1041 const std::string name_;
1042};
1043
1045 const auto do_move = [this](int64_t before_chain, int64_t destination) {
1046 DCHECK(!IsPathEnd(destination));
1047 int64_t chain_end = before_chain;
1048 for (int i = 0; i < chain_length_; ++i) {
1049 if (IsPathEnd(chain_end) || chain_end == destination) return false;
1050 chain_end = Next(chain_end);
1051 }
1052 return !IsPathEnd(chain_end) &&
1053 MoveChain(before_chain, chain_end, destination);
1054 };
1055 if (HasNeighbors()) {
1056 const int64_t node = GetNeighborForBaseNode(0);
1057 if (IsInactive(node)) return false;
1058 return do_move(/*before_chain=*/Prev(node),
1059 /*destination=*/BaseNode(0));
1060 }
1061 DCHECK(!single_path_ || StartNode(0) == StartNode(1));
1062 return do_move(/*before_chain=*/BaseNode(0), /*destination=*/BaseNode(1));
1063}
1064
1065// ----- Exchange -----
1066
1067// Exchanges the positions of two nodes.
1068// Possible neighbors for the path 1 -> 2 -> 3 -> 4 -> 5
1069// (where (1, 5) are first and last nodes of the path and can therefore not
1070// be moved):
1071// 1 -> 3 -> 2 -> 4 -> 5
1072// 1 -> 4 -> 3 -> 2 -> 5
1073// 1 -> 2 -> 4 -> 3 -> 5
1074
1075class Exchange : public PathOperator {
1076 public:
1078 const std::vector<IntVar*>& vars,
1079 const std::vector<IntVar*>& secondary_vars,
1080 std::function<int(int64_t)> start_empty_path_class,
1081 std::function<const std::vector<int>&(int, int)> get_neighbors = nullptr)
1082 : PathOperator(vars, secondary_vars, get_neighbors == nullptr ? 2 : 1,
1083 /*skip_locally_optimal_paths=*/true,
1084 /*accept_path_end_base=*/false,
1085 std::move(start_empty_path_class), get_neighbors) {}
1086 ~Exchange() override {}
1087 bool MakeNeighbor() override;
1088
1089 std::string DebugString() const override { return "Exchange"; }
1090};
1091
1093 const auto do_move = [this](int64_t node1, int64_t node2) {
1094 if (IsPathEnd(node1) || IsPathEnd(node2)) return false;
1095 if (node1 == node2) return false;
1096 const int64_t prev_node1 = Prev(node1);
1097 const bool ok = MoveChain(prev_node1, node1, Prev(node2));
1098 return MoveChain(Prev(node2), node2, prev_node1) || ok;
1099 };
1100 if (HasNeighbors()) {
1101 const int64_t node = GetNeighborForBaseNode(0);
1102 if (IsInactive(node)) return false;
1103 DCHECK(!IsPathStart(node));
1104 return do_move(Next(BaseNode(0)), node);
1105 }
1106 return do_move(Next(BaseNode(0)), Next(BaseNode(1)));
1107}
1108
1109// ----- Cross -----
1110
1111// Cross echanges the starting chains of 2 paths, including exchanging the
1112// whole paths.
1113// First and last nodes are not moved.
1114// Possible neighbors for the paths 1 -> 2 -> 3 -> 4 -> 5 and 6 -> 7 -> 8
1115// (where (1, 5) and (6, 8) are first and last nodes of the paths and can
1116// therefore not be moved):
1117// 1 -> 7 -> 3 -> 4 -> 5 6 -> 2 -> 8
1118// 1 -> 7 -> 4 -> 5 6 -> 2 -> 3 -> 8
1119// 1 -> 7 -> 5 6 -> 2 -> 3 -> 4 -> 8
1120
1121class Cross : public PathOperator {
1122 public:
1124 const std::vector<IntVar*>& vars,
1125 const std::vector<IntVar*>& secondary_vars,
1126 std::function<int(int64_t)> start_empty_path_class,
1127 std::function<const std::vector<int>&(int, int)> get_neighbors = nullptr)
1128 : PathOperator(
1129 vars, secondary_vars, get_neighbors == nullptr ? 2 : 1,
1130 /*skip_locally_optimal_paths=*/true, /*accept_path_end_base=*/true,
1131 std::move(start_empty_path_class), std::move(get_neighbors)) {}
1132 ~Cross() override {}
1133 bool MakeNeighbor() override;
1134
1135 std::string DebugString() const override { return "Cross"; }
1136};
1137
1139 const int64_t start0 = StartNode(0);
1140 int64_t start1 = -1;
1141 const int64_t node0 = BaseNode(0);
1142 int64_t node1 = -1;
1143 if (node0 == start0) return false;
1144 if (HasNeighbors()) {
1145 const int64_t neighbor = GetNeighborForBaseNode(0);
1146 DCHECK(!IsPathStart(neighbor));
1147 if (IsInactive(neighbor)) return false;
1148 start1 = CurrentNodePathStart(neighbor);
1149 // Tricky: In all cases we want to connect node0 to neighbor. If we are
1150 // crossing path starts, node0 is the end of a chain and neighbor is the
1151 // the first node after the other chain ending at node1, therefore
1152 // node1 = prev(neighbor).
1153 // If we are crossing path ends, node0 is the start of a chain and neighbor
1154 // is the last node before the other chain starting at node1, therefore
1155 // node1 = next(neighbor).
1156 // TODO(user): When neighbors are considered, explore if having two
1157 // versions of Cross makes sense, one exchanging path starts, the other
1158 // path ends. Rationale: neighborhoods might not be symmetric. In practice,
1159 // in particular when used through RoutingModel, neighborhoods are
1160 // actually symmetric.
1161 node1 = (start0 < start1) ? Prev(neighbor) : Next(neighbor);
1162 } else {
1163 start1 = StartNode(1);
1164 node1 = BaseNode(1);
1165 }
1166 if (start1 == start0 || node1 == start1) return false;
1167
1168 bool moved = false;
1169 if (start0 < start1) {
1170 // Cross path starts.
1171 // If two paths are equivalent don't exchange the full paths.
1172 if (PathClassFromStartNode(start0) == PathClassFromStartNode(start1) &&
1173 !IsPathEnd(node0) && IsPathEnd(Next(node0)) && !IsPathEnd(node1) &&
1174 IsPathEnd(Next(node1))) {
1175 return false;
1176 }
1177
1178 const int first1 = Next(start1);
1179 if (!IsPathEnd(node0)) moved |= MoveChain(start0, node0, start1);
1180 if (!IsPathEnd(node1)) moved |= MoveChain(Prev(first1), node1, start0);
1181 } else { // start1 > start0.
1182 // Cross path ends.
1183 // If paths are equivalent, every end crossing has a corresponding start
1184 // crossing, we don't generate those symmetric neighbors.
1185 if (PathClassFromStartNode(start0) == PathClassFromStartNode(start1) &&
1186 !HasNeighbors()) {
1187 return false;
1188 }
1189 // Never exchange full paths, equivalent or not.
1190 // Full path exchange is only performed when start0 < start1.
1191 if (IsPathStart(Prev(node0)) && IsPathStart(Prev(node1)) &&
1192 !HasNeighbors()) {
1193 return false;
1194 }
1195
1196 const int prev_end_node1 = Prev(CurrentNodePathEnd(node1));
1197 if (!IsPathEnd(node0)) {
1198 moved |= MoveChain(Prev(node0), Prev(EndNode(0)), prev_end_node1);
1199 }
1200 if (!IsPathEnd(node1)) {
1201 moved |= MoveChain(Prev(node1), prev_end_node1, Prev(EndNode(0)));
1202 }
1203 }
1204 return moved;
1205}
1206
1207// ----- BaseInactiveNodeToPathOperator -----
1208// Base class of path operators which make inactive nodes active.
1209
1211 public:
1213 const std::vector<IntVar*>& vars,
1214 const std::vector<IntVar*>& secondary_vars, int number_of_base_nodes,
1215 std::function<int(int64_t)> start_empty_path_class,
1216 std::function<const std::vector<int>&(int, int)> get_neighbors = nullptr)
1217 : PathOperator(vars, secondary_vars, number_of_base_nodes, false, false,
1218 std::move(start_empty_path_class),
1219 std::move(get_neighbors)),
1220 inactive_node_(0) {
1221 // TODO(user): Activate skipping optimal paths.
1222 }
1224
1225 protected:
1226 bool MakeOneNeighbor() override;
1227 int64_t GetInactiveNode() const { return inactive_node_; }
1228
1229 private:
1230 void OnNodeInitialization() override;
1231
1232 int inactive_node_;
1233};
1234
1235void BaseInactiveNodeToPathOperator::OnNodeInitialization() {
1236 for (int i = 0; i < Size(); ++i) {
1237 if (IsInactive(i)) {
1238 inactive_node_ = i;
1239 return;
1240 }
1241 }
1242 inactive_node_ = Size();
1243}
1244
1246 while (inactive_node_ < Size()) {
1247 if (!IsInactive(inactive_node_) || !PathOperator::MakeOneNeighbor()) {
1248 ResetPosition();
1249 ++inactive_node_;
1250 } else {
1251 return true;
1252 }
1253 }
1254 return false;
1255}
1256
1257// ----- MakeActiveOperator -----
1258
1259// MakeActiveOperator inserts an inactive node into a path.
1260// Possible neighbors for the path 1 -> 2 -> 3 -> 4 with 5 inactive (where 1 and
1261// 4 are first and last nodes of the path) are:
1262// 1 -> 5 -> 2 -> 3 -> 4
1263// 1 -> 2 -> 5 -> 3 -> 4
1264// 1 -> 2 -> 3 -> 5 -> 4
1265
1267 public:
1269 const std::vector<IntVar*>& vars,
1270 const std::vector<IntVar*>& secondary_vars,
1271 std::function<int(int64_t)> start_empty_path_class,
1272 std::function<const std::vector<int>&(int, int)> get_neighbors = nullptr)
1273 : BaseInactiveNodeToPathOperator(vars, secondary_vars, 1,
1274 std::move(start_empty_path_class),
1275 std::move(get_neighbors)) {}
1277 bool MakeNeighbor() override;
1278
1279 std::string DebugString() const override { return "MakeActiveOperator"; }
1280};
1281
1283 // TODO(user): Add support for neighbors of inactive nodes; would require
1284 // having a version without base nodes (probably not a PathOperator).
1285 return MakeActive(GetInactiveNode(), BaseNode(0));
1286}
1287
1288// ---- RelocateAndMakeActiveOperator -----
1289
1290// RelocateAndMakeActiveOperator relocates a node and replaces it by an inactive
1291// node.
1292// The idea is to make room for inactive nodes.
1293// Possible neighbor for paths 0 -> 4, 1 -> 2 -> 5 and 3 inactive is:
1294// 0 -> 2 -> 4, 1 -> 3 -> 5.
1295// TODO(user): Naming is close to MakeActiveAndRelocate but this one is
1296// correct; rename MakeActiveAndRelocate if it is actually used.
1298 public:
1300 const std::vector<IntVar*>& vars,
1301 const std::vector<IntVar*>& secondary_vars,
1302 std::function<int(int64_t)> start_empty_path_class)
1303 : BaseInactiveNodeToPathOperator(vars, secondary_vars, 2,
1304 std::move(start_empty_path_class)) {}
1306 bool MakeNeighbor() override {
1307 const int64_t before_node_to_move = BaseNode(1);
1308 const int64_t node = Next(before_node_to_move);
1309 return !IsPathEnd(node) &&
1310 MoveChain(before_node_to_move, node, BaseNode(0)) &&
1311 MakeActive(GetInactiveNode(), before_node_to_move);
1312 }
1313
1314 std::string DebugString() const override {
1315 return "RelocateAndMakeActiveOpertor";
1316 }
1317};
1318
1319// ----- MakeActiveAndRelocate -----
1320
1321// MakeActiveAndRelocate makes a node active next to a node being relocated.
1322// Possible neighbor for paths 0 -> 4, 1 -> 2 -> 5 and 3 inactive is:
1323// 0 -> 3 -> 2 -> 4, 1 -> 5.
1324
1326 public:
1327 MakeActiveAndRelocate(const std::vector<IntVar*>& vars,
1328 const std::vector<IntVar*>& secondary_vars,
1329 std::function<int(int64_t)> start_empty_path_class)
1330 : BaseInactiveNodeToPathOperator(vars, secondary_vars, 2,
1331 std::move(start_empty_path_class)) {}
1333 bool MakeNeighbor() override;
1334
1335 std::string DebugString() const override {
1336 return "MakeActiveAndRelocateOperator";
1337 }
1338};
1339
1341 const int64_t before_chain = BaseNode(1);
1342 const int64_t chain_end = Next(before_chain);
1343 const int64_t destination = BaseNode(0);
1344 return !IsPathEnd(chain_end) &&
1345 MoveChain(before_chain, chain_end, destination) &&
1346 MakeActive(GetInactiveNode(), destination);
1347}
1348
1349// ----- MakeInactiveOperator -----
1350
1351// MakeInactiveOperator makes path nodes inactive.
1352// Possible neighbors for the path 1 -> 2 -> 3 -> 4 (where 1 and 4 are first
1353// and last nodes of the path) are:
1354// 1 -> 3 -> 4 & 2 inactive
1355// 1 -> 2 -> 4 & 3 inactive
1356
1358 public:
1359 MakeInactiveOperator(const std::vector<IntVar*>& vars,
1360 const std::vector<IntVar*>& secondary_vars,
1361 std::function<int(int64_t)> start_empty_path_class)
1362 : PathOperator(vars, secondary_vars, 1, true, false,
1363 std::move(start_empty_path_class), nullptr) {}
1365 bool MakeNeighbor() override {
1366 const int64_t base = BaseNode(0);
1367 return MakeChainInactive(base, Next(base));
1368 }
1369
1370 std::string DebugString() const override { return "MakeInactiveOperator"; }
1371};
1372
1373// ----- RelocateAndMakeInactiveOperator -----
1374
1375// RelocateAndMakeInactiveOperator relocates a node to a new position and makes
1376// the node which was at that position inactive.
1377// Possible neighbors for paths 0 -> 2 -> 4, 1 -> 3 -> 5 are:
1378// 0 -> 3 -> 4, 1 -> 5 & 2 inactive
1379// 0 -> 4, 1 -> 2 -> 5 & 3 inactive
1380
1382 public:
1384 const std::vector<IntVar*>& vars,
1385 const std::vector<IntVar*>& secondary_vars,
1386 std::function<int(int64_t)> start_empty_path_class)
1387 : PathOperator(vars, secondary_vars, 2, true, false,
1388 std::move(start_empty_path_class), nullptr) {}
1390 bool MakeNeighbor() override {
1391 const int64_t destination = BaseNode(1);
1392 const int64_t before_to_move = BaseNode(0);
1393 const int64_t node_to_inactivate = Next(destination);
1394 if (node_to_inactivate == before_to_move || IsPathEnd(node_to_inactivate) ||
1395 !MakeChainInactive(destination, node_to_inactivate)) {
1396 return false;
1397 }
1398 const int64_t node = Next(before_to_move);
1399 return !IsPathEnd(node) && MoveChain(before_to_move, node, destination);
1400 }
1401
1402 std::string DebugString() const override {
1403 return "RelocateAndMakeInactiveOperator";
1404 }
1405};
1406
1407// ----- MakeChainInactiveOperator -----
1408
1409// Operator which makes a "chain" of path nodes inactive.
1410// Possible neighbors for the path 1 -> 2 -> 3 -> 4 (where 1 and 4 are first
1411// and last nodes of the path) are:
1412// 1 -> 3 -> 4 with 2 inactive
1413// 1 -> 2 -> 4 with 3 inactive
1414// 1 -> 4 with 2 and 3 inactive
1415
1417 public:
1418 MakeChainInactiveOperator(const std::vector<IntVar*>& vars,
1419 const std::vector<IntVar*>& secondary_vars,
1420 std::function<int(int64_t)> start_empty_path_class)
1421 : PathOperator(vars, secondary_vars, 2, true, false,
1422 std::move(start_empty_path_class), nullptr) {}
1424 bool MakeNeighbor() override {
1425 return MakeChainInactive(BaseNode(0), BaseNode(1));
1426 }
1427
1428 std::string DebugString() const override {
1429 return "MakeChainInactiveOperator";
1430 }
1431
1432 protected:
1433 bool OnSamePathAsPreviousBase(int64_t) override {
1434 // Start and end of chain (defined by both base nodes) must be on the same
1435 // path.
1436 return true;
1437 }
1438
1439 int64_t GetBaseNodeRestartPosition(int base_index) override {
1440 // Base node 1 must be after base node 0.
1441 return (base_index == 0) ? StartNode(base_index) : BaseNode(base_index - 1);
1442 }
1443};
1444
1445// ----- SwapActiveOperator -----
1446
1447// SwapActiveOperator replaces an active node by an inactive one.
1448// Possible neighbors for the path 1 -> 2 -> 3 -> 4 with 5 inactive (where 1 and
1449// 4 are first and last nodes of the path) are:
1450// 1 -> 5 -> 3 -> 4 & 2 inactive
1451// 1 -> 2 -> 5 -> 4 & 3 inactive
1452
1454 public:
1455 SwapActiveOperator(const std::vector<IntVar*>& vars,
1456 const std::vector<IntVar*>& secondary_vars,
1457 std::function<int(int64_t)> start_empty_path_class)
1458 : BaseInactiveNodeToPathOperator(vars, secondary_vars, 1,
1459 std::move(start_empty_path_class)) {}
1461 bool MakeNeighbor() override;
1462
1463 std::string DebugString() const override { return "SwapActiveOperator"; }
1464};
1465
1467 const int64_t base = BaseNode(0);
1468 return MakeChainInactive(base, Next(base)) &&
1469 MakeActive(GetInactiveNode(), base);
1470}
1471
1472// ----- ExtendedSwapActiveOperator -----
1473
1474// ExtendedSwapActiveOperator makes an inactive node active and an active one
1475// inactive. It is similar to SwapActiveOperator excepts that it tries to
1476// insert the inactive node in all possible positions instead of just the
1477// position of the node made inactive.
1478// Possible neighbors for the path 1 -> 2 -> 3 -> 4 with 5 inactive (where 1 and
1479// 4 are first and last nodes of the path) are:
1480// 1 -> 5 -> 3 -> 4 & 2 inactive
1481// 1 -> 3 -> 5 -> 4 & 2 inactive
1482// 1 -> 5 -> 2 -> 4 & 3 inactive
1483// 1 -> 2 -> 5 -> 4 & 3 inactive
1484
1486 public:
1487 ExtendedSwapActiveOperator(const std::vector<IntVar*>& vars,
1488 const std::vector<IntVar*>& secondary_vars,
1489 std::function<int(int64_t)> start_empty_path_class)
1490 : BaseInactiveNodeToPathOperator(vars, secondary_vars, 2,
1491 std::move(start_empty_path_class)) {}
1493 bool MakeNeighbor() override;
1494
1495 std::string DebugString() const override {
1496 return "ExtendedSwapActiveOperator";
1497 }
1498};
1499
1501 const int64_t base0 = BaseNode(0);
1502 const int64_t base1 = BaseNode(1);
1503 if (Next(base0) == base1) {
1504 return false;
1505 }
1506 return MakeChainInactive(base0, Next(base0)) &&
1507 MakeActive(GetInactiveNode(), base1);
1508}
1509
1510// ----- TSP-based operators -----
1511
1512// Sliding TSP operator
1513// Uses an exact dynamic programming algorithm to solve the TSP corresponding
1514// to path sub-chains.
1515// For a subchain 1 -> 2 -> 3 -> 4 -> 5 -> 6, solves the TSP on nodes A, 2, 3,
1516// 4, 5, where A is a merger of nodes 1 and 6 such that cost(A,i) = cost(1,i)
1517// and cost(i,A) = cost(i,6).
1518
1519class TSPOpt : public PathOperator {
1520 public:
1521 TSPOpt(const std::vector<IntVar*>& vars,
1522 const std::vector<IntVar*>& secondary_vars,
1523 Solver::IndexEvaluator3 evaluator, int chain_length);
1524 ~TSPOpt() override {}
1525 bool MakeNeighbor() override;
1526
1527 std::string DebugString() const override { return "TSPOpt"; }
1528
1529 private:
1530 std::vector<std::vector<int64_t>> cost_;
1532 hamiltonian_path_solver_;
1533 Solver::IndexEvaluator3 evaluator_;
1534 const int chain_length_;
1535};
1536
1537TSPOpt::TSPOpt(const std::vector<IntVar*>& vars,
1538 const std::vector<IntVar*>& secondary_vars,
1539 Solver::IndexEvaluator3 evaluator, int chain_length)
1540 : PathOperator(vars, secondary_vars, 1, true, false, nullptr, nullptr),
1541 hamiltonian_path_solver_(cost_),
1542 evaluator_(std::move(evaluator)),
1543 chain_length_(chain_length) {}
1544
1546 std::vector<int64_t> nodes;
1547 int64_t chain_end = BaseNode(0);
1548 for (int i = 0; i < chain_length_ + 1; ++i) {
1549 nodes.push_back(chain_end);
1550 if (IsPathEnd(chain_end)) {
1551 break;
1552 }
1553 chain_end = Next(chain_end);
1554 }
1555 if (nodes.size() <= 3) {
1556 return false;
1557 }
1558 int64_t chain_path = Path(BaseNode(0));
1559 const int size = nodes.size() - 1;
1560 cost_.resize(size);
1561 for (int i = 0; i < size; ++i) {
1562 cost_[i].resize(size);
1563 cost_[i][0] = evaluator_(nodes[i], nodes[size], chain_path);
1564 for (int j = 1; j < size; ++j) {
1565 cost_[i][j] = evaluator_(nodes[i], nodes[j], chain_path);
1566 }
1567 }
1568 hamiltonian_path_solver_.ChangeCostMatrix(cost_);
1569 std::vector<PathNodeIndex> path;
1570 hamiltonian_path_solver_.TravelingSalesmanPath(&path);
1571 CHECK_EQ(size + 1, path.size());
1572 for (int i = 0; i < size - 1; ++i) {
1573 SetNext(nodes[path[i]], nodes[path[i + 1]], chain_path);
1574 }
1575 SetNext(nodes[path[size - 1]], nodes[size], chain_path);
1576 return true;
1577}
1578
1579// TSP-base lns
1580// Randomly merge consecutive nodes until n "meta"-nodes remain and solve the
1581// corresponding TSP. This can be seen as a large neighborhood search operator
1582// although decisions are taken with the operator.
1583// This is an "unlimited" neighborhood which must be stopped by search limits.
1584// To force diversification, the operator iteratively forces each node to serve
1585// as base of a meta-node.
1586
1587class TSPLns : public PathOperator {
1588 public:
1589 TSPLns(const std::vector<IntVar*>& vars,
1590 const std::vector<IntVar*>& secondary_vars,
1591 Solver::IndexEvaluator3 evaluator, int tsp_size);
1592 ~TSPLns() override {}
1593 bool MakeNeighbor() override;
1594
1595 std::string DebugString() const override { return "TSPLns"; }
1596
1597 protected:
1598 bool MakeOneNeighbor() override;
1599
1600 private:
1601 void OnNodeInitialization() override {
1602 // NOTE: Avoid any computations if there are no vars added.
1603 has_long_enough_paths_ = Size() != 0;
1604 }
1605
1606 std::vector<std::vector<int64_t>> cost_;
1607 HamiltonianPathSolver<int64_t, std::vector<std::vector<int64_t>>>
1608 hamiltonian_path_solver_;
1609 Solver::IndexEvaluator3 evaluator_;
1610 const int tsp_size_;
1611 std::mt19937 rand_;
1612 bool has_long_enough_paths_;
1613};
1614
1615TSPLns::TSPLns(const std::vector<IntVar*>& vars,
1616 const std::vector<IntVar*>& secondary_vars,
1617 Solver::IndexEvaluator3 evaluator, int tsp_size)
1618 : PathOperator(vars, secondary_vars, 1, true, false, nullptr, nullptr),
1619 hamiltonian_path_solver_(cost_),
1620 evaluator_(std::move(evaluator)),
1621 tsp_size_(tsp_size),
1622 rand_(CpRandomSeed()),
1623 has_long_enough_paths_(true) {
1624 CHECK_GE(tsp_size_, 0);
1625 cost_.resize(tsp_size_);
1626 for (int i = 0; i < tsp_size_; ++i) {
1627 cost_[i].resize(tsp_size_);
1628 }
1629}
1630
1632 while (has_long_enough_paths_) {
1633 has_long_enough_paths_ = false;
1635 return true;
1636 }
1637 Var(0)->solver()->TopPeriodicCheck();
1638 }
1639 return false;
1640}
1641
1643 const int64_t base_node = BaseNode(0);
1644 std::vector<int64_t> nodes;
1645 for (int64_t node = StartNode(0); !IsPathEnd(node); node = Next(node)) {
1646 nodes.push_back(node);
1647 }
1648 if (nodes.size() <= tsp_size_) {
1649 return false;
1650 }
1651 has_long_enough_paths_ = true;
1652 // Randomly select break nodes (final nodes of a meta-node, after which
1653 // an arc is relaxed.
1654 absl::flat_hash_set<int64_t> breaks_set;
1655 // Always add base node to break nodes (diversification)
1656 breaks_set.insert(base_node);
1657 CHECK(!nodes.empty()); // Should have been caught earlier.
1658 while (breaks_set.size() < tsp_size_) {
1659 breaks_set.insert(nodes[absl::Uniform<int>(rand_, 0, nodes.size())]);
1660 }
1661 CHECK_EQ(breaks_set.size(), tsp_size_);
1662 // Setup break node indexing and internal meta-node cost (cost of partial
1663 // route starting at first node of the meta-node and ending at its last node);
1664 // this cost has to be added to the TSP matrix cost in order to respect the
1665 // triangle inequality.
1666 std::vector<int> breaks;
1667 std::vector<int64_t> meta_node_costs;
1668 int64_t cost = 0;
1669 int64_t node = StartNode(0);
1670 int64_t node_path = Path(node);
1671 while (!IsPathEnd(node)) {
1672 int64_t next = Next(node);
1673 if (breaks_set.contains(node)) {
1674 breaks.push_back(node);
1675 meta_node_costs.push_back(cost);
1676 cost = 0;
1677 } else {
1678 cost = CapAdd(cost, evaluator_(node, next, node_path));
1679 }
1680 node = next;
1681 }
1682 meta_node_costs[0] += cost;
1683 CHECK_EQ(breaks.size(), tsp_size_);
1684 // Setup TSP cost matrix
1685 CHECK_EQ(meta_node_costs.size(), tsp_size_);
1686 for (int i = 0; i < tsp_size_; ++i) {
1687 cost_[i][0] =
1688 CapAdd(meta_node_costs[i],
1689 evaluator_(breaks[i], Next(breaks[tsp_size_ - 1]), node_path));
1690 for (int j = 1; j < tsp_size_; ++j) {
1691 cost_[i][j] =
1692 CapAdd(meta_node_costs[i],
1693 evaluator_(breaks[i], Next(breaks[j - 1]), node_path));
1694 }
1695 cost_[i][i] = 0;
1696 }
1697 // Solve TSP and inject solution in delta (only if it leads to a new solution)
1698 hamiltonian_path_solver_.ChangeCostMatrix(cost_);
1699 std::vector<PathNodeIndex> path;
1700 hamiltonian_path_solver_.TravelingSalesmanPath(&path);
1701 bool nochange = true;
1702 for (int i = 0; i < path.size() - 1; ++i) {
1703 if (path[i] != i) {
1704 nochange = false;
1705 break;
1706 }
1707 }
1708 if (nochange) {
1709 return false;
1710 }
1711 CHECK_EQ(0, path[path.size() - 1]);
1712 for (int i = 0; i < tsp_size_ - 1; ++i) {
1713 SetNext(breaks[path[i]], OldNext(breaks[path[i + 1] - 1]), node_path);
1714 }
1715 SetNext(breaks[path[tsp_size_ - 1]], OldNext(breaks[tsp_size_ - 1]),
1716 node_path);
1717 return true;
1718}
1719
1720// ----- Lin Kernighan -----
1721
1722// For each variable in vars, stores the 'size' pairs(i,j) with the smallest
1723// value according to evaluator, where i is the index of the variable in vars
1724// and j is in the domain of the variable.
1725// Note that the resulting pairs are sorted.
1726// Works in O(size) per variable on average (same approach as qsort)
1727
1729 public:
1731 const PathOperator& path_operator, int size);
1732
1733 // This type is neither copyable nor movable.
1736
1738 void Initialize();
1739 const std::vector<int>& Neighbors(int index) const;
1740
1741 virtual std::string DebugString() const { return "NearestNeighbors"; }
1742
1743 private:
1744 void ComputeNearest(int row);
1745
1746 std::vector<std::vector<int>> neighbors_;
1747 Solver::IndexEvaluator3 evaluator_;
1748 const PathOperator& path_operator_;
1749 const int size_;
1750 bool initialized_;
1751};
1752
1754 const PathOperator& path_operator, int size)
1755 : evaluator_(std::move(evaluator)),
1756 path_operator_(path_operator),
1757 size_(size),
1758 initialized_(false) {}
1759
1761 // TODO(user): recompute if node changes path ?
1762 if (!initialized_) {
1763 initialized_ = true;
1764 for (int i = 0; i < path_operator_.number_of_nexts(); ++i) {
1765 neighbors_.push_back(std::vector<int>());
1766 ComputeNearest(i);
1767 }
1768 }
1769}
1770
1771const std::vector<int>& NearestNeighbors::Neighbors(int index) const {
1772 return neighbors_[index];
1773}
1774
1775void NearestNeighbors::ComputeNearest(int row) {
1776 // Find size_ nearest neighbors for row of index 'row'.
1777 const int path = path_operator_.Path(row);
1778 const IntVar* var = path_operator_.Var(row);
1779 const int64_t var_min = var->Min();
1780 const int var_size = var->Max() - var_min + 1;
1781 using ValuedIndex = std::pair<int64_t /*value*/, int /*index*/>;
1782 std::vector<ValuedIndex> neighbors(var_size);
1783 for (int i = 0; i < var_size; ++i) {
1784 const int index = i + var_min;
1785 neighbors[i] = std::make_pair(evaluator_(row, index, path), index);
1786 }
1787 if (var_size > size_) {
1788 std::nth_element(neighbors.begin(), neighbors.begin() + size_ - 1,
1789 neighbors.end());
1790 }
1791
1792 // Setup global neighbor matrix for row row_index
1793 for (int i = 0; i < std::min(size_, var_size); ++i) {
1794 neighbors_[row].push_back(neighbors[i].second);
1795 }
1796 std::sort(neighbors_[row].begin(), neighbors_[row].end());
1797}
1798
1800 public:
1801 LinKernighan(const std::vector<IntVar*>& vars,
1802 const std::vector<IntVar*>& secondary_vars,
1803 const Solver::IndexEvaluator3& evaluator, bool topt);
1804 ~LinKernighan() override;
1805 bool MakeNeighbor() override;
1806
1807 std::string DebugString() const override { return "LinKernighan"; }
1808
1809 private:
1810 void OnNodeInitialization() override;
1811
1812 static const int kNeighbors;
1813
1814 bool InFromOut(int64_t in_i, int64_t in_j, int64_t* out, int64_t* gain);
1815
1816 Solver::IndexEvaluator3 const evaluator_;
1817 NearestNeighbors neighbors_;
1818 absl::flat_hash_set<int64_t> marked_;
1819 const bool topt_;
1820};
1821
1822// While the accumulated local gain is positive, perform a 2opt or a 3opt move
1823// followed by a series of 2opt moves. Return a neighbor for which the global
1824// gain is positive.
1825
1826LinKernighan::LinKernighan(const std::vector<IntVar*>& vars,
1827 const std::vector<IntVar*>& secondary_vars,
1828 const Solver::IndexEvaluator3& evaluator, bool topt)
1829 : PathOperator(vars, secondary_vars, 1, true, false, nullptr, nullptr),
1830 evaluator_(evaluator),
1831 neighbors_(evaluator, *this, kNeighbors),
1832 topt_(topt) {}
1833
1835
1836void LinKernighan::OnNodeInitialization() { neighbors_.Initialize(); }
1837
1839 marked_.clear();
1840 int64_t node = BaseNode(0);
1841 int64_t path = Path(node);
1842 int64_t base = node;
1843 int64_t next = Next(node);
1844 if (IsPathEnd(next)) return false;
1845 int64_t out = -1;
1846 int64_t gain = 0;
1847 marked_.insert(node);
1848 if (topt_) { // Try a 3opt first
1849 if (!InFromOut(node, next, &out, &gain)) return false;
1850 marked_.insert(next);
1851 marked_.insert(out);
1852 const int64_t node1 = out;
1853 if (IsPathEnd(node1)) return false;
1854 const int64_t next1 = Next(node1);
1855 if (IsPathEnd(next1)) return false;
1856 if (!InFromOut(node1, next1, &out, &gain)) return false;
1857 marked_.insert(next1);
1858 marked_.insert(out);
1859 if (!CheckChainValidity(out, node1, node) || !MoveChain(out, node1, node)) {
1860 return false;
1861 }
1862 const int64_t next_out = Next(out);
1863 const int64_t in_cost = evaluator_(node, next_out, path);
1864 const int64_t out_cost = evaluator_(out, next_out, path);
1865 if (CapAdd(CapSub(gain, in_cost), out_cost) > 0) return true;
1866 node = out;
1867 if (IsPathEnd(node)) return false;
1868 next = next_out;
1869 if (IsPathEnd(next)) return false;
1870 }
1871 // Try 2opts
1872 while (InFromOut(node, next, &out, &gain)) {
1873 marked_.insert(next);
1874 marked_.insert(out);
1875 int64_t chain_last;
1876 if (!ReverseChain(node, out, &chain_last)) {
1877 return false;
1878 }
1879 int64_t in_cost = evaluator_(base, chain_last, path);
1880 int64_t out_cost = evaluator_(chain_last, out, path);
1881 if (CapAdd(CapSub(gain, in_cost), out_cost) > 0) {
1882 return true;
1883 }
1884 node = chain_last;
1885 if (IsPathEnd(node)) {
1886 return false;
1887 }
1888 next = out;
1889 if (IsPathEnd(next)) {
1890 return false;
1891 }
1892 }
1893 return false;
1894}
1895
1896const int LinKernighan::kNeighbors = 5 + 1;
1897
1898bool LinKernighan::InFromOut(int64_t in_i, int64_t in_j, int64_t* out,
1899 int64_t* gain) {
1900 const std::vector<int>& nexts = neighbors_.Neighbors(in_j);
1901 int64_t best_gain = std::numeric_limits<int64_t>::min();
1902 int64_t path = Path(in_i);
1903 int64_t out_cost = evaluator_(in_i, in_j, path);
1904 const int64_t current_gain = CapAdd(*gain, out_cost);
1905 for (int k = 0; k < nexts.size(); ++k) {
1906 const int64_t next = nexts[k];
1907 if (next != in_j) {
1908 int64_t in_cost = evaluator_(in_j, next, path);
1909 int64_t new_gain = CapSub(current_gain, in_cost);
1910 if (new_gain > 0 && next != Next(in_j) && marked_.count(in_j) == 0 &&
1911 marked_.count(next) == 0) {
1912 if (best_gain < new_gain) {
1913 *out = next;
1914 best_gain = new_gain;
1915 }
1916 }
1917 }
1918 }
1919 *gain = best_gain;
1920 return (best_gain > std::numeric_limits<int64_t>::min());
1921}
1922
1923// ----- Path-based Large Neighborhood Search -----
1924
1925// Breaks "number_of_chunks" chains of "chunk_size" arcs, and deactivate all
1926// inactive nodes if "unactive_fragments" is true.
1927// As a special case, if chunk_size=0, then we break full paths.
1928
1929class PathLns : public PathOperator {
1930 public:
1931 PathLns(const std::vector<IntVar*>& vars,
1932 const std::vector<IntVar*>& secondary_vars, int number_of_chunks,
1933 int chunk_size, bool unactive_fragments)
1934 : PathOperator(vars, secondary_vars, number_of_chunks, true, true,
1935 nullptr, nullptr),
1936 number_of_chunks_(number_of_chunks),
1937 chunk_size_(chunk_size),
1938 unactive_fragments_(unactive_fragments) {
1939 CHECK_GE(chunk_size_, 0);
1940 }
1941 ~PathLns() override {}
1942 bool MakeNeighbor() override;
1943
1944 std::string DebugString() const override { return "PathLns"; }
1945 bool HasFragments() const override { return true; }
1946
1947 private:
1948 inline bool ChainsAreFullPaths() const { return chunk_size_ == 0; }
1949 void DeactivateChain(int64_t node);
1950 void DeactivateUnactives();
1951
1952 const int number_of_chunks_;
1953 const int chunk_size_;
1954 const bool unactive_fragments_;
1955};
1956
1958 if (ChainsAreFullPaths()) {
1959 // Reject the current position as a neighbor if any of its base node
1960 // isn't at the start of a path.
1961 // TODO(user): make this more efficient.
1962 for (int i = 0; i < number_of_chunks_; ++i) {
1963 if (BaseNode(i) != StartNode(i)) return false;
1964 }
1965 }
1966 for (int i = 0; i < number_of_chunks_; ++i) {
1967 DeactivateChain(BaseNode(i));
1968 }
1969 DeactivateUnactives();
1970 return true;
1971}
1972
1973void PathLns::DeactivateChain(int64_t node) {
1974 for (int i = 0, current = node;
1975 (ChainsAreFullPaths() || i < chunk_size_) && !IsPathEnd(current);
1976 ++i, current = Next(current)) {
1977 Deactivate(current);
1978 if (!ignore_path_vars_) {
1979 Deactivate(number_of_nexts_ + current);
1980 }
1981 }
1982}
1983
1984void PathLns::DeactivateUnactives() {
1985 if (unactive_fragments_) {
1986 for (int i = 0; i < Size(); ++i) {
1987 if (IsInactive(i)) {
1988 Deactivate(i);
1989 if (!ignore_path_vars_) {
1991 }
1992 }
1993 }
1994 }
1995}
1996
1997// ----- Limit the number of neighborhoods explored -----
1998
2000 public:
2001 NeighborhoodLimit(LocalSearchOperator* const op, int64_t limit)
2002 : operator_(op), limit_(limit), next_neighborhood_calls_(0) {
2003 CHECK(op != nullptr);
2004 CHECK_GT(limit, 0);
2005 }
2006
2007 void Start(const Assignment* assignment) override {
2008 next_neighborhood_calls_ = 0;
2009 operator_->Start(assignment);
2010 }
2011
2012 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override {
2013 if (next_neighborhood_calls_ >= limit_) {
2014 return false;
2015 }
2016 ++next_neighborhood_calls_;
2017 return operator_->MakeNextNeighbor(delta, deltadelta);
2018 }
2019
2020 bool HoldsDelta() const override { return operator_->HoldsDelta(); }
2021
2022 std::string DebugString() const override { return "NeighborhoodLimit"; }
2023
2024 private:
2025 LocalSearchOperator* const operator_;
2026 const int64_t limit_;
2027 int64_t next_neighborhood_calls_;
2028};
2029
2031 LocalSearchOperator* const op, int64_t limit) {
2032 return RevAlloc(new NeighborhoodLimit(op, limit));
2033}
2034
2035// ----- Concatenation of operators -----
2036
2037namespace {
2038class CompoundOperator : public LocalSearchOperator {
2039 public:
2040 CompoundOperator(std::vector<LocalSearchOperator*> operators,
2041 std::function<int64_t(int, int)> evaluator);
2042 ~CompoundOperator() override {}
2043 void Reset() override;
2044 void Start(const Assignment* assignment) override;
2045 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
2046 bool HasFragments() const override { return has_fragments_; }
2047 bool HoldsDelta() const override { return true; }
2048
2049 std::string DebugString() const override {
2050 return operators_.empty()
2051 ? ""
2052 : operators_[operator_indices_[index_]]->DebugString();
2053 }
2054 const LocalSearchOperator* Self() const override {
2055 return operators_.empty() ? this
2056 : operators_[operator_indices_[index_]]->Self();
2057 }
2058
2059 private:
2060 class OperatorComparator {
2061 public:
2062 OperatorComparator(std::function<int64_t(int, int)> evaluator,
2063 int active_operator)
2064 : evaluator_(std::move(evaluator)), active_operator_(active_operator) {}
2065 bool operator()(int lhs, int rhs) const {
2066 const int64_t lhs_value = Evaluate(lhs);
2067 const int64_t rhs_value = Evaluate(rhs);
2068 return lhs_value < rhs_value || (lhs_value == rhs_value && lhs < rhs);
2069 }
2070
2071 private:
2072 int64_t Evaluate(int operator_index) const {
2073 return evaluator_(active_operator_, operator_index);
2074 }
2075
2076 std::function<int64_t(int, int)> evaluator_;
2077 const int active_operator_;
2078 };
2079
2080 int64_t index_;
2081 std::vector<LocalSearchOperator*> operators_;
2082 std::vector<int> operator_indices_;
2083 std::function<int64_t(int, int)> evaluator_;
2084 Bitset64<> started_;
2085 const Assignment* start_assignment_;
2086 bool has_fragments_;
2087};
2088
2089CompoundOperator::CompoundOperator(std::vector<LocalSearchOperator*> operators,
2090 std::function<int64_t(int, int)> evaluator)
2091 : index_(0),
2092 operators_(std::move(operators)),
2093 evaluator_(std::move(evaluator)),
2094 started_(operators_.size()),
2095 start_assignment_(nullptr),
2096 has_fragments_(false) {
2097 operators_.erase(std::remove(operators_.begin(), operators_.end(), nullptr),
2098 operators_.end());
2099 operator_indices_.resize(operators_.size());
2100 std::iota(operator_indices_.begin(), operator_indices_.end(), 0);
2101 for (LocalSearchOperator* const op : operators_) {
2102 if (op->HasFragments()) {
2103 has_fragments_ = true;
2104 break;
2105 }
2106 }
2107}
2108
2109void CompoundOperator::Reset() {
2110 for (LocalSearchOperator* const op : operators_) {
2111 op->Reset();
2112 }
2113}
2114
2115void CompoundOperator::Start(const Assignment* assignment) {
2116 start_assignment_ = assignment;
2117 started_.ClearAll();
2118 if (!operators_.empty()) {
2119 OperatorComparator comparator(evaluator_, operator_indices_[index_]);
2120 std::sort(operator_indices_.begin(), operator_indices_.end(), comparator);
2121 index_ = 0;
2122 }
2123}
2124
2125bool CompoundOperator::MakeNextNeighbor(Assignment* delta,
2126 Assignment* deltadelta) {
2127 if (!operators_.empty()) {
2128 do {
2129 // TODO(user): keep copy of delta in case MakeNextNeighbor
2130 // pollutes delta on a fail.
2131 const int64_t operator_index = operator_indices_[index_];
2132 if (!started_[operator_index]) {
2133 operators_[operator_index]->Start(start_assignment_);
2134 started_.Set(operator_index);
2135 }
2136 if (!operators_[operator_index]->HoldsDelta()) {
2137 delta->Clear();
2138 }
2139 if (operators_[operator_index]->MakeNextNeighbor(delta, deltadelta)) {
2140 return true;
2141 }
2142 ++index_;
2143 delta->Clear();
2144 if (index_ == operators_.size()) {
2145 index_ = 0;
2146 }
2147 } while (index_ != 0);
2148 }
2149 return false;
2150}
2151
2152int64_t CompoundOperatorNoRestart(int size, int active_index,
2153 int operator_index) {
2154 return (operator_index < active_index) ? size + operator_index - active_index
2155 : operator_index - active_index;
2156}
2157
2158int64_t CompoundOperatorRestart(int, int) { return 0; }
2159} // namespace
2160
2162 const std::vector<LocalSearchOperator*>& ops) {
2163 return ConcatenateOperators(ops, false);
2164}
2165
2167 const std::vector<LocalSearchOperator*>& ops, bool restart) {
2168 if (restart) {
2169 std::function<int64_t(int, int)> eval = CompoundOperatorRestart;
2170 return ConcatenateOperators(ops, eval);
2171 }
2172 const int size = ops.size();
2173 return ConcatenateOperators(ops, [size](int i, int j) {
2174 return CompoundOperatorNoRestart(size, i, j);
2175 });
2176}
2177
2179 const std::vector<LocalSearchOperator*>& ops,
2180 std::function<int64_t(int, int)> evaluator) {
2181 return RevAlloc(new CompoundOperator(ops, std::move(evaluator)));
2182}
2183
2184namespace {
2185class RandomCompoundOperator : public LocalSearchOperator {
2186 public:
2187 explicit RandomCompoundOperator(std::vector<LocalSearchOperator*> operators);
2188 RandomCompoundOperator(std::vector<LocalSearchOperator*> operators,
2189 int32_t seed);
2190 ~RandomCompoundOperator() override {}
2191 void Reset() override;
2192 void Start(const Assignment* assignment) override;
2193 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
2194 bool HoldsDelta() const override { return true; }
2195
2196 std::string DebugString() const override { return "RandomCompoundOperator"; }
2197 // TODO(user): define Self method.
2198
2199 private:
2200 std::mt19937 rand_;
2201 const std::vector<LocalSearchOperator*> operators_;
2202 bool has_fragments_;
2203};
2204
2205void RandomCompoundOperator::Start(const Assignment* assignment) {
2206 for (LocalSearchOperator* const op : operators_) {
2207 op->Start(assignment);
2208 }
2209}
2210
2211RandomCompoundOperator::RandomCompoundOperator(
2212 std::vector<LocalSearchOperator*> operators)
2213 : RandomCompoundOperator(std::move(operators), CpRandomSeed()) {}
2214
2215RandomCompoundOperator::RandomCompoundOperator(
2216 std::vector<LocalSearchOperator*> operators, int32_t seed)
2217 : rand_(seed), operators_(std::move(operators)), has_fragments_(false) {
2218 for (LocalSearchOperator* const op : operators_) {
2219 if (op->HasFragments()) {
2220 has_fragments_ = true;
2221 break;
2222 }
2223 }
2224}
2225
2226void RandomCompoundOperator::Reset() {
2227 for (LocalSearchOperator* const op : operators_) {
2228 op->Reset();
2229 }
2230}
2231
2232bool RandomCompoundOperator::MakeNextNeighbor(Assignment* delta,
2233 Assignment* deltadelta) {
2234 const int size = operators_.size();
2235 std::vector<int> indices(size);
2236 std::iota(indices.begin(), indices.end(), 0);
2237 std::shuffle(indices.begin(), indices.end(), rand_);
2238 for (int index : indices) {
2239 if (!operators_[index]->HoldsDelta()) {
2240 delta->Clear();
2241 }
2242 if (operators_[index]->MakeNextNeighbor(delta, deltadelta)) {
2243 return true;
2244 }
2245 delta->Clear();
2246 }
2247 return false;
2248}
2249} // namespace
2250
2252 const std::vector<LocalSearchOperator*>& ops) {
2253 return RevAlloc(new RandomCompoundOperator(ops));
2254}
2255
2257 const std::vector<LocalSearchOperator*>& ops, int32_t seed) {
2258 return RevAlloc(new RandomCompoundOperator(ops, seed));
2259}
2260
2261namespace {
2262class MultiArmedBanditCompoundOperator : public LocalSearchOperator {
2263 public:
2264 explicit MultiArmedBanditCompoundOperator(
2265 std::vector<LocalSearchOperator*> operators, double memory_coefficient,
2266 double exploration_coefficient, bool maximize);
2267 ~MultiArmedBanditCompoundOperator() override {}
2268 void Reset() override;
2269 void Start(const Assignment* assignment) override;
2270 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
2271 bool HoldsDelta() const override { return true; }
2272
2273 std::string DebugString() const override {
2274 return operators_.empty()
2275 ? ""
2276 : operators_[operator_indices_[index_]]->DebugString();
2277 }
2278 const LocalSearchOperator* Self() const override {
2279 return operators_.empty() ? this
2280 : operators_[operator_indices_[index_]]->Self();
2281 }
2282
2283 private:
2284 double Score(int index);
2285 int index_;
2286 std::vector<LocalSearchOperator*> operators_;
2287 Bitset64<> started_;
2288 const Assignment* start_assignment_;
2289 bool has_fragments_;
2290 std::vector<int> operator_indices_;
2291 int64_t last_objective_;
2292 std::vector<double> avg_improvement_;
2293 int num_neighbors_;
2294 std::vector<double> num_neighbors_per_operator_;
2295 const bool maximize_;
2296 // Sets how much the objective improvement of previous accepted neighbors
2297 // influence the current average improvement. The formula is
2298 // avg_improvement +=
2299 // memory_coefficient * (current_improvement - avg_improvement).
2300 const double memory_coefficient_;
2301 // Sets how often we explore rarely used and unsuccessful in the past
2302 // operators. Operators are sorted by
2303 // avg_improvement_[i] + exploration_coefficient_ *
2304 // sqrt(2 * log(1 + num_neighbors_) / (1 + num_neighbors_per_operator_[i])).
2305 // This definition uses the UCB1 exploration bonus for unstructured
2306 // multi-armed bandits.
2307 const double exploration_coefficient_;
2308};
2309
2310MultiArmedBanditCompoundOperator::MultiArmedBanditCompoundOperator(
2311 std::vector<LocalSearchOperator*> operators, double memory_coefficient,
2312 double exploration_coefficient, bool maximize)
2313 : index_(0),
2314 operators_(std::move(operators)),
2315 started_(operators_.size()),
2316 start_assignment_(nullptr),
2317 has_fragments_(false),
2318 last_objective_(std::numeric_limits<int64_t>::max()),
2319 num_neighbors_(0),
2320 maximize_(maximize),
2321 memory_coefficient_(memory_coefficient),
2322 exploration_coefficient_(exploration_coefficient) {
2323 DCHECK_GE(memory_coefficient_, 0);
2324 DCHECK_LE(memory_coefficient_, 1);
2325 DCHECK_GE(exploration_coefficient_, 0);
2326 operators_.erase(std::remove(operators_.begin(), operators_.end(), nullptr),
2327 operators_.end());
2328 operator_indices_.resize(operators_.size());
2329 std::iota(operator_indices_.begin(), operator_indices_.end(), 0);
2330 num_neighbors_per_operator_.resize(operators_.size(), 0);
2331 avg_improvement_.resize(operators_.size(), 0);
2332 for (LocalSearchOperator* const op : operators_) {
2333 if (op->HasFragments()) {
2334 has_fragments_ = true;
2335 break;
2336 }
2337 }
2338}
2339
2340void MultiArmedBanditCompoundOperator::Reset() {
2341 for (LocalSearchOperator* const op : operators_) {
2342 op->Reset();
2343 }
2344}
2345
2346double MultiArmedBanditCompoundOperator::Score(int index) {
2347 return avg_improvement_[index] +
2348 exploration_coefficient_ *
2349 sqrt(2 * log(1 + num_neighbors_) /
2350 (1 + num_neighbors_per_operator_[index]));
2351}
2352
2353void MultiArmedBanditCompoundOperator::Start(const Assignment* assignment) {
2354 start_assignment_ = assignment;
2355 started_.ClearAll();
2356 if (operators_.empty()) return;
2357
2358 const double objective = assignment->ObjectiveValue();
2359
2360 if (objective == last_objective_) return;
2361 // Skip a neighbor evaluation if last_objective_ hasn't been set yet.
2362 if (last_objective_ == std::numeric_limits<int64_t>::max()) {
2363 last_objective_ = objective;
2364 return;
2365 }
2366
2367 const double improvement =
2368 maximize_ ? objective - last_objective_ : last_objective_ - objective;
2369 if (improvement < 0) {
2370 return;
2371 }
2372 last_objective_ = objective;
2373 avg_improvement_[operator_indices_[index_]] +=
2374 memory_coefficient_ *
2375 (improvement - avg_improvement_[operator_indices_[index_]]);
2376
2377 std::sort(operator_indices_.begin(), operator_indices_.end(),
2378 [this](int lhs, int rhs) {
2379 const double lhs_score = Score(lhs);
2380 const double rhs_score = Score(rhs);
2381 return lhs_score > rhs_score ||
2382 (lhs_score == rhs_score && lhs < rhs);
2383 });
2384
2385 index_ = 0;
2386}
2387
2388bool MultiArmedBanditCompoundOperator::MakeNextNeighbor(
2389 Assignment* delta, Assignment* deltadelta) {
2390 if (operators_.empty()) return false;
2391 do {
2392 const int operator_index = operator_indices_[index_];
2393 if (!started_[operator_index]) {
2394 operators_[operator_index]->Start(start_assignment_);
2395 started_.Set(operator_index);
2396 }
2397 if (!operators_[operator_index]->HoldsDelta()) {
2398 delta->Clear();
2399 }
2400 if (operators_[operator_index]->MakeNextNeighbor(delta, deltadelta)) {
2401 ++num_neighbors_;
2402 ++num_neighbors_per_operator_[operator_index];
2403 return true;
2404 }
2405 ++index_;
2406 delta->Clear();
2407 if (index_ == operators_.size()) {
2408 index_ = 0;
2409 }
2410 } while (index_ != 0);
2411 return false;
2412}
2413} // namespace
2414
2416 const std::vector<LocalSearchOperator*>& ops, double memory_coefficient,
2417 double exploration_coefficient, bool maximize) {
2418 return RevAlloc(new MultiArmedBanditCompoundOperator(
2419 ops, memory_coefficient, exploration_coefficient, maximize));
2420}
2421
2422// ----- Operator factory -----
2423
2424template <class T>
2426 Solver* solver, const std::vector<IntVar*>& vars,
2427 const std::vector<IntVar*>& secondary_vars,
2428 std::function<int(int64_t)> start_empty_path_class) {
2429 return solver->RevAlloc(
2430 new T(vars, secondary_vars, std::move(start_empty_path_class), nullptr));
2431}
2432
2433template <class T>
2435 Solver* solver, const std::vector<IntVar*>& vars,
2436 const std::vector<IntVar*>& secondary_vars,
2437 std::function<int(int64_t)> start_empty_path_class,
2438 std::function<const std::vector<int>&(int, int)> get_neighbors) {
2439 return solver->RevAlloc(new T(vars, secondary_vars,
2440 std::move(start_empty_path_class),
2441 std::move(get_neighbors)));
2442}
2443
2444#define MAKE_LOCAL_SEARCH_OPERATOR(OperatorClass) \
2445 template <> \
2446 LocalSearchOperator* MakeLocalSearchOperator<OperatorClass>( \
2447 Solver * solver, const std::vector<IntVar*>& vars, \
2448 const std::vector<IntVar*>& secondary_vars, \
2449 std::function<int(int64_t)> start_empty_path_class) { \
2450 return solver->RevAlloc(new OperatorClass( \
2451 vars, secondary_vars, std::move(start_empty_path_class))); \
2452 }
2453
2454#define MAKE_LOCAL_SEARCH_OPERATOR_WITH_NEIGHBORS(OperatorClass) \
2455 template <> \
2456 LocalSearchOperator* MakeLocalSearchOperatorWithNeighbors<OperatorClass>( \
2457 Solver * solver, const std::vector<IntVar*>& vars, \
2458 const std::vector<IntVar*>& secondary_vars, \
2459 std::function<int(int64_t)> start_empty_path_class, \
2460 std::function<const std::vector<int>&(int, int)> get_neighbors) { \
2461 return solver->RevAlloc(new OperatorClass( \
2462 vars, secondary_vars, std::move(start_empty_path_class), \
2463 std::move(get_neighbors))); \
2464 }
2465
2474MAKE_LOCAL_SEARCH_OPERATOR(MakeActiveOperator)
2475MAKE_LOCAL_SEARCH_OPERATOR(MakeInactiveOperator)
2476MAKE_LOCAL_SEARCH_OPERATOR(MakeChainInactiveOperator)
2477MAKE_LOCAL_SEARCH_OPERATOR(SwapActiveOperator)
2478MAKE_LOCAL_SEARCH_OPERATOR(ExtendedSwapActiveOperator)
2479MAKE_LOCAL_SEARCH_OPERATOR(MakeActiveAndRelocate)
2480MAKE_LOCAL_SEARCH_OPERATOR(RelocateAndMakeActiveOperator)
2481MAKE_LOCAL_SEARCH_OPERATOR(RelocateAndMakeInactiveOperator)
2482
2483#undef MAKE_LOCAL_SEARCH_OPERATOR
2484
2485// TODO(user): Remove (parts of) the following methods as they are mostly
2486// redundant with the MakeLocalSearchOperatorWithNeighbors and
2487// MakeLocalSearchOperator functions.
2489 const std::vector<IntVar*>& vars, Solver::LocalSearchOperators op,
2490 std::function<const std::vector<int>&(int, int)> get_neighbors) {
2491 return MakeOperator(vars, std::vector<IntVar*>(), op, get_neighbors);
2492}
2493
2495 const std::vector<IntVar*>& vars,
2496 const std::vector<IntVar*>& secondary_vars, Solver::LocalSearchOperators op,
2497 std::function<const std::vector<int>&(int, int)> get_neighbors) {
2498 switch (op) {
2499 case Solver::TWOOPT: {
2501 this, vars, secondary_vars, nullptr, std::move(get_neighbors));
2502 }
2503 case Solver::OROPT: {
2504 std::vector<LocalSearchOperator*> operators;
2505 for (int i = 1; i < 4; ++i) {
2506 operators.push_back(
2507 RevAlloc(new Relocate(vars, secondary_vars,
2508 /*name=*/absl::StrCat("OrOpt<", i, ">"),
2509 /*start_empty_path_class=*/nullptr,
2510 /*get_neighbors=*/nullptr, /*chain_length=*/i,
2511 /*single_path=*/true)));
2512 }
2513 return ConcatenateOperators(operators);
2514 }
2515 case Solver::RELOCATE: {
2517 this, vars, secondary_vars, nullptr, std::move(get_neighbors));
2518 }
2519 case Solver::EXCHANGE: {
2521 this, vars, secondary_vars, nullptr, std::move(get_neighbors));
2522 }
2523 case Solver::CROSS: {
2525 this, vars, secondary_vars, nullptr, std::move(get_neighbors));
2526 }
2527 case Solver::MAKEACTIVE: {
2529 this, vars, secondary_vars, nullptr);
2530 }
2531 case Solver::MAKEINACTIVE: {
2533 this, vars, secondary_vars, nullptr);
2534 }
2537 this, vars, secondary_vars, nullptr);
2538 }
2539 case Solver::SWAPACTIVE: {
2541 this, vars, secondary_vars, nullptr);
2542 }
2545 this, vars, secondary_vars, nullptr);
2546 }
2547 case Solver::PATHLNS: {
2548 return RevAlloc(new PathLns(vars, secondary_vars, 2, 3, false));
2549 }
2550 case Solver::FULLPATHLNS: {
2551 return RevAlloc(new PathLns(vars, secondary_vars,
2552 /*number_of_chunks=*/1,
2553 /*chunk_size=*/0,
2554 /*unactive_fragments=*/true));
2555 }
2556 case Solver::UNACTIVELNS: {
2557 return RevAlloc(new PathLns(vars, secondary_vars, 1, 6, true));
2558 }
2559 case Solver::INCREMENT: {
2560 if (!secondary_vars.empty()) {
2561 LOG(FATAL) << "Operator " << op
2562 << " does not support secondary variables";
2563 }
2564 return RevAlloc(new IncrementValue(vars));
2565 }
2566 case Solver::DECREMENT: {
2567 if (!secondary_vars.empty()) {
2568 LOG(FATAL) << "Operator " << op
2569 << " does not support secondary variables";
2570 }
2571 return RevAlloc(new DecrementValue(vars));
2572 }
2573 case Solver::SIMPLELNS: {
2574 if (!secondary_vars.empty()) {
2575 LOG(FATAL) << "Operator " << op
2576 << " does not support secondary variables";
2577 }
2578 return RevAlloc(new SimpleLns(vars, 1));
2579 }
2580 default:
2581 LOG(FATAL) << "Unknown operator " << op;
2582 }
2583 return nullptr;
2584}
2585
2587 const std::vector<IntVar*>& vars, Solver::IndexEvaluator3 evaluator,
2589 return MakeOperator(vars, std::vector<IntVar*>(), std::move(evaluator), op);
2590}
2591
2593 const std::vector<IntVar*>& vars,
2594 const std::vector<IntVar*>& secondary_vars,
2595 Solver::IndexEvaluator3 evaluator,
2597 switch (op) {
2598 case Solver::LK: {
2599 std::vector<LocalSearchOperator*> operators;
2600 operators.push_back(RevAlloc(
2601 new LinKernighan(vars, secondary_vars, evaluator, /*topt=*/false)));
2602 operators.push_back(RevAlloc(
2603 new LinKernighan(vars, secondary_vars, evaluator, /*topt=*/true)));
2604 return ConcatenateOperators(operators);
2605 }
2606 case Solver::TSPOPT: {
2607 return RevAlloc(
2608 new TSPOpt(vars, secondary_vars, std::move(evaluator),
2609 absl::GetFlag(FLAGS_cp_local_search_tsp_opt_size)));
2610 }
2611 case Solver::TSPLNS: {
2612 return RevAlloc(
2613 new TSPLns(vars, secondary_vars, std::move(evaluator),
2614 absl::GetFlag(FLAGS_cp_local_search_tsp_lns_size)));
2615 }
2616 default:
2617 LOG(FATAL) << "Unknown operator " << op;
2618 }
2619 return nullptr;
2620}
2621
2622namespace {
2623// Always accepts deltas, cost 0.
2624class AcceptFilter : public LocalSearchFilter {
2625 public:
2626 std::string DebugString() const override { return "AcceptFilter"; }
2627 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
2628 return true;
2629 }
2630 void Synchronize(const Assignment*, const Assignment*) override {}
2631};
2632} // namespace
2633
2635 return RevAlloc(new AcceptFilter());
2636}
2637
2638namespace {
2639// Never accepts deltas, cost 0.
2640class RejectFilter : public LocalSearchFilter {
2641 public:
2642 std::string DebugString() const override { return "RejectFilter"; }
2643 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
2644 return false;
2645 }
2646 void Synchronize(const Assignment*, const Assignment*) override {}
2647};
2648} // namespace
2649
2651 return RevAlloc(new RejectFilter());
2652}
2653
2654PathState::PathState(int num_nodes, std::vector<int> path_start,
2655 std::vector<int> path_end)
2656 : num_nodes_(num_nodes),
2657 num_paths_(path_start.size()),
2658 num_nodes_threshold_(std::max(16, 4 * num_nodes_)) // Arbitrary value.
2659{
2660 DCHECK_EQ(path_start.size(), num_paths_);
2661 DCHECK_EQ(path_end.size(), num_paths_);
2662 for (int p = 0; p < num_paths_; ++p) {
2663 path_start_end_.push_back({path_start[p], path_end[p]});
2664 }
2665 // Initial state is all unperformed: paths go from start to end directly.
2666 committed_index_.assign(num_nodes_, -1);
2667 committed_paths_.assign(num_nodes_, -1);
2668 committed_nodes_.assign(2 * num_paths_, -1);
2669 chains_.assign(num_paths_ + 1, {-1, -1}); // Reserve 1 more for sentinel.
2670 paths_.assign(num_paths_, {-1, -1});
2671 for (int path = 0; path < num_paths_; ++path) {
2672 const int index = 2 * path;
2673 const auto& [start, end] = path_start_end_[path];
2674 committed_index_[start] = index;
2675 committed_index_[end] = index + 1;
2676
2677 committed_nodes_[index] = start;
2678 committed_nodes_[index + 1] = end;
2679
2680 committed_paths_[start] = path;
2681 committed_paths_[end] = path;
2682
2683 chains_[path] = {index, index + 2};
2684 paths_[path] = {path, path + 1};
2685 }
2686 chains_[num_paths_] = {0, 0}; // Sentinel.
2687 // Nodes that are not starts or ends are loops.
2688 for (int node = 0; node < num_nodes_; ++node) {
2689 if (committed_index_[node] != -1) continue; // node is start or end.
2690 committed_index_[node] = committed_nodes_.size();
2691 committed_nodes_.push_back(node);
2692 }
2693}
2694
2696 const PathBounds bounds = paths_[path];
2697 return PathState::ChainRange(chains_.data() + bounds.begin_index,
2698 chains_.data() + bounds.end_index,
2699 committed_nodes_.data());
2700}
2701
2703 const PathBounds bounds = paths_[path];
2704 return PathState::NodeRange(chains_.data() + bounds.begin_index,
2705 chains_.data() + bounds.end_index,
2706 committed_nodes_.data());
2707}
2708
2709void PathState::ChangePath(int path, const std::vector<ChainBounds>& chains) {
2710 changed_paths_.push_back(path);
2711 const int path_begin_index = chains_.size();
2712 chains_.insert(chains_.end(), chains.begin(), chains.end());
2713 const int path_end_index = chains_.size();
2714 paths_[path] = {path_begin_index, path_end_index};
2715 chains_.emplace_back(0, 0); // Sentinel.
2716}
2717
2718void PathState::ChangeLoops(const std::vector<int>& new_loops) {
2719 for (const int loop : new_loops) {
2720 if (Path(loop) == -1) continue;
2721 changed_loops_.push_back(loop);
2722 }
2723}
2724
2726 DCHECK(!IsInvalid());
2727 if (committed_nodes_.size() < num_nodes_threshold_) {
2728 IncrementalCommit();
2729 } else {
2730 FullCommit();
2731 }
2732}
2733
2735 is_invalid_ = false;
2736 chains_.resize(num_paths_ + 1); // One per path + sentinel.
2737 for (const int path : changed_paths_) {
2738 paths_[path] = {path, path + 1};
2739 }
2740 changed_paths_.clear();
2741 changed_loops_.clear();
2742}
2743
2744void PathState::CopyNewPathAtEndOfNodes(int path) {
2745 // Copy path's nodes, chain by chain.
2746 const PathBounds path_bounds = paths_[path];
2747 for (int i = path_bounds.begin_index; i < path_bounds.end_index; ++i) {
2748 const ChainBounds chain_bounds = chains_[i];
2749 committed_nodes_.insert(committed_nodes_.end(),
2750 committed_nodes_.data() + chain_bounds.begin_index,
2751 committed_nodes_.data() + chain_bounds.end_index);
2752 if (committed_paths_[committed_nodes_.back()] == path) continue;
2753 for (int i = chain_bounds.begin_index; i < chain_bounds.end_index; ++i) {
2754 const int node = committed_nodes_[i];
2755 committed_paths_[node] = path;
2756 }
2757 }
2758}
2759
2760// TODO(user): Instead of copying paths at the end systematically,
2761// reuse some of the memory when possible.
2762void PathState::IncrementalCommit() {
2763 const int new_nodes_begin = committed_nodes_.size();
2764 for (const int path : ChangedPaths()) {
2765 const int chain_begin = committed_nodes_.size();
2766 CopyNewPathAtEndOfNodes(path);
2767 const int chain_end = committed_nodes_.size();
2768 chains_[path] = {chain_begin, chain_end};
2769 }
2770 // Re-index all copied nodes.
2771 const int new_nodes_end = committed_nodes_.size();
2772 for (int i = new_nodes_begin; i < new_nodes_end; ++i) {
2773 const int node = committed_nodes_[i];
2774 committed_index_[node] = i;
2775 }
2776 // New loops stay in place: only change their path to -1,
2777 // committed_index_ does not change.
2778 for (const int loop : ChangedLoops()) {
2779 committed_paths_[loop] = -1;
2780 }
2781 // Committed part of the state is set up, erase incremental changes.
2782 Revert();
2783}
2784
2785void PathState::FullCommit() {
2786 // Copy all paths at the end of committed_nodes_,
2787 // then remove all old committed_nodes_.
2788 const int old_num_nodes = committed_nodes_.size();
2789 for (int path = 0; path < num_paths_; ++path) {
2790 const int new_path_begin = committed_nodes_.size() - old_num_nodes;
2791 CopyNewPathAtEndOfNodes(path);
2792 const int new_path_end = committed_nodes_.size() - old_num_nodes;
2793 chains_[path] = {new_path_begin, new_path_end};
2794 }
2795 committed_nodes_.erase(committed_nodes_.begin(),
2796 committed_nodes_.begin() + old_num_nodes);
2797
2798 // Reindex path nodes, then loop nodes.
2799 constexpr int kUnindexed = -1;
2800 committed_index_.assign(num_nodes_, kUnindexed);
2801 int index = 0;
2802 for (const int node : committed_nodes_) {
2803 committed_index_[node] = index++;
2804 }
2805 for (int node = 0; node < num_nodes_; ++node) {
2806 if (committed_index_[node] != kUnindexed) continue;
2807 committed_index_[node] = index++;
2808 committed_nodes_.push_back(node);
2809 committed_paths_[node] = -1;
2810 }
2811 // Committed part of the state is set up, erase incremental changes.
2812 Revert();
2813}
2814
2815namespace {
2816
2817class PathStateFilter : public LocalSearchFilter {
2818 public:
2819 std::string DebugString() const override { return "PathStateFilter"; }
2820 PathStateFilter(std::unique_ptr<PathState> path_state,
2821 const std::vector<IntVar*>& nexts);
2822 void Relax(const Assignment* delta, const Assignment* deltadelta) override;
2823 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
2824 return true;
2825 }
2826 void Synchronize(const Assignment*, const Assignment*) override{};
2827 void Commit(const Assignment* assignment, const Assignment* delta) override;
2828 void Revert() override;
2829 void Reset() override;
2830
2831 private:
2832 // Used in arc to chain translation, see below.
2833 struct TailHeadIndices {
2836 };
2837 struct IndexArc {
2839 int arc;
2840 bool operator<(const IndexArc& other) const { return index < other.index; }
2841 };
2842
2843 // Translate changed_arcs_ to chains, pass to underlying PathState.
2844 void CutChains();
2845 // From changed_paths_ and changed_arcs_, fill chains_ and paths_.
2846 // Selection-based algorithm in O(n^2), to use for small change sets.
2847 void MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm();
2848 // From changed_paths_ and changed_arcs_, fill chains_ and paths_.
2849 // Generic algorithm in O(sort(n)+n), to use for larger change sets.
2850 void MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm();
2851
2852 const std::unique_ptr<PathState> path_state_;
2853 // Map IntVar* index to node, offset by the min index in nexts.
2854 std::vector<int> variable_index_to_node_;
2855 int index_offset_;
2856 // Used only in Reset(), class member status avoids reallocations.
2857 std::vector<bool> node_is_assigned_;
2858 std::vector<int> loops_;
2859
2860 // Used in CutChains(), class member status avoids reallocations.
2861 std::vector<int> changed_paths_;
2862 std::vector<bool> path_has_changed_;
2863 std::vector<std::pair<int, int>> changed_arcs_;
2864 std::vector<int> changed_loops_;
2865 std::vector<TailHeadIndices> tail_head_indices_;
2866 std::vector<IndexArc> arcs_by_tail_index_;
2867 std::vector<IndexArc> arcs_by_head_index_;
2868 std::vector<int> next_arc_;
2869 std::vector<PathState::ChainBounds> path_chains_;
2870};
2871
2872PathStateFilter::PathStateFilter(std::unique_ptr<PathState> path_state,
2873 const std::vector<IntVar*>& nexts)
2874 : path_state_(std::move(path_state)) {
2875 {
2876 int min_index = std::numeric_limits<int>::max();
2877 int max_index = std::numeric_limits<int>::min();
2878 for (const IntVar* next : nexts) {
2879 const int index = next->index();
2880 min_index = std::min<int>(min_index, index);
2881 max_index = std::max<int>(max_index, index);
2882 }
2883 variable_index_to_node_.resize(max_index - min_index + 1, -1);
2884 index_offset_ = min_index;
2885 }
2886
2887 for (int node = 0; node < nexts.size(); ++node) {
2888 const int index = nexts[node]->index() - index_offset_;
2889 variable_index_to_node_[index] = node;
2890 }
2891 path_has_changed_.assign(path_state_->NumPaths(), false);
2892}
2893
2894void PathStateFilter::Relax(const Assignment* delta, const Assignment*) {
2895 path_state_->Revert();
2896 changed_arcs_.clear();
2897 for (const IntVarElement& var_value : delta->IntVarContainer().elements()) {
2898 if (var_value.Var() == nullptr) continue;
2899 const int index = var_value.Var()->index() - index_offset_;
2900 if (index < 0 || variable_index_to_node_.size() <= index) continue;
2901 const int node = variable_index_to_node_[index];
2902 if (node == -1) continue;
2903 if (var_value.Bound()) {
2904 changed_arcs_.emplace_back(node, var_value.Value());
2905 } else {
2906 path_state_->Revert();
2907 path_state_->SetInvalid();
2908 return;
2909 }
2910 }
2911 CutChains();
2912}
2913
2914void PathStateFilter::Reset() {
2915 path_state_->Revert();
2916 // Set all paths of path state to empty start -> end paths,
2917 // and all nonstart/nonend nodes to node -> node loops.
2918 const int num_nodes = path_state_->NumNodes();
2919 node_is_assigned_.assign(num_nodes, false);
2920 loops_.clear();
2921 const int num_paths = path_state_->NumPaths();
2922 for (int path = 0; path < num_paths; ++path) {
2923 const auto [start_index, end_index] = path_state_->CommittedPathRange(path);
2924 path_state_->ChangePath(
2925 path, {{start_index, start_index + 1}, {end_index - 1, end_index}});
2926 node_is_assigned_[path_state_->Start(path)] = true;
2927 node_is_assigned_[path_state_->End(path)] = true;
2928 }
2929 for (int node = 0; node < num_nodes; ++node) {
2930 if (!node_is_assigned_[node]) loops_.push_back(node);
2931 }
2932 path_state_->ChangeLoops(loops_);
2933 path_state_->Commit();
2934}
2935
2936// The solver does not guarantee that a given Commit() corresponds to
2937// the previous Relax() (or that there has been a call to Relax()),
2938// so we replay the full change call sequence.
2939void PathStateFilter::Commit(const Assignment* assignment,
2940 const Assignment* delta) {
2941 path_state_->Revert();
2942 if (delta == nullptr || delta->Empty()) {
2943 Relax(assignment, nullptr);
2944 } else {
2945 Relax(delta, nullptr);
2946 }
2947 path_state_->Commit();
2948}
2949
2950void PathStateFilter::Revert() { path_state_->Revert(); }
2951
2952void PathStateFilter::CutChains() {
2953 // Filter out unchanged arcs from changed_arcs_,
2954 // translate changed arcs to changed arc indices.
2955 // Fill changed_paths_ while we hold node_path.
2956 for (const int path : changed_paths_) path_has_changed_[path] = false;
2957 changed_paths_.clear();
2958 tail_head_indices_.clear();
2959 changed_loops_.clear();
2960 int num_changed_arcs = 0;
2961 for (const auto [node, next] : changed_arcs_) {
2962 const int node_index = path_state_->CommittedIndex(node);
2963 const int next_index = path_state_->CommittedIndex(next);
2964 const int node_path = path_state_->Path(node);
2965 if (next != node &&
2966 (next_index != node_index + 1 || node_path == -1)) { // New arc.
2967 tail_head_indices_.push_back({node_index, next_index});
2968 changed_arcs_[num_changed_arcs++] = {node, next};
2969 if (node_path != -1 && !path_has_changed_[node_path]) {
2970 path_has_changed_[node_path] = true;
2971 changed_paths_.push_back(node_path);
2972 }
2973 } else if (node == next && node_path != -1) { // New loop.
2974 changed_loops_.push_back(node);
2975 }
2976 }
2977 changed_arcs_.resize(num_changed_arcs);
2978
2979 path_state_->ChangeLoops(changed_loops_);
2980 if (tail_head_indices_.size() + changed_paths_.size() <= 8) {
2981 MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm();
2982 } else {
2983 MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm();
2984 }
2985}
2986
2987void PathStateFilter::
2988 MakeChainsFromChangedPathsAndArcsWithSelectionAlgorithm() {
2989 int num_visited_changed_arcs = 0;
2990 const int num_changed_arcs = tail_head_indices_.size();
2991 // For every path, find all its chains.
2992 for (const int path : changed_paths_) {
2993 path_chains_.clear();
2994 const auto [start_index, end_index] = path_state_->CommittedPathRange(path);
2995 int current_index = start_index;
2996 while (true) {
2997 // Look for smallest non-visited tail_index that is no smaller than
2998 // current_index.
2999 int selected_arc = -1;
3000 int selected_tail_index = std::numeric_limits<int>::max();
3001 for (int i = num_visited_changed_arcs; i < num_changed_arcs; ++i) {
3002 const int tail_index = tail_head_indices_[i].tail_index;
3003 if (current_index <= tail_index && tail_index < selected_tail_index) {
3004 selected_arc = i;
3005 selected_tail_index = tail_index;
3006 }
3007 }
3008 // If there is no such tail index, or more generally if the next chain
3009 // would be cut by end of path,
3010 // stack {current_index, end_index + 1} in chains_, and go to next path.
3011 // Otherwise, stack {current_index, tail_index+1} in chains_,
3012 // set current_index = head_index, set pair to visited.
3013 if (start_index <= current_index && current_index < end_index &&
3014 end_index <= selected_tail_index) {
3015 path_chains_.emplace_back(current_index, end_index);
3016 break;
3017 } else {
3018 path_chains_.emplace_back(current_index, selected_tail_index + 1);
3019 current_index = tail_head_indices_[selected_arc].head_index;
3020 std::swap(tail_head_indices_[num_visited_changed_arcs],
3021 tail_head_indices_[selected_arc]);
3022 ++num_visited_changed_arcs;
3023 }
3024 }
3025 path_state_->ChangePath(path, path_chains_);
3026 }
3027}
3028
3029void PathStateFilter::MakeChainsFromChangedPathsAndArcsWithGenericAlgorithm() {
3030 // TRICKY: For each changed path, we want to generate a sequence of chains
3031 // that represents the path in the changed state.
3032 // First, notice that if we add a fake end->start arc for each changed path,
3033 // then all chains will be from the head of an arc to the tail of an arc.
3034 // A way to generate the changed chains and paths would be, for each path,
3035 // to start from a fake arc's head (the path start), go down the path until
3036 // the tail of an arc, and go to the next arc until we return on the fake arc,
3037 // enqueuing the [head, tail] chains as we go.
3038 // In turn, to do that, we need to know which arc to go to.
3039 // If we sort all heads and tails by index in two separate arrays,
3040 // the head_index and tail_index at the same rank are such that
3041 // [head_index, tail_index] is a chain. Moreover, the arc that must be visited
3042 // after head_index's arc is tail_index's arc.
3043
3044 // Add a fake end->start arc for each path.
3045 for (const int path : changed_paths_) {
3046 const auto [start_index, end_index] = path_state_->CommittedPathRange(path);
3047 tail_head_indices_.push_back({end_index - 1, start_index});
3048 }
3049
3050 // Generate pairs (tail_index, arc) and (head_index, arc) for all arcs,
3051 // sort those pairs by index.
3052 const int num_arc_indices = tail_head_indices_.size();
3053 arcs_by_tail_index_.resize(num_arc_indices);
3054 arcs_by_head_index_.resize(num_arc_indices);
3055 for (int i = 0; i < num_arc_indices; ++i) {
3056 arcs_by_tail_index_[i] = {tail_head_indices_[i].tail_index, i};
3057 arcs_by_head_index_[i] = {tail_head_indices_[i].head_index, i};
3058 }
3059 std::sort(arcs_by_tail_index_.begin(), arcs_by_tail_index_.end());
3060 std::sort(arcs_by_head_index_.begin(), arcs_by_head_index_.end());
3061 // Generate the map from arc to next arc in path.
3062 next_arc_.resize(num_arc_indices);
3063 for (int i = 0; i < num_arc_indices; ++i) {
3064 next_arc_[arcs_by_head_index_[i].arc] = arcs_by_tail_index_[i].arc;
3065 }
3066
3067 // Generate chains: for every changed path, start from its fake arc,
3068 // jump to next_arc_ until going back to fake arc,
3069 // enqueuing chains as we go.
3070 const int first_fake_arc = num_arc_indices - changed_paths_.size();
3071 for (int fake_arc = first_fake_arc; fake_arc < num_arc_indices; ++fake_arc) {
3072 path_chains_.clear();
3073 int32_t arc = fake_arc;
3074 do {
3075 const int chain_begin = tail_head_indices_[arc].head_index;
3076 arc = next_arc_[arc];
3077 const int chain_end = tail_head_indices_[arc].tail_index + 1;
3078 path_chains_.emplace_back(chain_begin, chain_end);
3079 } while (arc != fake_arc);
3080 const int path = changed_paths_[fake_arc - first_fake_arc];
3081 path_state_->ChangePath(path, path_chains_);
3082 }
3083}
3084
3085} // namespace
3086
3088 std::unique_ptr<PathState> path_state,
3089 const std::vector<IntVar*>& nexts) {
3090 PathStateFilter* filter = new PathStateFilter(std::move(path_state), nexts);
3091 return solver->RevAlloc(filter);
3092}
3093
3094namespace {
3095using EInterval = DimensionChecker::ExtendedInterval;
3096
3097constexpr int64_t kint64min = std::numeric_limits<int64_t>::min();
3098constexpr int64_t kint64max = std::numeric_limits<int64_t>::max();
3099
3100EInterval operator&(const EInterval& i1, const EInterval& i2) {
3101 return {std::max(i1.num_negative_infinity == 0 ? i1.min : kint64min,
3102 i2.num_negative_infinity == 0 ? i2.min : kint64min),
3103 std::min(i1.num_positive_infinity == 0 ? i1.max : kint64max,
3104 i2.num_positive_infinity == 0 ? i2.max : kint64max),
3105 std::min(i1.num_negative_infinity, i2.num_negative_infinity),
3106 std::min(i1.num_positive_infinity, i2.num_positive_infinity)};
3107}
3108
3109EInterval operator&=(EInterval& i1, const EInterval& i2) {
3110 i1 = i1 & i2;
3111 return i1;
3112}
3113
3114bool IsEmpty(const EInterval& interval) {
3115 const int64_t minimum_value =
3116 interval.num_negative_infinity == 0 ? interval.min : kint64min;
3117 const int64_t maximum_value =
3118 interval.num_positive_infinity == 0 ? interval.max : kint64max;
3119 return minimum_value > maximum_value;
3120}
3121
3122EInterval operator+(const EInterval& i1, const EInterval& i2) {
3123 return {CapAdd(i1.min, i2.min), CapAdd(i1.max, i2.max),
3124 i1.num_negative_infinity + i2.num_negative_infinity,
3125 i1.num_positive_infinity + i2.num_positive_infinity};
3126}
3127
3128EInterval& operator+=(EInterval& i1, const EInterval& i2) {
3129 i1 = i1 + i2;
3130 return i1;
3131}
3132
3133EInterval operator-(const EInterval& i1, const EInterval& i2) {
3134 return {CapSub(i1.min, i2.max), CapSub(i1.max, i2.min),
3135 i1.num_negative_infinity + i2.num_positive_infinity,
3136 i1.num_positive_infinity + i2.num_negative_infinity};
3137}
3138
3139// Return the interval delta such that from + delta = to.
3140// Note that the result is not the same as "to + (-from)".
3141EInterval Delta(const EInterval& from, const EInterval& to) {
3142 return {CapSub(to.min, from.min), CapSub(to.max, from.max),
3143 to.num_negative_infinity - from.num_negative_infinity,
3144 to.num_positive_infinity - from.num_positive_infinity};
3145}
3146
3147EInterval ToExtendedInterval(DimensionChecker::Interval interval) {
3148 const bool is_neg_infinity = interval.min == kint64min;
3149 const bool is_pos_infinity = interval.max == kint64max;
3150 return {is_neg_infinity ? 0 : interval.min,
3151 is_pos_infinity ? 0 : interval.max, is_neg_infinity ? 1 : 0,
3152 is_pos_infinity ? 1 : 0};
3153}
3154
3155std::vector<EInterval> ToExtendedIntervals(
3156 absl::Span<const DimensionChecker::Interval> intervals) {
3157 std::vector<EInterval> extended_intervals;
3158 extended_intervals.reserve(intervals.size());
3159 for (const auto& interval : intervals) {
3160 extended_intervals.push_back(ToExtendedInterval(interval));
3161 }
3162 return extended_intervals;
3163}
3164} // namespace
3165
3166DimensionChecker::DimensionChecker(
3167 const PathState* path_state, std::vector<Interval> path_capacity,
3168 std::vector<int> path_class,
3169 std::vector<std::function<Interval(int64_t, int64_t)>>
3170 demand_per_path_class,
3171 std::vector<Interval> node_capacity, int min_range_size_for_riq)
3172 : path_state_(path_state),
3173 path_capacity_(ToExtendedIntervals(path_capacity)),
3174 path_class_(std::move(path_class)),
3175 demand_per_path_class_(std::move(demand_per_path_class)),
3176 node_capacity_(ToExtendedIntervals(node_capacity)),
3177 index_(path_state_->NumNodes(), 0),
3178 maximum_riq_layer_size_(std::max(
3179 16, 4 * path_state_->NumNodes())), // 16 and 4 are arbitrary.
3180 min_range_size_for_riq_(min_range_size_for_riq) {
3181 const int num_nodes = path_state_->NumNodes();
3182 cached_demand_.resize(num_nodes);
3183 const int num_paths = path_state_->NumPaths();
3184 DCHECK_EQ(num_paths, path_capacity_.size());
3185 DCHECK_EQ(num_paths, path_class_.size());
3186 const int maximum_riq_exponent = MostSignificantBitPosition32(num_nodes);
3187 riq_.resize(maximum_riq_exponent + 1);
3188 FullCommit();
3189}
3190
3192 if (path_state_->IsInvalid()) return true;
3193 for (const int path : path_state_->ChangedPaths()) {
3194 const EInterval path_capacity = path_capacity_[path];
3195 const int path_class = path_class_[path];
3196 // Loop invariant: except for the first chain, cumul represents the cumul
3197 // state of the last node of the previous chain, and it is nonempty.
3198 int prev_node = path_state_->Start(path);
3199 EInterval cumul = node_capacity_[prev_node] & path_capacity;
3200 if (IsEmpty(cumul)) return false;
3201
3202 for (const auto chain : path_state_->Chains(path)) {
3203 const int first_node = chain.First();
3204 const int last_node = chain.Last();
3205
3206 if (prev_node != first_node) {
3207 // Bring cumul state from last node of previous chain to first node of
3208 // current chain.
3209 const EInterval demand = ToExtendedInterval(
3210 demand_per_path_class_[path_class](prev_node, first_node));
3211 cumul += demand;
3212 cumul &= path_capacity;
3213 cumul &= node_capacity_[first_node];
3214 if (IsEmpty(cumul)) return false;
3215 prev_node = first_node;
3216 }
3217
3218 // Bring cumul state from first node to last node of the current chain.
3219 const int first_index = index_[first_node];
3220 const int last_index = index_[last_node];
3221 const int chain_path = path_state_->Path(first_node);
3222 const int chain_path_class =
3223 chain_path == -1 ? -1 : path_class_[chain_path];
3224 // Use a RIQ if the chain size is large enough;
3225 // the optimal size was found with the associated benchmark in tests,
3226 // in particular BM_DimensionChecker<ChangeSparsity::kSparse, *>.
3227 const bool chain_is_cached = chain_path_class == path_class;
3228 if (last_index - first_index > min_range_size_for_riq_ &&
3229 chain_is_cached) {
3230 UpdateCumulUsingChainRIQ(first_index, last_index, path_capacity, cumul);
3231 if (IsEmpty(cumul)) return false;
3232 prev_node = chain.Last();
3233 } else {
3234 for (const int node : chain.WithoutFirstNode()) {
3235 const EInterval demand =
3236 chain_is_cached
3237 ? cached_demand_[prev_node]
3238 : ToExtendedInterval(
3239 demand_per_path_class_[path_class](prev_node, node));
3240 cumul += demand;
3241 cumul &= node_capacity_[node];
3242 cumul &= path_capacity;
3243 if (IsEmpty(cumul)) return false;
3244 prev_node = node;
3245 }
3246 }
3247 }
3248 }
3249 return true;
3250}
3251
3253 const int current_layer_size = riq_[0].size();
3254 int change_size = path_state_->ChangedPaths().size();
3255 for (const int path : path_state_->ChangedPaths()) {
3256 for (const auto chain : path_state_->Chains(path)) {
3257 change_size += chain.NumNodes();
3258 }
3259 }
3260 if (current_layer_size + change_size <= maximum_riq_layer_size_) {
3261 IncrementalCommit();
3262 } else {
3263 FullCommit();
3264 }
3265}
3266
3267void DimensionChecker::IncrementalCommit() {
3268 for (const int path : path_state_->ChangedPaths()) {
3269 const int begin_index = riq_[0].size();
3270 AppendPathDemandsToSums(path);
3271 UpdateRIQStructure(begin_index, riq_[0].size());
3272 }
3273}
3274
3275void DimensionChecker::FullCommit() {
3276 // Clear all structures.
3277 for (auto& layer : riq_) layer.clear();
3278 // Append all paths.
3279 const int num_paths = path_state_->NumPaths();
3280 for (int path = 0; path < num_paths; ++path) {
3281 const int begin_index = riq_[0].size();
3282 AppendPathDemandsToSums(path);
3283 UpdateRIQStructure(begin_index, riq_[0].size());
3284 }
3285}
3286
3287void DimensionChecker::AppendPathDemandsToSums(int path) {
3288 // Value of forwards_demand_sums_riq_ at node_index must be the sum
3289 // of all demands of nodes from start of path to node.
3290 const int path_class = path_class_[path];
3291 EInterval demand_sum = {0, 0, 0, 0};
3292 int prev = path_state_->Start(path);
3293 int index = riq_[0].size();
3294 for (const int node : path_state_->Nodes(path)) {
3295 // Transition to current node.
3296 const EInterval demand =
3297 prev == node ? EInterval{0, 0, 0, 0}
3298 : ToExtendedInterval(
3299 demand_per_path_class_[path_class](prev, node));
3300 demand_sum += demand;
3301 cached_demand_[prev] = demand;
3302 prev = node;
3303 // Store all data of current node.
3304 index_[node] = index++;
3305 riq_[0].push_back({.cumuls_to_fst = node_capacity_[node],
3306 .tightest_tsum = demand_sum,
3307 .cumuls_to_lst = node_capacity_[node],
3308 .tsum_at_fst = demand_sum,
3309 .tsum_at_lst = demand_sum});
3310 }
3311 cached_demand_[path_state_->End(path)] = {0, 0, 0, 0};
3312}
3313
3314void DimensionChecker::UpdateRIQStructure(int begin_index, int end_index) {
3315 // The max layer is the one used by Range Intersection Query functions on
3316 // (begin_index, end_index - 1).
3317 const int max_layer =
3318 MostSignificantBitPosition32(end_index - begin_index - 1);
3319 for (int layer = 1, half_window = 1; layer <= max_layer;
3320 ++layer, half_window *= 2) {
3321 riq_[layer].resize(end_index);
3322 for (int i = begin_index + 2 * half_window - 1; i < end_index; ++i) {
3323 // The window covered by riq_[layer][i] goes from
3324 // first = i - 2 * half_window + 1 to last = i, inclusive.
3325 // Values are computed from two half-windows of the layer below,
3326 // the F-window = (i - 2 * half_window, i - half_window], and
3327 // the L-window - (i - half_window, i].
3328 const RIQNode& fw = riq_[layer - 1][i - half_window];
3329 const RIQNode& lw = riq_[layer - 1][i];
3330 const EInterval lst_to_lst = Delta(fw.tsum_at_lst, lw.tsum_at_lst);
3331 const EInterval fst_to_fst = Delta(fw.tsum_at_fst, lw.tsum_at_fst);
3332
3333 riq_[layer][i] = {
3334 .cumuls_to_fst = fw.cumuls_to_fst & lw.cumuls_to_fst - fst_to_fst,
3335 .tightest_tsum = fw.tightest_tsum & lw.tightest_tsum,
3336 .cumuls_to_lst = fw.cumuls_to_lst + lst_to_lst & lw.cumuls_to_lst,
3337 .tsum_at_fst = fw.tsum_at_fst,
3338 .tsum_at_lst = lw.tsum_at_lst};
3339 }
3340 }
3341}
3342
3343// The RIQ schema decomposes the request into two windows:
3344// - the F window covers indices [first_index, first_index + window)
3345// - the L window covers indices (last_index - window, last_index]
3346// The decomposition uses the first and last nodes of these windows.
3347void DimensionChecker::UpdateCumulUsingChainRIQ(
3348 int first_index, int last_index, const ExtendedInterval& path_capacity,
3349 ExtendedInterval& cumul) const {
3350 DCHECK_LE(0, first_index);
3351 DCHECK_LT(first_index, last_index);
3352 DCHECK_LT(last_index, riq_[0].size());
3353 const int layer = MostSignificantBitPosition32(last_index - first_index);
3354 const int window = 1 << layer;
3355 const RIQNode& fw = riq_[layer][first_index + window - 1];
3356 const RIQNode& lw = riq_[layer][last_index];
3357
3358 // Compute the set of cumul values that can reach the last node.
3359 cumul &= fw.cumuls_to_fst;
3360 cumul &= lw.cumuls_to_fst - Delta(fw.tsum_at_fst, lw.tsum_at_fst);
3361 cumul &= path_capacity -
3362 Delta(fw.tsum_at_fst, fw.tightest_tsum & lw.tightest_tsum);
3363
3364 // We need to check for emptiness before widening the interval with transit.
3365 if (IsEmpty(cumul)) return;
3366
3367 // Transit to last node.
3368 cumul += Delta(fw.tsum_at_fst, lw.tsum_at_lst);
3369
3370 // Compute the set of cumul values that are reached from first node.
3371 cumul &= fw.cumuls_to_lst + Delta(fw.tsum_at_lst, lw.tsum_at_lst);
3372 cumul &= lw.cumuls_to_lst;
3373}
3374
3375namespace {
3376
3377class DimensionFilter : public LocalSearchFilter {
3378 public:
3379 std::string DebugString() const override { return name_; }
3380 DimensionFilter(std::unique_ptr<DimensionChecker> checker,
3381 absl::string_view dimension_name)
3382 : checker_(std::move(checker)),
3383 name_(absl::StrCat("DimensionFilter(", dimension_name, ")")) {}
3384
3385 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
3386 return checker_->Check();
3387 }
3388
3389 void Synchronize(const Assignment*, const Assignment*) override {
3390 checker_->Commit();
3391 }
3392
3393 private:
3394 std::unique_ptr<DimensionChecker> checker_;
3395 const std::string name_;
3396};
3397
3398} // namespace
3399
3401 Solver* solver, std::unique_ptr<DimensionChecker> checker,
3402 const std::string& dimension_name) {
3403 DimensionFilter* filter =
3404 new DimensionFilter(std::move(checker), dimension_name);
3405 return solver->RevAlloc(filter);
3406}
3407
3408namespace {
3409// ----- Variable domain filter -----
3410// Rejects assignments to values outside the domain of variables
3411
3412class VariableDomainFilter : public LocalSearchFilter {
3413 public:
3414 VariableDomainFilter() {}
3415 ~VariableDomainFilter() override {}
3416 bool Accept(const Assignment* delta, const Assignment* deltadelta,
3417 int64_t objective_min, int64_t objective_max) override;
3418 void Synchronize(const Assignment*, const Assignment*) override {}
3419
3420 std::string DebugString() const override { return "VariableDomainFilter"; }
3421};
3422
3423bool VariableDomainFilter::Accept(const Assignment* delta, const Assignment*,
3424 int64_t, int64_t) {
3425 const Assignment::IntContainer& container = delta->IntVarContainer();
3426 const int size = container.Size();
3427 for (int i = 0; i < size; ++i) {
3428 const IntVarElement& element = container.Element(i);
3429 if (element.Activated() && !element.Var()->Contains(element.Value())) {
3430 return false;
3431 }
3432 }
3433 return true;
3434}
3435} // namespace
3436
3438 return RevAlloc(new VariableDomainFilter());
3439}
3440
3441// ----- IntVarLocalSearchFilter -----
3442
3443const int IntVarLocalSearchFilter::kUnassigned = -1;
3444
3446 const std::vector<IntVar*>& vars) {
3447 AddVars(vars);
3448}
3449
3450void IntVarLocalSearchFilter::AddVars(const std::vector<IntVar*>& vars) {
3451 if (!vars.empty()) {
3452 for (int i = 0; i < vars.size(); ++i) {
3453 const int index = vars[i]->index();
3454 if (index >= var_index_to_index_.size()) {
3455 var_index_to_index_.resize(index + 1, kUnassigned);
3456 }
3457 var_index_to_index_[index] = i + vars_.size();
3458 }
3459 vars_.insert(vars_.end(), vars.begin(), vars.end());
3460 values_.resize(vars_.size(), /*junk*/ 0);
3461 var_synced_.resize(vars_.size(), false);
3462 }
3463}
3464
3466
3468 const Assignment* delta) {
3469 if (delta == nullptr || delta->Empty()) {
3470 var_synced_.assign(var_synced_.size(), false);
3471 SynchronizeOnAssignment(assignment);
3472 } else {
3473 SynchronizeOnAssignment(delta);
3474 }
3475 OnSynchronize(delta);
3476}
3477
3479 const Assignment* assignment) {
3480 const Assignment::IntContainer& container = assignment->IntVarContainer();
3481 const int size = container.Size();
3482 for (int i = 0; i < size; ++i) {
3483 const IntVarElement& element = container.Element(i);
3484 IntVar* const var = element.Var();
3485 if (var != nullptr) {
3486 if (i < vars_.size() && vars_[i] == var) {
3487 values_[i] = element.Value();
3488 var_synced_[i] = true;
3489 } else {
3490 const int64_t kUnallocated = -1;
3491 int64_t index = kUnallocated;
3492 if (FindIndex(var, &index)) {
3493 values_[index] = element.Value();
3494 var_synced_[index] = true;
3495 }
3496 }
3497 }
3498 }
3499}
3500
3501// ----- Sum Objective filter ------
3502// Maintains the sum of costs of variables, where the subclass implements
3503// CostOfSynchronizedVariable() and FillCostOfBoundDeltaVariable() to compute
3504// the cost of a variable depending on its value.
3505// An assignment is accepted by this filter if the total cost is allowed
3506// depending on the relation defined by filter_enum:
3507// - Solver::LE -> total_cost <= objective_max.
3508// - Solver::GE -> total_cost >= objective_min.
3509// - Solver::EQ -> the conjunction of LE and GE.
3510namespace {
3511template <typename Filter>
3512class SumObjectiveFilter : public IntVarLocalSearchFilter {
3513 public:
3514 SumObjectiveFilter(const std::vector<IntVar*>& vars, Filter filter)
3516 primary_vars_size_(vars.size()),
3517 synchronized_costs_(vars.size()),
3518 delta_costs_(vars.size()),
3519 filter_(std::move(filter)),
3520 synchronized_sum_(std::numeric_limits<int64_t>::min()),
3521 delta_sum_(std::numeric_limits<int64_t>::min()),
3522 incremental_(false) {}
3523 ~SumObjectiveFilter() override {}
3524 bool Accept(const Assignment* delta, const Assignment* deltadelta,
3525 int64_t objective_min, int64_t objective_max) override {
3526 if (delta == nullptr) return false;
3527 if (deltadelta->Empty()) {
3528 if (incremental_) {
3529 for (int i = 0; i < primary_vars_size_; ++i) {
3531 }
3532 }
3533 incremental_ = false;
3534 delta_sum_ = CapAdd(synchronized_sum_, CostOfChanges(delta, false));
3535 } else {
3536 if (incremental_) {
3537 delta_sum_ = CapAdd(delta_sum_, CostOfChanges(deltadelta, true));
3538 } else {
3539 delta_sum_ = CapAdd(synchronized_sum_, CostOfChanges(delta, true));
3540 }
3541 incremental_ = true;
3542 }
3543 return filter_(delta_sum_, objective_min, objective_max);
3544 }
3545 // If the variable is synchronized, returns its associated cost, otherwise
3546 // returns 0.
3547 virtual int64_t CostOfSynchronizedVariable(int64_t index) = 0;
3548 // Returns the cost of applying changes to the current solution.
3549 virtual int64_t CostOfChanges(const Assignment* changes,
3550 bool incremental) = 0;
3551 bool IsIncremental() const override { return true; }
3552
3553 std::string DebugString() const override { return "SumObjectiveFilter"; }
3554
3555 int64_t GetSynchronizedObjectiveValue() const override {
3556 return synchronized_sum_;
3557 }
3558 int64_t GetAcceptedObjectiveValue() const override { return delta_sum_; }
3559
3560 protected:
3562 std::vector<int64_t> synchronized_costs_;
3563 std::vector<int64_t> delta_costs_;
3564 Filter filter_;
3566 int64_t delta_sum_;
3568
3569 private:
3570 void OnSynchronize(const Assignment*) override {
3572 for (int i = 0; i < primary_vars_size_; ++i) {
3573 const int64_t cost = CostOfSynchronizedVariable(i);
3574 synchronized_costs_[i] = cost;
3575 delta_costs_[i] = cost;
3576 synchronized_sum_ = CapAdd(synchronized_sum_, cost);
3577 }
3579 incremental_ = false;
3580 }
3581};
3582
3583template <typename Filter>
3584class BinaryObjectiveFilter : public SumObjectiveFilter<Filter> {
3585 public:
3586 BinaryObjectiveFilter(const std::vector<IntVar*>& vars,
3587 Solver::IndexEvaluator2 value_evaluator, Filter filter)
3588 : SumObjectiveFilter<Filter>(vars, std::move(filter)),
3589 value_evaluator_(std::move(value_evaluator)) {}
3590 ~BinaryObjectiveFilter() override {}
3591 int64_t CostOfSynchronizedVariable(int64_t index) override {
3592 return IntVarLocalSearchFilter::IsVarSynced(index)
3593 ? value_evaluator_(index, IntVarLocalSearchFilter::Value(index))
3594 : 0;
3595 }
3596 int64_t CostOfChanges(const Assignment* changes, bool incremental) override {
3597 int64_t total_cost = 0;
3598 const Assignment::IntContainer& container = changes->IntVarContainer();
3599 for (const IntVarElement& new_element : container.elements()) {
3600 IntVar* const var = new_element.Var();
3601 int64_t index = -1;
3602 if (this->FindIndex(var, &index)) {
3603 total_cost = CapSub(total_cost, this->delta_costs_[index]);
3604 int64_t new_cost = 0LL;
3605 if (new_element.Activated()) {
3606 new_cost = value_evaluator_(index, new_element.Value());
3607 } else if (var->Bound()) {
3608 new_cost = value_evaluator_(index, var->Min());
3609 }
3610 total_cost = CapAdd(total_cost, new_cost);
3611 if (incremental) {
3612 this->delta_costs_[index] = new_cost;
3613 }
3614 }
3615 }
3616 return total_cost;
3617 }
3618
3619 private:
3620 Solver::IndexEvaluator2 value_evaluator_;
3621};
3622
3623template <typename Filter>
3624class TernaryObjectiveFilter : public SumObjectiveFilter<Filter> {
3625 public:
3626 TernaryObjectiveFilter(const std::vector<IntVar*>& vars,
3627 const std::vector<IntVar*>& secondary_vars,
3628 Solver::IndexEvaluator3 value_evaluator, Filter filter)
3629 : SumObjectiveFilter<Filter>(vars, std::move(filter)),
3630 secondary_vars_offset_(vars.size()),
3631 secondary_values_(vars.size(), -1),
3632 value_evaluator_(std::move(value_evaluator)) {
3633 IntVarLocalSearchFilter::AddVars(secondary_vars);
3634 CHECK_GE(IntVarLocalSearchFilter::Size(), 0);
3635 }
3636 ~TernaryObjectiveFilter() override {}
3637 int64_t CostOfSynchronizedVariable(int64_t index) override {
3638 DCHECK_LT(index, secondary_vars_offset_);
3639 return IntVarLocalSearchFilter::IsVarSynced(index)
3640 ? value_evaluator_(index, IntVarLocalSearchFilter::Value(index),
3641 IntVarLocalSearchFilter::Value(
3642 index + secondary_vars_offset_))
3643 : 0;
3644 }
3645 int64_t CostOfChanges(const Assignment* changes, bool incremental) override {
3646 int64_t total_cost = 0;
3647 const Assignment::IntContainer& container = changes->IntVarContainer();
3648 for (const IntVarElement& new_element : container.elements()) {
3649 IntVar* const var = new_element.Var();
3650 int64_t index = -1;
3651 if (this->FindIndex(var, &index) && index < secondary_vars_offset_) {
3652 secondary_values_[index] = -1;
3653 }
3654 }
3655 // Primary variable indices range from 0 to secondary_vars_offset_ - 1,
3656 // matching secondary indices from secondary_vars_offset_ to
3657 // 2 * secondary_vars_offset_ - 1.
3658 const int max_secondary_index = 2 * secondary_vars_offset_;
3659 for (const IntVarElement& new_element : container.elements()) {
3660 IntVar* const var = new_element.Var();
3661 int64_t index = -1;
3662 if (new_element.Activated() && this->FindIndex(var, &index) &&
3663 index >= secondary_vars_offset_ &&
3664 // Only consider secondary_variables linked to primary ones.
3665 index < max_secondary_index) {
3666 secondary_values_[index - secondary_vars_offset_] = new_element.Value();
3667 }
3668 }
3669 for (const IntVarElement& new_element : container.elements()) {
3670 IntVar* const var = new_element.Var();
3671 int64_t index = -1;
3672 if (this->FindIndex(var, &index) && index < secondary_vars_offset_) {
3673 total_cost = CapSub(total_cost, this->delta_costs_[index]);
3674 int64_t new_cost = 0LL;
3675 if (new_element.Activated()) {
3676 new_cost = value_evaluator_(index, new_element.Value(),
3677 secondary_values_[index]);
3678 } else if (var->Bound() &&
3679 IntVarLocalSearchFilter::Var(index + secondary_vars_offset_)
3680 ->Bound()) {
3681 new_cost = value_evaluator_(
3682 index, var->Min(),
3683 IntVarLocalSearchFilter::Var(index + secondary_vars_offset_)
3684 ->Min());
3685 }
3686 total_cost = CapAdd(total_cost, new_cost);
3687 if (incremental) {
3688 this->delta_costs_[index] = new_cost;
3689 }
3690 }
3691 }
3692 return total_cost;
3693 }
3694
3695 private:
3696 int secondary_vars_offset_;
3697 std::vector<int64_t> secondary_values_;
3698 Solver::IndexEvaluator3 value_evaluator_;
3699};
3700} // namespace
3701
3703 const std::vector<IntVar*>& vars, Solver::IndexEvaluator2 values,
3704 Solver::LocalSearchFilterBound filter_enum) {
3705 switch (filter_enum) {
3706 case Solver::LE: {
3707 auto filter = [](int64_t value, int64_t, int64_t max_value) {
3708 return value <= max_value;
3709 };
3710 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
3711 vars, std::move(values), std::move(filter)));
3712 }
3713 case Solver::GE: {
3714 auto filter = [](int64_t value, int64_t min_value, int64_t) {
3715 return value >= min_value;
3716 };
3717 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
3718 vars, std::move(values), std::move(filter)));
3719 }
3720 case Solver::EQ: {
3721 auto filter = [](int64_t value, int64_t min_value, int64_t max_value) {
3722 return min_value <= value && value <= max_value;
3723 };
3724 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
3725 vars, std::move(values), std::move(filter)));
3726 }
3727 default: {
3728 LOG(ERROR) << "Unknown local search filter enum value";
3729 return nullptr;
3730 }
3731 }
3732}
3733
3735 const std::vector<IntVar*>& vars,
3736 const std::vector<IntVar*>& secondary_vars, Solver::IndexEvaluator3 values,
3737 Solver::LocalSearchFilterBound filter_enum) {
3738 switch (filter_enum) {
3739 case Solver::LE: {
3740 auto filter = [](int64_t value, int64_t, int64_t max_value) {
3741 return value <= max_value;
3742 };
3743 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
3744 vars, secondary_vars, std::move(values), std::move(filter)));
3745 }
3746 case Solver::GE: {
3747 auto filter = [](int64_t value, int64_t min_value, int64_t) {
3748 return value >= min_value;
3749 };
3750 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
3751 vars, secondary_vars, std::move(values), std::move(filter)));
3752 }
3753 case Solver::EQ: {
3754 auto filter = [](int64_t value, int64_t min_value, int64_t max_value) {
3755 return min_value <= value && value <= max_value;
3756 };
3757 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
3758 vars, secondary_vars, std::move(values), std::move(filter)));
3759 }
3760 default: {
3761 LOG(ERROR) << "Unknown local search filter enum value";
3762 return nullptr;
3763 }
3764 }
3765}
3766
3767void SubDagComputer::BuildGraph(int num_nodes) {
3768 DCHECK_GE(num_nodes, num_nodes_);
3769 DCHECK(!graph_was_built_);
3770 graph_was_built_ = true;
3771 std::sort(arcs_.begin(), arcs_.end());
3772 arcs_of_node_.clear();
3773 NodeId prev_tail(-1);
3774 for (int a = 0; a < arcs_.size(); ++a) {
3775 const NodeId tail = arcs_[a].tail;
3776 if (tail != prev_tail) {
3777 prev_tail = tail;
3778 arcs_of_node_.resize(tail.value() + 1, a);
3779 }
3780 }
3781 num_nodes_ = std::max(num_nodes_, num_nodes);
3782 arcs_of_node_.resize(num_nodes_ + 1, arcs_.size());
3783 DCHECK(!HasDirectedCycle()) << "Graph has a directed cycle";
3784}
3785
3786bool SubDagComputer::HasDirectedCycle() const {
3787 DCHECK(graph_was_built_);
3788 util_intops::StrongVector<NodeId, bool> node_is_open(num_nodes_, false);
3789 util_intops::StrongVector<NodeId, bool> node_was_visited(num_nodes_, false);
3790 // Depth first search event: a node and a boolean indicating whether
3791 // to open or to close it.
3792 struct DFSEvent {
3793 NodeId node;
3794 bool to_open;
3795 };
3796 std::vector<DFSEvent> event_stack;
3797
3798 for (NodeId node(0); node.value() < num_nodes_; ++node) {
3799 if (node_was_visited[node]) continue;
3800 event_stack.push_back({node, true});
3801 while (!event_stack.empty()) {
3802 const auto [node, to_open] = event_stack.back();
3803 event_stack.pop_back();
3804 if (node_was_visited[node]) continue;
3805 if (to_open) {
3806 if (node_is_open[node]) return true;
3807 node_is_open[node] = true;
3808 event_stack.push_back({node, false});
3809 for (int a = arcs_of_node_[node];
3810 a < arcs_of_node_[NodeId(node.value() + 1)]; ++a) {
3811 const NodeId head = arcs_[a].head;
3812 if (node_was_visited[head]) continue; // Optim, keeps stack smaller.
3813 event_stack.push_back({head, true});
3814 }
3815 } else {
3816 node_was_visited[node] = true;
3817 node_is_open[node] = false;
3818 }
3819 }
3820 }
3821 return false;
3822}
3823
3824const std::vector<SubDagComputer::ArcId>&
3826 DCHECK_LT(node.value(), num_nodes_);
3827 DCHECK(graph_was_built_);
3828 if (indegree_of_node_.size() < num_nodes_) {
3829 indegree_of_node_.resize(num_nodes_, 0);
3830 }
3831 // Compute indegrees of nodes of the sub-DAG reachable from node.
3832 nodes_to_visit_.clear();
3833 nodes_to_visit_.push_back(node);
3834 while (!nodes_to_visit_.empty()) {
3835 const NodeId node = nodes_to_visit_.back();
3836 nodes_to_visit_.pop_back();
3837 const NodeId next(node.value() + 1);
3838 for (int a = arcs_of_node_[node]; a < arcs_of_node_[next]; ++a) {
3839 const NodeId head = arcs_[a].head;
3840 if (indegree_of_node_[head] == 0) {
3841 nodes_to_visit_.push_back(head);
3842 }
3843 ++indegree_of_node_[head];
3844 }
3845 }
3846 // Generate arc ordering by iteratively removing zero-indegree nodes.
3847 sorted_arcs_.clear();
3848 nodes_to_visit_.push_back(node);
3849 while (!nodes_to_visit_.empty()) {
3850 const NodeId node = nodes_to_visit_.back();
3851 nodes_to_visit_.pop_back();
3852 const NodeId next(node.value() + 1);
3853 for (int a = arcs_of_node_[node]; a < arcs_of_node_[next]; ++a) {
3854 const NodeId head = arcs_[a].head;
3855 --indegree_of_node_[head];
3856 if (indegree_of_node_[head] == 0) {
3857 nodes_to_visit_.push_back(head);
3858 }
3859 sorted_arcs_.push_back(arcs_[a].arc_id);
3860 }
3861 }
3862 // Invariant: indegree_of_node_ must be all 0, or the graph has a cycle.
3863 DCHECK(absl::c_all_of(indegree_of_node_, [](int x) { return x == 0; }));
3864 return sorted_arcs_;
3865}
3866
3867using VariableDomainId = LocalSearchState::VariableDomainId;
3869 int64_t relaxed_max) {
3870 DCHECK(state_domains_are_all_nonempty_);
3871 DCHECK_LE(relaxed_min, relaxed_max);
3872 relaxed_domains_.push_back({relaxed_min, relaxed_max});
3873 current_domains_.push_back({relaxed_min, relaxed_max});
3874 domain_is_trailed_.push_back(false);
3875 return VariableDomainId(current_domains_.size() - 1);
3876}
3877
3879 VariableDomainId domain_id) {
3880 return {this, domain_id};
3881}
3882
3884 DCHECK(state_domains_are_all_nonempty_);
3885 if (!state_has_relaxed_domains_) {
3886 trailed_num_committed_empty_domains_ = num_committed_empty_domains_;
3887 }
3888 state_has_relaxed_domains_ = true;
3889 if (!domain_is_trailed_[domain_id]) {
3890 domain_is_trailed_[domain_id] = true;
3891 trailed_domains_.push_back({current_domains_[domain_id], domain_id});
3892 if (IntersectionIsEmpty(relaxed_domains_[domain_id],
3893 current_domains_[domain_id])) {
3894 DCHECK_GT(num_committed_empty_domains_, 0);
3895 --num_committed_empty_domains_;
3896 }
3897 current_domains_[domain_id] = relaxed_domains_[domain_id];
3898 }
3899}
3900
3902 DCHECK(state_domains_are_all_nonempty_);
3903 return current_domains_[domain_id].min;
3904}
3905
3907 DCHECK(state_domains_are_all_nonempty_);
3908 return current_domains_[domain_id].max;
3909}
3910
3912 int64_t min_value) {
3913 DCHECK(state_domains_are_all_nonempty_);
3914 DCHECK(domain_is_trailed_[domain_id]);
3915 VariableDomain& domain = current_domains_[domain_id];
3916 if (domain.max < min_value) {
3917 state_domains_are_all_nonempty_ = false;
3918 }
3919 domain.min = std::max(domain.min, min_value);
3920 return state_domains_are_all_nonempty_;
3921}
3922
3924 int64_t max_value) {
3925 DCHECK(state_domains_are_all_nonempty_);
3926 DCHECK(domain_is_trailed_[domain_id]);
3927 VariableDomain& domain = current_domains_[domain_id];
3928 if (domain.min > max_value) {
3929 state_domains_are_all_nonempty_ = false;
3930 }
3931 domain.max = std::min(domain.max, max_value);
3932 return state_domains_are_all_nonempty_;
3933}
3934
3936 int64_t min, int64_t max) {
3937 DCHECK(state_domains_are_all_nonempty_);
3938 DCHECK(!domain_is_trailed_[domain_id]);
3939 const bool domain_was_empty = IntersectionIsEmpty(
3940 relaxed_domains_[domain_id], current_domains_[domain_id]);
3941 relaxed_domains_[domain_id] = {min, max};
3942 const bool domain_is_empty =
3943 IntersectionIsEmpty({min, max}, current_domains_[domain_id]);
3944
3945 if (!domain_was_empty && domain_is_empty) {
3946 num_committed_empty_domains_++;
3947 } else if (domain_was_empty && !domain_is_empty) {
3948 num_committed_empty_domains_--;
3949 }
3950}
3951
3952// TODO(user): When the class has more users, find a threshold ratio of
3953// saved/total domains under which a sparse clear would be more efficient
3954// for both Commit() and Revert().
3956 DCHECK(StateIsFeasible());
3957 // Clear domains trail.
3958 state_has_relaxed_domains_ = false;
3959 trailed_domains_.clear();
3960 domain_is_trailed_.assign(domain_is_trailed_.size(), false);
3961 // Clear constraint trail.
3962 for (Constraint* constraint : trailed_constraints_) {
3963 constraint->Commit();
3964 }
3965 trailed_constraints_.clear();
3966}
3967
3969 // Revert trailed domains.
3970 for (const auto& [domain, id] : trailed_domains_) {
3971 DCHECK(domain_is_trailed_[id]);
3972 current_domains_[id] = domain;
3973 }
3974 trailed_domains_.clear();
3975 state_has_relaxed_domains_ = false;
3976 domain_is_trailed_.assign(domain_is_trailed_.size(), false);
3977 state_domains_are_all_nonempty_ = true;
3978 num_committed_empty_domains_ = trailed_num_committed_empty_domains_;
3979 // Revert trailed constraints.
3980 for (Constraint* constraint : trailed_constraints_) {
3981 constraint->Revert();
3982 }
3983 trailed_constraints_.clear();
3984}
3985
3986using NodeId = SubDagComputer::NodeId;
3987NodeId LocalSearchState::DependencyGraph::GetOrCreateNodeOfDomainId(
3988 VariableDomainId domain_id) {
3989 if (domain_id.value() >= dag_node_of_domain_.size()) {
3990 dag_node_of_domain_.resize(domain_id.value() + 1, NodeId(0));
3991 }
3992 if (dag_node_of_domain_[domain_id] == NodeId(0)) {
3993 dag_node_of_domain_[domain_id] = NodeId(num_dag_nodes_++);
3994 }
3995 return dag_node_of_domain_[domain_id];
3996}
3997
3998NodeId LocalSearchState::DependencyGraph::GetOrCreateNodeOfConstraintId(
3999 ConstraintId constraint_id) {
4000 if (constraint_id.value() >= dag_node_of_constraint_.size()) {
4001 dag_node_of_constraint_.resize(constraint_id.value() + 1, NodeId(0));
4002 }
4003 if (dag_node_of_constraint_[constraint_id] == NodeId(0)) {
4004 dag_node_of_constraint_[constraint_id] = NodeId(num_dag_nodes_++);
4005 }
4006 return dag_node_of_constraint_[constraint_id];
4007}
4008
4009void LocalSearchState::DependencyGraph::AddDomainsConstraintDependencies(
4010 const std::vector<VariableDomainId>& domain_ids,
4011 ConstraintId constraint_id) {
4012 const NodeId cnode = GetOrCreateNodeOfConstraintId(constraint_id);
4013 for (int i = 0; i < domain_ids.size(); ++i) {
4014 const NodeId dnode = GetOrCreateNodeOfDomainId(domain_ids[i]);
4015 dag_.AddArc(dnode, cnode);
4016 dependency_of_dag_arc_.push_back({.domain_id = domain_ids[i],
4017 .input_index = i,
4018 .constraint_id = constraint_id});
4019 }
4020}
4021
4022void LocalSearchState::DependencyGraph::AddConstraintDomainDependency(
4023 ConstraintId constraint_id, VariableDomainId domain_id) {
4024 const NodeId cnode = GetOrCreateNodeOfConstraintId(constraint_id);
4025 const NodeId dnode = GetOrCreateNodeOfDomainId(domain_id);
4026 dag_.AddArc(cnode, dnode);
4027 dependency_of_dag_arc_.push_back({.domain_id = domain_id,
4028 .input_index = -1,
4029 .constraint_id = constraint_id});
4030}
4031
4032using ArcId = SubDagComputer::ArcId;
4033const std::vector<LocalSearchState::DependencyGraph::Dependency>&
4034LocalSearchState::DependencyGraph::ComputeSortedDependencies(
4035 VariableDomainId domain_id) {
4036 sorted_dependencies_.clear();
4037 const NodeId node = dag_node_of_domain_[domain_id];
4038 for (const ArcId a : dag_.ComputeSortedSubDagArcs(node)) {
4039 if (dependency_of_dag_arc_[a].input_index == -1) continue;
4040 sorted_dependencies_.push_back(dependency_of_dag_arc_[a]);
4041 }
4042 return sorted_dependencies_;
4043}
4044
4046 const std::vector<VariableDomainId>& input_domain_ids,
4047 const std::vector<int64_t>& input_weights, int64_t input_offset,
4048 VariableDomainId output_domain_id) {
4049 DCHECK_EQ(input_domain_ids.size(), input_weights.size());
4050 // Store domain/constraint dependencies.
4051 const ConstraintId constraint_id(constraints_.size());
4052 dependency_graph_.AddDomainsConstraintDependencies(input_domain_ids,
4053 constraint_id);
4054 dependency_graph_.AddConstraintDomainDependency(constraint_id,
4055 output_domain_id);
4056 // Store constraint.
4057 auto constraint = std::make_unique<WeightedSum>(
4058 this, input_domain_ids, input_weights, input_offset, output_domain_id);
4059 constraints_.push_back(std::move(constraint));
4060}
4061
4062void LocalSearchState::DependencyGraph::BuildDependencyDAG(int num_domains) {
4063 dag_.BuildGraph(num_dag_nodes_);
4064 // Assign all unassigned nodes to dummy node 0.
4065 const int num_assigned_nodes = dag_node_of_domain_.size();
4066 DCHECK_GE(num_domains, num_assigned_nodes);
4067 num_domains = std::max(num_domains, num_assigned_nodes);
4068 dag_node_of_domain_.resize(num_domains, NodeId(0));
4069}
4070
4072 triggers_.clear();
4073 triggers_of_domain_.clear();
4074 const int num_domains = current_domains_.size();
4075 dependency_graph_.BuildDependencyDAG(num_domains);
4076 for (int vid = 0; vid < num_domains; ++vid) {
4077 triggers_of_domain_.push_back(triggers_.size());
4078 for (const auto& [domain_id, input_index, constraint_id] :
4079 dependency_graph_.ComputeSortedDependencies(VariableDomainId(vid))) {
4080 triggers_.push_back({.constraint = constraints_[constraint_id].get(),
4081 .input_index = input_index});
4082 }
4083 }
4084 triggers_of_domain_.push_back(triggers_.size());
4085}
4086
4087LocalSearchState::WeightedSum::WeightedSum(
4088 LocalSearchState* state,
4089 const std::vector<VariableDomainId>& input_domain_ids,
4090 const std::vector<int64_t>& input_weights, int64_t input_offset,
4091 VariableDomainId output)
4092 : output_(output), state_(state) {
4093 // Make trailable values that mirror last known value of inputs,
4094 // and others to represent internal counts and sums of inputs.
4095 invariants_.num_neg_inf = 0;
4096 invariants_.num_pos_inf = 0;
4097 invariants_.wsum_mins = input_offset;
4098 invariants_.wsum_maxs = input_offset;
4099 for (int i = 0; i < input_domain_ids.size(); ++i) {
4100 const VariableDomainId domain_id = input_domain_ids[i];
4101 const int64_t weight = input_weights[i];
4102 const int64_t min = state->VariableDomainMin(domain_id);
4103 const int64_t max = state->VariableDomainMax(domain_id);
4104 inputs_.push_back({.min = min,
4105 .max = max,
4106 .committed_min = min,
4107 .committed_max = max,
4108 .weight = weight,
4109 .domain = domain_id,
4110 .is_trailed = false});
4111
4112 if (weight > 0) {
4113 if (min == kint64min) {
4114 ++invariants_.num_neg_inf;
4115 } else {
4116 invariants_.wsum_mins =
4117 CapAdd(invariants_.wsum_mins, CapProd(weight, min));
4118 }
4119 if (max == kint64max) {
4120 ++invariants_.num_pos_inf;
4121 } else {
4122 invariants_.wsum_maxs =
4123 CapAdd(invariants_.wsum_maxs, CapProd(weight, max));
4124 }
4125 } else {
4126 if (min == kint64min) {
4127 ++invariants_.num_pos_inf;
4128 } else {
4129 invariants_.wsum_maxs =
4130 CapAdd(invariants_.wsum_maxs, CapProd(weight, min));
4131 }
4132 if (max == kint64max) {
4133 ++invariants_.num_neg_inf;
4134 } else {
4135 invariants_.wsum_mins =
4136 CapAdd(invariants_.wsum_mins, CapProd(weight, max));
4137 }
4138 }
4139 }
4140 committed_invariants_ = invariants_;
4141}
4142
4143LocalSearchState::VariableDomain LocalSearchState::WeightedSum::Propagate(
4144 int input_index) {
4145 if (!constraint_is_trailed_) {
4146 constraint_is_trailed_ = true;
4147 state_->TrailConstraint(this);
4148 }
4149 WeightedVariable& input = inputs_[input_index];
4150 if (!input.is_trailed) {
4151 input.is_trailed = true;
4152 trailed_inputs_.push_back(&input);
4153 }
4154 const int64_t new_min = state_->VariableDomainMin(input.domain);
4155 const int64_t new_max = state_->VariableDomainMax(input.domain);
4156 const int64_t weight = input.weight;
4157 if (weight > 0) {
4158 if (input.min != new_min) {
4159 // Remove contribution of last known min.
4160 if (input.min == kint64min) {
4161 --invariants_.num_neg_inf;
4162 } else {
4163 invariants_.wsum_mins =
4164 CapSub(invariants_.wsum_mins, CapProd(weight, input.min));
4165 }
4166 // Add contribution of new min.
4167 if (new_min == kint64min) {
4168 ++invariants_.num_neg_inf;
4169 } else {
4170 invariants_.wsum_mins =
4171 CapAdd(invariants_.wsum_mins, CapProd(weight, new_min));
4172 }
4173 input.min = new_min;
4174 }
4175 if (input.max != new_max) {
4176 // Remove contribution of last known max.
4177 if (input.max == kint64max) {
4178 --invariants_.num_pos_inf;
4179 } else {
4180 invariants_.wsum_maxs =
4181 CapSub(invariants_.wsum_maxs, CapProd(weight, input.max));
4182 }
4183 // Add contribution of new max.
4184 if (new_max == kint64max) {
4185 ++invariants_.num_pos_inf;
4186 } else {
4187 invariants_.wsum_maxs =
4188 CapAdd(invariants_.wsum_maxs, CapProd(weight, new_max));
4189 }
4190 input.max = new_max;
4191 }
4192 } else { // weight <= 0
4193 if (input.min != new_min) {
4194 // Remove contribution of last known min.
4195 if (input.min == kint64min) {
4196 --invariants_.num_pos_inf;
4197 } else {
4198 invariants_.wsum_maxs =
4199 CapSub(invariants_.wsum_maxs, CapProd(weight, input.min));
4200 }
4201 // Add contribution of new min.
4202 if (new_min == kint64min) {
4203 ++invariants_.num_pos_inf;
4204 } else {
4205 invariants_.wsum_maxs =
4206 CapAdd(invariants_.wsum_maxs, CapProd(weight, new_min));
4207 }
4208 input.min = new_min;
4209 }
4210 if (input.max != new_max) {
4211 // Remove contribution of last known max.
4212 if (input.max == kint64max) {
4213 --invariants_.num_neg_inf;
4214 } else {
4215 invariants_.wsum_mins =
4216 CapSub(invariants_.wsum_mins, CapProd(weight, input.max));
4217 }
4218 // Add contribution of new max.
4219 if (new_max == kint64max) {
4220 ++invariants_.num_neg_inf;
4221 } else {
4222 invariants_.wsum_mins =
4223 CapAdd(invariants_.wsum_mins, CapProd(weight, new_max));
4224 }
4225 input.max = new_max;
4226 }
4227 }
4228 return {invariants_.num_neg_inf == 0 ? invariants_.wsum_mins : kint64min,
4229 invariants_.num_pos_inf == 0 ? invariants_.wsum_maxs : kint64max};
4230}
4231
4232void LocalSearchState::WeightedSum::Commit() {
4233 committed_invariants_ = invariants_;
4234 constraint_is_trailed_ = false;
4235 for (WeightedVariable* wv : trailed_inputs_) wv->Commit();
4236 trailed_inputs_.clear();
4237}
4238
4239void LocalSearchState::WeightedSum::Revert() {
4240 invariants_ = committed_invariants_;
4241 constraint_is_trailed_ = false;
4242 for (WeightedVariable* wv : trailed_inputs_) wv->Revert();
4243 trailed_inputs_.clear();
4244}
4245
4247 const VariableDomainId next_id = VariableDomainId(domain_id.value() + 1);
4248 for (int t = triggers_of_domain_[domain_id]; t < triggers_of_domain_[next_id];
4249 ++t) {
4250 const auto& [constraint, input_index] = triggers_[t];
4251 constraint->Propagate(input_index);
4252 RelaxVariableDomain(constraint->Output());
4253 }
4254}
4255
4257 const VariableDomainId next_id = VariableDomainId(domain_id.value() + 1);
4258 for (int t = triggers_of_domain_[domain_id]; t < triggers_of_domain_[next_id];
4259 ++t) {
4260 const auto& [constraint, input_index] = triggers_[t];
4261 const auto [output_min, output_max] = constraint->Propagate(input_index);
4262 if (output_min != kint64min &&
4263 !TightenVariableDomainMin(constraint->Output(), output_min)) {
4264 return false;
4265 }
4266 if (output_max != kint64max &&
4267 !TightenVariableDomainMax(constraint->Output(), output_max)) {
4268 return false;
4269 }
4270 }
4271 return true;
4272}
4273
4274// ----- LocalSearchProfiler -----
4275
4277 public:
4278 explicit LocalSearchProfiler(Solver* solver) : LocalSearchMonitor(solver) {}
4279 std::string DebugString() const override { return "LocalSearchProfiler"; }
4280 void RestartSearch() override {
4281 operator_stats_.clear();
4282 filter_stats_.clear();
4283 }
4284 void ExitSearch() override {
4285 // Update times for current operator when the search ends.
4286 if (solver()->TopLevelSearch() == solver()->ActiveSearch()) {
4287 UpdateTime();
4288 }
4289 }
4290 template <typename Callback>
4291 void ParseFirstSolutionStatistics(const Callback& callback) const {
4292 for (ProfiledDecisionBuilder* db : profiled_decision_builders_) {
4293 if (db->seconds() == 0) continue;
4294 callback(db->name(), db->seconds());
4295 }
4296 }
4297
4298 template <typename Callback>
4299 void ParseLocalSearchOperatorStatistics(const Callback& callback) const {
4300 std::vector<const LocalSearchOperator*> operators;
4301 for (const auto& stat : operator_stats_) {
4302 operators.push_back(stat.first);
4303 }
4304 std::sort(
4305 operators.begin(), operators.end(),
4306 [this](const LocalSearchOperator* op1, const LocalSearchOperator* op2) {
4307 return gtl::FindOrDie(operator_stats_, op1).neighbors >
4308 gtl::FindOrDie(operator_stats_, op2).neighbors;
4309 });
4310 for (const LocalSearchOperator* const op : operators) {
4311 const OperatorStats& stats = gtl::FindOrDie(operator_stats_, op);
4312 callback(op->DebugString(), stats.neighbors, stats.filtered_neighbors,
4313 stats.accepted_neighbors, stats.seconds);
4314 }
4315 }
4316
4317 template <typename Callback>
4318 void ParseLocalSearchFilterStatistics(const Callback& callback) const {
4319 absl::flat_hash_map<std::string, std::vector<const LocalSearchFilter*>>
4320 filters_per_context;
4321 for (const auto& stat : filter_stats_) {
4322 filters_per_context[stat.second.context].push_back(stat.first);
4323 }
4324 for (auto& [context, filters] : filters_per_context) {
4325 std::sort(filters.begin(), filters.end(),
4326 [this](const LocalSearchFilter* filter1,
4327 const LocalSearchFilter* filter2) {
4328 return gtl::FindOrDie(filter_stats_, filter1).calls >
4329 gtl::FindOrDie(filter_stats_, filter2).calls;
4330 });
4331 for (const LocalSearchFilter* const filter : filters) {
4332 const FilterStats& stats = gtl::FindOrDie(filter_stats_, filter);
4333 callback(context, filter->DebugString(), stats.calls, stats.rejects,
4334 stats.seconds);
4335 }
4336 }
4337 }
4338 LocalSearchStatistics ExportToLocalSearchStatistics() const {
4339 LocalSearchStatistics statistics_proto;
4340 ParseFirstSolutionStatistics(
4341 [&statistics_proto](absl::string_view name, double duration_seconds) {
4342 LocalSearchStatistics::FirstSolutionStatistics* const
4343 first_solution_statistics =
4344 statistics_proto.add_first_solution_statistics();
4345 first_solution_statistics->set_strategy(name);
4346 first_solution_statistics->set_duration_seconds(duration_seconds);
4347 });
4348 ParseLocalSearchOperatorStatistics([&statistics_proto](
4349 absl::string_view name,
4350 int64_t num_neighbors,
4351 int64_t num_filtered_neighbors,
4352 int64_t num_accepted_neighbors,
4353 double duration_seconds) {
4354 LocalSearchStatistics::LocalSearchOperatorStatistics* const
4355 local_search_operator_statistics =
4356 statistics_proto.add_local_search_operator_statistics();
4357 local_search_operator_statistics->set_local_search_operator(name);
4358 local_search_operator_statistics->set_num_neighbors(num_neighbors);
4359 local_search_operator_statistics->set_num_filtered_neighbors(
4360 num_filtered_neighbors);
4361 local_search_operator_statistics->set_num_accepted_neighbors(
4362 num_accepted_neighbors);
4363 local_search_operator_statistics->set_duration_seconds(duration_seconds);
4364 });
4365 ParseLocalSearchFilterStatistics([&statistics_proto](
4366 const std::string& context,
4367 const std::string& name,
4368 int64_t num_calls, int64_t num_rejects,
4369 double duration_seconds) {
4370 LocalSearchStatistics::LocalSearchFilterStatistics* const
4371 local_search_filter_statistics =
4372 statistics_proto.add_local_search_filter_statistics();
4373 local_search_filter_statistics->set_local_search_filter(name);
4374 local_search_filter_statistics->set_num_calls(num_calls);
4375 local_search_filter_statistics->set_num_rejects(num_rejects);
4376 local_search_filter_statistics->set_duration_seconds(duration_seconds);
4377 local_search_filter_statistics->set_num_rejects_per_second(
4378 num_rejects / duration_seconds);
4379 local_search_filter_statistics->set_context(context);
4380 });
4381 statistics_proto.set_total_num_neighbors(solver()->neighbors());
4382 statistics_proto.set_total_num_filtered_neighbors(
4383 solver()->filtered_neighbors());
4384 statistics_proto.set_total_num_accepted_neighbors(
4385 solver()->accepted_neighbors());
4386 return statistics_proto;
4387 }
4388 std::string PrintOverview() const {
4389 std::string overview;
4390 size_t max_name_size = 0;
4391 ParseFirstSolutionStatistics(
4392 [&max_name_size](absl::string_view name, double) {
4393 max_name_size = std::max(max_name_size, name.length());
4394 });
4395 if (max_name_size > 0) {
4396 absl::StrAppendFormat(&overview,
4397 "First solution statistics:\n%*s | Time (s)\n",
4398 max_name_size, "");
4399 ParseFirstSolutionStatistics(
4400 [&overview, max_name_size](const std::string& name,
4401 double duration_seconds) {
4402 absl::StrAppendFormat(&overview, "%*s | %7.2g\n", max_name_size,
4403 name, duration_seconds);
4404 });
4405 }
4406 max_name_size = 0;
4407 ParseLocalSearchOperatorStatistics([&max_name_size](absl::string_view name,
4408 int64_t, int64_t,
4409 int64_t, double) {
4410 max_name_size = std::max(max_name_size, name.length());
4411 });
4412 if (max_name_size > 0) {
4413 absl::StrAppendFormat(
4414 &overview,
4415 "Local search operator statistics:\n%*s | Neighbors | Filtered "
4416 "| Accepted | Time (s)\n",
4417 max_name_size, "");
4418 OperatorStats total_stats;
4419 ParseLocalSearchOperatorStatistics(
4420 [&overview, &total_stats, max_name_size](
4421 absl::string_view name, int64_t num_neighbors,
4422 int64_t num_filtered_neighbors, int64_t num_accepted_neighbors,
4423 double duration_seconds) {
4424 absl::StrAppendFormat(
4425 &overview, "%*s | %9ld | %8ld | %8ld | %7.2g\n", max_name_size,
4426 name, num_neighbors, num_filtered_neighbors,
4427 num_accepted_neighbors, duration_seconds);
4428 total_stats.neighbors += num_neighbors;
4429 total_stats.filtered_neighbors += num_filtered_neighbors;
4430 total_stats.accepted_neighbors += num_accepted_neighbors;
4431 total_stats.seconds += duration_seconds;
4432 });
4433 absl::StrAppendFormat(
4434 &overview, "%*s | %9ld | %8ld | %8ld | %7.2g\n", max_name_size,
4435 "Total", total_stats.neighbors, total_stats.filtered_neighbors,
4436 total_stats.accepted_neighbors, total_stats.seconds);
4437 }
4438 max_name_size = 0;
4439 ParseLocalSearchFilterStatistics(
4440 [&max_name_size](absl::string_view, absl::string_view name, int64_t,
4441 int64_t, double) {
4442 max_name_size = std::max(max_name_size, name.length());
4443 });
4444 if (max_name_size > 0) {
4445 std::optional<std::string> filter_context;
4446 FilterStats total_filter_stats;
4447 ParseLocalSearchFilterStatistics(
4448 [&overview, &filter_context, &total_filter_stats, max_name_size](
4449 const std::string& context, const std::string& name,
4450 int64_t num_calls, int64_t num_rejects, double duration_seconds) {
4451 if (!filter_context.has_value() ||
4452 filter_context.value() != context) {
4453 if (filter_context.has_value()) {
4454 absl::StrAppendFormat(
4455 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n",
4456 max_name_size, "Total", total_filter_stats.calls,
4457 total_filter_stats.rejects, total_filter_stats.seconds,
4458 total_filter_stats.rejects / total_filter_stats.seconds);
4459 total_filter_stats = {};
4460 }
4461 filter_context = context;
4462 absl::StrAppendFormat(
4463 &overview,
4464 "Local search filter statistics%s:\n%*s | Calls | "
4465 " Rejects | Time (s) | Rejects/s\n",
4466 context.empty() ? "" : " (" + context + ")", max_name_size,
4467 "");
4468 }
4469 absl::StrAppendFormat(
4470 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n",
4471 max_name_size, name, num_calls, num_rejects, duration_seconds,
4472 num_rejects / duration_seconds);
4473 total_filter_stats.calls += num_calls;
4474 total_filter_stats.rejects += num_rejects;
4475 total_filter_stats.seconds += duration_seconds;
4476 });
4477 absl::StrAppendFormat(
4478 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n", max_name_size,
4479 "Total", total_filter_stats.calls, total_filter_stats.rejects,
4480 total_filter_stats.seconds,
4481 total_filter_stats.rejects / total_filter_stats.seconds);
4482 }
4483 return overview;
4484 }
4485 void BeginOperatorStart() override {}
4486 void EndOperatorStart() override {}
4488 if (last_operator_ != op->Self()) {
4489 UpdateTime();
4490 last_operator_ = op->Self();
4491 }
4492 }
4493 void EndMakeNextNeighbor(const LocalSearchOperator* op, bool neighbor_found,
4494 const Assignment*, const Assignment*) override {
4495 if (neighbor_found) {
4496 operator_stats_[op->Self()].neighbors++;
4497 }
4498 }
4501 bool neighbor_found) override {
4502 if (neighbor_found) {
4503 operator_stats_[op->Self()].filtered_neighbors++;
4504 }
4505 }
4508 bool neighbor_found) override {
4509 if (neighbor_found) {
4510 operator_stats_[op->Self()].accepted_neighbors++;
4511 }
4512 }
4513 void BeginFiltering(const LocalSearchFilter* filter) override {
4514 FilterStats& filter_stats = filter_stats_[filter];
4515 filter_stats.calls++;
4516 filter_stats.context = solver()->context();
4517 filter_timer_.Start();
4518 }
4519 void EndFiltering(const LocalSearchFilter* filter, bool reject) override {
4520 filter_timer_.Stop();
4521 auto& stats = filter_stats_[filter];
4522 stats.seconds += filter_timer_.Get();
4523 if (reject) {
4524 stats.rejects++;
4525 }
4526 }
4528 ProfiledDecisionBuilder* profiled_db) {
4529 profiled_decision_builders_.push_back(profiled_db);
4530 }
4531 bool IsActive() const override { return true; }
4532 void Install() override { SearchMonitor::Install(); }
4533
4534 private:
4535 void UpdateTime() {
4536 if (last_operator_ != nullptr) {
4537 timer_.Stop();
4538 operator_stats_[last_operator_].seconds += timer_.Get();
4539 }
4540 timer_.Start();
4541 }
4542
4543 struct OperatorStats {
4544 int64_t neighbors = 0;
4545 int64_t filtered_neighbors = 0;
4546 int64_t accepted_neighbors = 0;
4547 double seconds = 0;
4548 };
4549
4550 struct FilterStats {
4551 int64_t calls = 0;
4552 int64_t rejects = 0;
4553 double seconds = 0;
4554 std::string context;
4555 };
4556 WallTimer timer_;
4557 WallTimer filter_timer_;
4558 const LocalSearchOperator* last_operator_ = nullptr;
4559 absl::flat_hash_map<const LocalSearchOperator*, OperatorStats>
4560 operator_stats_;
4561 absl::flat_hash_map<const LocalSearchFilter*, FilterStats> filter_stats_;
4562 // Profiled decision builders.
4563 std::vector<ProfiledDecisionBuilder*> profiled_decision_builders_;
4564};
4565
4567 DecisionBuilder* db) {
4568 if (IsLocalSearchProfilingEnabled()) {
4569 ProfiledDecisionBuilder* profiled_db =
4570 RevAlloc(new ProfiledDecisionBuilder(db));
4571 local_search_profiler_->AddFirstSolutionProfiledDecisionBuilder(
4572 profiled_db);
4573 return profiled_db;
4574 }
4575 return db;
4576}
4577
4579 monitor->Install();
4580}
4581
4583 if (solver->IsLocalSearchProfilingEnabled()) {
4584 return new LocalSearchProfiler(solver);
4585 }
4586 return nullptr;
4587}
4588
4589void DeleteLocalSearchProfiler(LocalSearchProfiler* monitor) { delete monitor; }
4590
4591std::string Solver::LocalSearchProfile() const {
4592 if (local_search_profiler_ != nullptr) {
4593 return local_search_profiler_->PrintOverview();
4594 }
4595 return "";
4596}
4597
4598LocalSearchStatistics Solver::GetLocalSearchStatistics() const {
4599 if (local_search_profiler_ != nullptr) {
4600 return local_search_profiler_->ExportToLocalSearchStatistics();
4601 }
4602 return LocalSearchStatistics();
4603}
4604
4605void LocalSearchFilterManager::FindIncrementalEventEnd() {
4606 const int num_events = events_.size();
4607 incremental_events_end_ = num_events;
4608 int last_priority = -1;
4609 for (int e = num_events - 1; e >= 0; --e) {
4610 const auto& [filter, event_type, priority] = events_[e];
4611 if (priority != last_priority) {
4612 incremental_events_end_ = e + 1;
4613 last_priority = priority;
4614 }
4615 if (filter->IsIncremental()) break;
4616 }
4617}
4618
4620 std::vector<LocalSearchFilter*> filters)
4621 : synchronized_value_(std::numeric_limits<int64_t>::min()),
4622 accepted_value_(std::numeric_limits<int64_t>::min()) {
4623 events_.reserve(2 * filters.size());
4624 int priority = 0;
4625 for (LocalSearchFilter* filter : filters) {
4626 events_.push_back({filter, FilterEventType::kRelax, priority++});
4627 }
4628 for (LocalSearchFilter* filter : filters) {
4629 events_.push_back({filter, FilterEventType::kAccept, priority++});
4630 }
4631 FindIncrementalEventEnd();
4632}
4633
4635 std::vector<FilterEvent> filter_events)
4636 : events_(std::move(filter_events)),
4637 synchronized_value_(std::numeric_limits<int64_t>::min()),
4638 accepted_value_(std::numeric_limits<int64_t>::min()) {
4639 std::sort(events_.begin(), events_.end(),
4640 [](const FilterEvent& e1, const FilterEvent& e2) {
4641 return e1.priority < e2.priority;
4642 });
4643 FindIncrementalEventEnd();
4644}
4645
4646// Filters' Revert() must be called in the reverse order in which their
4647// Relax() was called.
4649 for (int e = last_event_called_; e >= 0; --e) {
4650 const auto [filter, event_type, _priority] = events_[e];
4651 if (event_type == FilterEventType::kRelax) filter->Revert();
4652 }
4653 last_event_called_ = -1;
4654}
4655
4656// TODO(user): the behaviour of Accept relies on the initial order of
4657// filters having at most one filter with negative objective values,
4658// this could be fixed by having filters return their general bounds.
4660 const Assignment* delta,
4661 const Assignment* deltadelta,
4662 int64_t objective_min,
4663 int64_t objective_max) {
4664 Revert();
4665 accepted_value_ = 0;
4666 bool feasible = true;
4667 bool reordered = false;
4668 int events_end = events_.size();
4669 for (int e = 0; e < events_end; ++e) {
4670 last_event_called_ = e;
4671 const auto [filter, event_type, priority] = events_[e];
4672 switch (event_type) {
4674 if (!feasible && !filter->IsIncremental()) continue;
4675 if (monitor != nullptr) monitor->BeginFiltering(filter);
4676 const bool accept = filter->Accept(
4677 delta, deltadelta, CapSub(objective_min, accepted_value_),
4678 CapSub(objective_max, accepted_value_));
4679 feasible &= accept;
4680 if (monitor != nullptr) monitor->EndFiltering(filter, !accept);
4681 if (feasible) {
4682 accepted_value_ =
4683 CapAdd(accepted_value_, filter->GetAcceptedObjectiveValue());
4684 // TODO(user): handle objective min.
4685 feasible = accepted_value_ <= objective_max;
4686 }
4687 if (!feasible) {
4688 events_end = incremental_events_end_;
4689 if (!reordered) {
4690 // Bump up rejected event, together with its kRelax event,
4691 // unless it is already first in its priority layer.
4692 reordered = true;
4693 int to_move = e - 1;
4694 if (to_move >= 0 && events_[to_move].filter == filter) --to_move;
4695 if (to_move >= 0 && events_[to_move].priority == priority) {
4696 std::rotate(events_.begin() + to_move,
4697 events_.begin() + to_move + 1,
4698 events_.begin() + e + 1);
4699 }
4700 }
4701 }
4702 break;
4703 }
4705 filter->Relax(delta, deltadelta);
4706 break;
4707 }
4708 default:
4709 LOG(FATAL) << "Unknown filter event type.";
4710 }
4711 }
4712 return feasible;
4713}
4714
4716 const Assignment* delta) {
4717 // If delta is nullptr or empty, then assignment may be a partial solution.
4718 // Send a signal to Relaxing filters to inform them,
4719 // so they can show the partial solution as a change from the empty solution.
4720 const bool reset_to_assignment = delta == nullptr || delta->Empty();
4721 // Relax in the forward direction.
4722 for (auto [filter, event_type, unused_priority] : events_) {
4723 switch (event_type) {
4725 break;
4726 }
4728 if (reset_to_assignment) {
4729 filter->Reset();
4730 filter->Relax(assignment, nullptr);
4731 } else {
4732 filter->Relax(delta, nullptr);
4733 }
4734 break;
4735 }
4736 default:
4737 LOG(FATAL) << "Unknown filter event type.";
4738 }
4739 }
4740 // Synchronize/Commit backwards, so filters can read changes from their
4741 // dependencies before those are synchronized/committed.
4742 synchronized_value_ = 0;
4743 for (auto [filter, event_type, _priority] : ::gtl::reversed_view(events_)) {
4744 switch (event_type) {
4746 filter->Synchronize(assignment, delta);
4747 synchronized_value_ = CapAdd(synchronized_value_,
4748 filter->GetSynchronizedObjectiveValue());
4749 break;
4750 }
4752 filter->Commit(assignment, delta);
4753 break;
4754 }
4755 default:
4756 LOG(FATAL) << "Unknown filter event type.";
4757 }
4758 }
4759}
4760
4761// ----- Finds a neighbor of the assignment passed -----
4762
4764 public:
4765 FindOneNeighbor(Assignment* assignment, IntVar* objective, SolutionPool* pool,
4766 LocalSearchOperator* ls_operator,
4767 DecisionBuilder* sub_decision_builder,
4768 const RegularLimit* limit,
4769 LocalSearchFilterManager* filter_manager);
4770 ~FindOneNeighbor() override {}
4771 Decision* Next(Solver* solver) override;
4772 std::string DebugString() const override { return "FindOneNeighbor"; }
4773
4774 private:
4775 bool FilterAccept(Solver* solver, Assignment* delta, Assignment* deltadelta,
4776 int64_t objective_min, int64_t objective_max);
4777 void SynchronizeAll(Solver* solver);
4778
4779 Assignment* const assignment_;
4780 IntVar* const objective_;
4781 std::unique_ptr<Assignment> reference_assignment_;
4782 std::unique_ptr<Assignment> last_synchronized_assignment_;
4783 Assignment* const filter_assignment_delta_;
4784 SolutionPool* const pool_;
4785 LocalSearchOperator* const ls_operator_;
4786 DecisionBuilder* const sub_decision_builder_;
4787 RegularLimit* limit_;
4788 const RegularLimit* const original_limit_;
4789 bool neighbor_found_;
4790 LocalSearchFilterManager* const filter_manager_;
4791 int64_t solutions_since_last_check_;
4792 int64_t check_period_;
4793 Assignment last_checked_assignment_;
4794 bool has_checked_assignment_ = false;
4795};
4796
4797// reference_assignment_ is used to keep track of the last assignment on which
4798// operators were started, assignment_ corresponding to the last successful
4799// neighbor.
4800// last_synchronized_assignment_ keeps track of the last assignment on which
4801// filters were synchronized and is used to compute the filter_assignment_delta_
4802// when synchronizing again.
4804 IntVar* objective, SolutionPool* const pool,
4805 LocalSearchOperator* const ls_operator,
4806 DecisionBuilder* const sub_decision_builder,
4807 const RegularLimit* const limit,
4808 LocalSearchFilterManager* filter_manager)
4809 : assignment_(assignment),
4810 objective_(objective),
4811 reference_assignment_(new Assignment(assignment_)),
4812 filter_assignment_delta_(assignment->solver()->MakeAssignment()),
4813 pool_(pool),
4814 ls_operator_(ls_operator),
4815 sub_decision_builder_(sub_decision_builder),
4816 limit_(nullptr),
4817 original_limit_(limit),
4818 neighbor_found_(false),
4819 filter_manager_(filter_manager),
4820 solutions_since_last_check_(0),
4821 check_period_(
4822 assignment_->solver()->parameters().check_solution_period()),
4823 last_checked_assignment_(assignment) {
4824 CHECK(nullptr != assignment);
4825 CHECK(nullptr != ls_operator);
4826
4827 Solver* const solver = assignment_->solver();
4828 // If limit is nullptr, default limit is 1 solution
4829 if (nullptr == limit) {
4830 limit_ = solver->MakeSolutionsLimit(1);
4831 } else {
4832 limit_ = limit->MakeIdenticalClone();
4833 // TODO(user): Support skipping neighborhood checks for limits accepting
4834 // more than one solution (e.g. best accept). For now re-enabling systematic
4835 // checks.
4836 if (limit_->solutions() != 1) {
4837 VLOG(1) << "Disabling neighbor-check skipping outside of first accept.";
4838 check_period_ = 1;
4839 }
4840 }
4841 // TODO(user): Support skipping neighborhood checks with LNS (at least on
4842 // the non-LNS operators).
4843 if (ls_operator->HasFragments()) {
4844 VLOG(1) << "Disabling neighbor-check skipping for LNS.";
4845 check_period_ = 1;
4846 }
4847
4848 if (!reference_assignment_->HasObjective()) {
4849 reference_assignment_->AddObjective(objective_);
4850 }
4851}
4852
4854 CHECK(nullptr != solver);
4855
4856 if (original_limit_ != nullptr) {
4857 limit_->Copy(original_limit_);
4858 }
4859
4860 if (!last_checked_assignment_.HasObjective()) {
4861 last_checked_assignment_.AddObjective(assignment_->Objective());
4862 }
4863
4864 if (!neighbor_found_) {
4865 // Only called on the first call to Next(), reference_assignment_ has not
4866 // been synced with assignment_ yet
4867
4868 // Keeping the code in case a performance problem forces us to
4869 // use the old code with a zero test on pool_.
4870 // reference_assignment_->CopyIntersection(assignment_);
4871 pool_->Initialize(assignment_);
4872 SynchronizeAll(solver);
4873 }
4874
4875 {
4876 // Another assignment is needed to apply the delta
4877 Assignment* assignment_copy =
4878 solver->MakeAssignment(reference_assignment_.get());
4879 int counter = 0;
4880
4881 DecisionBuilder* restore = solver->MakeRestoreAssignment(assignment_copy);
4882 if (sub_decision_builder_) {
4883 restore = solver->Compose(restore, sub_decision_builder_);
4884 }
4885 Assignment* delta = solver->MakeAssignment();
4886 Assignment* deltadelta = solver->MakeAssignment();
4887 while (true) {
4888 if (!ls_operator_->HoldsDelta()) {
4889 delta->Clear();
4890 }
4891 delta->ClearObjective();
4892 deltadelta->Clear();
4893 solver->TopPeriodicCheck();
4894 if (++counter >= absl::GetFlag(FLAGS_cp_local_search_sync_frequency) &&
4895 pool_->SyncNeeded(reference_assignment_.get())) {
4896 // TODO(user) : SyncNeed(assignment_) ?
4897 counter = 0;
4898 SynchronizeAll(solver);
4899 }
4900
4901 bool has_neighbor = false;
4902 if (!limit_->Check()) {
4903 solver->GetLocalSearchMonitor()->BeginMakeNextNeighbor(ls_operator_);
4904 has_neighbor = ls_operator_->MakeNextNeighbor(delta, deltadelta);
4906 ls_operator_, has_neighbor, delta, deltadelta);
4907 }
4908
4909 if (has_neighbor && !solver->IsUncheckedSolutionLimitReached()) {
4910 solver->neighbors_ += 1;
4911 // All filters must be called for incrementality reasons.
4912 // Empty deltas must also be sent to incremental filters; can be needed
4913 // to resync filters on non-incremental (empty) moves.
4914 // TODO(user): Don't call both if no filter is incremental and one
4915 // of them returned false.
4916 solver->GetLocalSearchMonitor()->BeginFilterNeighbor(ls_operator_);
4917 const bool mh_filter =
4918 AcceptDelta(solver->ParentSearch(), delta, deltadelta);
4919 int64_t objective_min = std::numeric_limits<int64_t>::min();
4920 int64_t objective_max = std::numeric_limits<int64_t>::max();
4921 if (objective_) {
4922 objective_min = objective_->Min();
4923 objective_max = objective_->Max();
4924 }
4925 if (delta->HasObjective() && delta->Objective() == objective_) {
4926 objective_min = std::max(objective_min, delta->ObjectiveMin());
4927 objective_max = std::min(objective_max, delta->ObjectiveMax());
4928 }
4929 const bool move_filter = FilterAccept(solver, delta, deltadelta,
4930 objective_min, objective_max);
4932 ls_operator_, mh_filter && move_filter);
4933 if (!mh_filter || !move_filter) {
4934 if (filter_manager_ != nullptr) filter_manager_->Revert();
4935 continue;
4936 }
4937 solver->filtered_neighbors_ += 1;
4938 if (delta->HasObjective()) {
4939 if (!assignment_copy->HasObjective()) {
4940 assignment_copy->AddObjective(delta->Objective());
4941 }
4942 if (!assignment_->HasObjective()) {
4943 assignment_->AddObjective(delta->Objective());
4944 last_checked_assignment_.AddObjective(delta->Objective());
4945 }
4946 }
4947 assignment_copy->CopyIntersection(reference_assignment_.get());
4948 assignment_copy->CopyIntersection(delta);
4949 solver->GetLocalSearchMonitor()->BeginAcceptNeighbor(ls_operator_);
4950 const bool check_solution = (solutions_since_last_check_ == 0) ||
4951 !solver->UseFastLocalSearch() ||
4952 // LNS deltas need to be restored
4953 !delta->AreAllElementsBound();
4954 if (has_checked_assignment_) solutions_since_last_check_++;
4955 if (solutions_since_last_check_ >= check_period_) {
4956 solutions_since_last_check_ = 0;
4957 }
4958 const bool accept = !check_solution || solver->SolveAndCommit(restore);
4959 solver->GetLocalSearchMonitor()->EndAcceptNeighbor(ls_operator_,
4960 accept);
4961 if (accept) {
4962 solver->accepted_neighbors_ += 1;
4963 if (check_solution) {
4964 solver->SetSearchContext(solver->ParentSearch(),
4965 ls_operator_->DebugString());
4966 assignment_->Store();
4967 last_checked_assignment_.CopyIntersection(assignment_);
4968 neighbor_found_ = true;
4969 has_checked_assignment_ = true;
4970 return nullptr;
4971 }
4972 solver->SetSearchContext(solver->ActiveSearch(),
4973 ls_operator_->DebugString());
4974 assignment_->CopyIntersection(assignment_copy);
4975 assignment_->SetObjectiveValue(
4976 filter_manager_ ? filter_manager_->GetAcceptedObjectiveValue()
4977 : 0);
4978 // Advancing local search to the current solution without
4979 // checking.
4980 // TODO(user): support the case were limit_ accepts more than
4981 // one solution (e.g. best accept).
4982 AcceptUncheckedNeighbor(solver->ParentSearch());
4983 solver->IncrementUncheckedSolutionCounter();
4984 pool_->RegisterNewSolution(assignment_);
4985 SynchronizeAll(solver);
4986 // NOTE: SynchronizeAll() sets neighbor_found_ to false, force it
4987 // back to true when skipping checks.
4988 neighbor_found_ = true;
4989 } else {
4990 if (filter_manager_ != nullptr) filter_manager_->Revert();
4991 if (check_period_ > 1 && has_checked_assignment_) {
4992 // Filtering is not perfect, disabling fast local search and
4993 // resynchronizing with the last checked solution.
4994 // TODO(user): Restore state of local search operators to
4995 // make sure we are exploring neighbors in the same order. This can
4996 // affect the local optimum found.
4997 VLOG(1) << "Imperfect filtering detected, backtracking to last "
4998 "checked solution and checking all solutions.";
4999 check_period_ = 1;
5000 solutions_since_last_check_ = 0;
5001 pool_->RegisterNewSolution(&last_checked_assignment_);
5002 SynchronizeAll(solver);
5003 assignment_->CopyIntersection(&last_checked_assignment_);
5004 }
5005 }
5006 } else {
5007 // Reset the last synchronized assignment in case it's no longer up to
5008 // date or we fail below.
5009 last_synchronized_assignment_.reset();
5010 if (neighbor_found_) {
5011 // In case the last checked assignment isn't the current one, restore
5012 // it to make sure the solver knows about it, especially if this is
5013 // the end of the search.
5014 // TODO(user): Compare assignments in addition to their cost.
5015 if (last_checked_assignment_.ObjectiveValue() !=
5016 assignment_->ObjectiveValue()) {
5017 // If restoring fails this means filtering is not perfect and the
5018 // solver will consider the last checked assignment.
5019 assignment_copy->CopyIntersection(assignment_);
5020 if (!solver->SolveAndCommit(restore)) solver->Fail();
5021 last_checked_assignment_.CopyIntersection(assignment_);
5022 has_checked_assignment_ = true;
5023 return nullptr;
5024 }
5025 AcceptNeighbor(solver->ParentSearch());
5026 // Keeping the code in case a performance problem forces us to
5027 // use the old code with a zero test on pool_.
5028 // reference_assignment_->CopyIntersection(assignment_);
5029 pool_->RegisterNewSolution(assignment_);
5030 SynchronizeAll(solver);
5031 } else {
5032 break;
5033 }
5034 }
5035 }
5036 }
5037 // NOTE(user): The last synchronized assignment must be reset here to
5038 // guarantee filters will be properly synched in case we re-solve using an
5039 // assignment that wasn't the last accepted and synchronized assignment.
5040 last_synchronized_assignment_.reset();
5041 solver->Fail();
5042 return nullptr;
5043}
5044
5045bool FindOneNeighbor::FilterAccept(Solver* solver, Assignment* delta,
5046 Assignment* deltadelta,
5047 int64_t objective_min,
5048 int64_t objective_max) {
5049 if (filter_manager_ == nullptr) return true;
5050 LocalSearchMonitor* const monitor = solver->GetLocalSearchMonitor();
5051 return filter_manager_->Accept(monitor, delta, deltadelta, objective_min,
5052 objective_max);
5053}
5054
5055namespace {
5056
5057template <typename Container>
5058void AddDeltaElements(const Container& old_container,
5059 const Container& new_container, Assignment* delta) {
5060 for (const auto& new_element : new_container.elements()) {
5061 const auto var = new_element.Var();
5062 const auto old_element_ptr = old_container.ElementPtrOrNull(var);
5063 if (old_element_ptr == nullptr || *old_element_ptr != new_element) {
5064 delta->FastAdd(var)->Copy(new_element);
5065 }
5066 }
5067}
5068
5069void MakeDelta(const Assignment* old_assignment,
5070 const Assignment* new_assignment, Assignment* delta) {
5071 DCHECK_NE(delta, nullptr);
5072 delta->Clear();
5073 AddDeltaElements(old_assignment->IntVarContainer(),
5074 new_assignment->IntVarContainer(), delta);
5075 AddDeltaElements(old_assignment->IntervalVarContainer(),
5076 new_assignment->IntervalVarContainer(), delta);
5077 AddDeltaElements(old_assignment->SequenceVarContainer(),
5078 new_assignment->SequenceVarContainer(), delta);
5079}
5080} // namespace
5081
5082void FindOneNeighbor::SynchronizeAll(Solver* solver) {
5083 Assignment* const reference_assignment = reference_assignment_.get();
5084 pool_->GetNextSolution(reference_assignment);
5085 neighbor_found_ = false;
5086 limit_->Init();
5087 solver->GetLocalSearchMonitor()->BeginOperatorStart();
5088 ls_operator_->Start(reference_assignment);
5089 if (filter_manager_ != nullptr) {
5090 Assignment* delta = nullptr;
5091 if (last_synchronized_assignment_ == nullptr) {
5092 last_synchronized_assignment_ =
5093 std::make_unique<Assignment>(reference_assignment);
5094 } else {
5095 MakeDelta(last_synchronized_assignment_.get(), reference_assignment,
5096 filter_assignment_delta_);
5097 delta = filter_assignment_delta_;
5098 last_synchronized_assignment_->Copy(reference_assignment);
5099 }
5100 filter_manager_->Synchronize(reference_assignment_.get(), delta);
5101 }
5102 solver->GetLocalSearchMonitor()->EndOperatorStart();
5103}
5104
5105// ---------- Local Search Phase Parameters ----------
5106
5108 public:
5112 RegularLimit* const limit,
5114 : objective_(objective),
5115 solution_pool_(pool),
5116 ls_operator_(ls_operator),
5117 sub_decision_builder_(sub_decision_builder),
5118 limit_(limit),
5119 filter_manager_(filter_manager) {}
5121 std::string DebugString() const override {
5122 return "LocalSearchPhaseParameters";
5123 }
5124
5125 IntVar* objective() const { return objective_; }
5126 SolutionPool* solution_pool() const { return solution_pool_; }
5127 LocalSearchOperator* ls_operator() const { return ls_operator_; }
5129 return sub_decision_builder_;
5130 }
5131 RegularLimit* limit() const { return limit_; }
5132 LocalSearchFilterManager* filter_manager() const { return filter_manager_; }
5133
5134 private:
5135 IntVar* const objective_;
5136 SolutionPool* const solution_pool_;
5137 LocalSearchOperator* const ls_operator_;
5138 DecisionBuilder* const sub_decision_builder_;
5139 RegularLimit* const limit_;
5140 LocalSearchFilterManager* const filter_manager_;
5141};
5142
5144 IntVar* objective, LocalSearchOperator* const ls_operator,
5145 DecisionBuilder* const sub_decision_builder) {
5147 ls_operator, sub_decision_builder,
5148 nullptr, nullptr);
5149}
5150
5152 IntVar* objective, LocalSearchOperator* const ls_operator,
5153 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit) {
5155 ls_operator, sub_decision_builder,
5156 limit, nullptr);
5157}
5158
5160 IntVar* objective, LocalSearchOperator* const ls_operator,
5161 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
5162 LocalSearchFilterManager* filter_manager) {
5164 ls_operator, sub_decision_builder,
5165 limit, filter_manager);
5166}
5167
5169 IntVar* objective, SolutionPool* const pool,
5170 LocalSearchOperator* const ls_operator,
5171 DecisionBuilder* const sub_decision_builder) {
5172 return MakeLocalSearchPhaseParameters(objective, pool, ls_operator,
5173 sub_decision_builder, nullptr, nullptr);
5174}
5175
5177 IntVar* objective, SolutionPool* const pool,
5178 LocalSearchOperator* const ls_operator,
5179 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit) {
5180 return MakeLocalSearchPhaseParameters(objective, pool, ls_operator,
5181 sub_decision_builder, limit, nullptr);
5182}
5183
5185 IntVar* objective, SolutionPool* const pool,
5186 LocalSearchOperator* const ls_operator,
5187 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
5188 LocalSearchFilterManager* filter_manager) {
5189 return RevAlloc(new LocalSearchPhaseParameters(objective, pool, ls_operator,
5190 sub_decision_builder, limit,
5191 filter_manager));
5192}
5193
5194namespace {
5195// ----- NestedSolve decision wrapper -----
5196
5197// This decision calls a nested Solve on the given DecisionBuilder in its
5198// left branch; does nothing in the left branch.
5199// The state of the decision corresponds to the result of the nested Solve:
5200// DECISION_PENDING - Nested Solve not called yet
5201// DECISION_FAILED - Nested Solve failed
5202// DECISION_FOUND - Nested Solve succeeded
5203
5204class NestedSolveDecision : public Decision {
5205 public:
5206 // This enum is used internally to tag states in the local search tree.
5207 enum StateType { DECISION_PENDING, DECISION_FAILED, DECISION_FOUND };
5208
5209 NestedSolveDecision(DecisionBuilder* db, bool restore,
5210 const std::vector<SearchMonitor*>& monitors);
5211 NestedSolveDecision(DecisionBuilder* db, bool restore);
5212 ~NestedSolveDecision() override {}
5213 void Apply(Solver* solver) override;
5214 void Refute(Solver* solver) override;
5215 std::string DebugString() const override { return "NestedSolveDecision"; }
5216 int state() const { return state_; }
5217
5218 private:
5219 DecisionBuilder* const db_;
5220 bool restore_;
5221 std::vector<SearchMonitor*> monitors_;
5222 int state_;
5223};
5224
5225NestedSolveDecision::NestedSolveDecision(
5226 DecisionBuilder* const db, bool restore,
5227 const std::vector<SearchMonitor*>& monitors)
5228 : db_(db),
5229 restore_(restore),
5230 monitors_(monitors),
5231 state_(DECISION_PENDING) {
5232 CHECK(nullptr != db);
5233}
5234
5235NestedSolveDecision::NestedSolveDecision(DecisionBuilder* const db,
5236 bool restore)
5237 : db_(db), restore_(restore), state_(DECISION_PENDING) {
5238 CHECK(nullptr != db);
5239}
5240
5241void NestedSolveDecision::Apply(Solver* const solver) {
5242 CHECK(nullptr != solver);
5243 if (restore_) {
5244 if (solver->Solve(db_, monitors_)) {
5245 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FOUND));
5246 } else {
5247 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FAILED));
5248 }
5249 } else {
5250 if (solver->SolveAndCommit(db_, monitors_)) {
5251 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FOUND));
5252 } else {
5253 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FAILED));
5254 }
5255 }
5256}
5257
5258void NestedSolveDecision::Refute(Solver* const) {}
5259} // namespace
5260
5261// ----- Local search decision builder -----
5262
5263// Given a first solution (resulting from either an initial assignment or the
5264// result of a decision builder), it searches for neighbors using a local
5265// search operator. The first solution corresponds to the first leaf of the
5266// search.
5267// The local search applies to the variables contained either in the assignment
5268// or the vector of variables passed.
5269
5271 public:
5272 LocalSearch(Assignment* assignment, IntVar* objective, SolutionPool* pool,
5273 LocalSearchOperator* ls_operator,
5274 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
5275 LocalSearchFilterManager* filter_manager);
5276 // TODO(user): find a way to not have to pass vars here: redundant with
5277 // variables in operators
5278 LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
5279 SolutionPool* pool, DecisionBuilder* first_solution,
5280 LocalSearchOperator* ls_operator,
5281 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
5282 LocalSearchFilterManager* filter_manager);
5283 LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
5284 SolutionPool* pool, DecisionBuilder* first_solution,
5285 DecisionBuilder* first_solution_sub_decision_builder,
5286 LocalSearchOperator* ls_operator,
5287 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
5288 LocalSearchFilterManager* filter_manager);
5289 LocalSearch(const std::vector<SequenceVar*>& vars, IntVar* objective,
5290 SolutionPool* pool, DecisionBuilder* first_solution,
5291 LocalSearchOperator* ls_operator,
5292 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
5293 LocalSearchFilterManager* filter_manager);
5294 ~LocalSearch() override;
5295 Decision* Next(Solver* solver) override;
5296 std::string DebugString() const override { return "LocalSearch"; }
5297 void Accept(ModelVisitor* visitor) const override;
5298
5299 protected:
5300 void PushFirstSolutionDecision(DecisionBuilder* first_solution);
5301 void PushLocalSearchDecision();
5302
5303 private:
5304 Assignment* assignment_;
5305 IntVar* const objective_ = nullptr;
5306 SolutionPool* const pool_;
5307 LocalSearchOperator* const ls_operator_;
5308 DecisionBuilder* const first_solution_sub_decision_builder_;
5309 DecisionBuilder* const sub_decision_builder_;
5310 std::vector<NestedSolveDecision*> nested_decisions_;
5311 int nested_decision_index_;
5312 RegularLimit* const limit_;
5313 LocalSearchFilterManager* const filter_manager_;
5314 bool has_started_;
5315};
5316
5317LocalSearch::LocalSearch(Assignment* const assignment, IntVar* objective,
5318 SolutionPool* const pool,
5319 LocalSearchOperator* const ls_operator,
5320 DecisionBuilder* const sub_decision_builder,
5321 RegularLimit* const limit,
5322 LocalSearchFilterManager* filter_manager)
5323 : assignment_(nullptr),
5324 objective_(objective),
5325 pool_(pool),
5326 ls_operator_(ls_operator),
5327 first_solution_sub_decision_builder_(sub_decision_builder),
5328 sub_decision_builder_(sub_decision_builder),
5329 nested_decision_index_(0),
5330 limit_(limit),
5331 filter_manager_(filter_manager),
5332 has_started_(false) {
5333 CHECK(nullptr != assignment);
5334 CHECK(nullptr != ls_operator);
5335 Solver* const solver = assignment->solver();
5336 assignment_ = solver->GetOrCreateLocalSearchState();
5337 assignment_->Copy(assignment);
5338 DecisionBuilder* restore = solver->MakeRestoreAssignment(assignment);
5341}
5342
5343LocalSearch::LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
5344 SolutionPool* const pool,
5345 DecisionBuilder* const first_solution,
5346 LocalSearchOperator* const ls_operator,
5347 DecisionBuilder* const sub_decision_builder,
5348 RegularLimit* const limit,
5349 LocalSearchFilterManager* filter_manager)
5350 : assignment_(nullptr),
5351 objective_(objective),
5352 pool_(pool),
5353 ls_operator_(ls_operator),
5354 first_solution_sub_decision_builder_(sub_decision_builder),
5355 sub_decision_builder_(sub_decision_builder),
5356 nested_decision_index_(0),
5357 limit_(limit),
5358 filter_manager_(filter_manager),
5359 has_started_(false) {
5360 CHECK(nullptr != first_solution);
5361 CHECK(nullptr != ls_operator);
5362 CHECK(!vars.empty());
5363 Solver* const solver = vars[0]->solver();
5364 assignment_ = solver->GetOrCreateLocalSearchState();
5365 assignment_->Add(vars);
5366 PushFirstSolutionDecision(first_solution);
5368}
5369
5371 const std::vector<IntVar*>& vars, IntVar* objective,
5372 SolutionPool* const pool, DecisionBuilder* const first_solution,
5373 DecisionBuilder* const first_solution_sub_decision_builder,
5374 LocalSearchOperator* const ls_operator,
5375 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
5376 LocalSearchFilterManager* filter_manager)
5377 : assignment_(nullptr),
5378 objective_(objective),
5379 pool_(pool),
5380 ls_operator_(ls_operator),
5381 first_solution_sub_decision_builder_(first_solution_sub_decision_builder),
5382 sub_decision_builder_(sub_decision_builder),
5383 nested_decision_index_(0),
5384 limit_(limit),
5385 filter_manager_(filter_manager),
5386 has_started_(false) {
5387 CHECK(nullptr != first_solution);
5388 CHECK(nullptr != ls_operator);
5389 CHECK(!vars.empty());
5390 Solver* const solver = vars[0]->solver();
5391 assignment_ = solver->GetOrCreateLocalSearchState();
5392 assignment_->Add(vars);
5393 PushFirstSolutionDecision(first_solution);
5395}
5396
5397LocalSearch::LocalSearch(const std::vector<SequenceVar*>& vars,
5398 IntVar* objective, SolutionPool* const pool,
5399 DecisionBuilder* const first_solution,
5400 LocalSearchOperator* const ls_operator,
5401 DecisionBuilder* const sub_decision_builder,
5402 RegularLimit* const limit,
5403 LocalSearchFilterManager* filter_manager)
5404 : assignment_(nullptr),
5405 objective_(objective),
5406 pool_(pool),
5407 ls_operator_(ls_operator),
5408 first_solution_sub_decision_builder_(sub_decision_builder),
5409 sub_decision_builder_(sub_decision_builder),
5410 nested_decision_index_(0),
5411 limit_(limit),
5412 filter_manager_(filter_manager),
5413 has_started_(false) {
5414 CHECK(nullptr != first_solution);
5415 CHECK(nullptr != ls_operator);
5416 CHECK(!vars.empty());
5417 Solver* const solver = vars[0]->solver();
5418 assignment_ = solver->GetOrCreateLocalSearchState();
5419 assignment_->Add(vars);
5420 PushFirstSolutionDecision(first_solution);
5422}
5423
5425
5426// Model Visitor support.
5427void LocalSearch::Accept(ModelVisitor* const visitor) const {
5428 DCHECK(assignment_ != nullptr);
5430 // We collect decision variables from the assignment.
5431 const std::vector<IntVarElement>& elements =
5432 assignment_->IntVarContainer().elements();
5433 if (!elements.empty()) {
5434 std::vector<IntVar*> vars;
5435 for (const IntVarElement& elem : elements) {
5436 vars.push_back(elem.Var());
5437 }
5439 vars);
5440 }
5441 const std::vector<IntervalVarElement>& interval_elements =
5442 assignment_->IntervalVarContainer().elements();
5443 if (!interval_elements.empty()) {
5444 std::vector<IntervalVar*> interval_vars;
5445 for (const IntervalVarElement& elem : interval_elements) {
5446 interval_vars.push_back(elem.Var());
5447 }
5449 interval_vars);
5450 }
5452}
5453
5454// This is equivalent to a multi-restart decision builder
5455// TODO(user): abstract this from the local search part
5456// TODO(user): handle the case where the tree depth is not enough to hold
5457// all solutions.
5458
5460 CHECK(nullptr != solver);
5461 CHECK_LT(0, nested_decisions_.size());
5462 if (!has_started_) {
5463 nested_decision_index_ = 0;
5464 solver->SaveAndSetValue(&has_started_, true);
5465 } else if (nested_decision_index_ < 0) {
5466 solver->Fail();
5467 }
5468 NestedSolveDecision* decision = nested_decisions_[nested_decision_index_];
5469 const int state = decision->state();
5470 switch (state) {
5471 case NestedSolveDecision::DECISION_FAILED: {
5472 const bool local_optimum_reached =
5474 if (local_optimum_reached) {
5475 // A local optimum has been reached. The search will continue only if we
5476 // accept up-hill moves (due to metaheuristics). In this case we need to
5477 // reset neighborhood optimal routes.
5478 ls_operator_->Reset();
5479 }
5480 if (!local_optimum_reached || solver->IsUncheckedSolutionLimitReached()) {
5481 nested_decision_index_ = -1; // Stop the search
5482 }
5483 solver->Fail();
5484 return nullptr;
5485 }
5486 case NestedSolveDecision::DECISION_PENDING: {
5487 // TODO(user): Find a way to make this balancing invisible to the
5488 // user (no increase in branch or fail counts for instance).
5489 const int32_t kLocalSearchBalancedTreeDepth = 32;
5490 const int depth = solver->SearchDepth();
5491 if (depth < kLocalSearchBalancedTreeDepth) {
5492 return solver->balancing_decision();
5493 }
5494 if (depth > kLocalSearchBalancedTreeDepth) {
5495 solver->Fail();
5496 }
5497 return decision;
5498 }
5499 case NestedSolveDecision::DECISION_FOUND: {
5500 // Next time go to next decision
5501 if (nested_decision_index_ + 1 < nested_decisions_.size()) {
5502 ++nested_decision_index_;
5503 }
5504 return nullptr;
5505 }
5506 default: {
5507 LOG(ERROR) << "Unknown local search state";
5508 return nullptr;
5509 }
5510 }
5511 return nullptr;
5512}
5513
5515 CHECK(first_solution);
5516 Solver* const solver = assignment_->solver();
5517 DecisionBuilder* store = solver->MakeStoreAssignment(assignment_);
5518 DecisionBuilder* first_solution_and_store = solver->Compose(
5519 solver->MakeProfiledDecisionBuilderWrapper(first_solution),
5520 first_solution_sub_decision_builder_, store);
5521 std::vector<SearchMonitor*> monitor;
5522 monitor.push_back(limit_);
5523 nested_decisions_.push_back(solver->RevAlloc(
5524 new NestedSolveDecision(first_solution_and_store, false, monitor)));
5525}
5526
5528 Solver* const solver = assignment_->solver();
5529 DecisionBuilder* find_neighbors = solver->RevAlloc(
5530 new FindOneNeighbor(assignment_, objective_, pool_, ls_operator_,
5531 sub_decision_builder_, limit_, filter_manager_));
5532 nested_decisions_.push_back(
5533 solver->RevAlloc(new NestedSolveDecision(find_neighbors, false)));
5534}
5535
5537 public:
5539
5541
5542 void Initialize(Assignment* const assignment) override {
5543 reference_assignment_ = std::make_unique<Assignment>(assignment);
5544 }
5545
5546 void RegisterNewSolution(Assignment* const assignment) override {
5547 reference_assignment_->CopyIntersection(assignment);
5548 }
5549
5550 void GetNextSolution(Assignment* const assignment) override {
5551 assignment->CopyIntersection(reference_assignment_.get());
5552 }
5553
5554 bool SyncNeeded(Assignment* const) override { return false; }
5555
5556 std::string DebugString() const override { return "DefaultSolutionPool"; }
5557
5558 private:
5559 std::unique_ptr<Assignment> reference_assignment_;
5560};
5561
5565
5568 return RevAlloc(new LocalSearch(
5569 assignment, parameters->objective(), parameters->solution_pool(),
5570 parameters->ls_operator(), parameters->sub_decision_builder(),
5571 parameters->limit(), parameters->filter_manager()));
5572}
5573
5575 const std::vector<IntVar*>& vars, DecisionBuilder* first_solution,
5577 return RevAlloc(new LocalSearch(
5578 vars, parameters->objective(), parameters->solution_pool(),
5579 first_solution, parameters->ls_operator(),
5580 parameters->sub_decision_builder(), parameters->limit(),
5581 parameters->filter_manager()));
5582}
5583
5585 const std::vector<IntVar*>& vars, DecisionBuilder* first_solution,
5586 DecisionBuilder* first_solution_sub_decision_builder,
5588 return RevAlloc(new LocalSearch(
5589 vars, parameters->objective(), parameters->solution_pool(),
5590 first_solution, first_solution_sub_decision_builder,
5591 parameters->ls_operator(), parameters->sub_decision_builder(),
5592 parameters->limit(), parameters->filter_manager()));
5593}
5594
5596 const std::vector<SequenceVar*>& vars, DecisionBuilder* first_solution,
5598 return RevAlloc(new LocalSearch(
5599 vars, parameters->objective(), parameters->solution_pool(),
5600 first_solution, parameters->ls_operator(),
5601 parameters->sub_decision_builder(), parameters->limit(),
5602 parameters->filter_manager()));
5603}
5604
5605template <bool is_profile_active>
5606Assignment* Solver::RunUncheckedLocalSearchInternal(
5607 const Assignment* initial_solution,
5608 LocalSearchFilterManager* filter_manager, LocalSearchOperator* ls_operator,
5609 const std::vector<SearchMonitor*>& monitors, RegularLimit* limit,
5610 absl::flat_hash_set<IntVar*>* touched) {
5611 DCHECK(initial_solution != nullptr);
5612 DCHECK(initial_solution->Objective() != nullptr);
5613 DCHECK(filter_manager != nullptr);
5614 if (limit != nullptr) limit->Init();
5615 Assignment delta(this);
5616 Assignment deltadelta(this);
5617 Assignment* const current_solution = GetOrCreateLocalSearchState();
5618 current_solution->Copy(initial_solution);
5619 std::function<bool(Assignment*, Assignment*)> make_next_neighbor;
5620 std::function<bool(Assignment*, Assignment*)> filter_neighbor;
5621 LocalSearchMonitor* const ls_monitor =
5622 is_profile_active ? GetLocalSearchMonitor() : nullptr;
5623 const auto sync_with_solution =
5624 [this, ls_monitor, ls_operator, // NOLINT: ls_monitor is used when
5625 // is_profile_active is true
5626 filter_manager, current_solution](Assignment* delta) {
5627 IncrementUncheckedSolutionCounter();
5628 if constexpr (is_profile_active) {
5629 ls_monitor->BeginOperatorStart();
5630 }
5631 ls_operator->Start(current_solution);
5632 filter_manager->Synchronize(current_solution, delta);
5633 if constexpr (is_profile_active) {
5634 ls_monitor->EndOperatorStart();
5635 }
5636 };
5637 sync_with_solution(/*delta=*/nullptr);
5638 while (true) {
5639 if (!ls_operator->HoldsDelta()) {
5640 delta.Clear();
5641 }
5642 delta.ClearObjective();
5643 deltadelta.Clear();
5644 if (limit != nullptr && (limit->CheckWithOffset(absl::ZeroDuration()) ||
5646 break;
5647 }
5648 if constexpr (is_profile_active) {
5649 ls_monitor->BeginMakeNextNeighbor(ls_operator);
5650 }
5651 const bool has_neighbor =
5652 ls_operator->MakeNextNeighbor(&delta, &deltadelta);
5653 if constexpr (is_profile_active) {
5654 ls_monitor->EndMakeNextNeighbor(ls_operator, has_neighbor, &delta,
5655 &deltadelta);
5656 }
5657 if (!has_neighbor) {
5658 break;
5659 }
5660 if constexpr (is_profile_active) {
5661 ls_monitor->BeginFilterNeighbor(ls_operator);
5662 }
5663 for (SearchMonitor* monitor : monitors) {
5664 const bool mh_accept = monitor->AcceptDelta(&delta, &deltadelta);
5665 DCHECK(mh_accept);
5666 }
5667 const bool filter_accept =
5668 filter_manager->Accept(ls_monitor, &delta, &deltadelta,
5669 delta.ObjectiveMin(), delta.ObjectiveMax());
5670 if constexpr (is_profile_active) {
5671 ls_monitor->EndFilterNeighbor(ls_operator, filter_accept);
5672 ls_monitor->BeginAcceptNeighbor(ls_operator);
5673 ls_monitor->EndAcceptNeighbor(ls_operator, filter_accept);
5674 }
5675 if (!filter_accept) {
5676 filter_manager->Revert();
5677 continue;
5678 }
5679 filtered_neighbors_ += 1;
5680 current_solution->CopyIntersection(&delta);
5681 current_solution->SetObjectiveValue(
5682 filter_manager->GetAcceptedObjectiveValue());
5683 DCHECK(delta.AreAllElementsBound());
5684 accepted_neighbors_ += 1;
5685 SetSearchContext(ActiveSearch(), ls_operator->DebugString());
5686 for (SearchMonitor* monitor : monitors) {
5687 monitor->AtSolution();
5688 }
5689 if (touched != nullptr) {
5690 for (const auto& element : delta.IntVarContainer().elements()) {
5691 touched->insert(element.Var());
5692 }
5693 }
5694 // Syncing here to avoid resyncing when filtering fails.
5695 sync_with_solution(/*delta=*/&delta);
5696 }
5697 if (parameters_.print_local_search_profile()) {
5698 const std::string profile = LocalSearchProfile();
5699 if (!profile.empty()) LOG(INFO) << profile;
5700 }
5701 return MakeAssignment(current_solution);
5702}
5703
5705 const Assignment* initial_solution,
5706 LocalSearchFilterManager* filter_manager, LocalSearchOperator* ls_operator,
5707 const std::vector<SearchMonitor*>& monitors, RegularLimit* limit,
5708 absl::flat_hash_set<IntVar*>* touched) {
5709 if (GetLocalSearchMonitor()->IsActive()) {
5710 return RunUncheckedLocalSearchInternal<true>(initial_solution,
5711 filter_manager, ls_operator,
5712 monitors, limit, touched);
5713 } else {
5714 return RunUncheckedLocalSearchInternal<false>(initial_solution,
5715 filter_manager, ls_operator,
5716 monitors, limit, touched);
5717 }
5718}
5719
5720} // namespace operations_research
IntegerValue size
const std::vector< IntVar * > vars_
int64_t max
int64_t min
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
void Stop()
Definition timer.h:40
const E & Element(const V *const var) const
const std::vector< E > & elements() const
IntVarElement * Add(IntVar *var)
const IntervalContainer & IntervalVarContainer() const
std::string DebugString() const override
void Copy(const Assignment *assignment)
const IntContainer & IntVarContainer() const
void CopyIntersection(const Assignment *assignment)
BaseInactiveNodeToPathOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, int number_of_base_nodes, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
BaseLns(const std::vector< IntVar * > &vars)
--— Base Large Neighborhood Search operator --—
bool MakeOneNeighbor() override
This method should not be overridden. Override NextFragment() instead.
virtual bool NextFragment()=0
virtual std::string DebugString() const
void ClearAll()
Sets all bits to 0.
Definition bitset.h:507
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:548
virtual int64_t ModifyValue(int64_t index, int64_t value)=0
bool MakeOneNeighbor() override
This method should not be overridden. Override ModifyValue() instead.
ChangeValue(const std::vector< IntVar * > &vars)
--— ChangeValue Operators --—
Cross(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
std::string DebugString() const override
void GetNextSolution(Assignment *const assignment) override
std::string DebugString() const override
bool SyncNeeded(Assignment *const) override
void Initialize(Assignment *const assignment) override
void RegisterNewSolution(Assignment *const assignment) override
std::string DebugString() const override
Exchange(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
--— ExtendedSwapActiveOperator --—
ExtendedSwapActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— Finds a neighbor of the assignment passed --—
std::string DebugString() const override
-------— Decision Builder -------—
Decision * Next(Solver *solver) override
FindOneNeighbor(Assignment *assignment, IntVar *objective, SolutionPool *pool, LocalSearchOperator *ls_operator, DecisionBuilder *sub_decision_builder, const RegularLimit *limit, LocalSearchFilterManager *filter_manager)
void ChangeCostMatrix(CostFunction cost)
Replaces the cost matrix while avoiding re-allocating memory.
std::vector< int > TravelingSalesmanPath()
Returns the TSP tour in the vector pointed to by the argument.
virtual int64_t Min() const =0
virtual int64_t Max() const =0
IntVarLocalSearchFilter(const std::vector< IntVar * > &vars)
void SynchronizeOnAssignment(const Assignment *assignment)
void AddVars(const std::vector< IntVar * > &vars)
Add variables to "track" to the filter.
void Synchronize(const Assignment *assignment, const Assignment *delta) override
void SetValue(int64_t index, int64_t value)
void RevertChanges(bool change_was_incremental)
bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta) override
--— Base operator class for operators manipulating IntVars --—
void AddVars(const std::vector< IntVar * > &vars)
IntVar * Var(int64_t index) const
Returns the variable of given index.
bool ApplyChanges(Assignment *delta, Assignment *deltadelta) const
IntVar * Var() override
Creates a variable from the expression.
LinKernighan(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, const Solver::IndexEvaluator3 &evaluator, bool topt)
std::string DebugString() const override
LocalSearchFilterManager(std::vector< FilterEvent > filter_events)
void Revert()
Calls Revert() of filters, in reverse order of Relax events.
void Synchronize(const Assignment *assignment, const Assignment *delta)
Synchronizes all filters to assignment.
bool Accept(LocalSearchMonitor *monitor, const Assignment *delta, const Assignment *deltadelta, int64_t objective_min, int64_t objective_max)
virtual void EndAcceptNeighbor(const LocalSearchOperator *op, bool neighbor_found)=0
virtual void BeginAcceptNeighbor(const LocalSearchOperator *op)=0
virtual void BeginMakeNextNeighbor(const LocalSearchOperator *op)=0
virtual void BeginOperatorStart()=0
Local search operator events.
virtual void BeginFilterNeighbor(const LocalSearchOperator *op)=0
virtual void BeginFiltering(const LocalSearchFilter *filter)=0
virtual void EndFilterNeighbor(const LocalSearchOperator *op, bool neighbor_found)=0
virtual void EndMakeNextNeighbor(const LocalSearchOperator *op, bool neighbor_found, const Assignment *delta, const Assignment *deltadelta)=0
virtual void EndFiltering(const LocalSearchFilter *filter, bool reject)=0
virtual bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta)=0
virtual const LocalSearchOperator * Self() const
virtual void Start(const Assignment *assignment)=0
-------— Local Search Phase Parameters -------—
LocalSearchFilterManager * filter_manager() const
LocalSearchPhaseParameters(IntVar *objective, SolutionPool *const pool, LocalSearchOperator *ls_operator, DecisionBuilder *sub_decision_builder, RegularLimit *const limit, LocalSearchFilterManager *filter_manager)
--— LocalSearchProfiler --—
void BeginOperatorStart() override
Local search operator events.
void EndFiltering(const LocalSearchFilter *filter, bool reject) override
void BeginMakeNextNeighbor(const LocalSearchOperator *op) override
std::string DebugString() const override
void BeginFiltering(const LocalSearchFilter *filter) override
LocalSearchStatistics ExportToLocalSearchStatistics() const
void ExitSearch() override
End of the search.
void EndAcceptNeighbor(const LocalSearchOperator *op, bool neighbor_found) override
void ParseLocalSearchOperatorStatistics(const Callback &callback) const
void ParseLocalSearchFilterStatistics(const Callback &callback) const
void AddFirstSolutionProfiledDecisionBuilder(ProfiledDecisionBuilder *profiled_db)
void BeginAcceptNeighbor(const LocalSearchOperator *) override
void Install() override
Install itself on the solver.
void BeginFilterNeighbor(const LocalSearchOperator *) override
void ParseFirstSolutionStatistics(const Callback &callback) const
void RestartSearch() override
Restart the search.
void EndFilterNeighbor(const LocalSearchOperator *op, bool neighbor_found) override
void EndMakeNextNeighbor(const LocalSearchOperator *op, bool neighbor_found, const Assignment *, const Assignment *) override
void AddWeightedSumConstraint(const std::vector< VariableDomainId > &input_domain_ids, const std::vector< int64_t > &input_weights, int64_t input_offset, VariableDomainId output_domain_id)
Adds a constraint that output = input_offset + sum_i weight_i * input_i.
int64_t VariableDomainMin(VariableDomainId domain_id) const
VariableDomainId AddVariableDomain(int64_t relaxed_min, int64_t relaxed_max)
Adds a variable domain to this state, returns a handler to the new domain.
bool TightenVariableDomainMin(VariableDomainId domain_id, int64_t value)
void ChangeRelaxedVariableDomain(VariableDomainId domain_id, int64_t min, int64_t max)
void PropagateRelax(VariableDomainId domain_id)
Propagation of all events.
Variable MakeVariable(VariableDomainId domain_id)
void RelaxVariableDomain(VariableDomainId domain_id)
bool TightenVariableDomainMax(VariableDomainId domain_id, int64_t value)
int64_t VariableDomainMax(VariableDomainId domain_id) const
bool PropagateTighten(VariableDomainId domain_id)
--— Local search decision builder --—
void PushFirstSolutionDecision(DecisionBuilder *first_solution)
std::string DebugString() const override
-------— Decision Builder -------—
LocalSearch(Assignment *assignment, IntVar *objective, SolutionPool *pool, LocalSearchOperator *ls_operator, DecisionBuilder *sub_decision_builder, RegularLimit *limit, LocalSearchFilterManager *filter_manager)
void Accept(ModelVisitor *visitor) const override
Model Visitor support.
Decision * Next(Solver *solver) override
--— MakeActiveAndRelocate --—
MakeActiveAndRelocate(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
std::string DebugString() const override
--— MakeActiveOperator --—
MakeActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
std::string DebugString() const override
--— MakeChainInactiveOperator --—
int64_t GetBaseNodeRestartPosition(int base_index) override
MakeChainInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— MakeInactiveOperator --—
MakeInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
std::string DebugString() const override
virtual void VisitIntervalArrayArgument(const std::string &arg_name, const std::vector< IntervalVar * > &arguments)
virtual void VisitIntegerVariableArrayArgument(const std::string &arg_name, const std::vector< IntVar * > &arguments)
virtual void EndVisitExtension(const std::string &type)
virtual void BeginVisitExtension(const std::string &type)
const std::vector< int > & Neighbors(int index) const
NearestNeighbors(const NearestNeighbors &)=delete
This type is neither copyable nor movable.
NearestNeighbors(Solver::IndexEvaluator3 evaluator, const PathOperator &path_operator, int size)
virtual std::string DebugString() const
NearestNeighbors & operator=(const NearestNeighbors &)=delete
--— Limit the number of neighborhoods explored --—
bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta) override
NeighborhoodLimit(LocalSearchOperator *const op, int64_t limit)
std::string DebugString() const override
void Start(const Assignment *assignment) override
--— Path-based Large Neighborhood Search --—
std::string DebugString() const override
PathLns(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, int number_of_chunks, int chunk_size, bool unactive_fragments)
bool HasFragments() const override
int64_t Prev(int64_t node) const
Returns the node before node in the current delta.
PathOperator(const std::vector< IntVar * > &next_vars, const std::vector< IntVar * > &path_vars, IterationParameters iteration_parameters)
Builds an instance of PathOperator from next and path variables.
bool MakeActive(int64_t node, int64_t destination)
Insert the inactive node after destination.
bool IsPathStart(int64_t node) const
Returns true if node is the first node on the path.
bool CheckChainValidity(int64_t before_chain, int64_t chain_end, int64_t exclude) const
bool SkipUnchanged(int index) const override
int GetNeighborForBaseNode(int64_t base_index) const
int number_of_nexts() const
Number of next variables.
int GetSiblingAlternativeIndex(int node) const
Returns the index of the alternative set of the sibling of node.
int64_t OldNext(int64_t node) const
bool MakeChainInactive(int64_t before_chain, int64_t chain_end)
int64_t PrevNext(int64_t node) const
bool IsInactive(int64_t node) const
Returns true if node is inactive.
virtual bool ConsiderAlternatives(int64_t base_index) const
bool SwapActiveAndInactive(int64_t active, int64_t inactive)
Replaces active by inactive in the current path, making active inactive.
int64_t EndNode(int i) const
Returns the end node of the ith base node.
int PathClassFromStartNode(int64_t start_node) const
bool MakeOneNeighbor() override
This method should not be overridden. Override MakeNeighbor() instead.
int CurrentNodePathStart(int64_t node) const
int64_t Path(int64_t node) const
int CurrentNodePathEnd(int64_t node) const
bool MoveChain(int64_t before_chain, int64_t chain_end, int64_t destination)
virtual int64_t GetBaseNodeRestartPosition(int base_index)
int64_t StartNode(int i) const
Returns the start node of the ith base node.
virtual bool OnSamePathAsPreviousBase(int64_t base_index)
void SetNext(int64_t from, int64_t to, int64_t path)
Sets 'to' to be the node after 'from' on the given path.
int64_t BaseNode(int i) const
Returns the ith base node of the operator.
bool ReverseChain(int64_t before_chain, int64_t after_chain, int64_t *chain_last)
A ChainRange is a range of Chains, committed or not.
int NumNodes() const
Instance-constant accessors.
const std::vector< int > & ChangedLoops() const
Returns the set of loops that were added by the change.
const std::vector< int > & ChangedPaths() const
void Commit()
Set the current state G1 as committed. See class comment for details.
ChainRange Chains(int path) const
Returns the current range of chains of path.
int NumPaths() const
Returns the number of paths (empty paths included).
void Revert()
Erase incremental changes. See class comment for details.
NodeRange Nodes(int path) const
Returns the current range of nodes of path.
int Path(int node) const
State-dependent accessors.
void ChangePath(int path, const std::vector< ChainBounds > &chains)
State modifiers.
int End(int path) const
Returns the end of a path.
PathState(int num_nodes, std::vector< int > path_start, std::vector< int > path_end)
int Start(int path) const
Returns the start of a path.
void ChangeLoops(const std::vector< int > &new_loops)
Describes the nodes that are newly loops in this change.
bool IsUncheckedSolutionLimitReached() override
Definition search.cc:4448
bool CheckWithOffset(absl::Duration offset) override
Definition search.cc:4396
void Init() override
This method is called when the search limit is initialized.
Definition search.cc:4417
void Copy(const SearchLimit *limit) override
Definition search.cc:4377
RegularLimit * MakeIdenticalClone() const
Definition search.cc:4390
-— RelocateAndMakeActiveOperator --—
RelocateAndMakeActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— RelocateAndMakeInactiveOperator --—
RelocateAndMakeInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
Relocate(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, int64_t chain_length=1LL, bool single_path=false)
Relocate(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors, int64_t chain_length=1LL, bool single_path=false)
std::string DebugString() const override
Relocate(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, const std::string &name, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors, int64_t chain_length=1LL, bool single_path=false)
bool OnSamePathAsPreviousBase(int64_t) override
virtual bool AcceptDelta(Assignment *delta, Assignment *deltadelta)
virtual bool SyncNeeded(Assignment *local_assignment)=0
virtual void Initialize(Assignment *assignment)=0
virtual void RegisterNewSolution(Assignment *assignment)=0
virtual void GetNextSolution(Assignment *assignment)=0
For the time being, Solver is neither MT_SAFE nor MT_HOT.
Search * ActiveSearch() const
Returns the active search, nullptr outside search.
bool IsLocalSearchProfilingEnabled() const
Returns whether we are profiling local search.
DecisionBuilder * MakeRestoreAssignment(Assignment *assignment)
LocalSearchOperator * MakeMoveTowardTargetOperator(const Assignment &target)
LocalSearchOperator * ConcatenateOperators(const std::vector< LocalSearchOperator * > &ops)
LocalSearchFilter * MakeVariableDomainFilter()
ABSL_MUST_USE_RESULT RegularLimit * MakeSolutionsLimit(int64_t solutions)
Definition search.cc:4522
LocalSearchPhaseParameters * MakeLocalSearchPhaseParameters(IntVar *objective, LocalSearchOperator *ls_operator, DecisionBuilder *sub_decision_builder)
Local Search Phase Parameters.
void Fail()
Abandon the current branch in the search tree. A backtrack will follow.
Decision * balancing_decision() const
IntVarLocalSearchFilter * MakeSumObjectiveFilter(const std::vector< IntVar * > &vars, IndexEvaluator2 values, Solver::LocalSearchFilterBound filter_enum)
DecisionBuilder * MakeProfiledDecisionBuilderWrapper(DecisionBuilder *db)
Activates profiling on a decision builder.
bool UseFastLocalSearch() const
Returns true if fast local search is enabled.
LocalSearchMonitor * GetLocalSearchMonitor() const
Returns the local search monitor.
Assignment * RunUncheckedLocalSearch(const Assignment *initial_solution, LocalSearchFilterManager *filter_manager, LocalSearchOperator *ls_operator, const std::vector< SearchMonitor * > &monitors, RegularLimit *limit, absl::flat_hash_set< IntVar * > *touched=nullptr)
const std::string & context() const
Gets the current context of the search.
LocalSearchStatistics GetLocalSearchStatistics() const
Returns detailed local search statistics.
DecisionBuilder * Compose(DecisionBuilder *db1, DecisionBuilder *db2)
Definition search.cc:609
std::string LocalSearchProfile() const
LocalSearchOperator * RandomConcatenateOperators(const std::vector< LocalSearchOperator * > &ops)
void SaveAndSetValue(T *adr, T val)
All-in-one SaveAndSetValue.
LocalSearchFilter * MakeAcceptFilter()
Local Search Filters.
SolutionPool * MakeDefaultSolutionPool()
Solution Pool.
LocalSearchOperator * MakeNeighborhoodLimit(LocalSearchOperator *op, int64_t limit)
DecisionBuilder * MakeLocalSearchPhase(Assignment *assignment, LocalSearchPhaseParameters *parameters)
@ LE
Move is accepted when the current objective value <= objective.Max.
@ GE
Move is accepted when the current objective value >= objective.Min.
LocalSearchFilter * MakeRejectFilter()
std::function< int64_t(int64_t, int64_t, int64_t)> IndexEvaluator3
DecisionBuilder * MakeStoreAssignment(Assignment *assignment)
LocalSearchOperator * MultiArmedBanditConcatenateOperators(const std::vector< LocalSearchOperator * > &ops, double memory_coefficient, double exploration_coefficient, bool maximize)
LocalSearchOperator * MakeOperator(const std::vector< IntVar * > &vars, LocalSearchOperators op, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
Local Search Operators.
Assignment * MakeAssignment()
This method creates an empty assignment.
@ RELOCATE
Relocate neighborhood with length of 1 (see OROPT comment).
void SetSearchContext(Search *search, absl::string_view search_context)
ConstraintSolverParameters parameters() const
Stored Parameters.
std::function< int64_t(int64_t, int64_t)> IndexEvaluator2
LocalSearchOperator * MakeRandomLnsOperator(const std::vector< IntVar * > &vars, int number_of_variables)
bool SolveAndCommit(DecisionBuilder *db, const std::vector< SearchMonitor * > &monitors)
const std::vector< ArcId > & ComputeSortedSubDagArcs(NodeId node)
--— SwapActiveOperator --—
std::string DebugString() const override
SwapActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
TSPLns(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int tsp_size)
bool MakeOneNeighbor() override
This method should not be overridden. Override MakeNeighbor() instead.
std::string DebugString() const override
--— TSP-based operators --—
TSPOpt(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int chain_length)
std::string DebugString() const override
int64_t GetBaseNodeRestartPosition(int base_index) override
std::string DebugString() const override
TwoOpt(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors=nullptr)
bool IsIncremental() const override
bool OnSamePathAsPreviousBase(int64_t) override
int64_t a
Definition table.cc:44
Block * next
SatParameters parameters
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
const int64_t limit_
GurobiMPCallbackContext * context
MPCallback * callback
#define MAKE_LOCAL_SEARCH_OPERATOR_WITH_NEIGHBORS(OperatorClass)
int arc
ABSL_FLAG(int, cp_local_search_sync_frequency, 16, "Frequency of checks for better solutions in the solution pool.")
Filter filter_
int head_index
std::vector< int64_t > synchronized_costs_
int index
int64_t synchronized_sum_
int64_t delta_sum_
int tail_index
const int primary_vars_size_
#define MAKE_LOCAL_SEARCH_OPERATOR(OperatorClass)
std::vector< int64_t > delta_costs_
bool incremental_
RowIndex row
Definition markowitz.cc:186
ReverseView< Container > reversed_view(const Container &c)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition map_util.h:211
For infeasible and unbounded see Not checked if options check_solutions_if_inf_or_unbounded and the If options first_solution_only is false
problem is infeasible or unbounded (default).
Definition matchers.h:468
In SWIG mode, we don't want anything besides these top-level includes.
void DeleteLocalSearchProfiler(LocalSearchProfiler *monitor)
int64_t CapAdd(int64_t x, int64_t y)
SubDagComputer::ArcId ArcId
void AcceptNeighbor(Search *search)
Notifies the search that a neighbor has been accepted by local search.
void AcceptUncheckedNeighbor(Search *search)
int64_t CapSub(int64_t x, int64_t y)
LinearExpr operator-(LinearExpr lhs, const LinearExpr &rhs)
LocalSearchOperator * MakeLocalSearchOperator(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
Operator Factories.
LocalSearchFilter * MakePathStateFilter(Solver *solver, std::unique_ptr< PathState > path_state, const std::vector< IntVar * > &nexts)
bool LocalOptimumReached(Search *search)
Returns true if a local optimum has been reached and cannot be improved.
LinearExpr operator+(LinearExpr lhs, const LinearExpr &rhs)
LocalSearchOperator * MakeLocalSearchOperatorWithNeighbors(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::function< const std::vector< int > &(int, int)> get_neighbors)
void InstallLocalSearchProfiler(LocalSearchProfiler *monitor)
int64_t CapProd(int64_t x, int64_t y)
LocalSearchProfiler * BuildLocalSearchProfiler(Solver *solver)
static const int kUnassigned
--— Routing model --—
Definition routing.cc:361
int MostSignificantBitPosition32(uint32_t n)
Definition bitset.h:277
LocalSearchState::VariableDomainId VariableDomainId
LocalSearchFilter * MakeDimensionFilter(Solver *solver, std::unique_ptr< DimensionChecker > checker, const std::string &dimension_name)
bool AcceptDelta(Search *search, Assignment *delta, Assignment *deltadelta)
SubDagComputer::NodeId NodeId
STL namespace.
bool Next()
trees with all degrees equal to
int64_t weight
Definition pack.cc:510
static int input(yyscan_t yyscanner)
const Variable x
Definition qp_tests.cc:127
int64_t demand
Definition resource.cc:126
int64_t delta
Definition resource.cc:1709
IntervalVar * interval
Definition resource.cc:101
int head
int tail
int nodes
std::optional< int64_t > end
int64_t start
Set of parameters used to configure how the neighnorhood is traversed.
std::function< const std::vector< int > &(int, int)> get_neighbors
Callback returning neighbors of a node on a path starting at start_node.
bool accept_path_end_base
True if path ends should be considered when iterating over neighbors.
int number_of_base_nodes
Number of nodes needed to define a neighbor.
static const int64_t kint64max
Definition types.h:32
static const int64_t kint64min
Definition types.h:31