Google OR-Tools v9.15
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-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include <algorithm>
15#include <cmath>
16#include <cstddef>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <memory>
21#include <optional>
22#include <random>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/algorithm/container.h"
28#include "absl/base/nullability.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"
46#include "ortools/base/types.h"
50#include "ortools/util/bitset.h"
52
53ABSL_FLAG(int, cp_local_search_sync_frequency, 16,
54 "Frequency of checks for better solutions in the solution pool.");
55
56ABSL_FLAG(int, cp_local_search_tsp_opt_size, 13,
57 "Size of TSPs solved in the TSPOpt operator.");
58
59ABSL_FLAG(int, cp_local_search_tsp_lns_size, 10,
60 "Size of TSPs solved in the TSPLns operator.");
61
62namespace operations_research {
63
64// Utility methods to ensure the communication between local search and the
65// search.
66
67// Returns true if the search must continue after reaching the local optimum.
68bool ContinueAtLocalOptimum(Search* search);
69
70// Returns true if the search accepts the delta (actually checking this by
71// calling AcceptDelta on the monitors of the search).
72bool AcceptDelta(Search* search, Assignment* delta, Assignment* deltadelta);
73
74// Notifies the search that a neighbor has been accepted by local search.
75void AcceptNeighbor(Search* search);
76void AcceptUncheckedNeighbor(Search* search);
77
78// ----- Base operator class for operators manipulating IntVars -----
79
81 Assignment* deltadelta) {
82 CHECK(delta != nullptr);
83 VLOG(2) << DebugString() << "::MakeNextNeighbor(delta=("
84 << delta->DebugString() << "), deltadelta=("
85 << (deltadelta ? deltadelta->DebugString() : std::string("nullptr"))
86 << "))";
87 while (true) {
88 RevertChanges(true);
89
90 if (!MakeOneNeighbor()) {
91 return false;
92 }
93
94 if (ApplyChanges(delta, deltadelta)) {
95 VLOG(2) << "Delta (" << DebugString() << ") = " << delta->DebugString();
96 return true;
97 }
98 }
99 return false;
100}
101// TODO(user): Make this a pure virtual.
103
104// ----- Base Large Neighborhood Search operator -----
105
106BaseLns::BaseLns(const std::vector<IntVar*>& vars)
108
110
112 fragment_.clear();
113 if (NextFragment()) {
114 for (int candidate : fragment_) {
115 Deactivate(candidate);
116 }
117 return true;
118 }
119 return false;
120}
121
123
125
127 if (index >= 0 && index < Size()) {
128 fragment_.push_back(index);
129 }
130}
131
132int BaseLns::FragmentSize() const { return fragment_.size(); }
133
134// ----- Simple Large Neighborhood Search operator -----
135
136// Frees number_of_variables (contiguous in vars) variables.
137
138namespace {
139class SimpleLns : public BaseLns {
140 public:
141 SimpleLns(const std::vector<IntVar*>& vars, int number_of_variables)
142 : BaseLns(vars), index_(0), number_of_variables_(number_of_variables) {
143 CHECK_GT(number_of_variables_, 0);
144 }
145 ~SimpleLns() override {}
146 void InitFragments() override { index_ = 0; }
147 bool NextFragment() override;
148 std::string DebugString() const override { return "SimpleLns"; }
149
150 private:
151 int index_;
152 const int number_of_variables_;
153};
154
155bool SimpleLns::NextFragment() {
156 const int size = Size();
157 if (index_ < size) {
158 for (int i = index_; i < index_ + number_of_variables_; ++i) {
159 AppendToFragment(i % size);
160 }
161 ++index_;
162 return true;
163 }
164 return false;
165}
166
167// ----- Random Large Neighborhood Search operator -----
168
169// Frees up to number_of_variables random variables.
170
171class RandomLns : public BaseLns {
172 public:
173 RandomLns(const std::vector<IntVar*>& vars, int number_of_variables,
174 int32_t seed)
175 : BaseLns(vars), rand_(seed), number_of_variables_(number_of_variables) {
176 CHECK_GT(number_of_variables_, 0);
177 CHECK_LE(number_of_variables_, Size());
178 }
179 ~RandomLns() override {}
180 bool NextFragment() override;
181
182 std::string DebugString() const override { return "RandomLns"; }
183
184 private:
185 std::mt19937 rand_;
186 const int number_of_variables_;
187};
188
189bool RandomLns::NextFragment() {
190 DCHECK_GT(Size(), 0);
191 for (int i = 0; i < number_of_variables_; ++i) {
192 AppendToFragment(absl::Uniform<int>(rand_, 0, Size()));
193 }
194 return true;
195}
196} // namespace
197
199 const std::vector<IntVar*>& vars, int number_of_variables) {
200 return MakeRandomLnsOperator(vars, number_of_variables, CpRandomSeed());
201}
202
204 const std::vector<IntVar*>& vars, int number_of_variables, int32_t seed) {
205 return RevAlloc(new RandomLns(vars, number_of_variables, seed));
206}
207
208// ----- Move Toward Target Local Search operator -----
209
210// A local search operator that compares the current assignment with a target
211// one, and that generates neighbors corresponding to a single variable being
212// changed from its current value to its target value.
213namespace {
214class MoveTowardTargetLS : public IntVarLocalSearchOperator {
215 public:
216 MoveTowardTargetLS(const std::vector<IntVar*>& variables,
217 const std::vector<int64_t>& target_values)
218 : IntVarLocalSearchOperator(variables),
219 target_(target_values),
220 // Initialize variable_index_ at the number of the of variables minus
221 // one, so that the first to be tried (after one increment) is the one
222 // of index 0.
223 variable_index_(Size() - 1) {
224 CHECK_EQ(target_values.size(), variables.size()) << "Illegal arguments.";
225 }
226
227 ~MoveTowardTargetLS() override {}
228
229 std::string DebugString() const override { return "MoveTowardTargetLS"; }
230
231 protected:
232 // Make a neighbor assigning one variable to its target value.
233 bool MakeOneNeighbor() override {
234 while (num_var_since_last_start_ < Size()) {
235 ++num_var_since_last_start_;
236 variable_index_ = (variable_index_ + 1) % Size();
237 const int64_t target_value = target_.at(variable_index_);
238 const int64_t current_value = OldValue(variable_index_);
239 if (current_value != target_value) {
240 SetValue(variable_index_, target_value);
241 return true;
242 }
243 }
244 return false;
245 }
246
247 private:
248 void OnStart() override {
249 // Do not change the value of variable_index_: this way, we keep going from
250 // where we last modified something. This is because we expect that most
251 // often, the variables we have just checked are less likely to be able
252 // to be changed to their target values than the ones we have not yet
253 // checked.
254 //
255 // Consider the case where oddly indexed variables can be assigned to their
256 // target values (no matter in what order they are considered), while even
257 // indexed ones cannot. Restarting at index 0 each time an odd-indexed
258 // variable is modified will cause a total of Theta(n^2) neighbors to be
259 // generated, while not restarting will produce only Theta(n) neighbors.
260 CHECK_GE(variable_index_, 0);
261 CHECK_LT(variable_index_, Size());
262 num_var_since_last_start_ = 0;
263 }
264
265 // Target values
266 const std::vector<int64_t> target_;
267
268 // Index of the next variable to try to restore
269 int64_t variable_index_;
270
271 // Number of variables checked since the last call to OnStart().
272 int64_t num_var_since_last_start_;
273};
274} // namespace
275
277 const Assignment& target) {
278 typedef std::vector<IntVarElement> Elements;
279 const Elements& elements = target.IntVarContainer().elements();
280 // Copy target values and construct the vector of variables
281 std::vector<IntVar*> vars;
282 std::vector<int64_t> values;
283 vars.reserve(target.NumIntVars());
284 values.reserve(target.NumIntVars());
285 for (const auto& it : elements) {
286 vars.push_back(it.Var());
287 values.push_back(it.Value());
288 }
289 return MakeMoveTowardTargetOperator(vars, values);
290}
291
293 const std::vector<IntVar*>& variables,
294 const std::vector<int64_t>& target_values) {
295 return RevAlloc(new MoveTowardTargetLS(variables, target_values));
296}
297
298// ----- ChangeValue Operators -----
299
300ChangeValue::ChangeValue(const std::vector<IntVar*>& vars)
301 : IntVarLocalSearchOperator(vars), index_(0) {}
302
304
306 const int size = Size();
307 while (index_ < size) {
308 const int64_t value = ModifyValue(index_, Value(index_));
309 SetValue(index_, value);
310 ++index_;
311 return true;
312 }
313 return false;
314}
315
316void ChangeValue::OnStart() { index_ = 0; }
317
318// Increments the current value of variables.
319
320namespace {
321class IncrementValue : public ChangeValue {
322 public:
323 explicit IncrementValue(const std::vector<IntVar*>& vars)
324 : ChangeValue(vars) {}
325 ~IncrementValue() override {}
326 int64_t ModifyValue(int64_t, int64_t value) override { return value + 1; }
327
328 std::string DebugString() const override { return "IncrementValue"; }
329};
330
331// Decrements the current value of variables.
332
333class DecrementValue : public ChangeValue {
334 public:
335 explicit DecrementValue(const std::vector<IntVar*>& vars)
336 : ChangeValue(vars) {}
337 ~DecrementValue() override {}
338 int64_t ModifyValue(int64_t, int64_t value) override { return value - 1; }
339
340 std::string DebugString() const override { return "DecrementValue"; }
341};
342} // namespace
343
344// ----- Path-based Operators -----
345
347 std::function<const std::vector<int>&(/*node=*/int, /*start_node=*/int)>;
348
349// ----- 2Opt -----
350
351template <bool ignore_path_vars>
352class TwoOpt : public PathOperator<ignore_path_vars> {
353 public:
354 TwoOpt(const std::vector<IntVar*>& vars,
355 const std::vector<IntVar*>& secondary_vars,
356 std::function<int(int64_t)> start_empty_path_class,
357 NeighborAccessor get_incoming_neighbors,
358 NeighborAccessor get_outgoing_neighbors)
359 : PathOperator<ignore_path_vars>(vars, secondary_vars,
360 (get_incoming_neighbors == nullptr &&
361 get_outgoing_neighbors == nullptr)
362 ? 2
363 : 1,
364 /*skip_locally_optimal_paths=*/true,
365 /*accept_path_end_base=*/true,
366 std::move(start_empty_path_class),
367 std::move(get_incoming_neighbors),
368 std::move(get_outgoing_neighbors)),
369 last_base_(-1),
370 last_(-1) {}
371 ~TwoOpt() override = default;
372 bool MakeNeighbor() override;
373 bool IsIncremental() const override { return true; }
374
375 std::string DebugString() const override { return "TwoOpt"; }
376
377 protected:
378 void ResetIncrementalism() override { last_ = -1; }
379 bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
380 // Both base nodes have to be on the same path.
381 return true;
382 }
383 int64_t GetBaseNodeRestartPosition(int base_index) override {
384 return (base_index == 0) ? this->StartNode(0) : this->BaseNode(0);
385 }
386
387 private:
388 void OnNodeInitialization() override { last_ = -1; }
389
390 int64_t last_base_;
391 int64_t last_;
392};
393
394template <bool ignore_path_vars>
396 const int64_t node0 = this->BaseNode(0);
397 int64_t before_chain = node0;
398 int64_t after_chain = -1;
399 if (this->HasNeighbors()) {
400 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
401 if (neighbor < 0 || this->IsInactive(neighbor)) return false;
402 if (this->CurrentNodePathStart(node0) !=
403 this->CurrentNodePathStart(neighbor)) {
404 return false;
405 }
406 if (outgoing) {
407 if (this->IsPathEnd(neighbor)) return false;
408 // Reverse the chain starting *after" node0 and ending with 'neighbor'.
409 after_chain = this->Next(neighbor);
410 } else {
411 if (this->IsPathStart(neighbor)) return false;
412 // Reverse the chain starting with 'neighbor' and ending before node0.
413 before_chain = this->Prev(neighbor);
414 after_chain = node0;
415 }
416 } else {
417 DCHECK_EQ(this->StartNode(0), this->StartNode(1));
418 after_chain = this->BaseNode(1);
419 }
420 // Incrementality is disabled with neighbors.
421 if (last_base_ != node0 || last_ == -1 || this->HasNeighbors()) {
422 this->RevertChanges(false);
423 if (this->IsPathEnd(node0)) {
424 last_ = -1;
425 return false;
426 }
427 last_base_ = node0;
428 last_ = this->Next(before_chain);
429 int64_t chain_last;
430 if (this->ReverseChain(before_chain, after_chain, &chain_last)
431 // Check there are more than one node in the chain (reversing a
432 // single node is a NOP).
433 && last_ != chain_last) {
434 return true;
435 }
436 last_ = -1;
437 return false;
438 }
439 DCHECK_EQ(before_chain, node0);
440 const int64_t to_move = this->Next(last_);
441 DCHECK_EQ(this->Next(to_move), after_chain);
442 return this->MoveChain(last_, to_move, before_chain);
443}
444
446 Solver* solver, const std::vector<IntVar*>& vars,
447 const std::vector<IntVar*>& secondary_vars,
448 std::function<int(int64_t)> start_empty_path_class,
449 NeighborAccessor get_incoming_neighbors,
450 NeighborAccessor get_outgoing_neighbors) {
451 if (secondary_vars.empty()) {
452 return solver->RevAlloc(new TwoOpt<true>(
453 vars, secondary_vars, std::move(start_empty_path_class),
454 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
455 }
456 return solver->RevAlloc(new TwoOpt<false>(
457 vars, secondary_vars, std::move(start_empty_path_class),
458 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
459}
460
461// ----- Relocate -----
462
463template <bool ignore_path_vars>
464class Relocate : public PathOperator<ignore_path_vars> {
465 public:
466 Relocate(const std::vector<IntVar*>& vars,
467 const std::vector<IntVar*>& secondary_vars, absl::string_view name,
468 std::function<int(int64_t)> start_empty_path_class,
469 NeighborAccessor get_incoming_neighbors,
470 NeighborAccessor get_outgoing_neighbors, int64_t chain_length = 1LL,
471 bool single_path = false)
472 : PathOperator<ignore_path_vars>(
473 vars, secondary_vars,
474 (get_incoming_neighbors == nullptr &&
475 get_outgoing_neighbors == nullptr)
476 ? 2
477 : 1,
478 /*skip_locally_optimal_paths=*/true,
479 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
480 chain_length == 1 ? std::move(get_incoming_neighbors) : nullptr,
481 std::move(get_outgoing_neighbors)),
482 chain_length_(chain_length),
483 single_path_(single_path),
484 name_(absl::StrCat(name, "<", chain_length, ">")) {
485 CHECK_GT(chain_length_, 0);
486 }
487 ~Relocate() override = default;
488 bool MakeNeighbor() override;
489
490 std::string DebugString() const override { return name_; }
491
492 protected:
493 bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
494 // Both base nodes have to be on the same path when it's the single path
495 // version.
496 return single_path_;
497 }
498
499 private:
500 const int64_t chain_length_;
501 const bool single_path_;
502 const std::string name_;
503};
504
505template <bool ignore_path_vars>
507 const auto do_move = [this](int64_t before_chain, int64_t destination) {
508 DCHECK(!this->IsPathEnd(destination));
509 int64_t chain_end = before_chain;
510 for (int i = 0; i < chain_length_; ++i) {
511 if (this->IsPathEnd(chain_end) || chain_end == destination) return false;
512 chain_end = this->Next(chain_end);
513 }
514 return !this->IsPathEnd(chain_end) &&
515 this->MoveChain(before_chain, chain_end, destination);
516 };
517 const int64_t node0 = this->BaseNode(0);
518 if (this->HasNeighbors()) {
519 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
520 if (neighbor < 0 || this->IsInactive(neighbor)) return false;
521 if (outgoing) {
522 return do_move(/*before_chain=*/this->Prev(neighbor),
523 /*destination=*/node0);
524 }
525 DCHECK_EQ(chain_length_, 1);
526 // TODO(user): Handle chain_length_ > 1 for incoming neighbors by going
527 // backwards on the chain. NOTE: In this setting it makes sense to have path
528 // ends as base nodes as we move the chain "before" the base node.
529 DCHECK(!this->IsPathStart(node0))
530 << "Path starts have no incoming neighbors.";
531 return do_move(/*before_chain=*/this->Prev(neighbor),
532 /*destination=*/this->Prev(node0));
533 }
534 DCHECK(!single_path_ || this->StartNode(0) == this->StartNode(1));
535 return do_move(/*before_chain=*/node0, /*destination=*/this->BaseNode(1));
536}
537
539 Solver* solver, const std::vector<IntVar*>& vars,
540 const std::vector<IntVar*>& secondary_vars,
541 std::function<int(int64_t)> start_empty_path_class,
542 NeighborAccessor get_incoming_neighbors,
543 NeighborAccessor get_outgoing_neighbors, int64_t chain_length,
544 bool single_path, const std::string& name) {
545 if (secondary_vars.empty()) {
546 return solver->RevAlloc(new Relocate<true>(
547 vars, secondary_vars, name, std::move(start_empty_path_class),
548 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
549 chain_length, single_path));
550 }
551 return solver->RevAlloc(new Relocate<false>(
552 vars, secondary_vars, name, std::move(start_empty_path_class),
553 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
554 chain_length, single_path));
555}
556
557// ----- Exchange -----
558
559template <bool ignore_path_vars>
560class Exchange : public PathOperator<ignore_path_vars> {
561 public:
562 Exchange(const std::vector<IntVar*>& vars,
563 const std::vector<IntVar*>& secondary_vars,
564 std::function<int(int64_t)> start_empty_path_class,
565 NeighborAccessor get_incoming_neighbors,
566 NeighborAccessor get_outgoing_neighbors)
567 : PathOperator<ignore_path_vars>(vars, secondary_vars,
568 (get_incoming_neighbors == nullptr &&
569 get_outgoing_neighbors == nullptr)
570 ? 2
571 : 1,
572 /*skip_locally_optimal_paths=*/true,
573 /*accept_path_end_base=*/false,
574 std::move(start_empty_path_class),
575 std::move(get_incoming_neighbors),
576 std::move(get_outgoing_neighbors)) {}
577 ~Exchange() override = default;
578 bool MakeNeighbor() override;
579
580 std::string DebugString() const override { return "Exchange"; }
581};
582
583template <bool ignore_path_vars>
585 const int64_t node0 = this->BaseNode(0);
586 if (this->HasNeighbors()) {
587 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
588 if (neighbor < 0 || this->IsInactive(neighbor)) return false;
589 if (outgoing) {
590 // Exchange node0's next with 'neighbor'.
591 return this->SwapNodes(this->Next(node0), neighbor);
592 }
593 DCHECK(!this->IsPathStart(node0))
594 << "Path starts have no incoming neighbors.";
595 // Exchange node0's prev with 'neighbor'.
596 return this->SwapNodes(this->Prev(node0), neighbor);
597 }
598 return this->SwapNodes(this->Next(node0), this->Next(this->BaseNode(1)));
599}
600
602 Solver* solver, const std::vector<IntVar*>& vars,
603 const std::vector<IntVar*>& secondary_vars,
604 std::function<int(int64_t)> start_empty_path_class,
605 NeighborAccessor get_incoming_neighbors,
606 NeighborAccessor get_outgoing_neighbors) {
607 if (secondary_vars.empty()) {
608 return solver->RevAlloc(new Exchange<true>(
609 vars, secondary_vars, std::move(start_empty_path_class),
610 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
611 }
612 return solver->RevAlloc(new Exchange<false>(
613 vars, secondary_vars, std::move(start_empty_path_class),
614 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
615}
616
617// ----- Cross -----
618
619template <bool ignore_path_vars>
620class Cross : public PathOperator<ignore_path_vars> {
621 public:
622 Cross(const std::vector<IntVar*>& vars,
623 const std::vector<IntVar*>& secondary_vars,
624 std::function<int(int64_t)> start_empty_path_class,
625 NeighborAccessor get_incoming_neighbors,
626 NeighborAccessor get_outgoing_neighbors)
627 : PathOperator<ignore_path_vars>(vars, secondary_vars,
628 (get_incoming_neighbors == nullptr &&
629 get_outgoing_neighbors == nullptr)
630 ? 2
631 : 1,
632 /*skip_locally_optimal_paths=*/true,
633 /*accept_path_end_base=*/true,
634 std::move(start_empty_path_class),
635 std::move(get_incoming_neighbors),
636 std::move(get_outgoing_neighbors)) {}
637 ~Cross() override = default;
638 bool MakeNeighbor() override;
639
640 std::string DebugString() const override { return "Cross"; }
641};
642
643template <bool ignore_path_vars>
645 const int64_t start0 = this->StartNode(0);
646 int64_t start1 = -1;
647 const int64_t node0 = this->BaseNode(0);
648 int64_t node1 = -1;
649 if (node0 == start0) return false;
650 bool cross_path_starts = false;
651 if (this->HasNeighbors()) {
652 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
653 if (neighbor < 0) return false;
654 cross_path_starts = outgoing;
655 DCHECK(!this->IsPathStart(neighbor));
656 if (this->IsInactive(neighbor)) return false;
657 start1 = this->CurrentNodePathStart(neighbor);
658 // Tricky: In all cases we want to connect node0 to neighbor. If we are
659 // crossing path starts, node0 is the end of a chain and neighbor is the
660 // the first node after the other chain ending at node1, therefore
661 // node1 = prev(neighbor).
662 // If we are crossing path ends, node0 is the start of a chain and neighbor
663 // is the last node before the other chain starting at node1, therefore
664 // node1 = next(neighbor).
665 node1 = cross_path_starts ? this->Prev(neighbor) : this->Next(neighbor);
666 } else {
667 start1 = this->StartNode(1);
668 node1 = this->BaseNode(1);
669 cross_path_starts = start0 < start1;
670 }
671 if (start1 == start0 || node1 == start1) return false;
672
673 bool moved = false;
674 if (cross_path_starts) {
675 // Cross path starts.
676 // If two paths are equivalent don't exchange the full paths.
677 if (this->PathClassFromStartNode(start0) ==
678 this->PathClassFromStartNode(start1) &&
679 !this->IsPathEnd(node0) && this->IsPathEnd(this->Next(node0)) &&
680 !this->IsPathEnd(node1) && this->IsPathEnd(this->Next(node1))) {
681 return false;
682 }
683 const int first1 = this->Next(start1);
684 if (!this->IsPathEnd(node0))
685 moved |= this->MoveChain(start0, node0, start1);
686 if (!this->IsPathEnd(node1))
687 moved |= this->MoveChain(this->Prev(first1), node1, start0);
688 } else {
689 // Cross path ends.
690 // If paths are equivalent, every end crossing has a corresponding start
691 // crossing, we don't generate those symmetric neighbors.
692 if (this->PathClassFromStartNode(start0) ==
693 this->PathClassFromStartNode(start1) &&
694 !this->HasNeighbors()) {
695 return false;
696 }
697 // Never exchange full paths, equivalent or not.
698 // Full path exchange is only performed when start0 < start1.
699 if (this->IsPathStart(this->Prev(node0)) &&
700 this->IsPathStart(this->Prev(node1)) && !this->HasNeighbors()) {
701 return false;
702 }
703
704 const int prev_end_node1 = this->Prev(this->CurrentNodePathEnd(node1));
705 if (!this->IsPathEnd(node0)) {
706 moved |= this->MoveChain(this->Prev(node0), this->Prev(this->EndNode(0)),
707 prev_end_node1);
708 }
709 if (!this->IsPathEnd(node1)) {
710 moved |= this->MoveChain(this->Prev(node1), prev_end_node1,
711 this->Prev(this->EndNode(0)));
712 }
713 }
714 return moved;
715}
716
718 Solver* solver, const std::vector<IntVar*>& vars,
719 const std::vector<IntVar*>& secondary_vars,
720 std::function<int(int64_t)> start_empty_path_class,
721 NeighborAccessor get_incoming_neighbors,
722 NeighborAccessor get_outgoing_neighbors) {
723 if (secondary_vars.empty()) {
724 return solver->RevAlloc(new Cross<true>(
725 vars, secondary_vars, std::move(start_empty_path_class),
726 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
727 }
728 return solver->RevAlloc(new Cross<false>(
729 vars, secondary_vars, std::move(start_empty_path_class),
730 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
731}
732
733// ----- BaseInactiveNodeToPathOperator -----
734// Base class of path operators which make inactive nodes active.
735
736template <bool ignore_path_vars>
737class BaseInactiveNodeToPathOperator : public PathOperator<ignore_path_vars> {
738 public:
740 const std::vector<IntVar*>& vars,
741 const std::vector<IntVar*>& secondary_vars, int number_of_base_nodes,
742 std::function<int(int64_t)> start_empty_path_class,
743 NeighborAccessor get_incoming_neighbors = nullptr,
744 NeighborAccessor get_outgoing_neighbors = nullptr)
745 : PathOperator<ignore_path_vars>(vars, secondary_vars,
746 number_of_base_nodes, false, false,
747 std::move(start_empty_path_class),
748 std::move(get_incoming_neighbors),
749 std::move(get_outgoing_neighbors)),
750 inactive_node_(0) {
751 // TODO(user): Activate skipping optimal paths.
752 }
754
755 protected:
756 bool MakeOneNeighbor() override;
757 int64_t GetInactiveNode() const { return inactive_node_; }
758
759 private:
760 void OnNodeInitialization() override;
761
762 int inactive_node_;
763};
764
765template <bool ignore_path_vars>
767 for (int i = 0; i < this->Size(); ++i) {
768 if (this->IsInactive(i)) {
769 inactive_node_ = i;
770 return;
771 }
772 }
773 inactive_node_ = this->Size();
774}
775
776template <bool ignore_path_vars>
778 while (inactive_node_ < this->Size()) {
779 if (!this->IsInactive(inactive_node_) ||
781 this->ResetPosition();
782 ++inactive_node_;
783 } else {
784 return true;
785 }
786 }
787 return false;
788}
789
790// ----- MakeActiveOperator -----
791
792template <bool ignore_path_vars>
794 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
795 public:
796 MakeActiveOperator(const std::vector<IntVar*>& vars,
797 const std::vector<IntVar*>& secondary_vars,
798 std::function<int(int64_t)> start_empty_path_class,
799 NeighborAccessor get_incoming_neighbors,
800 NeighborAccessor get_outgoing_neighbors)
801 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
802 vars, secondary_vars, 1, std::move(start_empty_path_class),
803 std::move(get_incoming_neighbors),
804 std::move(get_outgoing_neighbors)) {}
805 ~MakeActiveOperator() override = default;
806 bool MakeNeighbor() override;
807
808 std::string DebugString() const override { return "MakeActiveOperator"; }
809};
810
811template <bool ignore_path_vars>
813 // TODO(user): Add support for neighbors of inactive nodes; would require
814 // having a version without base nodes (probably not a PathOperator).
815 return this->MakeActive(this->GetInactiveNode(), this->BaseNode(0));
816}
817
819 Solver* solver, const std::vector<IntVar*>& vars,
820 const std::vector<IntVar*>& secondary_vars,
821 std::function<int(int64_t)> start_empty_path_class,
822 NeighborAccessor get_incoming_neighbors,
823 NeighborAccessor get_outgoing_neighbors) {
824 if (secondary_vars.empty()) {
825 return solver->RevAlloc(new MakeActiveOperator<true>(
826 vars, secondary_vars, std::move(start_empty_path_class),
827 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
828 }
829 return solver->RevAlloc(new MakeActiveOperator<false>(
830 vars, secondary_vars, std::move(start_empty_path_class),
831 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)));
832}
833
834// ---- RelocateAndMakeActiveOperator -----
835
836template <bool ignore_path_vars>
838 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
839 public:
841 const std::vector<IntVar*>& vars,
842 const std::vector<IntVar*>& secondary_vars,
843 std::function<int(int64_t)> start_empty_path_class)
844 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
845 vars, secondary_vars, 2, std::move(start_empty_path_class)) {}
846 ~RelocateAndMakeActiveOperator() override = default;
847 bool MakeNeighbor() override {
848 const int64_t before_node_to_move = this->BaseNode(1);
849 const int64_t node = this->Next(before_node_to_move);
850 return !this->IsPathEnd(node) &&
851 this->MoveChain(before_node_to_move, node, this->BaseNode(0)) &&
852 this->MakeActive(this->GetInactiveNode(), before_node_to_move);
853 }
854
855 std::string DebugString() const override {
856 return "RelocateAndMakeActiveOperator";
857 }
858};
859
861 Solver* solver, const std::vector<IntVar*>& vars,
862 const std::vector<IntVar*>& secondary_vars,
863 std::function<int(int64_t)> start_empty_path_class) {
864 if (secondary_vars.empty()) {
866 vars, secondary_vars, std::move(start_empty_path_class)));
867 }
869 vars, secondary_vars, std::move(start_empty_path_class)));
870}
871
872// ----- ExchangeAndMakeActiveOperator -----
873
874template <bool ignore_path_vars>
876 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
877 public:
879 const std::vector<IntVar*>& vars,
880 const std::vector<IntVar*>& secondary_vars,
881 std::function<int(int64_t)> start_empty_path_class)
882 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
883 vars, secondary_vars, 3, std::move(start_empty_path_class)) {}
885 bool MakeNeighbor() override {
886 return this->SwapNodes(this->Next(this->BaseNode(0)),
887 this->Next(this->BaseNode(1))) &&
888 this->MakeActive(this->GetInactiveNode(), this->BaseNode(2));
889 }
890 std::string DebugString() const override {
891 return "ExchangeAndMakeActiveOperator";
892 }
893
894 protected:
895 bool OnSamePathAsPreviousBase(int64_t base_index) override {
896 return base_index == 2;
897 }
898};
899
901 Solver* solver, const std::vector<IntVar*>& vars,
902 const std::vector<IntVar*>& secondary_vars,
903 std::function<int(int64_t)> start_empty_path_class) {
904 if (secondary_vars.empty()) {
906 vars, secondary_vars, std::move(start_empty_path_class)));
907 }
909 vars, secondary_vars, std::move(start_empty_path_class)));
910}
911
912// ----- ExchangePathEndsAndMakeActiveOperator -----
913
914template <bool ignore_path_vars>
916 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
917 public:
919 const std::vector<IntVar*>& vars,
920 const std::vector<IntVar*>& secondary_vars,
921 std::function<int(int64_t)> start_empty_path_class)
922 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
923 vars, secondary_vars, 2, std::move(start_empty_path_class)) {}
925 int64_t GetBaseNodeRestartPosition(int base_index) override {
926 return (base_index == 1) ? this->Prev(this->EndNode(1))
927 : this->StartNode(base_index);
928 }
929 bool MakeNeighbor() override {
930 const int64_t end_node0 = this->Prev(this->EndNode(0));
931 const int64_t end_node1 = this->BaseNode(1);
932 if (end_node0 == end_node1 || end_node1 != this->Prev(this->EndNode(1))) {
933 return false;
934 }
935 const int64_t start_node0 = this->Next(this->StartNode(0));
936 const int64_t start_node1 = this->Next(this->StartNode(1));
937 DCHECK_NE(start_node0, start_node1);
938 return this->SwapNodes(end_node0, end_node1) &&
939 this->SwapNodes(start_node0, start_node1) &&
940 this->MakeActive(this->GetInactiveNode(), this->BaseNode(0));
941 }
942 std::string DebugString() const override {
943 return "ExchangePathEndsAndMakeActiveOperator";
944 }
945};
946
948 Solver* solver, const std::vector<IntVar*>& vars,
949 const std::vector<IntVar*>& secondary_vars,
950 std::function<int(int64_t)> start_empty_path_class) {
951 if (secondary_vars.empty()) {
952 return solver->RevAlloc(
954 vars, secondary_vars, std::move(start_empty_path_class)));
955 }
957 vars, secondary_vars, std::move(start_empty_path_class)));
958}
959
960// ----- MakeActiveAndRelocate -----
961
962template <bool ignore_path_vars>
964 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
965 public:
967 const std::vector<IntVar*>& vars,
968 const std::vector<IntVar*>& secondary_vars,
969 std::function<int(int64_t)> start_empty_path_class)
970 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
971 vars, secondary_vars, 2, std::move(start_empty_path_class)) {}
972 ~MakeActiveAndRelocateOperator() override = default;
973 bool MakeNeighbor() override;
974
975 std::string DebugString() const override {
976 return "MakeActiveAndRelocateOperator";
977 }
978};
979
980template <bool ignore_path_vars>
982 const int64_t before_chain = this->BaseNode(1);
983 const int64_t chain_end = this->Next(before_chain);
984 const int64_t destination = this->BaseNode(0);
985 return !this->IsPathEnd(chain_end) &&
986 this->MoveChain(before_chain, chain_end, destination) &&
987 this->MakeActive(this->GetInactiveNode(), destination);
988}
989
991 Solver* solver, const std::vector<IntVar*>& vars,
992 const std::vector<IntVar*>& secondary_vars,
993 std::function<int(int64_t)> start_empty_path_class) {
994 if (secondary_vars.empty()) {
996 vars, secondary_vars, std::move(start_empty_path_class)));
997 }
999 vars, secondary_vars, std::move(start_empty_path_class)));
1000}
1001
1002// ----- MakeInactiveOperator -----
1003
1004template <bool ignore_path_vars>
1005class MakeInactiveOperator : public PathOperator<ignore_path_vars> {
1006 public:
1007 MakeInactiveOperator(const std::vector<IntVar*>& vars,
1008 const std::vector<IntVar*>& secondary_vars,
1009 std::function<int(int64_t)> start_empty_path_class)
1010 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
1011 std::move(start_empty_path_class),
1012 nullptr, nullptr) {}
1013 ~MakeInactiveOperator() override = default;
1014 bool MakeNeighbor() override {
1015 const int64_t base = this->BaseNode(0);
1016 return this->MakeChainInactive(base, this->Next(base));
1017 }
1018
1019 std::string DebugString() const override { return "MakeInactiveOperator"; }
1020};
1021
1023 Solver* solver, const std::vector<IntVar*>& vars,
1024 const std::vector<IntVar*>& secondary_vars,
1025 std::function<int(int64_t)> start_empty_path_class) {
1026 if (secondary_vars.empty()) {
1027 return solver->RevAlloc(new MakeInactiveOperator<true>(
1028 vars, secondary_vars, std::move(start_empty_path_class)));
1029 }
1030 return solver->RevAlloc(new MakeInactiveOperator<false>(
1031 vars, secondary_vars, std::move(start_empty_path_class)));
1032}
1033
1034// ----- RelocateAndMakeInactiveOperator -----
1035
1036template <bool ignore_path_vars>
1037class RelocateAndMakeInactiveOperator : public PathOperator<ignore_path_vars> {
1038 public:
1040 const std::vector<IntVar*>& vars,
1041 const std::vector<IntVar*>& secondary_vars,
1042 std::function<int(int64_t)> start_empty_path_class)
1043 : PathOperator<ignore_path_vars>(vars, secondary_vars, 2, true, false,
1044 std::move(start_empty_path_class),
1045 nullptr, nullptr) {}
1047 bool MakeNeighbor() override {
1048 const int64_t destination = this->BaseNode(1);
1049 const int64_t before_to_move = this->BaseNode(0);
1050 const int64_t node_to_inactivate = this->Next(destination);
1051 if (node_to_inactivate == before_to_move ||
1052 this->IsPathEnd(node_to_inactivate) ||
1053 !this->MakeChainInactive(destination, node_to_inactivate)) {
1054 return false;
1055 }
1056 const int64_t node = this->Next(before_to_move);
1057 return !this->IsPathEnd(node) &&
1058 this->MoveChain(before_to_move, node, destination);
1059 }
1060
1061 std::string DebugString() const override {
1062 return "RelocateAndMakeInactiveOperator";
1063 }
1064};
1065
1067 Solver* solver, const std::vector<IntVar*>& vars,
1068 const std::vector<IntVar*>& secondary_vars,
1069 std::function<int(int64_t)> start_empty_path_class) {
1070 if (secondary_vars.empty()) {
1072 vars, secondary_vars, std::move(start_empty_path_class)));
1073 }
1075 vars, secondary_vars, std::move(start_empty_path_class)));
1076}
1077
1078// ----- MakeChainInactiveOperator -----
1079
1080template <bool ignore_path_vars>
1081class MakeChainInactiveOperator : public PathOperator<ignore_path_vars> {
1082 public:
1083 MakeChainInactiveOperator(const std::vector<IntVar*>& vars,
1084 const std::vector<IntVar*>& secondary_vars,
1085 std::function<int(int64_t)> start_empty_path_class)
1086 : PathOperator<ignore_path_vars>(vars, secondary_vars, 2,
1087 /*skip_locally_optimal_paths=*/true,
1088 /*accept_path_end_base=*/false,
1089 std::move(start_empty_path_class),
1090 nullptr, nullptr) {}
1091 ~MakeChainInactiveOperator() override = default;
1092 bool MakeNeighbor() override {
1093 const int64_t chain_end = this->BaseNode(1);
1094 if (!this->IsPathEnd(chain_end) && chain_end != this->BaseNode(0) &&
1095 !this->Var(chain_end)->Contains(chain_end)) {
1096 // Move to the next before_chain since an unskippable node has been
1097 // encountered.
1098 this->SetNextBaseToIncrement(0);
1099 return false;
1100 }
1101 return this->MakeChainInactive(this->BaseNode(0), chain_end);
1102 }
1103
1104 std::string DebugString() const override {
1105 return "MakeChainInactiveOperator";
1106 }
1107
1108 protected:
1109 bool OnSamePathAsPreviousBase(int64_t) override {
1110 // Start and end of chain (defined by both base nodes) must be on the same
1111 // path.
1112 return true;
1113 }
1114
1115 int64_t GetBaseNodeRestartPosition(int base_index) override {
1116 // Base node 1 must be after base node 0.
1117 return (base_index == 0) ? this->StartNode(base_index)
1118 : this->BaseNode(base_index - 1);
1119 }
1120};
1121
1123 Solver* solver, const std::vector<IntVar*>& vars,
1124 const std::vector<IntVar*>& secondary_vars,
1125 std::function<int(int64_t)> start_empty_path_class) {
1126 if (secondary_vars.empty()) {
1127 return solver->RevAlloc(new MakeChainInactiveOperator<true>(
1128 vars, secondary_vars, std::move(start_empty_path_class)));
1129 }
1130 return solver->RevAlloc(new MakeChainInactiveOperator<false>(
1131 vars, secondary_vars, std::move(start_empty_path_class)));
1132}
1133
1134// ----- SwapActiveOperator -----
1135
1136template <bool ignore_path_vars>
1138 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
1139 public:
1140 SwapActiveOperator(const std::vector<IntVar*>& vars,
1141 const std::vector<IntVar*>& secondary_vars,
1142 std::function<int(int64_t)> start_empty_path_class)
1143 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
1144 vars, secondary_vars, 1, std::move(start_empty_path_class)) {}
1145 ~SwapActiveOperator() override = default;
1146 bool MakeNeighbor() override {
1147 const int64_t base = this->BaseNode(0);
1148 return this->MakeChainInactive(base, this->Next(base)) &&
1149 this->MakeActive(this->GetInactiveNode(), base);
1150 }
1151
1152 std::string DebugString() const override { return "SwapActiveOperator"; }
1153};
1154
1156 Solver* solver, const std::vector<IntVar*>& vars,
1157 const std::vector<IntVar*>& secondary_vars,
1158 std::function<int(int64_t)> start_empty_path_class) {
1159 if (secondary_vars.empty()) {
1160 return solver->RevAlloc(new SwapActiveOperator<true>(
1161 vars, secondary_vars, std::move(start_empty_path_class)));
1162 }
1163 return solver->RevAlloc(new SwapActiveOperator<false>(
1164 vars, secondary_vars, std::move(start_empty_path_class)));
1165}
1166
1167// ----- SwapActiveChainOperator -----
1168
1169template <bool ignore_path_vars>
1171 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
1172 public:
1173 SwapActiveChainOperator(const std::vector<IntVar*>& vars,
1174 const std::vector<IntVar*>& secondary_vars,
1175 std::function<int(int64_t)> start_empty_path_class,
1176 int max_chain_size)
1177 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
1178 vars, secondary_vars, 2, std::move(start_empty_path_class)),
1179 last_before_chain_(-1),
1180 last_chain_end_(-1),
1181 current_chain_size_(0),
1182 max_chain_size_(max_chain_size) {
1183 DCHECK_GE(max_chain_size_, 1);
1184 }
1185 ~SwapActiveChainOperator() override = default;
1186 bool MakeNeighbor() override;
1187 bool IsIncremental() const override { return true; }
1188
1189 protected:
1190 void ResetIncrementalism() override {
1191 last_chain_end_ = -1;
1192 current_chain_size_ = 0;
1193 }
1194 bool OnSamePathAsPreviousBase(int64_t /*base_index*/) override {
1195 return true;
1196 }
1197 // TODO(user): Skip unfeasible chains by forcing the first base node to be
1198 // before the second one. Ideally this should be done as follows:
1199 // int64_t GetBaseNodeRestartPosition(int base_index) override {
1200 // return (base_index == 0) ? StartNode(base_index) : BaseNode(base_index -
1201 // 1);
1202 // }
1203 // However due to the fact we are iterating over the chains multiple times
1204 // (once for each unperformed node), this breaks the ending position of the
1205 // neighborhood (causing an infinite iteration).
1206
1207 std::string DebugString() const override { return "SwapActiveChainOperator"; }
1208
1209 private:
1210 void OnNodeInitialization() override {
1211 last_chain_end_ = -1;
1212 current_chain_size_ = 0;
1213 }
1214
1215 int64_t last_before_chain_;
1216 int64_t last_chain_end_;
1217 int current_chain_size_;
1218 const int max_chain_size_;
1219};
1220
1221template <bool ignore_path_vars>
1223 const int64_t before_chain = this->BaseNode(0);
1224 const int64_t chain_end = this->BaseNode(1);
1225 if (last_before_chain_ != before_chain || last_chain_end_ == -1) {
1226 this->RevertChanges(/*change_was_incremental=*/false);
1227 last_before_chain_ = before_chain;
1228 last_chain_end_ = this->GetInactiveNode();
1229 if (!this->IsPathEnd(chain_end) && before_chain != chain_end &&
1230 this->MakeChainInactive(before_chain, chain_end) &&
1231 this->MakeActive(this->GetInactiveNode(), before_chain)) {
1232 ++current_chain_size_;
1233 return true;
1234 } else {
1235 last_chain_end_ = -1;
1236 current_chain_size_ = 0;
1237 return false;
1238 }
1239 }
1240 if (current_chain_size_ >= max_chain_size_) {
1241 // Move to the next before_chain.
1242 this->SetNextBaseToIncrement(0);
1243 current_chain_size_ = 0;
1244 return false;
1245 }
1246 if (!this->IsPathEnd(last_chain_end_) &&
1247 this->MakeChainInactive(last_chain_end_, this->Next(last_chain_end_))) {
1248 ++current_chain_size_;
1249 return true;
1250 }
1251 last_chain_end_ = -1;
1252 current_chain_size_ = 0;
1253 return false;
1254}
1255
1257 Solver* solver, const std::vector<IntVar*>& vars,
1258 const std::vector<IntVar*>& secondary_vars,
1259 std::function<int(int64_t)> start_empty_path_class, int max_chain_size) {
1260 if (secondary_vars.empty()) {
1261 return solver->RevAlloc(new SwapActiveChainOperator<true>(
1262 vars, secondary_vars, std::move(start_empty_path_class),
1263 max_chain_size));
1264 }
1265 return solver->RevAlloc(new SwapActiveChainOperator<false>(
1266 vars, secondary_vars, std::move(start_empty_path_class), max_chain_size));
1267}
1268
1269// ----- ExtendedSwapActiveOperator -----
1270
1271template <bool ignore_path_vars>
1273 : public BaseInactiveNodeToPathOperator<ignore_path_vars> {
1274 public:
1275 ExtendedSwapActiveOperator(const std::vector<IntVar*>& vars,
1276 const std::vector<IntVar*>& secondary_vars,
1277 std::function<int(int64_t)> start_empty_path_class)
1278 : BaseInactiveNodeToPathOperator<ignore_path_vars>(
1279 vars, secondary_vars, 2, std::move(start_empty_path_class)) {}
1280 ~ExtendedSwapActiveOperator() override = default;
1281 bool MakeNeighbor() override {
1282 const int64_t base0 = this->BaseNode(0);
1283 const int64_t base1 = this->BaseNode(1);
1284 if (this->Next(base0) == base1) {
1285 return false;
1286 }
1287 return this->MakeChainInactive(base0, this->Next(base0)) &&
1288 this->MakeActive(this->GetInactiveNode(), base1);
1289 }
1290
1291 std::string DebugString() const override {
1292 return "ExtendedSwapActiveOperator";
1293 }
1294};
1295
1297 Solver* solver, const std::vector<IntVar*>& vars,
1298 const std::vector<IntVar*>& secondary_vars,
1299 std::function<int(int64_t)> start_empty_path_class) {
1300 if (secondary_vars.empty()) {
1301 return solver->RevAlloc(new ExtendedSwapActiveOperator<true>(
1302 vars, secondary_vars, std::move(start_empty_path_class)));
1303 }
1305 vars, secondary_vars, std::move(start_empty_path_class)));
1306}
1307
1308// ----- TSP-based operators -----
1309
1310template <bool ignore_path_vars>
1311class TSPOpt : public PathOperator<ignore_path_vars> {
1312 public:
1313 TSPOpt(const std::vector<IntVar*>& vars,
1314 const std::vector<IntVar*>& secondary_vars,
1315 Solver::IndexEvaluator3 evaluator, int chain_length);
1316 ~TSPOpt() override = default;
1317 bool MakeNeighbor() override;
1318
1319 std::string DebugString() const override { return "TSPOpt"; }
1320
1321 private:
1322 std::vector<std::vector<int64_t>> cost_;
1324 hamiltonian_path_solver_;
1325 Solver::IndexEvaluator3 evaluator_;
1326 const int chain_length_;
1327};
1328
1329template <bool ignore_path_vars>
1330TSPOpt<ignore_path_vars>::TSPOpt(const std::vector<IntVar*>& vars,
1331 const std::vector<IntVar*>& secondary_vars,
1332 Solver::IndexEvaluator3 evaluator,
1333 int chain_length)
1334 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
1335 nullptr, nullptr, nullptr),
1336 hamiltonian_path_solver_(cost_),
1337 evaluator_(std::move(evaluator)),
1338 chain_length_(chain_length) {}
1339
1340template <bool ignore_path_vars>
1342 std::vector<int64_t> nodes;
1343 int64_t chain_end = this->BaseNode(0);
1344 for (int i = 0; i < chain_length_ + 1; ++i) {
1345 nodes.push_back(chain_end);
1346 if (this->IsPathEnd(chain_end)) {
1347 break;
1348 }
1349 chain_end = this->Next(chain_end);
1350 }
1351 if (nodes.size() <= 3) {
1352 return false;
1353 }
1354 int64_t chain_path = this->Path(this->BaseNode(0));
1355 const int size = nodes.size() - 1;
1356 cost_.resize(size);
1357 for (int i = 0; i < size; ++i) {
1358 cost_[i].resize(size);
1359 cost_[i][0] = evaluator_(nodes[i], nodes[size], chain_path);
1360 for (int j = 1; j < size; ++j) {
1361 cost_[i][j] = evaluator_(nodes[i], nodes[j], chain_path);
1362 }
1363 }
1364 hamiltonian_path_solver_.ChangeCostMatrix(cost_);
1365 std::vector<PathNodeIndex> path;
1366 hamiltonian_path_solver_.TravelingSalesmanPath(&path);
1367 CHECK_EQ(size + 1, path.size());
1368 for (int i = 0; i < size - 1; ++i) {
1369 this->SetNext(nodes[path[i]], nodes[path[i + 1]], chain_path);
1370 }
1371 this->SetNext(nodes[path[size - 1]], nodes[size], chain_path);
1372 return true;
1373}
1374
1376 const std::vector<IntVar*>& vars,
1377 const std::vector<IntVar*>& secondary_vars,
1378 Solver::IndexEvaluator3 evaluator,
1379 int chain_length) {
1380 if (secondary_vars.empty()) {
1381 return solver->RevAlloc(new TSPOpt<true>(
1382 vars, secondary_vars, std::move(evaluator), chain_length));
1383 }
1384 return solver->RevAlloc(new TSPOpt<false>(
1385 vars, secondary_vars, std::move(evaluator), chain_length));
1386}
1387
1388template <bool ignore_path_vars>
1389class TSPLns : public PathOperator<ignore_path_vars> {
1390 public:
1391 TSPLns(const std::vector<IntVar*>& vars,
1392 const std::vector<IntVar*>& secondary_vars,
1393 Solver::IndexEvaluator3 evaluator, int tsp_size);
1394 ~TSPLns() override = default;
1395 bool MakeNeighbor() override;
1396
1397 std::string DebugString() const override { return "TSPLns"; }
1398
1399 protected:
1400 bool MakeOneNeighbor() override;
1401
1402 private:
1403 void OnNodeInitialization() override {
1404 // NOTE: Avoid any computations if there are no vars added.
1405 has_long_enough_paths_ = this->Size() != 0;
1406 }
1407
1408 std::vector<std::vector<int64_t>> cost_;
1409 HamiltonianPathSolver<int64_t, std::vector<std::vector<int64_t>>>
1410 hamiltonian_path_solver_;
1411 Solver::IndexEvaluator3 evaluator_;
1412 const int tsp_size_;
1413 std::mt19937 rand_;
1414 bool has_long_enough_paths_;
1415};
1416
1417template <bool ignore_path_vars>
1418TSPLns<ignore_path_vars>::TSPLns(const std::vector<IntVar*>& vars,
1419 const std::vector<IntVar*>& secondary_vars,
1420 Solver::IndexEvaluator3 evaluator,
1421 int tsp_size)
1422 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
1423 nullptr, nullptr, nullptr),
1424 hamiltonian_path_solver_(cost_),
1425 evaluator_(std::move(evaluator)),
1426 tsp_size_(tsp_size),
1427 rand_(CpRandomSeed()),
1428 has_long_enough_paths_(true) {
1429 CHECK_GE(tsp_size_, 0);
1430 cost_.resize(tsp_size_);
1431 for (int i = 0; i < tsp_size_; ++i) {
1432 cost_[i].resize(tsp_size_);
1433 }
1434}
1435
1436template <bool ignore_path_vars>
1438 while (has_long_enough_paths_) {
1439 has_long_enough_paths_ = false;
1441 return true;
1442 }
1443 this->Var(0)->solver()->TopPeriodicCheck();
1444 }
1445 return false;
1446}
1447
1448template <bool ignore_path_vars>
1450 const int64_t base_node = this->BaseNode(0);
1451 std::vector<int64_t> nodes;
1452 for (int64_t node = this->StartNode(0); !this->IsPathEnd(node);
1453 node = this->Next(node)) {
1454 nodes.push_back(node);
1455 }
1456 if (nodes.size() <= tsp_size_) {
1457 return false;
1458 }
1459 has_long_enough_paths_ = true;
1460 // Randomly select break nodes (final nodes of a meta-node, after which
1461 // an arc is relaxed.
1462 absl::flat_hash_set<int64_t> breaks_set;
1463 // Always add base node to break nodes (diversification)
1464 breaks_set.insert(base_node);
1465 CHECK(!nodes.empty()); // Should have been caught earlier.
1466 while (breaks_set.size() < tsp_size_) {
1467 breaks_set.insert(nodes[absl::Uniform<int>(rand_, 0, nodes.size())]);
1468 }
1469 CHECK_EQ(breaks_set.size(), tsp_size_);
1470 // Setup break node indexing and internal meta-node cost (cost of partial
1471 // route starting at first node of the meta-node and ending at its last node);
1472 // this cost has to be added to the TSP matrix cost in order to respect the
1473 // triangle inequality.
1474 std::vector<int> breaks;
1475 std::vector<int64_t> meta_node_costs;
1476 int64_t cost = 0;
1477 int64_t node = this->StartNode(0);
1478 int64_t node_path = this->Path(node);
1479 while (!this->IsPathEnd(node)) {
1480 int64_t next = this->Next(node);
1481 if (breaks_set.contains(node)) {
1482 breaks.push_back(node);
1483 meta_node_costs.push_back(cost);
1484 cost = 0;
1485 } else {
1486 cost = CapAdd(cost, evaluator_(node, next, node_path));
1487 }
1488 node = next;
1489 }
1490 meta_node_costs[0] += cost;
1491 CHECK_EQ(breaks.size(), tsp_size_);
1492 // Setup TSP cost matrix
1493 CHECK_EQ(meta_node_costs.size(), tsp_size_);
1494 for (int i = 0; i < tsp_size_; ++i) {
1495 cost_[i][0] = CapAdd(
1496 meta_node_costs[i],
1497 evaluator_(breaks[i], this->Next(breaks[tsp_size_ - 1]), node_path));
1498 for (int j = 1; j < tsp_size_; ++j) {
1499 cost_[i][j] =
1500 CapAdd(meta_node_costs[i],
1501 evaluator_(breaks[i], this->Next(breaks[j - 1]), node_path));
1502 }
1503 cost_[i][i] = 0;
1504 }
1505 // Solve TSP and inject solution in delta (only if it leads to a new solution)
1506 hamiltonian_path_solver_.ChangeCostMatrix(cost_);
1507 std::vector<PathNodeIndex> path;
1508 hamiltonian_path_solver_.TravelingSalesmanPath(&path);
1509 bool nochange = true;
1510 for (int i = 0; i < path.size() - 1; ++i) {
1511 if (path[i] != i) {
1512 nochange = false;
1513 break;
1514 }
1515 }
1516 if (nochange) {
1517 return false;
1518 }
1519 CHECK_EQ(0, path[path.size() - 1]);
1520 for (int i = 0; i < tsp_size_ - 1; ++i) {
1521 this->SetNext(breaks[path[i]], this->OldNext(breaks[path[i + 1] - 1]),
1522 node_path);
1523 }
1524 this->SetNext(breaks[path[tsp_size_ - 1]],
1525 this->OldNext(breaks[tsp_size_ - 1]), node_path);
1526 return true;
1527}
1528
1530 const std::vector<IntVar*>& vars,
1531 const std::vector<IntVar*>& secondary_vars,
1532 Solver::IndexEvaluator3 evaluator,
1533 int tsp_size) {
1534 if (secondary_vars.empty()) {
1535 return solver->RevAlloc(
1536 new TSPLns<true>(vars, secondary_vars, std::move(evaluator), tsp_size));
1537 }
1538 return solver->RevAlloc(
1539 new TSPLns<false>(vars, secondary_vars, std::move(evaluator), tsp_size));
1540}
1541
1542// ----- Lin-Kernighan -----
1543
1544// For each variable in vars, stores the 'size' pairs(i,j) with the smallest
1545// value according to evaluator, where i is the index of the variable in vars
1546// and j is in the domain of the variable.
1547// Note that the resulting pairs are sorted.
1548// Works in O(size) per variable on average (same approach as qsort)
1549
1550template <bool ignore_path_vars>
1552 public:
1554 const PathOperator<ignore_path_vars>& path_operator,
1555 int size);
1556
1557 // This type is neither copyable nor movable.
1560
1561 virtual ~NearestNeighbors() = default;
1562 void Initialize(absl::Span<const int> path);
1563 const std::vector<int>& Neighbors(int index) const;
1564
1565 virtual std::string DebugString() const { return "NearestNeighbors"; }
1566
1567 private:
1568 void ComputeNearest(int row, absl::Span<const int> path);
1569
1570 std::vector<std::vector<int>> neighbors_;
1571 Solver::IndexEvaluator3 evaluator_;
1572 const PathOperator<ignore_path_vars>& path_operator_;
1573 const int size_;
1574};
1575
1576template <bool ignore_path_vars>
1578 Solver::IndexEvaluator3 evaluator,
1579 const PathOperator<ignore_path_vars>& path_operator, int size)
1580 : neighbors_(path_operator.number_of_nexts()),
1581 evaluator_(std::move(evaluator)),
1582 path_operator_(path_operator),
1583 size_(size) {}
1584
1585template <bool ignore_path_vars>
1587 absl::Span<const int> path) {
1588 for (int node : path) {
1589 if (node < path_operator_.number_of_nexts()) ComputeNearest(node, path);
1590 }
1591}
1592
1593template <bool ignore_path_vars>
1595 int index) const {
1596 return neighbors_[index];
1597}
1598
1599template <bool ignore_path_vars>
1600void NearestNeighbors<ignore_path_vars>::ComputeNearest(
1601 int row, absl::Span<const int> path_nodes) {
1602 // Find size_ nearest neighbors for row of index 'row'.
1603 const int path = path_operator_.Path(row);
1604 const IntVar* var = path_operator_.Var(row);
1605 using ValuedIndex = std::pair<int64_t /*value*/, int /*index*/>;
1606 std::vector<ValuedIndex> neighbors;
1607 for (int index : path_nodes) {
1608 if (!var->Contains(index)) continue;
1609 neighbors.push_back({evaluator_(row, index, path), index});
1610 }
1611 const int neighbors_size = neighbors.size();
1612 if (neighbors_size > size_) {
1613 std::nth_element(neighbors.begin(), neighbors.begin() + size_ - 1,
1614 neighbors.end());
1615 }
1616
1617 // Setup global neighbor matrix for row row_index
1618 neighbors_[row].clear();
1619 for (int i = 0; i < std::min(size_, neighbors_size); ++i) {
1620 neighbors_[row].push_back(neighbors[i].second);
1621 }
1622 std::sort(neighbors_[row].begin(), neighbors_[row].end());
1623}
1624
1625template <bool ignore_path_vars>
1626class LinKernighan : public PathOperator<ignore_path_vars> {
1627 public:
1628 LinKernighan(const std::vector<IntVar*>& vars,
1629 const std::vector<IntVar*>& secondary_vars,
1630 const Solver::IndexEvaluator3& evaluator, bool topt);
1631 ~LinKernighan() override = default;
1632 bool MakeNeighbor() override;
1633
1634 std::string DebugString() const override { return "LinKernighan"; }
1635
1636 private:
1637 void OnNodeInitialization() override;
1638
1639 bool GetBestOut(int64_t in_i, int64_t in_j, int64_t* out, int64_t* gain);
1640
1641 Solver::IndexEvaluator3 const evaluator_;
1643 absl::flat_hash_set<int64_t> marked_;
1644 const bool topt_;
1645 std::vector<int> old_path_starts_;
1646};
1647
1648// While the accumulated local gain is positive, perform a 2opt or a 3opt move
1649// followed by a series of 2opt moves. Return a neighbor for which the global
1650// gain is positive.
1651
1652template <bool ignore_path_vars>
1654 const std::vector<IntVar*>& vars,
1655 const std::vector<IntVar*>& secondary_vars,
1656 const Solver::IndexEvaluator3& evaluator, bool topt)
1657 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
1658 nullptr, nullptr, nullptr),
1659 evaluator_(evaluator),
1660 neighbors_(evaluator, *this, /*size=*/5 + 1),
1661 topt_(topt) {
1662 old_path_starts_.resize(this->number_of_nexts(), -1);
1663}
1664
1665template <bool ignore_path_vars>
1667 absl::flat_hash_set<int> touched_paths;
1668 for (int i = 0; i < this->number_of_nexts(); ++i) {
1669 if (this->IsPathStart(i)) {
1670 for (int node = i; !this->IsPathEnd(node); node = this->Next(node)) {
1671 if (i != old_path_starts_[node]) {
1672 touched_paths.insert(old_path_starts_[node]);
1673 touched_paths.insert(i);
1674 old_path_starts_[node] = i;
1675 }
1676 }
1677 } else if (this->Next(i) == i) {
1678 touched_paths.insert(old_path_starts_[i]);
1679 old_path_starts_[i] = -1;
1680 }
1681 }
1682 for (int touched_path_start : touched_paths) {
1683 if (touched_path_start == -1) continue;
1684 std::vector<int> path;
1685 int node = touched_path_start;
1686 while (!this->IsPathEnd(node)) {
1687 path.push_back(node);
1688 node = this->Next(node);
1689 }
1690 path.push_back(node);
1691 neighbors_.Initialize(path);
1692 }
1693}
1694
1695template <bool ignore_path_vars>
1697 marked_.clear();
1698 int64_t node = this->BaseNode(0);
1699 int64_t path = this->Path(node);
1700 int64_t base = node;
1701 int64_t next = this->Next(node);
1702 if (this->IsPathEnd(next)) return false;
1703 int64_t out = -1;
1704 int64_t gain = 0;
1705 marked_.insert(node);
1706 if (topt_) { // Try a 3opt first
1707 if (!GetBestOut(node, next, &out, &gain)) return false;
1708 marked_.insert(next);
1709 marked_.insert(out);
1710 const int64_t node1 = out;
1711 if (this->IsPathEnd(node1)) return false;
1712 const int64_t next1 = this->Next(node1);
1713 if (this->IsPathEnd(next1)) return false;
1714 if (!GetBestOut(node1, next1, &out, &gain)) return false;
1715 marked_.insert(next1);
1716 marked_.insert(out);
1717 if (!this->CheckChainValidity(out, node1, node) ||
1718 !this->MoveChain(out, node1, node)) {
1719 return false;
1720 }
1721 const int64_t next_out = this->Next(out);
1722 const int64_t in_cost = evaluator_(node, next_out, path);
1723 const int64_t out_cost = evaluator_(out, next_out, path);
1724 if (CapAdd(CapSub(gain, in_cost), out_cost) > 0) return true;
1725 node = out;
1726 if (this->IsPathEnd(node)) return false;
1727 next = next_out;
1728 if (this->IsPathEnd(next)) return false;
1729 }
1730 // Try 2opts
1731 while (GetBestOut(node, next, &out, &gain)) {
1732 marked_.insert(next);
1733 marked_.insert(out);
1734 int64_t chain_last;
1735 if (this->Next(base) == out ||
1736 (!this->IsPathEnd(out) && this->Next(out) == base)) {
1737 return false;
1738 }
1739 const bool success = this->ReverseChain(base, out, &chain_last) ||
1740 this->ReverseChain(out, base, &chain_last);
1741 if (!success) {
1742#ifndef NDEBUG
1743 LOG(ERROR) << "ReverseChain failed: " << base << " " << out;
1744 for (int node = this->StartNode(0); !this->IsPathEnd(node);
1745 node = this->Next(node)) {
1746 LOG(ERROR) << "node: " << node;
1747 }
1748 LOG(ERROR) << "node: " << node;
1749 DCHECK(false);
1750#endif
1751 }
1752 const int64_t in_cost = evaluator_(base, chain_last, path);
1753 const int64_t out_cost = evaluator_(chain_last, out, path);
1754 if (CapAdd(CapSub(gain, in_cost), out_cost) > 0) {
1755 return true;
1756 }
1757 node = out;
1758 if (this->IsPathEnd(node)) {
1759 return false;
1760 }
1761 next = chain_last;
1762 if (this->IsPathEnd(next)) {
1763 return false;
1764 }
1765 }
1766 return false;
1767}
1768
1769template <bool ignore_path_vars>
1770bool LinKernighan<ignore_path_vars>::GetBestOut(int64_t in_i, int64_t in_j,
1771 int64_t* out, int64_t* gain) {
1772 int64_t best_gain = std::numeric_limits<int64_t>::min();
1773 const int64_t path = this->Path(in_i);
1774 const int64_t out_cost = evaluator_(in_i, in_j, path);
1775 const int64_t current_gain = CapAdd(*gain, out_cost);
1776 for (int next : neighbors_.Neighbors(in_j)) {
1777 if (next != in_j && next != this->Next(in_j) && !marked_.contains(in_j) &&
1778 !marked_.contains(next)) {
1779 const int64_t in_cost = evaluator_(in_j, next, path);
1780 const int64_t new_gain = CapSub(current_gain, in_cost);
1781 if (new_gain > 0 && best_gain < new_gain) {
1782 *out = next;
1783 best_gain = new_gain;
1784 }
1785 }
1786 }
1787 *gain = best_gain;
1788 return (best_gain > std::numeric_limits<int64_t>::min());
1789}
1790
1792 Solver* solver, const std::vector<IntVar*>& vars,
1793 const std::vector<IntVar*>& secondary_vars,
1794 const Solver::IndexEvaluator3& evaluator, bool topt) {
1795 if (secondary_vars.empty()) {
1796 return solver->RevAlloc(new LinKernighan<true>(vars, secondary_vars,
1797 std::move(evaluator), topt));
1798 }
1799 return solver->RevAlloc(new LinKernighan<false>(vars, secondary_vars,
1800 std::move(evaluator), topt));
1801}
1802
1803// ----- Path-based Large Neighborhood Search -----
1804
1805template <bool ignore_path_vars>
1806class PathLns : public PathOperator<ignore_path_vars> {
1807 public:
1808 PathLns(const std::vector<IntVar*>& vars,
1809 const std::vector<IntVar*>& secondary_vars, int number_of_chunks,
1810 int chunk_size, bool unactive_fragments)
1811 : PathOperator<ignore_path_vars>(vars, secondary_vars, number_of_chunks,
1812 true, true, nullptr, nullptr, nullptr),
1813 number_of_chunks_(number_of_chunks),
1814 chunk_size_(chunk_size),
1815 unactive_fragments_(unactive_fragments) {
1816 CHECK_GE(chunk_size_, 0);
1817 }
1818 ~PathLns() override = default;
1819 bool MakeNeighbor() override;
1820
1821 std::string DebugString() const override { return "PathLns"; }
1822 bool HasFragments() const override { return true; }
1823
1824 private:
1825 inline bool ChainsAreFullPaths() const { return chunk_size_ == 0; }
1826 void DeactivateChain(int64_t node);
1827 void DeactivateUnactives();
1828
1829 const int number_of_chunks_;
1830 const int chunk_size_;
1831 const bool unactive_fragments_;
1832};
1833
1834template <bool ignore_path_vars>
1836 if (ChainsAreFullPaths()) {
1837 // Reject the current position as a neighbor if any of its base node
1838 // isn't at the start of a path.
1839 // TODO(user): make this more efficient.
1840 for (int i = 0; i < number_of_chunks_; ++i) {
1841 if (this->BaseNode(i) != this->StartNode(i)) return false;
1842 }
1843 }
1844 for (int i = 0; i < number_of_chunks_; ++i) {
1845 DeactivateChain(this->BaseNode(i));
1846 }
1847 DeactivateUnactives();
1848 return true;
1849}
1850
1851template <bool ignore_path_vars>
1852void PathLns<ignore_path_vars>::DeactivateChain(int64_t node) {
1853 for (int i = 0, current = node;
1854 (ChainsAreFullPaths() || i < chunk_size_) && !this->IsPathEnd(current);
1855 ++i, current = this->Next(current)) {
1856 this->Deactivate(current);
1857 if constexpr (!ignore_path_vars) {
1858 this->Deactivate(this->number_of_nexts_ + current);
1859 }
1860 }
1861}
1862
1863template <bool ignore_path_vars>
1864void PathLns<ignore_path_vars>::DeactivateUnactives() {
1865 if (unactive_fragments_) {
1866 for (int i = 0; i < this->Size(); ++i) {
1867 if (this->IsInactive(i)) {
1868 this->Deactivate(i);
1869 if constexpr (!ignore_path_vars) {
1870 this->Deactivate(this->number_of_nexts_ + i);
1871 }
1872 }
1873 }
1874 }
1875}
1876
1878 const std::vector<IntVar*>& vars,
1879 const std::vector<IntVar*>& secondary_vars,
1880 int number_of_chunks, int chunk_size,
1881 bool unactive_fragments) {
1882 if (secondary_vars.empty()) {
1883 return solver->RevAlloc(new PathLns<true>(vars, secondary_vars,
1884 number_of_chunks, chunk_size,
1885 unactive_fragments));
1886 }
1887 return solver->RevAlloc(new PathLns<false>(
1888 vars, secondary_vars, number_of_chunks, chunk_size, unactive_fragments));
1889}
1890
1891// ----- Limit the number of neighborhoods explored -----
1892
1894 public:
1895 NeighborhoodLimit(LocalSearchOperator* const op, int64_t limit)
1896 : operator_(op), limit_(limit), next_neighborhood_calls_(0) {
1897 CHECK(op != nullptr);
1898 CHECK_GT(limit, 0);
1899 }
1900
1901 void Start(const Assignment* assignment) override {
1902 next_neighborhood_calls_ = 0;
1903 operator_->Start(assignment);
1904 }
1905
1906 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override {
1907 if (next_neighborhood_calls_ >= limit_) {
1908 return false;
1909 }
1910 ++next_neighborhood_calls_;
1911 return operator_->MakeNextNeighbor(delta, deltadelta);
1912 }
1913
1914 bool HoldsDelta() const override { return operator_->HoldsDelta(); }
1915
1916 std::string DebugString() const override { return "NeighborhoodLimit"; }
1917
1918 private:
1919 LocalSearchOperator* const operator_;
1920 const int64_t limit_;
1921 int64_t next_neighborhood_calls_;
1922};
1923
1925 LocalSearchOperator* const op, int64_t limit) {
1926 return RevAlloc(new NeighborhoodLimit(op, limit));
1927}
1928
1929// ----- Concatenation of operators -----
1930
1931namespace {
1932class CompoundOperator : public LocalSearchOperator {
1933 public:
1934 CompoundOperator(std::vector<LocalSearchOperator*> operators,
1935 std::function<int64_t(int, int)> evaluator);
1936 ~CompoundOperator() override {}
1937 void EnterSearch() override;
1938 void Reset() override;
1939 void Start(const Assignment* assignment) override;
1940 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
1941 bool HasFragments() const override { return has_fragments_; }
1942 bool HoldsDelta() const override { return true; }
1943
1944 std::string DebugString() const override {
1945 return operators_.empty()
1946 ? ""
1947 : operators_[operator_indices_[index_]]->DebugString();
1948 }
1949 const LocalSearchOperator* Self() const override {
1950 return operators_.empty() ? this
1951 : operators_[operator_indices_[index_]]->Self();
1952 }
1953
1954 private:
1955 class OperatorComparator {
1956 public:
1957 OperatorComparator(std::function<int64_t(int, int)> evaluator,
1958 int active_operator)
1959 : evaluator_(std::move(evaluator)), active_operator_(active_operator) {}
1960 bool operator()(int lhs, int rhs) const {
1961 const int64_t lhs_value = Evaluate(lhs);
1962 const int64_t rhs_value = Evaluate(rhs);
1963 return lhs_value < rhs_value || (lhs_value == rhs_value && lhs < rhs);
1964 }
1965
1966 private:
1967 int64_t Evaluate(int operator_index) const {
1968 return evaluator_(active_operator_, operator_index);
1969 }
1970
1971 std::function<int64_t(int, int)> evaluator_;
1972 const int active_operator_;
1973 };
1974
1975 int64_t index_;
1976 std::vector<LocalSearchOperator*> operators_;
1977 std::vector<int> operator_indices_;
1978 std::function<int64_t(int, int)> evaluator_;
1979 Bitset64<> started_;
1980 const Assignment* start_assignment_;
1981 bool has_fragments_;
1982};
1983
1984CompoundOperator::CompoundOperator(std::vector<LocalSearchOperator*> operators,
1985 std::function<int64_t(int, int)> evaluator)
1986 : index_(0),
1987 operators_(std::move(operators)),
1988 evaluator_(std::move(evaluator)),
1989 started_(operators_.size()),
1990 start_assignment_(nullptr),
1991 has_fragments_(false) {
1992 operators_.erase(std::remove(operators_.begin(), operators_.end(), nullptr),
1993 operators_.end());
1994 operator_indices_.resize(operators_.size());
1995 absl::c_iota(operator_indices_, 0);
1996 for (LocalSearchOperator* const op : operators_) {
1997 if (op->HasFragments()) {
1998 has_fragments_ = true;
1999 break;
2000 }
2001 }
2002}
2003
2004void CompoundOperator::EnterSearch() {
2005 absl::c_iota(operator_indices_, 0);
2006 index_ = 0;
2007 for (LocalSearchOperator* const op : operators_) {
2008 op->EnterSearch();
2009 }
2010}
2011
2012void CompoundOperator::Reset() {
2013 for (LocalSearchOperator* const op : operators_) {
2014 op->Reset();
2015 }
2016}
2017
2018void CompoundOperator::Start(const Assignment* assignment) {
2019 start_assignment_ = assignment;
2020 started_.ClearAll();
2021 if (!operators_.empty()) {
2022 std::sort(operator_indices_.begin(), operator_indices_.end(),
2023 OperatorComparator(evaluator_, operator_indices_[index_]));
2024 index_ = 0;
2025 }
2026}
2027
2028bool CompoundOperator::MakeNextNeighbor(Assignment* delta,
2029 Assignment* deltadelta) {
2030 const auto is_leaf = [](const LocalSearchOperator* op) {
2031 return op == op->Self();
2032 };
2033 if (!operators_.empty()) {
2034 Solver* solver = delta->solver();
2035 do {
2036 // TODO(user): keep copy of delta in case MakeNextNeighbor
2037 // pollutes delta on a fail.
2038 const int64_t operator_index = operator_indices_[index_];
2039 LocalSearchOperator* op = operators_[operator_index];
2040 if (!started_[operator_index]) {
2041 op->Start(start_assignment_);
2042 started_.Set(operator_index);
2043 }
2044 if (!op->HoldsDelta()) {
2045 delta->Clear();
2046 }
2047 if (is_leaf(op)) {
2048 solver->GetLocalSearchMonitor()->BeginMakeNextNeighbor(op);
2049 }
2050 if (op->MakeNextNeighbor(delta, deltadelta)) return true;
2051 if (is_leaf(op)) {
2052 solver->GetLocalSearchMonitor()->EndMakeNextNeighbor(
2053 op, /*has_neighbor*/ false, delta, deltadelta);
2054 }
2055 ++index_;
2056 delta->Clear();
2057 if (index_ == operators_.size()) {
2058 index_ = 0;
2059 }
2060 } while (index_ != 0);
2061 }
2062 return false;
2063}
2064
2065int64_t CompoundOperatorNoRestart(int size, int active_index,
2066 int operator_index) {
2067 return (operator_index < active_index) ? size + operator_index - active_index
2068 : operator_index - active_index;
2069}
2070} // namespace
2071
2073 const std::vector<LocalSearchOperator*>& ops) {
2074 return ConcatenateOperators(ops, false);
2075}
2076
2078 const std::vector<LocalSearchOperator*>& ops, bool restart) {
2079 if (restart) {
2080 return ConcatenateOperators(
2081 ops, std::function<int64_t(int, int)>([](int, int) { return 0; }));
2082 }
2083 const int size = ops.size();
2084 return ConcatenateOperators(ops, [size](int i, int j) {
2085 return CompoundOperatorNoRestart(size, i, j);
2086 });
2087}
2088
2090 const std::vector<LocalSearchOperator*>& ops,
2091 std::function<int64_t(int, int)> evaluator) {
2092 return RevAlloc(new CompoundOperator(ops, std::move(evaluator)));
2093}
2094
2095namespace {
2096class RandomCompoundOperator : public LocalSearchOperator {
2097 public:
2098 explicit RandomCompoundOperator(std::vector<LocalSearchOperator*> operators);
2099 RandomCompoundOperator(std::vector<LocalSearchOperator*> operators,
2100 int32_t seed);
2101 ~RandomCompoundOperator() override {}
2102 void EnterSearch() override;
2103 void Reset() override;
2104 void Start(const Assignment* assignment) override;
2105 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
2106 bool HoldsDelta() const override { return true; }
2107
2108 std::string DebugString() const override { return "RandomCompoundOperator"; }
2109 // TODO(user): define Self method.
2110
2111 private:
2112 std::mt19937 rand_;
2113 const std::vector<LocalSearchOperator*> operators_;
2114 bool has_fragments_;
2115};
2116
2117void RandomCompoundOperator::Start(const Assignment* assignment) {
2118 for (LocalSearchOperator* const op : operators_) {
2119 op->Start(assignment);
2120 }
2121}
2122
2123RandomCompoundOperator::RandomCompoundOperator(
2124 std::vector<LocalSearchOperator*> operators)
2125 : RandomCompoundOperator(std::move(operators), CpRandomSeed()) {}
2126
2127RandomCompoundOperator::RandomCompoundOperator(
2128 std::vector<LocalSearchOperator*> operators, int32_t seed)
2129 : rand_(seed), operators_(std::move(operators)), has_fragments_(false) {
2130 for (LocalSearchOperator* const op : operators_) {
2131 if (op->HasFragments()) {
2132 has_fragments_ = true;
2133 break;
2134 }
2135 }
2136}
2137
2138void RandomCompoundOperator::EnterSearch() {
2139 for (LocalSearchOperator* const op : operators_) {
2140 op->EnterSearch();
2141 }
2142}
2143
2144void RandomCompoundOperator::Reset() {
2145 for (LocalSearchOperator* const op : operators_) {
2146 op->Reset();
2147 }
2148}
2149
2150bool RandomCompoundOperator::MakeNextNeighbor(Assignment* delta,
2151 Assignment* deltadelta) {
2152 const int size = operators_.size();
2153 std::vector<int> indices(size);
2154 absl::c_iota(indices, 0);
2155 std::shuffle(indices.begin(), indices.end(), rand_);
2156 for (int index : indices) {
2157 if (!operators_[index]->HoldsDelta()) {
2158 delta->Clear();
2159 }
2160 if (operators_[index]->MakeNextNeighbor(delta, deltadelta)) {
2161 return true;
2162 }
2163 delta->Clear();
2164 }
2165 return false;
2166}
2167} // namespace
2168
2170 const std::vector<LocalSearchOperator*>& ops) {
2171 return RevAlloc(new RandomCompoundOperator(ops));
2172}
2173
2175 const std::vector<LocalSearchOperator*>& ops, int32_t seed) {
2176 return RevAlloc(new RandomCompoundOperator(ops, seed));
2177}
2178
2179namespace {
2180class MultiArmedBanditCompoundOperator : public LocalSearchOperator {
2181 public:
2182 explicit MultiArmedBanditCompoundOperator(
2183 std::vector<LocalSearchOperator*> operators, double memory_coefficient,
2184 double exploration_coefficient, bool maximize);
2185 ~MultiArmedBanditCompoundOperator() override {}
2186 void EnterSearch() override;
2187 void Reset() override;
2188 void Start(const Assignment* assignment) override;
2189 bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override;
2190 bool HoldsDelta() const override { return true; }
2191
2192 std::string DebugString() const override {
2193 return operators_.empty()
2194 ? ""
2195 : operators_[operator_indices_[index_]]->DebugString();
2196 }
2197 const LocalSearchOperator* Self() const override {
2198 return operators_.empty() ? this
2199 : operators_[operator_indices_[index_]]->Self();
2200 }
2201
2202 private:
2203 double Score(int index);
2204 int index_;
2205 std::vector<LocalSearchOperator*> operators_;
2206 Bitset64<> started_;
2207 const Assignment* start_assignment_;
2208 bool has_fragments_;
2209 std::vector<int> operator_indices_;
2210 int64_t last_objective_;
2211 std::vector<double> avg_improvement_;
2212 int num_neighbors_;
2213 std::vector<double> num_neighbors_per_operator_;
2214 const bool maximize_;
2215 // Sets how much the objective improvement of previous accepted neighbors
2216 // influence the current average improvement. The formula is
2217 // avg_improvement +=
2218 // memory_coefficient * (current_improvement - avg_improvement).
2219 const double memory_coefficient_;
2220 // Sets how often we explore rarely used and unsuccessful in the past
2221 // operators. Operators are sorted by
2222 // avg_improvement_[i] + exploration_coefficient_ *
2223 // sqrt(2 * log(1 + num_neighbors_) / (1 + num_neighbors_per_operator_[i])).
2224 // This definition uses the UCB1 exploration bonus for unstructured
2225 // multi-armed bandits.
2226 const double exploration_coefficient_;
2227};
2228
2229MultiArmedBanditCompoundOperator::MultiArmedBanditCompoundOperator(
2230 std::vector<LocalSearchOperator*> operators, double memory_coefficient,
2231 double exploration_coefficient, bool maximize)
2232 : index_(0),
2233 operators_(std::move(operators)),
2234 started_(operators_.size()),
2235 start_assignment_(nullptr),
2236 has_fragments_(false),
2237 last_objective_(std::numeric_limits<int64_t>::max()),
2238 num_neighbors_(0),
2239 maximize_(maximize),
2240 memory_coefficient_(memory_coefficient),
2241 exploration_coefficient_(exploration_coefficient) {
2242 DCHECK_GE(memory_coefficient_, 0);
2243 DCHECK_LE(memory_coefficient_, 1);
2244 DCHECK_GE(exploration_coefficient_, 0);
2245 operators_.erase(std::remove(operators_.begin(), operators_.end(), nullptr),
2246 operators_.end());
2247 operator_indices_.resize(operators_.size());
2248 absl::c_iota(operator_indices_, 0);
2249 num_neighbors_per_operator_.resize(operators_.size(), 0);
2250 avg_improvement_.resize(operators_.size(), 0);
2251 for (LocalSearchOperator* const op : operators_) {
2252 if (op->HasFragments()) {
2253 has_fragments_ = true;
2254 break;
2255 }
2256 }
2257}
2258
2259void MultiArmedBanditCompoundOperator::EnterSearch() {
2260 last_objective_ = std::numeric_limits<int64_t>::max();
2261 num_neighbors_ = 0;
2262 absl::c_iota(operator_indices_, 0);
2263 index_ = 0;
2264 num_neighbors_per_operator_.resize(operators_.size(), 0);
2265 avg_improvement_.resize(operators_.size(), 0);
2266 for (LocalSearchOperator* const op : operators_) {
2267 op->EnterSearch();
2268 }
2269}
2270
2271void MultiArmedBanditCompoundOperator::Reset() {
2272 for (LocalSearchOperator* const op : operators_) {
2273 op->Reset();
2274 }
2275}
2276
2277double MultiArmedBanditCompoundOperator::Score(int index) {
2278 return avg_improvement_[index] +
2279 exploration_coefficient_ *
2280 sqrt(2 * log(1 + num_neighbors_) /
2281 (1 + num_neighbors_per_operator_[index]));
2282}
2283
2284void MultiArmedBanditCompoundOperator::Start(const Assignment* assignment) {
2285 start_assignment_ = assignment;
2286 started_.ClearAll();
2287 if (operators_.empty()) return;
2288
2289 const double objective = assignment->ObjectiveValue();
2290
2291 if (objective == last_objective_) return;
2292 // Skip a neighbor evaluation if last_objective_ hasn't been set yet.
2293 if (last_objective_ == std::numeric_limits<int64_t>::max()) {
2294 last_objective_ = objective;
2295 return;
2296 }
2297
2298 const double improvement =
2299 maximize_ ? objective - last_objective_ : last_objective_ - objective;
2300 if (improvement < 0) {
2301 return;
2302 }
2303 last_objective_ = objective;
2304 avg_improvement_[operator_indices_[index_]] +=
2305 memory_coefficient_ *
2306 (improvement - avg_improvement_[operator_indices_[index_]]);
2307
2308 std::sort(operator_indices_.begin(), operator_indices_.end(),
2309 [this](int lhs, int rhs) {
2310 const double lhs_score = Score(lhs);
2311 const double rhs_score = Score(rhs);
2312 return lhs_score > rhs_score ||
2313 (lhs_score == rhs_score && lhs < rhs);
2314 });
2315
2316 index_ = 0;
2317}
2318
2319bool MultiArmedBanditCompoundOperator::MakeNextNeighbor(
2320 Assignment* delta, Assignment* deltadelta) {
2321 if (operators_.empty()) return false;
2322 do {
2323 const int operator_index = operator_indices_[index_];
2324 if (!started_[operator_index]) {
2325 operators_[operator_index]->Start(start_assignment_);
2326 started_.Set(operator_index);
2327 }
2328 if (!operators_[operator_index]->HoldsDelta()) {
2329 delta->Clear();
2330 }
2331 if (operators_[operator_index]->MakeNextNeighbor(delta, deltadelta)) {
2332 ++num_neighbors_;
2333 ++num_neighbors_per_operator_[operator_index];
2334 return true;
2335 }
2336 ++index_;
2337 delta->Clear();
2338 if (index_ == operators_.size()) {
2339 index_ = 0;
2340 }
2341 } while (index_ != 0);
2342 return false;
2343}
2344} // namespace
2345
2347 const std::vector<LocalSearchOperator*>& ops, double memory_coefficient,
2348 double exploration_coefficient, bool maximize) {
2349 return RevAlloc(new MultiArmedBanditCompoundOperator(
2350 ops, memory_coefficient, exploration_coefficient, maximize));
2351}
2352
2353// TODO(user): Remove (parts of) the following methods as they are mostly
2354// redundant with individual operator builder functions.
2356 const std::vector<IntVar*>& vars, Solver::LocalSearchOperators op,
2357 NeighborAccessor get_incoming_neighbors,
2358 NeighborAccessor get_outgoing_neighbors) {
2359 return MakeOperator(vars, std::vector<IntVar*>(), op,
2360 std::move(get_incoming_neighbors),
2361 std::move(get_outgoing_neighbors));
2362}
2363
2365 const std::vector<IntVar*>& vars,
2366 const std::vector<IntVar*>& secondary_vars, Solver::LocalSearchOperators op,
2367 NeighborAccessor get_incoming_neighbors,
2368 NeighborAccessor get_outgoing_neighbors) {
2369 switch (op) {
2370 case Solver::TWOOPT: {
2371 return MakeTwoOpt(this, vars, secondary_vars, nullptr,
2372 std::move(get_incoming_neighbors),
2373 std::move(get_outgoing_neighbors));
2374 }
2375 case Solver::OROPT: {
2376 std::vector<LocalSearchOperator*> operators;
2377 for (int i = 1; i < 4; ++i) {
2378 operators.push_back(MakeRelocate(this, vars, secondary_vars,
2379 /*start_empty_path_class=*/nullptr,
2380 /*get_incoming_neighbors=*/nullptr,
2381 /*get_outgoing_neighbors=*/nullptr,
2382 /*chain_length=*/i,
2383 /*single_path=*/true,
2384 /*name=*/"OrOpt"));
2385 }
2386 return ConcatenateOperators(operators);
2387 }
2388 case Solver::RELOCATE: {
2389 return MakeRelocate(
2390 this, vars, secondary_vars, /*start_empty_path_class=*/nullptr,
2391 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors));
2392 }
2393 case Solver::EXCHANGE: {
2394 return MakeExchange(this, vars, secondary_vars, nullptr,
2395 std::move(get_incoming_neighbors),
2396 std::move(get_outgoing_neighbors));
2397 }
2398 case Solver::CROSS: {
2399 return MakeCross(this, vars, secondary_vars, nullptr,
2400 std::move(get_incoming_neighbors),
2401 std::move(get_outgoing_neighbors));
2402 }
2403 case Solver::MAKEACTIVE: {
2404 return MakeActive(this, vars, secondary_vars, nullptr);
2405 }
2406 case Solver::MAKEINACTIVE: {
2407 return MakeInactive(this, vars, secondary_vars, nullptr);
2408 }
2410 return MakeChainInactive(this, vars, secondary_vars, nullptr);
2411 }
2412 case Solver::SWAPACTIVE: {
2413 return MakeSwapActive(this, vars, secondary_vars, nullptr);
2414 }
2416 return MakeSwapActiveChain(this, vars, secondary_vars, nullptr,
2417 kint32max);
2418 }
2420 return MakeExtendedSwapActive(this, vars, secondary_vars, nullptr);
2421 }
2422 case Solver::PATHLNS: {
2423 return MakePathLns(this, vars, secondary_vars, /*number_of_chunks=*/2,
2424 /*chunk_size=*/3, /*unactive_fragments=*/false);
2425 }
2426 case Solver::FULLPATHLNS: {
2427 return MakePathLns(this, vars, secondary_vars,
2428 /*number_of_chunks=*/1,
2429 /*chunk_size=*/0,
2430 /*unactive_fragments=*/true);
2431 }
2432 case Solver::UNACTIVELNS: {
2433 return MakePathLns(this, vars, secondary_vars, /*number_of_chunks=*/1,
2434 /*chunk_size=*/6, /*unactive_fragments=*/true);
2435 }
2436 case Solver::INCREMENT: {
2437 if (!secondary_vars.empty()) {
2438 LOG(FATAL) << "Operator " << op
2439 << " does not support secondary variables";
2440 }
2441 return RevAlloc(new IncrementValue(vars));
2442 }
2443 case Solver::DECREMENT: {
2444 if (!secondary_vars.empty()) {
2445 LOG(FATAL) << "Operator " << op
2446 << " does not support secondary variables";
2447 }
2448 return RevAlloc(new DecrementValue(vars));
2449 }
2450 case Solver::SIMPLELNS: {
2451 if (!secondary_vars.empty()) {
2452 LOG(FATAL) << "Operator " << op
2453 << " does not support secondary variables";
2454 }
2455 return RevAlloc(new SimpleLns(vars, 1));
2456 }
2457 default:
2458 LOG(FATAL) << "Unknown operator " << op;
2459 }
2460 return nullptr;
2461}
2462
2464 const std::vector<IntVar*>& vars, Solver::IndexEvaluator3 evaluator,
2466 return MakeOperator(vars, std::vector<IntVar*>(), std::move(evaluator), op);
2467}
2468
2470 const std::vector<IntVar*>& vars,
2471 const std::vector<IntVar*>& secondary_vars,
2472 Solver::IndexEvaluator3 evaluator,
2474 switch (op) {
2475 case Solver::LK: {
2476 std::vector<LocalSearchOperator*> operators;
2477 operators.push_back(MakeLinKernighan(this, vars, secondary_vars,
2478 evaluator, /*topt=*/false));
2479 operators.push_back(MakeLinKernighan(this, vars, secondary_vars,
2480 evaluator, /*topt=*/true));
2481 return ConcatenateOperators(operators);
2482 }
2483 case Solver::TSPOPT: {
2484 return MakeTSPOpt(this, vars, secondary_vars, std::move(evaluator),
2485 absl::GetFlag(FLAGS_cp_local_search_tsp_opt_size));
2486 }
2487 case Solver::TSPLNS: {
2488 return MakeTSPLns(this, vars, secondary_vars, std::move(evaluator),
2489 absl::GetFlag(FLAGS_cp_local_search_tsp_lns_size));
2490 }
2491 default:
2492 LOG(FATAL) << "Unknown operator " << op;
2493 }
2494 return nullptr;
2495}
2496
2497namespace {
2498// Always accepts deltas, cost 0.
2499class AcceptFilter : public LocalSearchFilter {
2500 public:
2501 std::string DebugString() const override { return "AcceptFilter"; }
2502 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
2503 return true;
2504 }
2505 void Synchronize(const Assignment*, const Assignment*) override {}
2506};
2507} // namespace
2508
2510 return RevAlloc(new AcceptFilter());
2511}
2512
2513namespace {
2514// Never accepts deltas, cost 0.
2515class RejectFilter : public LocalSearchFilter {
2516 public:
2517 std::string DebugString() const override { return "RejectFilter"; }
2518 bool Accept(const Assignment*, const Assignment*, int64_t, int64_t) override {
2519 return false;
2520 }
2521 void Synchronize(const Assignment*, const Assignment*) override {}
2522};
2523} // namespace
2524
2526 return RevAlloc(new RejectFilter());
2527}
2528
2529namespace {
2530// ----- Variable domain filter -----
2531// Rejects assignments to values outside the domain of variables
2532
2533class VariableDomainFilter : public LocalSearchFilter {
2534 public:
2535 VariableDomainFilter() {}
2536 ~VariableDomainFilter() override {}
2537 bool Accept(const Assignment* delta, const Assignment* deltadelta,
2538 int64_t objective_min, int64_t objective_max) override;
2539 void Synchronize(const Assignment*, const Assignment*) override {}
2540
2541 std::string DebugString() const override { return "VariableDomainFilter"; }
2542};
2543
2544bool VariableDomainFilter::Accept(const Assignment* delta, const Assignment*,
2545 int64_t, int64_t) {
2546 const Assignment::IntContainer& container = delta->IntVarContainer();
2547 const int size = container.Size();
2548 for (int i = 0; i < size; ++i) {
2549 const IntVarElement& element = container.Element(i);
2550 if (element.Activated() && !element.Var()->Contains(element.Value())) {
2551 return false;
2552 }
2553 }
2554 return true;
2555}
2556} // namespace
2557
2559 return RevAlloc(new VariableDomainFilter());
2560}
2561
2562// ----- IntVarLocalSearchFilter -----
2563
2564const int IntVarLocalSearchFilter::kUnassigned = -1;
2565
2567 const std::vector<IntVar*>& vars) {
2568 AddVars(vars);
2569}
2570
2571void IntVarLocalSearchFilter::AddVars(const std::vector<IntVar*>& vars) {
2572 if (!vars.empty()) {
2573 for (int i = 0; i < vars.size(); ++i) {
2574 const int index = vars[i]->index();
2575 if (index >= var_index_to_index_.size()) {
2576 var_index_to_index_.resize(index + 1, kUnassigned);
2577 }
2578 var_index_to_index_[index] = i + vars_.size();
2579 }
2580 vars_.insert(vars_.end(), vars.begin(), vars.end());
2581 values_.resize(vars_.size(), /*junk*/ 0);
2582 var_synced_.resize(vars_.size(), false);
2583 }
2584}
2585
2587
2589 const Assignment* delta) {
2590 if (delta == nullptr || delta->Empty()) {
2591 var_synced_.assign(var_synced_.size(), false);
2592 SynchronizeOnAssignment(assignment);
2593 } else {
2595 }
2596 OnSynchronize(delta);
2597}
2598
2600 const Assignment* assignment) {
2601 const Assignment::IntContainer& container = assignment->IntVarContainer();
2602 const int size = container.Size();
2603 for (int i = 0; i < size; ++i) {
2604 const IntVarElement& element = container.Element(i);
2605 IntVar* const var = element.Var();
2606 if (var != nullptr) {
2607 if (i < vars_.size() && vars_[i] == var) {
2608 values_[i] = element.Value();
2609 var_synced_[i] = true;
2610 } else {
2611 const int64_t kUnallocated = -1;
2612 int64_t index = kUnallocated;
2613 if (FindIndex(var, &index)) {
2614 values_[index] = element.Value();
2615 var_synced_[index] = true;
2616 }
2617 }
2618 }
2619 }
2620}
2621
2622// ----- Sum Objective filter ------
2623// Maintains the sum of costs of variables, where the subclass implements
2624// CostOfSynchronizedVariable() and FillCostOfBoundDeltaVariable() to compute
2625// the cost of a variable depending on its value.
2626// An assignment is accepted by this filter if the total cost is allowed
2627// depending on the relation defined by filter_enum:
2628// - Solver::LE -> total_cost <= objective_max.
2629// - Solver::GE -> total_cost >= objective_min.
2630// - Solver::EQ -> the conjunction of LE and GE.
2631namespace {
2632template <typename Filter>
2633class SumObjectiveFilter : public IntVarLocalSearchFilter {
2634 public:
2635 SumObjectiveFilter(const std::vector<IntVar*>& vars, Filter filter)
2637 primary_vars_size_(vars.size()),
2638 synchronized_costs_(vars.size()),
2639 delta_costs_(vars.size()),
2640 filter_(std::move(filter)),
2641 synchronized_sum_(std::numeric_limits<int64_t>::min()),
2642 delta_sum_(std::numeric_limits<int64_t>::min()),
2643 incremental_(false) {}
2644 ~SumObjectiveFilter() override {}
2645 bool Accept(const Assignment* delta, const Assignment* deltadelta,
2646 int64_t objective_min, int64_t objective_max) override {
2647 if (delta == nullptr) return false;
2648 if (deltadelta->Empty()) {
2649 if (incremental_) {
2650 for (int i = 0; i < primary_vars_size_; ++i) {
2651 delta_costs_[i] = synchronized_costs_[i];
2652 }
2653 }
2654 incremental_ = false;
2655 delta_sum_ = CapAdd(synchronized_sum_, CostOfChanges(delta, false));
2656 } else {
2657 if (incremental_) {
2658 delta_sum_ = CapAdd(delta_sum_, CostOfChanges(deltadelta, true));
2659 } else {
2660 delta_sum_ = CapAdd(synchronized_sum_, CostOfChanges(delta, true));
2661 }
2662 incremental_ = true;
2663 }
2664 return filter_(delta_sum_, objective_min, objective_max);
2665 }
2666 // If the variable is synchronized, returns its associated cost, otherwise
2667 // returns 0.
2668 virtual int64_t CostOfSynchronizedVariable(int64_t index) = 0;
2669 // Returns the cost of applying changes to the current solution.
2670 virtual int64_t CostOfChanges(const Assignment* changes,
2671 bool incremental) = 0;
2672 bool IsIncremental() const override { return true; }
2673
2674 std::string DebugString() const override { return "SumObjectiveFilter"; }
2675
2676 int64_t GetSynchronizedObjectiveValue() const override {
2677 return synchronized_sum_;
2678 }
2679 int64_t GetAcceptedObjectiveValue() const override { return delta_sum_; }
2680
2681 protected:
2682 const int primary_vars_size_;
2683 std::vector<int64_t> synchronized_costs_;
2684 std::vector<int64_t> delta_costs_;
2685 Filter filter_;
2686 int64_t synchronized_sum_;
2687 int64_t delta_sum_;
2688 bool incremental_;
2689
2690 private:
2691 void OnSynchronize(const Assignment*) override {
2692 synchronized_sum_ = 0;
2693 for (int i = 0; i < primary_vars_size_; ++i) {
2694 const int64_t cost = CostOfSynchronizedVariable(i);
2695 synchronized_costs_[i] = cost;
2696 delta_costs_[i] = cost;
2697 synchronized_sum_ = CapAdd(synchronized_sum_, cost);
2698 }
2699 delta_sum_ = synchronized_sum_;
2700 incremental_ = false;
2701 }
2702};
2703
2704template <typename Filter>
2705class BinaryObjectiveFilter : public SumObjectiveFilter<Filter> {
2706 public:
2707 BinaryObjectiveFilter(const std::vector<IntVar*>& vars,
2708 Solver::IndexEvaluator2 value_evaluator, Filter filter)
2709 : SumObjectiveFilter<Filter>(vars, std::move(filter)),
2710 value_evaluator_(std::move(value_evaluator)) {}
2711 ~BinaryObjectiveFilter() override {}
2712 int64_t CostOfSynchronizedVariable(int64_t index) override {
2713 return IntVarLocalSearchFilter::IsVarSynced(index)
2714 ? value_evaluator_(index, IntVarLocalSearchFilter::Value(index))
2715 : 0;
2716 }
2717 int64_t CostOfChanges(const Assignment* changes, bool incremental) override {
2718 int64_t total_cost = 0;
2719 const Assignment::IntContainer& container = changes->IntVarContainer();
2720 for (const IntVarElement& new_element : container.elements()) {
2721 IntVar* const var = new_element.Var();
2722 int64_t index = -1;
2723 if (this->FindIndex(var, &index)) {
2724 total_cost = CapSub(total_cost, this->delta_costs_[index]);
2725 int64_t new_cost = 0LL;
2726 if (new_element.Activated()) {
2727 new_cost = value_evaluator_(index, new_element.Value());
2728 } else if (var->Bound()) {
2729 new_cost = value_evaluator_(index, var->Min());
2730 }
2731 total_cost = CapAdd(total_cost, new_cost);
2732 if (incremental) {
2733 this->delta_costs_[index] = new_cost;
2734 }
2735 }
2736 }
2737 return total_cost;
2738 }
2739
2740 private:
2741 Solver::IndexEvaluator2 value_evaluator_;
2742};
2743
2744template <typename Filter>
2745class TernaryObjectiveFilter : public SumObjectiveFilter<Filter> {
2746 public:
2747 TernaryObjectiveFilter(const std::vector<IntVar*>& vars,
2748 const std::vector<IntVar*>& secondary_vars,
2749 Solver::IndexEvaluator3 value_evaluator, Filter filter)
2750 : SumObjectiveFilter<Filter>(vars, std::move(filter)),
2751 secondary_vars_offset_(vars.size()),
2752 secondary_values_(vars.size(), -1),
2753 value_evaluator_(std::move(value_evaluator)) {
2754 IntVarLocalSearchFilter::AddVars(secondary_vars);
2755 CHECK_GE(IntVarLocalSearchFilter::Size(), 0);
2756 }
2757 ~TernaryObjectiveFilter() override {}
2758 int64_t CostOfSynchronizedVariable(int64_t index) override {
2759 DCHECK_LT(index, secondary_vars_offset_);
2760 return IntVarLocalSearchFilter::IsVarSynced(index)
2761 ? value_evaluator_(index, IntVarLocalSearchFilter::Value(index),
2762 IntVarLocalSearchFilter::Value(
2763 index + secondary_vars_offset_))
2764 : 0;
2765 }
2766 int64_t CostOfChanges(const Assignment* changes, bool incremental) override {
2767 int64_t total_cost = 0;
2768 const Assignment::IntContainer& container = changes->IntVarContainer();
2769 for (const IntVarElement& new_element : container.elements()) {
2770 IntVar* const var = new_element.Var();
2771 int64_t index = -1;
2772 if (this->FindIndex(var, &index) && index < secondary_vars_offset_) {
2773 secondary_values_[index] = -1;
2774 }
2775 }
2776 // Primary variable indices range from 0 to secondary_vars_offset_ - 1,
2777 // matching secondary indices from secondary_vars_offset_ to
2778 // 2 * secondary_vars_offset_ - 1.
2779 const int max_secondary_index = 2 * secondary_vars_offset_;
2780 for (const IntVarElement& new_element : container.elements()) {
2781 IntVar* const var = new_element.Var();
2782 int64_t index = -1;
2783 if (new_element.Activated() && this->FindIndex(var, &index) &&
2784 index >= secondary_vars_offset_ &&
2785 // Only consider secondary_variables linked to primary ones.
2786 index < max_secondary_index) {
2787 secondary_values_[index - secondary_vars_offset_] = new_element.Value();
2788 }
2789 }
2790 for (const IntVarElement& new_element : container.elements()) {
2791 IntVar* const var = new_element.Var();
2792 int64_t index = -1;
2793 if (this->FindIndex(var, &index) && index < secondary_vars_offset_) {
2794 total_cost = CapSub(total_cost, this->delta_costs_[index]);
2795 int64_t new_cost = 0LL;
2796 if (new_element.Activated()) {
2797 new_cost = value_evaluator_(index, new_element.Value(),
2798 secondary_values_[index]);
2799 } else if (var->Bound() &&
2800 IntVarLocalSearchFilter::Var(index + secondary_vars_offset_)
2801 ->Bound()) {
2802 new_cost = value_evaluator_(
2803 index, var->Min(),
2804 IntVarLocalSearchFilter::Var(index + secondary_vars_offset_)
2805 ->Min());
2806 }
2807 total_cost = CapAdd(total_cost, new_cost);
2808 if (incremental) {
2809 this->delta_costs_[index] = new_cost;
2810 }
2811 }
2812 }
2813 return total_cost;
2814 }
2815
2816 private:
2817 int secondary_vars_offset_;
2818 std::vector<int64_t> secondary_values_;
2819 Solver::IndexEvaluator3 value_evaluator_;
2820};
2821} // namespace
2822
2824 const std::vector<IntVar*>& vars, Solver::IndexEvaluator2 values,
2825 Solver::LocalSearchFilterBound filter_enum) {
2826 switch (filter_enum) {
2827 case Solver::LE: {
2828 auto filter = [](int64_t value, int64_t, int64_t max_value) {
2829 return value <= max_value;
2830 };
2831 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
2832 vars, std::move(values), std::move(filter)));
2833 }
2834 case Solver::GE: {
2835 auto filter = [](int64_t value, int64_t min_value, int64_t) {
2836 return value >= min_value;
2837 };
2838 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
2839 vars, std::move(values), std::move(filter)));
2840 }
2841 case Solver::EQ: {
2842 auto filter = [](int64_t value, int64_t min_value, int64_t max_value) {
2843 return min_value <= value && value <= max_value;
2844 };
2845 return RevAlloc(new BinaryObjectiveFilter<decltype(filter)>(
2846 vars, std::move(values), std::move(filter)));
2847 }
2848 default: {
2849 LOG(ERROR) << "Unknown local search filter enum value";
2850 return nullptr;
2851 }
2852 }
2853}
2854
2856 const std::vector<IntVar*>& vars,
2857 const std::vector<IntVar*>& secondary_vars, Solver::IndexEvaluator3 values,
2858 Solver::LocalSearchFilterBound filter_enum) {
2859 switch (filter_enum) {
2860 case Solver::LE: {
2861 auto filter = [](int64_t value, int64_t, int64_t max_value) {
2862 return value <= max_value;
2863 };
2864 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
2865 vars, secondary_vars, std::move(values), std::move(filter)));
2866 }
2867 case Solver::GE: {
2868 auto filter = [](int64_t value, int64_t min_value, int64_t) {
2869 return value >= min_value;
2870 };
2871 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
2872 vars, secondary_vars, std::move(values), std::move(filter)));
2873 }
2874 case Solver::EQ: {
2875 auto filter = [](int64_t value, int64_t min_value, int64_t max_value) {
2876 return min_value <= value && value <= max_value;
2877 };
2878 return RevAlloc(new TernaryObjectiveFilter<decltype(filter)>(
2879 vars, secondary_vars, std::move(values), std::move(filter)));
2880 }
2881 default: {
2882 LOG(ERROR) << "Unknown local search filter enum value";
2883 return nullptr;
2884 }
2885 }
2886}
2887
2888void SubDagComputer::BuildGraph(int num_nodes) {
2889 DCHECK_GE(num_nodes, num_nodes_);
2890 DCHECK(!graph_was_built_);
2891 graph_was_built_ = true;
2892 std::sort(arcs_.begin(), arcs_.end());
2893 arcs_of_node_.clear();
2894 NodeId prev_tail(-1);
2895 for (int a = 0; a < arcs_.size(); ++a) {
2896 const NodeId tail = arcs_[a].tail;
2897 if (tail != prev_tail) {
2898 prev_tail = tail;
2899 arcs_of_node_.resize(tail.value() + 1, a);
2900 }
2901 }
2902 num_nodes_ = std::max(num_nodes_, num_nodes);
2903 arcs_of_node_.resize(num_nodes_ + 1, arcs_.size());
2904 DCHECK(!HasDirectedCycle()) << "Graph has a directed cycle";
2905}
2906
2907bool SubDagComputer::HasDirectedCycle() const {
2908 DCHECK(graph_was_built_);
2909 util_intops::StrongVector<NodeId, bool> node_is_open(num_nodes_, false);
2910 util_intops::StrongVector<NodeId, bool> node_was_visited(num_nodes_, false);
2911 // Depth first search event: a node and a boolean indicating whether
2912 // to open or to close it.
2913 struct DFSEvent {
2914 NodeId node;
2915 bool to_open;
2916 };
2917 std::vector<DFSEvent> event_stack;
2918
2919 for (NodeId node(0); node.value() < num_nodes_; ++node) {
2920 if (node_was_visited[node]) continue;
2921 event_stack.push_back({node, true});
2922 while (!event_stack.empty()) {
2923 const auto [node, to_open] = event_stack.back();
2924 event_stack.pop_back();
2925 if (node_was_visited[node]) continue;
2926 if (to_open) {
2927 if (node_is_open[node]) return true;
2928 node_is_open[node] = true;
2929 event_stack.push_back({node, false});
2930 for (int a = arcs_of_node_[node];
2931 a < arcs_of_node_[NodeId(node.value() + 1)]; ++a) {
2932 const NodeId head = arcs_[a].head;
2933 if (node_was_visited[head]) continue; // Optim, keeps stack smaller.
2934 event_stack.push_back({head, true});
2935 }
2936 } else {
2937 node_was_visited[node] = true;
2938 node_is_open[node] = false;
2939 }
2940 }
2941 }
2942 return false;
2943}
2944
2945const std::vector<SubDagComputer::ArcId>&
2947 DCHECK_LT(node.value(), num_nodes_);
2948 DCHECK(graph_was_built_);
2949 if (indegree_of_node_.size() < num_nodes_) {
2950 indegree_of_node_.resize(num_nodes_, 0);
2951 }
2952 // Compute indegrees of nodes of the sub-DAG reachable from node.
2953 nodes_to_visit_.clear();
2954 nodes_to_visit_.push_back(node);
2955 while (!nodes_to_visit_.empty()) {
2956 const NodeId node = nodes_to_visit_.back();
2957 nodes_to_visit_.pop_back();
2958 const NodeId next(node.value() + 1);
2959 for (int a = arcs_of_node_[node]; a < arcs_of_node_[next]; ++a) {
2960 const NodeId head = arcs_[a].head;
2961 if (indegree_of_node_[head] == 0) {
2962 nodes_to_visit_.push_back(head);
2963 }
2964 ++indegree_of_node_[head];
2965 }
2966 }
2967 // Generate arc ordering by iteratively removing zero-indegree nodes.
2968 sorted_arcs_.clear();
2969 nodes_to_visit_.push_back(node);
2970 while (!nodes_to_visit_.empty()) {
2971 const NodeId node = nodes_to_visit_.back();
2972 nodes_to_visit_.pop_back();
2973 const NodeId next(node.value() + 1);
2974 for (int a = arcs_of_node_[node]; a < arcs_of_node_[next]; ++a) {
2975 const NodeId head = arcs_[a].head;
2976 --indegree_of_node_[head];
2977 if (indegree_of_node_[head] == 0) {
2978 nodes_to_visit_.push_back(head);
2979 }
2980 sorted_arcs_.push_back(arcs_[a].arc_id);
2981 }
2982 }
2983 // Invariant: indegree_of_node_ must be all 0, or the graph has a cycle.
2984 DCHECK(absl::c_all_of(indegree_of_node_, [](int x) { return x == 0; }));
2985 return sorted_arcs_;
2986}
2987
2988using VariableDomainId = LocalSearchState::VariableDomainId;
2990 int64_t relaxed_max) {
2991 DCHECK(state_domains_are_all_nonempty_);
2992 DCHECK_LE(relaxed_min, relaxed_max);
2993 relaxed_domains_.push_back({relaxed_min, relaxed_max});
2994 current_domains_.push_back({relaxed_min, relaxed_max});
2995 domain_is_trailed_.push_back(false);
2996 return VariableDomainId(current_domains_.size() - 1);
2997}
2998
3000 VariableDomainId domain_id) {
3001 return {this, domain_id};
3002}
3003
3005 int64_t min, int64_t max) {
3006 const VariableDomainId domain_id = AddVariableDomain(min, max);
3007 return {this, domain_id};
3008}
3009
3013
3015 DCHECK(state_domains_are_all_nonempty_);
3016 if (!state_has_relaxed_domains_) {
3017 trailed_num_committed_empty_domains_ = num_committed_empty_domains_;
3018 }
3019 state_has_relaxed_domains_ = true;
3020 if (!domain_is_trailed_[domain_id]) {
3021 domain_is_trailed_[domain_id] = true;
3022 trailed_domains_.push_back({current_domains_[domain_id], domain_id});
3023 if (IntersectionIsEmpty(relaxed_domains_[domain_id],
3024 current_domains_[domain_id])) {
3025 DCHECK_GT(num_committed_empty_domains_, 0);
3026 --num_committed_empty_domains_;
3027 }
3028 current_domains_[domain_id] = relaxed_domains_[domain_id];
3029 return true;
3030 }
3031 return false;
3032}
3033
3035 DCHECK(state_domains_are_all_nonempty_);
3036 return current_domains_[domain_id].min;
3037}
3038
3040 DCHECK(state_domains_are_all_nonempty_);
3041 return current_domains_[domain_id].max;
3042}
3043
3045 int64_t min_value) {
3046 DCHECK(state_domains_are_all_nonempty_);
3047 DCHECK(domain_is_trailed_[domain_id]);
3048 VariableDomain& domain = current_domains_[domain_id];
3049 if (domain.max < min_value) {
3050 state_domains_are_all_nonempty_ = false;
3051 }
3052 domain.min = std::max(domain.min, min_value);
3053 return state_domains_are_all_nonempty_;
3054}
3055
3057 int64_t max_value) {
3058 DCHECK(state_domains_are_all_nonempty_);
3059 DCHECK(domain_is_trailed_[domain_id]);
3060 VariableDomain& domain = current_domains_[domain_id];
3061 if (domain.min > max_value) {
3062 state_domains_are_all_nonempty_ = false;
3063 }
3064 domain.max = std::min(domain.max, max_value);
3065 return state_domains_are_all_nonempty_;
3066}
3067
3069 int64_t min, int64_t max) {
3070 DCHECK(state_domains_are_all_nonempty_);
3071 DCHECK(!domain_is_trailed_[domain_id]);
3072 const bool domain_was_empty = IntersectionIsEmpty(
3073 relaxed_domains_[domain_id], current_domains_[domain_id]);
3074 relaxed_domains_[domain_id] = {min, max};
3075 const bool domain_is_empty =
3076 IntersectionIsEmpty({min, max}, current_domains_[domain_id]);
3077
3078 if (!domain_was_empty && domain_is_empty) {
3079 num_committed_empty_domains_++;
3080 } else if (domain_was_empty && !domain_is_empty) {
3081 num_committed_empty_domains_--;
3082 }
3083}
3084
3085// TODO(user): When the class has more users, find a threshold ratio of
3086// saved/total domains under which a sparse clear would be more efficient
3087// for both Commit() and Revert().
3089 DCHECK(StateIsFeasible());
3090 // Clear domains trail.
3091 state_has_relaxed_domains_ = false;
3092 trailed_domains_.clear();
3093 domain_is_trailed_.assign(domain_is_trailed_.size(), false);
3094 // Clear constraint trail.
3095 for (Constraint* constraint : trailed_constraints_) {
3096 constraint->Commit();
3097 }
3098 trailed_constraints_.clear();
3099}
3100
3102 // Revert trailed domains.
3103 for (const auto& [domain, id] : trailed_domains_) {
3104 DCHECK(domain_is_trailed_[id]);
3105 current_domains_[id] = domain;
3106 }
3107 trailed_domains_.clear();
3108 state_has_relaxed_domains_ = false;
3109 domain_is_trailed_.assign(domain_is_trailed_.size(), false);
3110 state_domains_are_all_nonempty_ = true;
3111 num_committed_empty_domains_ = trailed_num_committed_empty_domains_;
3112 // Revert trailed constraints.
3113 for (Constraint* constraint : trailed_constraints_) {
3114 constraint->Revert();
3115 }
3116 trailed_constraints_.clear();
3117}
3118
3119using NodeId = SubDagComputer::NodeId;
3120NodeId LocalSearchState::DependencyGraph::GetOrCreateNodeOfDomainId(
3121 VariableDomainId domain_id) {
3122 if (domain_id.value() >= dag_node_of_domain_.size()) {
3123 dag_node_of_domain_.resize(domain_id.value() + 1, NodeId(0));
3124 }
3125 if (dag_node_of_domain_[domain_id] == NodeId(0)) {
3126 dag_node_of_domain_[domain_id] = NodeId(num_dag_nodes_++);
3127 }
3128 return dag_node_of_domain_[domain_id];
3129}
3130
3131NodeId LocalSearchState::DependencyGraph::GetOrCreateNodeOfConstraintId(
3132 ConstraintId constraint_id) {
3133 if (constraint_id.value() >= dag_node_of_constraint_.size()) {
3134 dag_node_of_constraint_.resize(constraint_id.value() + 1, NodeId(0));
3135 }
3136 if (dag_node_of_constraint_[constraint_id] == NodeId(0)) {
3137 dag_node_of_constraint_[constraint_id] = NodeId(num_dag_nodes_++);
3138 }
3139 return dag_node_of_constraint_[constraint_id];
3140}
3141
3142void LocalSearchState::DependencyGraph::AddDomainsConstraintDependencies(
3143 const std::vector<VariableDomainId>& domain_ids,
3144 ConstraintId constraint_id) {
3145 const NodeId cnode = GetOrCreateNodeOfConstraintId(constraint_id);
3146 for (int i = 0; i < domain_ids.size(); ++i) {
3147 const NodeId dnode = GetOrCreateNodeOfDomainId(domain_ids[i]);
3148 dag_.AddArc(dnode, cnode);
3149 dependency_of_dag_arc_.push_back({.domain_id = domain_ids[i],
3150 .input_index = i,
3151 .constraint_id = constraint_id});
3152 }
3153}
3154
3155void LocalSearchState::DependencyGraph::AddConstraintDomainDependency(
3156 ConstraintId constraint_id, VariableDomainId domain_id) {
3157 const NodeId cnode = GetOrCreateNodeOfConstraintId(constraint_id);
3158 const NodeId dnode = GetOrCreateNodeOfDomainId(domain_id);
3159 dag_.AddArc(cnode, dnode);
3160 dependency_of_dag_arc_.push_back({.domain_id = domain_id,
3161 .input_index = -1,
3162 .constraint_id = constraint_id});
3163}
3164
3165using ArcId = SubDagComputer::ArcId;
3166const std::vector<LocalSearchState::DependencyGraph::Dependency>&
3167LocalSearchState::DependencyGraph::ComputeSortedDependencies(
3168 VariableDomainId domain_id) {
3169 sorted_dependencies_.clear();
3170 const NodeId node = dag_node_of_domain_[domain_id];
3171 for (const ArcId a : dag_.ComputeSortedSubDagArcs(node)) {
3172 if (dependency_of_dag_arc_[a].input_index == -1) continue;
3173 sorted_dependencies_.push_back(dependency_of_dag_arc_[a]);
3174 }
3175 return sorted_dependencies_;
3176}
3177
3179 const std::vector<VariableDomainId>& input_domain_ids,
3180 const std::vector<int64_t>& input_weights, int64_t input_offset,
3181 VariableDomainId output_domain_id) {
3182 DCHECK_EQ(input_domain_ids.size(), input_weights.size());
3183 // Store domain/constraint dependencies.
3184 const ConstraintId constraint_id(constraints_.size());
3185 dependency_graph_.AddDomainsConstraintDependencies(input_domain_ids,
3186 constraint_id);
3187 dependency_graph_.AddConstraintDomainDependency(constraint_id,
3188 output_domain_id);
3189 // Store constraint.
3190 auto constraint = std::make_unique<WeightedSum>(
3191 this, input_domain_ids, input_weights, input_offset, output_domain_id);
3192 constraints_.push_back(std::move(constraint));
3193}
3194
3195void LocalSearchState::DependencyGraph::BuildDependencyDAG(int num_domains) {
3196 dag_.BuildGraph(num_dag_nodes_);
3197 // Assign all unassigned nodes to dummy node 0.
3198 const int num_assigned_nodes = dag_node_of_domain_.size();
3199 DCHECK_GE(num_domains, num_assigned_nodes);
3200 num_domains = std::max(num_domains, num_assigned_nodes);
3201 dag_node_of_domain_.resize(num_domains, NodeId(0));
3202}
3203
3205 triggers_.clear();
3206 triggers_of_domain_.clear();
3207 const int num_domains = current_domains_.size();
3208 dependency_graph_.BuildDependencyDAG(num_domains);
3209 for (int vid = 0; vid < num_domains; ++vid) {
3210 triggers_of_domain_.push_back(triggers_.size());
3211 for (const auto& [domain_id, input_index, constraint_id] :
3212 dependency_graph_.ComputeSortedDependencies(VariableDomainId(vid))) {
3213 triggers_.push_back({.constraint = constraints_[constraint_id].get(),
3214 .input_index = input_index});
3215 }
3216 }
3217 triggers_of_domain_.push_back(triggers_.size());
3218}
3219
3220LocalSearchState::WeightedSum::WeightedSum(
3221 LocalSearchState* state,
3222 const std::vector<VariableDomainId>& input_domain_ids,
3223 const std::vector<int64_t>& input_weights, int64_t input_offset,
3224 VariableDomainId output)
3225 : output_(output), state_(state) {
3226 // Make trailable values that mirror last known value of inputs,
3227 // and others to represent internal counts and sums of inputs.
3228 invariants_.num_neg_inf = 0;
3229 invariants_.num_pos_inf = 0;
3230 invariants_.wsum_mins = input_offset;
3231 invariants_.wsum_maxs = input_offset;
3232 for (int i = 0; i < input_domain_ids.size(); ++i) {
3233 const VariableDomainId domain_id = input_domain_ids[i];
3234 const int64_t weight = input_weights[i];
3235 const int64_t min = state->VariableDomainMin(domain_id);
3236 const int64_t max = state->VariableDomainMax(domain_id);
3237 inputs_.push_back({.min = min,
3238 .max = max,
3239 .committed_min = min,
3240 .committed_max = max,
3241 .weight = weight,
3242 .domain = domain_id,
3243 .is_trailed = false});
3244
3245 if (weight > 0) {
3246 if (min == kint64min) {
3247 ++invariants_.num_neg_inf;
3248 } else {
3249 invariants_.wsum_mins =
3250 CapAdd(invariants_.wsum_mins, CapProd(weight, min));
3251 }
3252 if (max == kint64max) {
3253 ++invariants_.num_pos_inf;
3254 } else {
3255 invariants_.wsum_maxs =
3256 CapAdd(invariants_.wsum_maxs, CapProd(weight, max));
3257 }
3258 } else {
3259 if (min == kint64min) {
3260 ++invariants_.num_pos_inf;
3261 } else {
3262 invariants_.wsum_maxs =
3263 CapAdd(invariants_.wsum_maxs, CapProd(weight, min));
3264 }
3265 if (max == kint64max) {
3266 ++invariants_.num_neg_inf;
3267 } else {
3268 invariants_.wsum_mins =
3269 CapAdd(invariants_.wsum_mins, CapProd(weight, max));
3270 }
3271 }
3272 }
3273 committed_invariants_ = invariants_;
3274}
3275
3276LocalSearchState::VariableDomain LocalSearchState::WeightedSum::Propagate(
3277 int input_index) {
3278 if (!constraint_is_trailed_) {
3279 constraint_is_trailed_ = true;
3280 state_->TrailConstraint(this);
3281 }
3282 WeightedVariable& input = inputs_[input_index];
3283 if (!input.is_trailed) {
3284 input.is_trailed = true;
3285 trailed_inputs_.push_back(&input);
3286 }
3287 const int64_t new_min = state_->VariableDomainMin(input.domain);
3288 const int64_t new_max = state_->VariableDomainMax(input.domain);
3289 const int64_t weight = input.weight;
3290 if (weight > 0) {
3291 if (input.min != new_min) {
3292 // Remove contribution of last known min.
3293 if (input.min == kint64min) {
3294 --invariants_.num_neg_inf;
3295 } else {
3296 invariants_.wsum_mins =
3297 CapSub(invariants_.wsum_mins, CapProd(weight, input.min));
3298 }
3299 // Add contribution of new min.
3300 if (new_min == kint64min) {
3301 ++invariants_.num_neg_inf;
3302 } else {
3303 invariants_.wsum_mins =
3304 CapAdd(invariants_.wsum_mins, CapProd(weight, new_min));
3305 }
3306 input.min = new_min;
3307 }
3308 if (input.max != new_max) {
3309 // Remove contribution of last known max.
3310 if (input.max == kint64max) {
3311 --invariants_.num_pos_inf;
3312 } else {
3313 invariants_.wsum_maxs =
3314 CapSub(invariants_.wsum_maxs, CapProd(weight, input.max));
3315 }
3316 // Add contribution of new max.
3317 if (new_max == kint64max) {
3318 ++invariants_.num_pos_inf;
3319 } else {
3320 invariants_.wsum_maxs =
3321 CapAdd(invariants_.wsum_maxs, CapProd(weight, new_max));
3322 }
3323 input.max = new_max;
3324 }
3325 } else { // weight <= 0
3326 if (input.min != new_min) {
3327 // Remove contribution of last known min.
3328 if (input.min == kint64min) {
3329 --invariants_.num_pos_inf;
3330 } else {
3331 invariants_.wsum_maxs =
3332 CapSub(invariants_.wsum_maxs, CapProd(weight, input.min));
3333 }
3334 // Add contribution of new min.
3335 if (new_min == kint64min) {
3336 ++invariants_.num_pos_inf;
3337 } else {
3338 invariants_.wsum_maxs =
3339 CapAdd(invariants_.wsum_maxs, CapProd(weight, new_min));
3340 }
3341 input.min = new_min;
3342 }
3343 if (input.max != new_max) {
3344 // Remove contribution of last known max.
3345 if (input.max == kint64max) {
3346 --invariants_.num_neg_inf;
3347 } else {
3348 invariants_.wsum_mins =
3349 CapSub(invariants_.wsum_mins, CapProd(weight, input.max));
3350 }
3351 // Add contribution of new max.
3352 if (new_max == kint64max) {
3353 ++invariants_.num_neg_inf;
3354 } else {
3355 invariants_.wsum_mins =
3356 CapAdd(invariants_.wsum_mins, CapProd(weight, new_max));
3357 }
3358 input.max = new_max;
3359 }
3360 }
3361 return {invariants_.num_neg_inf == 0 ? invariants_.wsum_mins : kint64min,
3362 invariants_.num_pos_inf == 0 ? invariants_.wsum_maxs : kint64max};
3363}
3364
3365void LocalSearchState::WeightedSum::Commit() {
3366 committed_invariants_ = invariants_;
3367 constraint_is_trailed_ = false;
3368 for (WeightedVariable* wv : trailed_inputs_) wv->Commit();
3369 trailed_inputs_.clear();
3370}
3371
3372void LocalSearchState::WeightedSum::Revert() {
3373 invariants_ = committed_invariants_;
3374 constraint_is_trailed_ = false;
3375 for (WeightedVariable* wv : trailed_inputs_) wv->Revert();
3376 trailed_inputs_.clear();
3377}
3378
3380 const VariableDomainId next_id = VariableDomainId(domain_id.value() + 1);
3381 for (int t = triggers_of_domain_[domain_id]; t < triggers_of_domain_[next_id];
3382 ++t) {
3383 const auto& [constraint, input_index] = triggers_[t];
3384 constraint->Propagate(input_index);
3385 RelaxVariableDomain(constraint->Output());
3386 }
3387}
3388
3390 const VariableDomainId next_id = VariableDomainId(domain_id.value() + 1);
3391 for (int t = triggers_of_domain_[domain_id]; t < triggers_of_domain_[next_id];
3392 ++t) {
3393 const auto& [constraint, input_index] = triggers_[t];
3394 const auto [output_min, output_max] = constraint->Propagate(input_index);
3395 if (output_min != kint64min &&
3396 !TightenVariableDomainMin(constraint->Output(), output_min)) {
3397 return false;
3398 }
3399 if (output_max != kint64max &&
3400 !TightenVariableDomainMax(constraint->Output(), output_max)) {
3401 return false;
3402 }
3403 }
3404 return true;
3405}
3406
3407// ----- LocalSearchProfiler -----
3408
3410 public:
3412 std::string DebugString() const override { return "LocalSearchProfiler"; }
3413 void RestartSearch() override {
3414 operator_stats_.clear();
3415 filter_stats_per_context_.clear();
3416 last_operator_ = nullptr;
3417 }
3418 void ExitSearch() override {
3419 // Update times for current operator when the search ends.
3420 UpdateTime();
3421 last_operator_ = nullptr;
3422 }
3423 template <typename Callback>
3424 void ParseFirstSolutionStatistics(const Callback& callback) const {
3425 for (ProfiledDecisionBuilder* db : profiled_decision_builders_) {
3426 if (db->seconds() == 0) continue;
3427 callback(db->name(), db->seconds());
3428 }
3429 }
3430
3431 template <typename Callback>
3432 void ParseLocalSearchOperatorStatistics(const Callback& callback) const {
3433 std::vector<const LocalSearchOperator*> operators;
3434 for (const auto& stat : operator_stats_) {
3435 operators.push_back(stat.first);
3436 }
3437 std::sort(
3438 operators.begin(), operators.end(),
3439 [this](const LocalSearchOperator* op1, const LocalSearchOperator* op2) {
3440 return gtl::FindOrDie(operator_stats_, op1).neighbors >
3441 gtl::FindOrDie(operator_stats_, op2).neighbors;
3442 });
3443 for (const LocalSearchOperator* const op : operators) {
3444 const OperatorStats& stats = gtl::FindOrDie(operator_stats_, op);
3445 const std::string& label = op->DebugString();
3446 // Skip operators with no name: these come from empty compound operators.
3447 if (label.empty() &&
3448 dynamic_cast<const CompoundOperator*>(op) != nullptr) {
3449 continue;
3450 }
3451 callback(label, stats.neighbors, stats.filtered_neighbors,
3452 stats.accepted_neighbors, stats.seconds,
3453 stats.make_next_neighbor_seconds, stats.accept_neighbor_seconds);
3454 }
3455 }
3456
3457 template <typename Callback>
3458 void ParseLocalSearchFilterStatistics(const Callback& callback) const {
3459 for (const auto& [context, filter_stats] : filter_stats_per_context_) {
3460 std::vector<const LocalSearchFilter*> filters;
3461 for (const auto& [filter, stats] : filter_stats) {
3462 filters.push_back(filter);
3463 }
3464 std::sort(
3465 filters.begin(), filters.end(),
3466 [filter_stats_ptr = &filter_stats](const LocalSearchFilter* filter1,
3467 const LocalSearchFilter* filter2) {
3468 return gtl::FindOrDie(*filter_stats_ptr, filter1).calls >
3469 gtl::FindOrDie(*filter_stats_ptr, filter2).calls;
3470 });
3471 for (const LocalSearchFilter* const filter : filters) {
3472 const FilterStats& stats = gtl::FindOrDie(filter_stats, filter);
3473 callback(context, filter->DebugString(), stats.calls, stats.rejects,
3474 stats.seconds);
3475 }
3476 }
3477 }
3479 LocalSearchStatistics statistics_proto;
3481 [&statistics_proto](absl::string_view name, double duration_seconds) {
3483 first_solution_statistics =
3484 statistics_proto.add_first_solution_statistics();
3485 first_solution_statistics->set_strategy(name);
3486 first_solution_statistics->set_duration_seconds(duration_seconds);
3487 });
3489 [&statistics_proto](
3490 absl::string_view name, int64_t num_neighbors,
3491 int64_t num_filtered_neighbors, int64_t num_accepted_neighbors,
3492 double duration_seconds, double make_next_neighbor_duration_seconds,
3493 double accept_neighbor_duration_seconds) {
3495 local_search_operator_statistics =
3496 statistics_proto.add_local_search_operator_statistics();
3497 local_search_operator_statistics->set_local_search_operator(name);
3498 local_search_operator_statistics->set_num_neighbors(num_neighbors);
3499 local_search_operator_statistics->set_num_filtered_neighbors(
3500 num_filtered_neighbors);
3501 local_search_operator_statistics->set_num_accepted_neighbors(
3502 num_accepted_neighbors);
3503 local_search_operator_statistics->set_duration_seconds(
3504 duration_seconds);
3505 local_search_operator_statistics
3507 make_next_neighbor_duration_seconds);
3508 local_search_operator_statistics
3510 accept_neighbor_duration_seconds);
3511 });
3512 ParseLocalSearchFilterStatistics([&statistics_proto](
3513 absl::string_view context,
3514 absl::string_view name,
3515 int64_t num_calls, int64_t num_rejects,
3516 double duration_seconds) {
3518 local_search_filter_statistics =
3519 statistics_proto.add_local_search_filter_statistics();
3520 local_search_filter_statistics->set_local_search_filter(name);
3521 local_search_filter_statistics->set_num_calls(num_calls);
3522 local_search_filter_statistics->set_num_rejects(num_rejects);
3523 local_search_filter_statistics->set_duration_seconds(duration_seconds);
3524 local_search_filter_statistics->set_num_rejects_per_second(
3525 num_rejects / duration_seconds);
3526 local_search_filter_statistics->set_context(context);
3527 });
3528 statistics_proto.set_total_num_neighbors(solver()->neighbors());
3529 statistics_proto.set_total_num_filtered_neighbors(
3530 solver()->filtered_neighbors());
3531 statistics_proto.set_total_num_accepted_neighbors(
3532 solver()->accepted_neighbors());
3533 return statistics_proto;
3534 }
3535 std::string PrintOverview() const {
3536 std::string overview;
3537 size_t max_name_size = 0;
3539 [&max_name_size](absl::string_view name, double) {
3540 max_name_size = std::max(max_name_size, name.length());
3541 });
3542 if (max_name_size > 0) {
3543 absl::StrAppendFormat(&overview,
3544 "First solution statistics:\n%*s | Time (s)\n",
3545 max_name_size, "");
3547 [&overview, max_name_size](absl::string_view name,
3548 double duration_seconds) {
3549 absl::StrAppendFormat(&overview, "%*s | %7.2g\n", max_name_size,
3550 name, duration_seconds);
3551 });
3552 }
3553 max_name_size = 0;
3555 [&max_name_size](absl::string_view name, int64_t, int64_t, int64_t,
3556 double, double, double) {
3557 max_name_size = std::max(max_name_size, name.length());
3558 });
3559 if (max_name_size > 0) {
3560 absl::StrAppendFormat(
3561 &overview,
3562 "Local search operator statistics:\n%*s | Neighbors | Filtered "
3563 "| Accepted | Time (s)\n",
3564 max_name_size, "");
3565 OperatorStats total_stats;
3567 [&overview, &total_stats, max_name_size](
3568 absl::string_view name, int64_t num_neighbors,
3569 int64_t num_filtered_neighbors, int64_t num_accepted_neighbors,
3570 double duration_seconds,
3571 double /*make_next_neighbor_duration_seconds*/,
3572 double /*accept_neighbor_duration_seconds*/) {
3573 // TODO(user): Add make_next_neighbor_duration_seconds and
3574 // accept_neighbor_duration_seconds to stats.
3575 absl::StrAppendFormat(
3576 &overview, "%*s | %9ld | %8ld | %8ld | %7.2g\n", max_name_size,
3577 name, num_neighbors, num_filtered_neighbors,
3578 num_accepted_neighbors, duration_seconds);
3579 total_stats.neighbors += num_neighbors;
3580 total_stats.filtered_neighbors += num_filtered_neighbors;
3581 total_stats.accepted_neighbors += num_accepted_neighbors;
3582 total_stats.seconds += duration_seconds;
3583 });
3584 absl::StrAppendFormat(
3585 &overview, "%*s | %9ld | %8ld | %8ld | %7.2g\n", max_name_size,
3586 "Total", total_stats.neighbors, total_stats.filtered_neighbors,
3587 total_stats.accepted_neighbors, total_stats.seconds);
3588 }
3589 max_name_size = 0;
3591 [&max_name_size](absl::string_view, absl::string_view name, int64_t,
3592 int64_t, double) {
3593 max_name_size = std::max(max_name_size, name.length());
3594 });
3595 if (max_name_size > 0) {
3596 std::optional<std::string> filter_context;
3597 FilterStats total_filter_stats;
3599 [&overview, &filter_context, &total_filter_stats, max_name_size](
3600 const std::string& context, const std::string& name,
3601 int64_t num_calls, int64_t num_rejects, double duration_seconds) {
3602 if (!filter_context.has_value() ||
3603 filter_context.value() != context) {
3604 if (filter_context.has_value()) {
3605 absl::StrAppendFormat(
3606 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n",
3607 max_name_size, "Total", total_filter_stats.calls,
3608 total_filter_stats.rejects, total_filter_stats.seconds,
3609 total_filter_stats.rejects / total_filter_stats.seconds);
3610 total_filter_stats = {};
3611 }
3612 filter_context = context;
3613 absl::StrAppendFormat(
3614 &overview,
3615 "Local search filter statistics%s:\n%*s | Calls | "
3616 " Rejects | Time (s) | Rejects/s\n",
3617 context.empty() ? "" : " (" + context + ")", max_name_size,
3618 "");
3619 }
3620 absl::StrAppendFormat(
3621 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n",
3622 max_name_size, name, num_calls, num_rejects, duration_seconds,
3623 num_rejects / duration_seconds);
3624 total_filter_stats.calls += num_calls;
3625 total_filter_stats.rejects += num_rejects;
3626 total_filter_stats.seconds += duration_seconds;
3627 });
3628 absl::StrAppendFormat(
3629 &overview, "%*s | %9ld | %9ld | %7.2g | %7.2g\n", max_name_size,
3630 "Total", total_filter_stats.calls, total_filter_stats.rejects,
3631 total_filter_stats.seconds,
3632 total_filter_stats.rejects / total_filter_stats.seconds);
3633 }
3634 return overview;
3635 }
3636 void BeginOperatorStart() override {}
3637 void EndOperatorStart() override {}
3639 if (last_operator_ != op->Self()) {
3640 UpdateTime();
3641 last_operator_ = op->Self();
3642 }
3643 make_next_neighbor_timer_.Start();
3644 }
3645 void EndMakeNextNeighbor(const LocalSearchOperator* op, bool neighbor_found,
3646 const Assignment*, const Assignment*) override {
3647 // To be robust to multiple calls to EndMakeNextNeighbor, we only collect
3648 // data if the timer was not stopped.
3649 if (!make_next_neighbor_timer_.IsRunning()) return;
3650 make_next_neighbor_timer_.Stop();
3651 operator_stats_[op->Self()].make_next_neighbor_seconds +=
3652 make_next_neighbor_timer_.Get();
3653 if (neighbor_found) {
3654 operator_stats_[op->Self()].neighbors++;
3655 }
3656 }
3659 bool neighbor_found) override {
3660 if (neighbor_found) {
3661 operator_stats_[op->Self()].filtered_neighbors++;
3662 }
3663 }
3665 accept_neighbor_timer_.Start();
3666 }
3668 bool neighbor_found) override {
3669 accept_neighbor_timer_.Stop();
3670 operator_stats_[op->Self()].accept_neighbor_seconds +=
3671 accept_neighbor_timer_.Get();
3672 if (neighbor_found) {
3673 operator_stats_[op->Self()].accepted_neighbors++;
3674 }
3675 }
3676 void BeginFiltering(const LocalSearchFilter* filter) override {
3677 FilterStats& filter_stats =
3678 filter_stats_per_context_[solver()->context()][filter];
3679 filter_stats.calls++;
3680 filter_timer_.Start();
3681 }
3682 void EndFiltering(const LocalSearchFilter* filter, bool reject) override {
3683 filter_timer_.Stop();
3684 auto& stats = filter_stats_per_context_[solver()->context()][filter];
3685 stats.seconds += filter_timer_.Get();
3686 if (reject) {
3687 stats.rejects++;
3688 }
3689 }
3691 ProfiledDecisionBuilder* profiled_db) {
3692 profiled_decision_builders_.push_back(profiled_db);
3693 }
3694 bool IsActive() const override { return true; }
3695 void Install() override { SearchMonitor::Install(); }
3696
3697 private:
3698 void UpdateTime() {
3699 if (last_operator_ != nullptr) {
3700 timer_.Stop();
3701 operator_stats_[last_operator_].seconds += timer_.Get();
3702 }
3703 timer_.Start();
3704 }
3705
3706 struct OperatorStats {
3707 int64_t neighbors = 0;
3708 int64_t filtered_neighbors = 0;
3709 int64_t accepted_neighbors = 0;
3710 double seconds = 0;
3711 double make_next_neighbor_seconds = 0;
3712 double accept_neighbor_seconds = 0;
3713 };
3714
3715 struct FilterStats {
3716 int64_t calls = 0;
3717 int64_t rejects = 0;
3718 double seconds = 0;
3719 };
3720 WallTimer timer_;
3721 WallTimer make_next_neighbor_timer_;
3722 WallTimer accept_neighbor_timer_;
3723 WallTimer filter_timer_;
3724 const LocalSearchOperator* last_operator_ = nullptr;
3725 absl::flat_hash_map<const LocalSearchOperator*, OperatorStats>
3726 operator_stats_;
3727 absl::flat_hash_map<
3728 std::string, absl::flat_hash_map<const LocalSearchFilter*, FilterStats>>
3729 filter_stats_per_context_;
3730 // Profiled decision builders.
3731 std::vector<ProfiledDecisionBuilder*> profiled_decision_builders_;
3732};
3733
3735 DecisionBuilder* db) {
3737 ProfiledDecisionBuilder* profiled_db =
3739 local_search_profiler_->AddFirstSolutionProfiledDecisionBuilder(
3740 profiled_db);
3741 return profiled_db;
3742 }
3743 return db;
3744}
3745
3747 monitor->Install();
3748}
3749
3751 if (solver->IsLocalSearchProfilingEnabled()) {
3752 return new LocalSearchProfiler(solver);
3753 }
3754 return nullptr;
3755}
3756
3757void DeleteLocalSearchProfiler(LocalSearchProfiler* monitor) { delete monitor; }
3758
3759std::string Solver::LocalSearchProfile() const {
3760 if (local_search_profiler_ != nullptr) {
3761 return local_search_profiler_->PrintOverview();
3762 }
3763 return "";
3764}
3765
3767 if (local_search_profiler_ != nullptr) {
3768 return local_search_profiler_->ExportToLocalSearchStatistics();
3769 }
3770 return LocalSearchStatistics();
3771}
3772
3773void LocalSearchFilterManager::FindIncrementalEventEnd() {
3774 const int num_events = events_.size();
3775 incremental_events_end_ = num_events;
3776 int last_priority = -1;
3777 for (int e = num_events - 1; e >= 0; --e) {
3778 const auto& [filter, event_type, priority] = events_[e];
3779 if (priority != last_priority) {
3780 incremental_events_end_ = e + 1;
3781 last_priority = priority;
3782 }
3783 if (filter->IsIncremental()) break;
3784 }
3785}
3786
3788 std::vector<LocalSearchFilter*> filters)
3789 : synchronized_value_(std::numeric_limits<int64_t>::min()),
3790 accepted_value_(std::numeric_limits<int64_t>::min()) {
3791 events_.reserve(2 * filters.size());
3792 int priority = 0;
3793 for (LocalSearchFilter* filter : filters) {
3794 events_.push_back({filter, FilterEventType::kRelax, priority++});
3795 }
3796 for (LocalSearchFilter* filter : filters) {
3797 events_.push_back({filter, FilterEventType::kAccept, priority++});
3798 }
3799 FindIncrementalEventEnd();
3800}
3801
3803 std::vector<FilterEvent> filter_events)
3804 : events_(std::move(filter_events)),
3805 synchronized_value_(std::numeric_limits<int64_t>::min()),
3806 accepted_value_(std::numeric_limits<int64_t>::min()) {
3807 std::sort(events_.begin(), events_.end(),
3808 [](const FilterEvent& e1, const FilterEvent& e2) {
3809 return e1.priority < e2.priority;
3810 });
3811 FindIncrementalEventEnd();
3812}
3813
3814// Filters' Revert() must be called in the reverse order in which their
3815// Relax() was called.
3817 for (int e = last_event_called_; e >= 0; --e) {
3818 const auto [filter, event_type, _priority] = events_[e];
3819 if (event_type == FilterEventType::kRelax) filter->Revert();
3820 }
3821 last_event_called_ = -1;
3822}
3823
3824// TODO(user): the behaviour of Accept relies on the initial order of
3825// filters having at most one filter with negative objective values,
3826// this could be fixed by having filters return their general bounds.
3828 const Assignment* delta,
3829 const Assignment* deltadelta,
3830 int64_t objective_min,
3831 int64_t objective_max) {
3832 Revert();
3833 accepted_value_ = 0;
3834 bool feasible = true;
3835 bool reordered = false;
3836 int events_end = events_.size();
3837 for (int e = 0; e < events_end; ++e) {
3838 last_event_called_ = e;
3839 const auto [filter, event_type, priority] = events_[e];
3840 switch (event_type) {
3842 if (!feasible && !filter->IsIncremental()) continue;
3843 if (monitor != nullptr) monitor->BeginFiltering(filter);
3844 const bool accept = filter->Accept(
3845 delta, deltadelta, CapSub(objective_min, accepted_value_),
3846 CapSub(objective_max, accepted_value_));
3847 feasible &= accept;
3848 if (monitor != nullptr) monitor->EndFiltering(filter, !accept);
3849 if (feasible) {
3850 accepted_value_ =
3851 CapAdd(accepted_value_, filter->GetAcceptedObjectiveValue());
3852 // TODO(user): handle objective min.
3853 feasible = accepted_value_ <= objective_max;
3854 }
3855 if (!feasible) {
3856 events_end = incremental_events_end_;
3857 if (!reordered) {
3858 // Bump up rejected event, together with its kRelax event,
3859 // unless it is already first in its priority layer.
3860 reordered = true;
3861 int to_move = e - 1;
3862 if (to_move >= 0 && events_[to_move].filter == filter) --to_move;
3863 if (to_move >= 0 && events_[to_move].priority == priority) {
3864 std::rotate(events_.begin() + to_move,
3865 events_.begin() + to_move + 1,
3866 events_.begin() + e + 1);
3867 }
3868 }
3869 }
3870 break;
3871 }
3873 filter->Relax(delta, deltadelta);
3874 break;
3875 }
3876 default:
3877 LOG(FATAL) << "Unknown filter event type.";
3878 }
3879 }
3880 return feasible;
3881}
3882
3884 const Assignment* delta) {
3885 // If delta is nullptr or empty, then assignment may be a partial solution.
3886 // Send a signal to Relaxing filters to inform them,
3887 // so they can show the partial solution as a change from the empty solution.
3888 const bool reset_to_assignment = delta == nullptr || delta->Empty();
3889 // Relax in the forward direction.
3890 for (auto [filter, event_type, unused_priority] : events_) {
3891 switch (event_type) {
3893 break;
3894 }
3896 if (reset_to_assignment) {
3897 filter->Reset();
3898 filter->Relax(assignment, nullptr);
3899 } else {
3900 filter->Relax(delta, nullptr);
3901 }
3902 break;
3903 }
3904 default:
3905 LOG(FATAL) << "Unknown filter event type.";
3906 }
3907 }
3908 // Synchronize/Commit backwards, so filters can read changes from their
3909 // dependencies before those are synchronized/committed.
3910 synchronized_value_ = 0;
3911 for (auto [filter, event_type, _priority] : ::gtl::reversed_view(events_)) {
3912 switch (event_type) {
3914 filter->Synchronize(assignment, delta);
3915 synchronized_value_ = CapAdd(synchronized_value_,
3916 filter->GetSynchronizedObjectiveValue());
3917 break;
3918 }
3920 filter->Commit(assignment, delta);
3921 break;
3922 }
3923 default:
3924 LOG(FATAL) << "Unknown filter event type.";
3925 }
3926 }
3927}
3928
3929// ----- Finds a neighbor of the assignment passed -----
3930
3932 public:
3933 FindOneNeighbor(Assignment* assignment, IntVar* objective, SolutionPool* pool,
3934 LocalSearchOperator* ls_operator,
3935 DecisionBuilder* sub_decision_builder,
3936 const RegularLimit* limit,
3937 LocalSearchFilterManager* filter_manager);
3938 ~FindOneNeighbor() override {}
3939 void EnterSearch();
3940 Decision* Next(Solver* solver) override;
3941 std::string DebugString() const override { return "FindOneNeighbor"; }
3942
3943 private:
3944 bool FilterAccept(Solver* solver, Assignment* delta, Assignment* deltadelta,
3945 int64_t objective_min, int64_t objective_max);
3946 void SynchronizeAll(Solver* solver);
3947
3948 Assignment* const assignment_;
3949 IntVar* const objective_;
3950 std::unique_ptr<Assignment> reference_assignment_;
3951 std::unique_ptr<Assignment> last_synchronized_assignment_;
3952 Assignment* const filter_assignment_delta_;
3953 SolutionPool* const pool_;
3954 LocalSearchOperator* const ls_operator_;
3955 DecisionBuilder* const sub_decision_builder_;
3956 RegularLimit* limit_;
3957 const RegularLimit* const original_limit_;
3958 bool neighbor_found_;
3959 LocalSearchFilterManager* const filter_manager_;
3960 int64_t solutions_since_last_check_;
3961 int64_t check_period_;
3962 Assignment last_checked_assignment_;
3963 bool has_checked_assignment_ = false;
3964};
3965
3966// reference_assignment_ is used to keep track of the last assignment on which
3967// operators were started, assignment_ corresponding to the last successful
3968// neighbor.
3969// last_synchronized_assignment_ keeps track of the last assignment on which
3970// filters were synchronized and is used to compute the filter_assignment_delta_
3971// when synchronizing again.
3973 IntVar* objective, SolutionPool* const pool,
3974 LocalSearchOperator* const ls_operator,
3975 DecisionBuilder* const sub_decision_builder,
3976 const RegularLimit* const limit,
3977 LocalSearchFilterManager* filter_manager)
3978 : assignment_(assignment),
3979 objective_(objective),
3980 reference_assignment_(new Assignment(assignment_)),
3981 filter_assignment_delta_(assignment->solver()->MakeAssignment()),
3982 pool_(pool),
3983 ls_operator_(ls_operator),
3984 sub_decision_builder_(sub_decision_builder),
3985 limit_(nullptr),
3986 original_limit_(limit),
3987 neighbor_found_(false),
3988 filter_manager_(filter_manager),
3989 solutions_since_last_check_(0),
3990 check_period_(
3991 assignment_->solver()->parameters().check_solution_period()),
3992 last_checked_assignment_(assignment) {
3993 CHECK(nullptr != assignment);
3994 CHECK(nullptr != ls_operator);
3995
3996 Solver* const solver = assignment_->solver();
3997 // If limit is nullptr, default limit is 1 solution
3998 if (nullptr == limit) {
3999 limit_ = solver->MakeSolutionsLimit(1);
4000 } else {
4001 limit_ = limit->MakeIdenticalClone();
4002 // TODO(user): Support skipping neighborhood checks for limits accepting
4003 // more than one solution (e.g. best accept). For now re-enabling systematic
4004 // checks.
4005 if (limit_->solutions() != 1) {
4006 VLOG(1) << "Disabling neighbor-check skipping outside of first accept.";
4007 check_period_ = 1;
4008 }
4009 }
4010 // TODO(user): Support skipping neighborhood checks with LNS (at least on
4011 // the non-LNS operators).
4012 if (ls_operator->HasFragments()) {
4013 VLOG(1) << "Disabling neighbor-check skipping for LNS.";
4014 check_period_ = 1;
4015 }
4016
4017 if (!reference_assignment_->HasObjective()) {
4018 reference_assignment_->AddObjective(objective_);
4019 }
4020}
4021
4023 // Reset neighbor_found_ to false to ensure everything is properly
4024 // synchronized at the beginning of the search.
4025 neighbor_found_ = false;
4026 last_synchronized_assignment_.reset();
4027}
4028
4030 CHECK(nullptr != solver);
4031
4032 if (original_limit_ != nullptr) {
4033 limit_->Copy(original_limit_);
4034 }
4035
4036 if (!last_checked_assignment_.HasObjective()) {
4037 last_checked_assignment_.AddObjective(assignment_->Objective());
4038 }
4039
4040 if (!neighbor_found_) {
4041 // Only called on the first call to Next(), reference_assignment_ has not
4042 // been synced with assignment_ yet
4043
4044 // Keeping the code in case a performance problem forces us to
4045 // use the old code with a zero test on pool_.
4046 // reference_assignment_->CopyIntersection(assignment_);
4047 pool_->Initialize(assignment_);
4048 SynchronizeAll(solver);
4049 }
4050
4051 {
4052 // Another assignment is needed to apply the delta
4053 Assignment* assignment_copy =
4054 solver->MakeAssignment(reference_assignment_.get());
4055 int counter = 0;
4056
4057 DecisionBuilder* restore = solver->MakeRestoreAssignment(assignment_copy);
4058 if (sub_decision_builder_) {
4059 restore = solver->Compose(restore, sub_decision_builder_);
4060 }
4061 Assignment* delta = solver->MakeAssignment();
4062 Assignment* deltadelta = solver->MakeAssignment();
4063 while (true) {
4064 if (!ls_operator_->HoldsDelta()) {
4065 delta->Clear();
4066 }
4067 delta->ClearObjective();
4068 deltadelta->Clear();
4069 solver->TopPeriodicCheck();
4070 if (++counter >= absl::GetFlag(FLAGS_cp_local_search_sync_frequency) &&
4071 pool_->SyncNeeded(reference_assignment_.get())) {
4072 // TODO(user) : SyncNeed(assignment_) ?
4073 counter = 0;
4074 SynchronizeAll(solver);
4075 }
4076
4077 bool has_neighbor = false;
4078 if (!limit_->Check()) {
4079 solver->GetLocalSearchMonitor()->BeginMakeNextNeighbor(ls_operator_);
4080 has_neighbor = ls_operator_->MakeNextNeighbor(delta, deltadelta);
4082 ls_operator_, has_neighbor, delta, deltadelta);
4083 }
4084
4085 if (has_neighbor && !solver->IsUncheckedSolutionLimitReached()) {
4086 solver->neighbors_ += 1;
4087 // All filters must be called for incrementality reasons.
4088 // Empty deltas must also be sent to incremental filters; can be needed
4089 // to resync filters on non-incremental (empty) moves.
4090 // TODO(user): Don't call both if no filter is incremental and one
4091 // of them returned false.
4092 solver->GetLocalSearchMonitor()->BeginFilterNeighbor(ls_operator_);
4093 const bool mh_filter =
4094 AcceptDelta(solver->ParentSearch(), delta, deltadelta);
4095 int64_t objective_min = std::numeric_limits<int64_t>::min();
4096 int64_t objective_max = std::numeric_limits<int64_t>::max();
4097 if (objective_) {
4098 objective_min = objective_->Min();
4099 objective_max = objective_->Max();
4100 }
4101 if (delta->HasObjective() && delta->Objective() == objective_) {
4102 objective_min = std::max(objective_min, delta->ObjectiveMin());
4103 objective_max = std::min(objective_max, delta->ObjectiveMax());
4104 }
4105 const bool move_filter = FilterAccept(solver, delta, deltadelta,
4106 objective_min, objective_max);
4108 ls_operator_, mh_filter && move_filter);
4109 if (!mh_filter || !move_filter) {
4110 if (filter_manager_ != nullptr) filter_manager_->Revert();
4111 continue;
4112 }
4113 solver->filtered_neighbors_ += 1;
4114 if (delta->HasObjective()) {
4115 if (!assignment_copy->HasObjective()) {
4116 assignment_copy->AddObjective(delta->Objective());
4117 }
4118 if (!assignment_->HasObjective()) {
4119 assignment_->AddObjective(delta->Objective());
4120 last_checked_assignment_.AddObjective(delta->Objective());
4121 }
4122 }
4123 assignment_copy->CopyIntersection(reference_assignment_.get());
4124 assignment_copy->CopyIntersection(delta);
4125 solver->GetLocalSearchMonitor()->BeginAcceptNeighbor(ls_operator_);
4126 const bool check_solution = (solutions_since_last_check_ == 0) ||
4127 !solver->UseFastLocalSearch() ||
4128 // LNS deltas need to be restored
4129 !delta->AreAllElementsBound();
4130 if (has_checked_assignment_) solutions_since_last_check_++;
4131 if (solutions_since_last_check_ >= check_period_) {
4132 solutions_since_last_check_ = 0;
4133 }
4134 const bool accept = !check_solution ||
4135 (solver->SolveAndCommit(restore) &&
4136 solver->AcceptSolution(solver->TopLevelSearch()));
4137 solver->GetLocalSearchMonitor()->EndAcceptNeighbor(ls_operator_,
4138 accept);
4139 if (accept) {
4140 solver->accepted_neighbors_ += 1;
4141 if (check_solution) {
4142 solver->SetSearchContext(solver->ParentSearch(),
4143 ls_operator_->DebugString());
4144 assignment_->Store();
4145 last_checked_assignment_.CopyIntersection(assignment_);
4146 neighbor_found_ = true;
4147 has_checked_assignment_ = true;
4148 return nullptr;
4149 }
4150 solver->SetSearchContext(solver->ActiveSearch(),
4151 ls_operator_->DebugString());
4152 assignment_->CopyIntersection(assignment_copy);
4153 assignment_->SetObjectiveValue(
4154 filter_manager_ ? filter_manager_->GetAcceptedObjectiveValue()
4155 : 0);
4156 // Advancing local search to the current solution without
4157 // checking.
4158 // TODO(user): support the case were limit_ accepts more than
4159 // one solution (e.g. best accept).
4160 AcceptUncheckedNeighbor(solver->ParentSearch());
4161 solver->IncrementUncheckedSolutionCounter();
4162 pool_->RegisterNewSolution(assignment_);
4163 SynchronizeAll(solver);
4164 // NOTE: SynchronizeAll() sets neighbor_found_ to false, force it
4165 // back to true when skipping checks.
4166 neighbor_found_ = true;
4167 } else {
4168 if (filter_manager_ != nullptr) filter_manager_->Revert();
4169 if (check_period_ > 1 && has_checked_assignment_) {
4170 // Filtering is not perfect, disabling fast local search and
4171 // resynchronizing with the last checked solution.
4172 // TODO(user): Restore state of local search operators to
4173 // make sure we are exploring neighbors in the same order. This can
4174 // affect the local optimum found.
4175 VLOG(1) << "Imperfect filtering detected, backtracking to last "
4176 "checked solution and checking all solutions.";
4177 check_period_ = 1;
4178 solutions_since_last_check_ = 0;
4179 pool_->RegisterNewSolution(&last_checked_assignment_);
4180 SynchronizeAll(solver);
4181 assignment_->CopyIntersection(&last_checked_assignment_);
4182 }
4183 }
4184 } else {
4185 // Reset the last synchronized assignment in case it's no longer up to
4186 // date or we fail below.
4187 last_synchronized_assignment_.reset();
4188 if (neighbor_found_) {
4189 // In case the last checked assignment isn't the current one, restore
4190 // it to make sure the solver knows about it, especially if this is
4191 // the end of the search.
4192 // TODO(user): Compare assignments in addition to their cost.
4193 if (last_checked_assignment_.ObjectiveValue() !=
4194 assignment_->ObjectiveValue()) {
4195 // If restoring fails this means filtering is not perfect and the
4196 // solver will consider the last checked assignment.
4197 assignment_copy->CopyIntersection(assignment_);
4198 if (!solver->SolveAndCommit(restore)) solver->Fail();
4199 last_checked_assignment_.CopyIntersection(assignment_);
4200 has_checked_assignment_ = true;
4201 return nullptr;
4202 }
4203 AcceptNeighbor(solver->ParentSearch());
4204 // Keeping the code in case a performance problem forces us to
4205 // use the old code with a zero test on pool_.
4206 // reference_assignment_->CopyIntersection(assignment_);
4207 pool_->RegisterNewSolution(assignment_);
4208 SynchronizeAll(solver);
4209 } else {
4210 break;
4211 }
4212 }
4213 }
4214 }
4215 // NOTE(user): The last synchronized assignment must be reset here to
4216 // guarantee filters will be properly synched in case we re-solve using an
4217 // assignment that wasn't the last accepted and synchronized assignment.
4218 last_synchronized_assignment_.reset();
4219 solver->Fail();
4220 return nullptr;
4221}
4222
4223bool FindOneNeighbor::FilterAccept(Solver* solver, Assignment* delta,
4224 Assignment* deltadelta,
4225 int64_t objective_min,
4226 int64_t objective_max) {
4227 if (filter_manager_ == nullptr) return true;
4228 LocalSearchMonitor* const monitor = solver->GetLocalSearchMonitor();
4229 return filter_manager_->Accept(monitor, delta, deltadelta, objective_min,
4230 objective_max);
4231}
4232
4233namespace {
4234
4235template <typename Container>
4236void AddDeltaElements(const Container& old_container,
4237 const Container& new_container, Assignment* delta) {
4238 for (const auto& new_element : new_container.elements()) {
4239 const auto var = new_element.Var();
4240 const auto old_element_ptr = old_container.ElementPtrOrNull(var);
4241 if (old_element_ptr == nullptr || *old_element_ptr != new_element) {
4242 delta->FastAdd(var)->Copy(new_element);
4243 }
4244 }
4245}
4246
4247void MakeDelta(const Assignment* old_assignment,
4248 const Assignment* new_assignment, Assignment* delta) {
4249 DCHECK_NE(delta, nullptr);
4250 delta->Clear();
4251 AddDeltaElements(old_assignment->IntVarContainer(),
4252 new_assignment->IntVarContainer(), delta);
4253 AddDeltaElements(old_assignment->IntervalVarContainer(),
4254 new_assignment->IntervalVarContainer(), delta);
4255 AddDeltaElements(old_assignment->SequenceVarContainer(),
4256 new_assignment->SequenceVarContainer(), delta);
4257}
4258} // namespace
4259
4260void FindOneNeighbor::SynchronizeAll(Solver* solver) {
4261 Assignment* const reference_assignment = reference_assignment_.get();
4262 pool_->GetNextSolution(reference_assignment);
4263 neighbor_found_ = false;
4264 limit_->Init();
4265 solver->GetLocalSearchMonitor()->BeginOperatorStart();
4266 ls_operator_->Start(reference_assignment);
4267 if (filter_manager_ != nullptr) {
4268 Assignment* delta = nullptr;
4269 if (last_synchronized_assignment_ == nullptr) {
4270 last_synchronized_assignment_ =
4271 std::make_unique<Assignment>(reference_assignment);
4272 } else {
4273 MakeDelta(last_synchronized_assignment_.get(), reference_assignment,
4274 filter_assignment_delta_);
4275 delta = filter_assignment_delta_;
4276 last_synchronized_assignment_->Copy(reference_assignment);
4277 }
4278 filter_manager_->Synchronize(reference_assignment_.get(), delta);
4279 }
4280 solver->GetLocalSearchMonitor()->EndOperatorStart();
4281}
4282
4283// ---------- Local Search Phase Parameters ----------
4284
4286 public:
4290 RegularLimit* const limit,
4292 : objective_(objective),
4293 solution_pool_(pool),
4294 ls_operator_(ls_operator),
4295 sub_decision_builder_(sub_decision_builder),
4296 limit_(limit),
4297 filter_manager_(filter_manager) {}
4299 std::string DebugString() const override {
4300 return "LocalSearchPhaseParameters";
4301 }
4302
4303 IntVar* objective() const { return objective_; }
4304 SolutionPool* solution_pool() const { return solution_pool_; }
4305 LocalSearchOperator* ls_operator() const { return ls_operator_; }
4307 return sub_decision_builder_;
4308 }
4309 RegularLimit* limit() const { return limit_; }
4310 LocalSearchFilterManager* filter_manager() const { return filter_manager_; }
4311
4312 private:
4313 IntVar* const objective_;
4314 SolutionPool* const solution_pool_;
4315 LocalSearchOperator* const ls_operator_;
4316 DecisionBuilder* const sub_decision_builder_;
4317 RegularLimit* const limit_;
4318 LocalSearchFilterManager* const filter_manager_;
4319};
4320
4322 IntVar* objective, LocalSearchOperator* const ls_operator,
4323 DecisionBuilder* const sub_decision_builder) {
4325 ls_operator, sub_decision_builder,
4326 nullptr, nullptr);
4327}
4328
4330 IntVar* objective, LocalSearchOperator* const ls_operator,
4331 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit) {
4333 ls_operator, sub_decision_builder,
4334 limit, nullptr);
4335}
4336
4338 IntVar* objective, LocalSearchOperator* const ls_operator,
4339 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
4340 LocalSearchFilterManager* filter_manager) {
4342 ls_operator, sub_decision_builder,
4343 limit, filter_manager);
4344}
4345
4347 IntVar* objective, SolutionPool* const pool,
4348 LocalSearchOperator* const ls_operator,
4349 DecisionBuilder* const sub_decision_builder) {
4350 return MakeLocalSearchPhaseParameters(objective, pool, ls_operator,
4351 sub_decision_builder, nullptr, nullptr);
4352}
4353
4355 IntVar* objective, SolutionPool* const pool,
4356 LocalSearchOperator* const ls_operator,
4357 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit) {
4358 return MakeLocalSearchPhaseParameters(objective, pool, ls_operator,
4359 sub_decision_builder, limit, nullptr);
4360}
4361
4363 IntVar* objective, SolutionPool* const pool,
4364 LocalSearchOperator* const ls_operator,
4365 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
4366 LocalSearchFilterManager* filter_manager) {
4367 return RevAlloc(new LocalSearchPhaseParameters(objective, pool, ls_operator,
4368 sub_decision_builder, limit,
4369 filter_manager));
4370}
4371
4372namespace {
4373// ----- NestedSolve decision wrapper -----
4374
4375// This decision calls a nested Solve on the given DecisionBuilder in its
4376// left branch; does nothing in the right branch.
4377// The state of the decision corresponds to the result of the nested Solve:
4378// DECISION_PENDING - Nested Solve not called yet
4379// DECISION_FAILED - Nested Solve failed
4380// DECISION_FOUND - Nested Solve succeeded
4381
4382class NestedSolveDecision : public Decision {
4383 public:
4384 // This enum is used internally to tag states in the local search tree.
4385 enum StateType { DECISION_PENDING, DECISION_FAILED, DECISION_FOUND };
4386
4387 NestedSolveDecision(DecisionBuilder* absl_nonnull db, bool restore,
4388 const std::vector<SearchMonitor*>& monitors);
4389 NestedSolveDecision(DecisionBuilder* absl_nonnull db, bool restore);
4390 ~NestedSolveDecision() override {}
4391 void Apply(Solver* solver) override;
4392 void Refute(Solver* solver) override;
4393 std::string DebugString() const override { return "NestedSolveDecision"; }
4394 int state() const { return state_; }
4395
4396 private:
4397 DecisionBuilder* const db_;
4398 const bool restore_;
4399 const std::vector<SearchMonitor*> monitors_;
4400 int state_;
4401};
4402
4403NestedSolveDecision::NestedSolveDecision(
4404 DecisionBuilder* absl_nonnull db, bool restore,
4405 const std::vector<SearchMonitor*>& monitors)
4406 : db_(db),
4407 restore_(restore),
4408 monitors_(monitors),
4409 state_(DECISION_PENDING) {}
4410
4411NestedSolveDecision::NestedSolveDecision(DecisionBuilder* absl_nonnull db,
4412 bool restore)
4413 : NestedSolveDecision(db, restore, {}) {}
4414
4415void NestedSolveDecision::Apply(Solver* const solver) {
4416 CHECK(nullptr != solver);
4417 if (restore_) {
4418 if (solver->Solve(db_, monitors_)) {
4419 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FOUND));
4420 } else {
4421 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FAILED));
4422 }
4423 } else {
4424 if (solver->SolveAndCommit(db_, monitors_)) {
4425 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FOUND));
4426 } else {
4427 solver->SaveAndSetValue(&state_, static_cast<int>(DECISION_FAILED));
4428 }
4429 }
4430}
4431
4432void NestedSolveDecision::Refute(Solver* const) {}
4433} // namespace
4434
4435// ----- Local search decision builder -----
4436
4437// Given a first solution (resulting from either an initial assignment or the
4438// result of a decision builder), it searches for neighbors using a local
4439// search operator. The first solution corresponds to the first leaf of the
4440// search.
4441// The local search applies to the variables contained either in the assignment
4442// or the vector of variables passed.
4443
4445 public:
4446 LocalSearch(Assignment* assignment, IntVar* objective, SolutionPool* pool,
4447 LocalSearchOperator* ls_operator,
4448 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
4449 LocalSearchFilterManager* filter_manager);
4450 // TODO(user): find a way to not have to pass vars here: redundant with
4451 // variables in operators
4452 LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
4453 SolutionPool* pool, DecisionBuilder* first_solution,
4454 LocalSearchOperator* ls_operator,
4455 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
4456 LocalSearchFilterManager* filter_manager);
4457 LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
4458 SolutionPool* pool, DecisionBuilder* first_solution,
4459 DecisionBuilder* first_solution_sub_decision_builder,
4460 LocalSearchOperator* ls_operator,
4461 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
4462 LocalSearchFilterManager* filter_manager);
4463 LocalSearch(const std::vector<SequenceVar*>& vars, IntVar* objective,
4464 SolutionPool* pool, DecisionBuilder* first_solution,
4465 LocalSearchOperator* ls_operator,
4466 DecisionBuilder* sub_decision_builder, RegularLimit* limit,
4467 LocalSearchFilterManager* filter_manager);
4468 ~LocalSearch() override;
4469 Decision* Next(Solver* solver) override;
4470 std::string DebugString() const override { return "LocalSearch"; }
4471 void Accept(ModelVisitor* visitor) const override;
4472
4473 protected:
4474 void PushFirstSolutionDecision(DecisionBuilder* first_solution);
4475 void PushLocalSearchDecision();
4476
4477 private:
4478 Assignment* assignment_;
4479 IntVar* const objective_ = nullptr;
4480 SolutionPool* const pool_;
4481 LocalSearchOperator* const ls_operator_;
4482 DecisionBuilder* const first_solution_sub_decision_builder_;
4483 DecisionBuilder* const sub_decision_builder_;
4484 FindOneNeighbor* find_neighbors_db_;
4485 std::vector<NestedSolveDecision*> nested_decisions_;
4486 int nested_decision_index_;
4487 RegularLimit* const limit_;
4488 LocalSearchFilterManager* const filter_manager_;
4489 bool has_started_;
4490};
4491
4492LocalSearch::LocalSearch(Assignment* const assignment, IntVar* objective,
4493 SolutionPool* const pool,
4494 LocalSearchOperator* const ls_operator,
4495 DecisionBuilder* const sub_decision_builder,
4496 RegularLimit* const limit,
4497 LocalSearchFilterManager* filter_manager)
4498 : assignment_(nullptr),
4499 objective_(objective),
4500 pool_(pool),
4501 ls_operator_(ls_operator),
4502 first_solution_sub_decision_builder_(sub_decision_builder),
4503 sub_decision_builder_(sub_decision_builder),
4504 nested_decision_index_(0),
4505 limit_(limit),
4506 filter_manager_(filter_manager),
4507 has_started_(false) {
4508 CHECK(nullptr != assignment);
4509 CHECK(nullptr != ls_operator);
4510 Solver* const solver = assignment->solver();
4511 assignment_ = solver->GetOrCreateLocalSearchState();
4512 assignment_->Copy(assignment);
4513 DecisionBuilder* restore = solver->MakeRestoreAssignment(assignment);
4516}
4517
4518LocalSearch::LocalSearch(const std::vector<IntVar*>& vars, IntVar* objective,
4519 SolutionPool* const pool,
4520 DecisionBuilder* const first_solution,
4521 LocalSearchOperator* const ls_operator,
4522 DecisionBuilder* const sub_decision_builder,
4523 RegularLimit* const limit,
4524 LocalSearchFilterManager* filter_manager)
4525 : assignment_(nullptr),
4526 objective_(objective),
4527 pool_(pool),
4528 ls_operator_(ls_operator),
4529 first_solution_sub_decision_builder_(sub_decision_builder),
4530 sub_decision_builder_(sub_decision_builder),
4531 nested_decision_index_(0),
4532 limit_(limit),
4533 filter_manager_(filter_manager),
4534 has_started_(false) {
4535 CHECK(nullptr != first_solution);
4536 CHECK(nullptr != ls_operator);
4537 CHECK(!vars.empty());
4538 Solver* const solver = vars[0]->solver();
4539 assignment_ = solver->GetOrCreateLocalSearchState();
4540 assignment_->Add(vars);
4541 PushFirstSolutionDecision(first_solution);
4543}
4544
4546 const std::vector<IntVar*>& vars, IntVar* objective,
4547 SolutionPool* const pool, DecisionBuilder* const first_solution,
4548 DecisionBuilder* const first_solution_sub_decision_builder,
4549 LocalSearchOperator* const ls_operator,
4550 DecisionBuilder* const sub_decision_builder, RegularLimit* const limit,
4551 LocalSearchFilterManager* filter_manager)
4552 : assignment_(nullptr),
4553 objective_(objective),
4554 pool_(pool),
4555 ls_operator_(ls_operator),
4556 first_solution_sub_decision_builder_(first_solution_sub_decision_builder),
4557 sub_decision_builder_(sub_decision_builder),
4558 nested_decision_index_(0),
4559 limit_(limit),
4560 filter_manager_(filter_manager),
4561 has_started_(false) {
4562 CHECK(nullptr != first_solution);
4563 CHECK(nullptr != ls_operator);
4564 CHECK(!vars.empty());
4565 Solver* const solver = vars[0]->solver();
4566 assignment_ = solver->GetOrCreateLocalSearchState();
4567 assignment_->Add(vars);
4568 PushFirstSolutionDecision(first_solution);
4570}
4571
4572LocalSearch::LocalSearch(const std::vector<SequenceVar*>& vars,
4573 IntVar* objective, SolutionPool* const pool,
4574 DecisionBuilder* const first_solution,
4575 LocalSearchOperator* const ls_operator,
4576 DecisionBuilder* const sub_decision_builder,
4577 RegularLimit* const limit,
4578 LocalSearchFilterManager* filter_manager)
4579 : assignment_(nullptr),
4580 objective_(objective),
4581 pool_(pool),
4582 ls_operator_(ls_operator),
4583 first_solution_sub_decision_builder_(sub_decision_builder),
4584 sub_decision_builder_(sub_decision_builder),
4585 nested_decision_index_(0),
4586 limit_(limit),
4587 filter_manager_(filter_manager),
4588 has_started_(false) {
4589 CHECK(nullptr != first_solution);
4590 CHECK(nullptr != ls_operator);
4591 CHECK(!vars.empty());
4592 Solver* const solver = vars[0]->solver();
4593 assignment_ = solver->GetOrCreateLocalSearchState();
4594 assignment_->Add(vars);
4595 PushFirstSolutionDecision(first_solution);
4597}
4598
4600
4601// Model Visitor support.
4602void LocalSearch::Accept(ModelVisitor* const visitor) const {
4603 DCHECK(assignment_ != nullptr);
4605 // We collect decision variables from the assignment.
4606 const std::vector<IntVarElement>& elements =
4607 assignment_->IntVarContainer().elements();
4608 if (!elements.empty()) {
4609 std::vector<IntVar*> vars;
4610 for (const IntVarElement& elem : elements) {
4611 vars.push_back(elem.Var());
4612 }
4614 vars);
4615 }
4616 const std::vector<IntervalVarElement>& interval_elements =
4617 assignment_->IntervalVarContainer().elements();
4618 if (!interval_elements.empty()) {
4619 std::vector<IntervalVar*> interval_vars;
4620 for (const IntervalVarElement& elem : interval_elements) {
4621 interval_vars.push_back(elem.Var());
4622 }
4624 interval_vars);
4625 }
4627}
4628
4629// This is equivalent to a multi-restart decision builder
4630// TODO(user): abstract this from the local search part
4631// TODO(user): handle the case where the tree depth is not enough to hold
4632// all solutions.
4633
4635 CHECK(nullptr != solver);
4636 CHECK_LT(0, nested_decisions_.size());
4637 if (!has_started_) {
4638 nested_decision_index_ = 0;
4639 solver->SaveAndSetValue(&has_started_, true);
4640 find_neighbors_db_->EnterSearch();
4641 ls_operator_->EnterSearch();
4642 } else if (nested_decision_index_ < 0) {
4643 solver->Fail();
4644 }
4645 NestedSolveDecision* decision = nested_decisions_[nested_decision_index_];
4646 const int state = decision->state();
4647 switch (state) {
4648 case NestedSolveDecision::DECISION_FAILED: {
4649 // NOTE: The DECISION_FAILED state can be reached when no first solution
4650 // was found by the solver, so we should only consider to be at a local
4651 // optimum and call ContinueAtLocalOptimum() when we've reached the last
4652 // nested decision.
4653 const bool continue_at_local_optimum =
4654 nested_decision_index_ == nested_decisions_.size() - 1 &&
4656 if (continue_at_local_optimum) {
4657 // A local optimum has been reached. The search will continue only if we
4658 // accept up-hill moves (due to metaheuristics). In this case we need to
4659 // reset neighborhood optimal routes.
4660 ls_operator_->Reset();
4661 }
4662 if (!continue_at_local_optimum ||
4663 solver->IsUncheckedSolutionLimitReached()) {
4664 nested_decision_index_ = -1; // Stop the search
4665 }
4666 solver->Fail();
4667 return nullptr;
4668 }
4669 case NestedSolveDecision::DECISION_PENDING: {
4670 // TODO(user): Find a way to make this balancing invisible to the
4671 // user (no increase in branch or fail counts for instance).
4672 const int32_t kLocalSearchBalancedTreeDepth = 32;
4673 const int depth = solver->SearchDepth();
4674 if (depth < kLocalSearchBalancedTreeDepth) {
4675 return solver->balancing_decision();
4676 }
4677 if (depth > kLocalSearchBalancedTreeDepth) {
4678 solver->Fail();
4679 }
4680 return decision;
4681 }
4682 case NestedSolveDecision::DECISION_FOUND: {
4683 // Next time go to next decision
4684 if (nested_decision_index_ + 1 < nested_decisions_.size()) {
4685 ++nested_decision_index_;
4686 }
4687 return nullptr;
4688 }
4689 default: {
4690 LOG(ERROR) << "Unknown local search state";
4691 return nullptr;
4692 }
4693 }
4694 return nullptr;
4695}
4696
4698 CHECK(first_solution);
4699 Solver* const solver = assignment_->solver();
4700 DecisionBuilder* store = solver->MakeStoreAssignment(assignment_);
4701 DecisionBuilder* first_solution_and_store = solver->Compose(
4702 solver->MakeProfiledDecisionBuilderWrapper(first_solution),
4703 first_solution_sub_decision_builder_, store);
4704 std::vector<SearchMonitor*> monitor;
4705 monitor.push_back(limit_);
4706 nested_decisions_.push_back(solver->RevAlloc(
4707 new NestedSolveDecision(first_solution_and_store, false, monitor)));
4708}
4709
4711 Solver* const solver = assignment_->solver();
4712 find_neighbors_db_ = solver->RevAlloc(
4713 new FindOneNeighbor(assignment_, objective_, pool_, ls_operator_,
4714 sub_decision_builder_, limit_, filter_manager_));
4715 nested_decisions_.push_back(
4716 solver->RevAlloc(new NestedSolveDecision(find_neighbors_db_, false)));
4717}
4718
4720 public:
4722
4724
4725 void Initialize(Assignment* const assignment) override {
4726 reference_assignment_ = std::make_unique<Assignment>(assignment);
4727 }
4728
4729 void RegisterNewSolution(Assignment* const assignment) override {
4730 reference_assignment_->CopyIntersection(assignment);
4731 }
4732
4733 void GetNextSolution(Assignment* const assignment) override {
4734 assignment->CopyIntersection(reference_assignment_.get());
4735 }
4736
4737 bool SyncNeeded(Assignment* const) override { return false; }
4738
4739 std::string DebugString() const override { return "DefaultSolutionPool"; }
4740
4741 private:
4742 std::unique_ptr<Assignment> reference_assignment_;
4743};
4744
4748
4751 return RevAlloc(new LocalSearch(
4752 assignment, parameters->objective(), parameters->solution_pool(),
4753 parameters->ls_operator(), parameters->sub_decision_builder(),
4754 parameters->limit(), parameters->filter_manager()));
4755}
4756
4758 const std::vector<IntVar*>& vars, DecisionBuilder* first_solution,
4760 return RevAlloc(new LocalSearch(
4761 vars, parameters->objective(), parameters->solution_pool(),
4762 first_solution, parameters->ls_operator(),
4763 parameters->sub_decision_builder(), parameters->limit(),
4764 parameters->filter_manager()));
4765}
4766
4768 const std::vector<IntVar*>& vars, DecisionBuilder* first_solution,
4769 DecisionBuilder* first_solution_sub_decision_builder,
4771 return RevAlloc(new LocalSearch(
4772 vars, parameters->objective(), parameters->solution_pool(),
4773 first_solution, first_solution_sub_decision_builder,
4774 parameters->ls_operator(), parameters->sub_decision_builder(),
4775 parameters->limit(), parameters->filter_manager()));
4776}
4777
4779 const std::vector<SequenceVar*>& vars, DecisionBuilder* first_solution,
4781 return RevAlloc(new LocalSearch(
4782 vars, parameters->objective(), parameters->solution_pool(),
4783 first_solution, parameters->ls_operator(),
4784 parameters->sub_decision_builder(), parameters->limit(),
4785 parameters->filter_manager()));
4786}
4787
4788template <bool is_profile_active>
4789Assignment* Solver::RunUncheckedLocalSearchInternal(
4790 const Assignment* initial_solution,
4791 LocalSearchFilterManager* filter_manager, LocalSearchOperator* ls_operator,
4792 const std::vector<SearchMonitor*>& monitors, RegularLimit* limit,
4793 absl::flat_hash_set<IntVar*>* touched) {
4794 DCHECK(initial_solution != nullptr);
4795 DCHECK(initial_solution->Objective() != nullptr);
4796 DCHECK(filter_manager != nullptr);
4797 if (limit != nullptr) limit->Init();
4798 Assignment delta(this);
4799 Assignment deltadelta(this);
4800 Assignment* const current_solution = GetOrCreateLocalSearchState();
4801 current_solution->Copy(initial_solution);
4802 std::function<bool(Assignment*, Assignment*)> make_next_neighbor;
4803 std::function<bool(Assignment*, Assignment*)> filter_neighbor;
4804 LocalSearchMonitor* const ls_monitor =
4805 is_profile_active ? GetLocalSearchMonitor() : nullptr;
4806 const auto sync_with_solution =
4807 [this, ls_monitor, ls_operator, // NOLINT: ls_monitor is used when
4808 // is_profile_active is true
4809 filter_manager, current_solution](Assignment* delta) {
4810 IncrementUncheckedSolutionCounter();
4811 if constexpr (is_profile_active) {
4812 ls_monitor->BeginOperatorStart();
4813 }
4814 ls_operator->Start(current_solution);
4815 filter_manager->Synchronize(current_solution, delta);
4816 if constexpr (is_profile_active) {
4817 ls_monitor->EndOperatorStart();
4818 }
4819 };
4820 sync_with_solution(/*delta=*/nullptr);
4821 while (true) {
4822 if (!ls_operator->HoldsDelta()) {
4823 delta.Clear();
4824 }
4825 delta.ClearObjective();
4826 deltadelta.Clear();
4827 if (limit != nullptr && (limit->CheckWithOffset(absl::ZeroDuration()) ||
4829 break;
4830 }
4831 if constexpr (is_profile_active) {
4832 ls_monitor->BeginMakeNextNeighbor(ls_operator);
4833 }
4834 const bool has_neighbor =
4835 ls_operator->MakeNextNeighbor(&delta, &deltadelta);
4836 if constexpr (is_profile_active) {
4837 ls_monitor->EndMakeNextNeighbor(ls_operator, has_neighbor, &delta,
4838 &deltadelta);
4839 }
4840 if (!has_neighbor) {
4841 break;
4842 }
4843 if constexpr (is_profile_active) {
4844 ls_monitor->BeginFilterNeighbor(ls_operator);
4845 }
4846 for (SearchMonitor* monitor : monitors) {
4847 const bool mh_accept = monitor->AcceptDelta(&delta, &deltadelta);
4848 DCHECK(mh_accept);
4849 }
4850 const bool filter_accept =
4851 filter_manager->Accept(ls_monitor, &delta, &deltadelta,
4852 delta.ObjectiveMin(), delta.ObjectiveMax());
4853 if constexpr (is_profile_active) {
4854 ls_monitor->EndFilterNeighbor(ls_operator, filter_accept);
4855 ls_monitor->BeginAcceptNeighbor(ls_operator);
4856 ls_monitor->EndAcceptNeighbor(ls_operator, filter_accept);
4857 }
4858 if (!filter_accept) {
4859 filter_manager->Revert();
4860 continue;
4861 }
4862 filtered_neighbors_ += 1;
4863 current_solution->CopyIntersection(&delta);
4864 current_solution->SetObjectiveValue(
4865 filter_manager->GetAcceptedObjectiveValue());
4866 DCHECK(delta.AreAllElementsBound());
4867 accepted_neighbors_ += 1;
4868 SetSearchContext(ActiveSearch(), ls_operator->DebugString());
4869 for (SearchMonitor* monitor : monitors) {
4870 monitor->AtSolution();
4871 }
4872 if (touched != nullptr) {
4873 for (const auto& element : delta.IntVarContainer().elements()) {
4874 touched->insert(element.Var());
4875 }
4876 }
4877 // Syncing here to avoid resyncing when filtering fails.
4878 sync_with_solution(/*delta=*/&delta);
4879 }
4880 if (parameters_.print_local_search_profile()) {
4881 const std::string profile = LocalSearchProfile();
4882 if (!profile.empty()) LOG(INFO) << profile;
4883 }
4884 return MakeAssignment(current_solution);
4885}
4886
4888 const Assignment* initial_solution,
4889 LocalSearchFilterManager* filter_manager, LocalSearchOperator* ls_operator,
4890 const std::vector<SearchMonitor*>& monitors, RegularLimit* limit,
4891 absl::flat_hash_set<IntVar*>* touched) {
4892 if (GetLocalSearchMonitor()->IsActive()) {
4893 return RunUncheckedLocalSearchInternal<true>(initial_solution,
4894 filter_manager, ls_operator,
4895 monitors, limit, touched);
4896 } else {
4897 return RunUncheckedLocalSearchInternal<false>(initial_solution,
4898 filter_manager, ls_operator,
4899 monitors, limit, touched);
4900 }
4901}
4902
4903} // namespace operations_research
const E & Element(const V *const var) const
const std::vector< E > & elements() const
IntVarElement * Add(IntVar *var)
std::string DebugString() const override
void Copy(const Assignment *assignment)
AssignmentContainer< IntVar, IntVarElement > IntContainer
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, NeighborAccessor get_incoming_neighbors=nullptr, NeighborAccessor get_outgoing_neighbors=nullptr)
bool MakeOneNeighbor() override
MakeNextNeighbor() in a subclass of IntVarLocalSearchOperator.
Here's a sample relaxing one variable at a time:
BaseLns(const std::vector< IntVar * > &vars)
bool MakeOneNeighbor() override
This method should not be overridden. Override NextFragment() instead.
virtual bool NextFragment()=0
virtual std::string DebugString() const
void Set(IndexType i)
Definition bitset.h:543
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)
std::string DebugString() const override
Cross(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, NeighborAccessor get_incoming_neighbors, NeighborAccessor get_outgoing_neighbors)
~Cross() override=default
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
ExchangeAndMakeActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
bool OnSamePathAsPreviousBase(int64_t base_index) override
it's currently way more complicated to implement.
ExchangePathStartEndsAndMakeActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
~Exchange() override=default
Exchange(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, NeighborAccessor get_incoming_neighbors, NeighborAccessor get_outgoing_neighbors)
std::string DebugString() const override
ExtendedSwapActiveOperator(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
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)
IntVarLocalSearchFilter(const std::vector< IntVar * > &vars)
bool FindIndex(IntVar *const var, int64_t *index) const
virtual void OnSynchronize(const Assignment *)
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
IntVar * Var(int64_t index) const
Returns the variable of given index.
bool ApplyChanges(Assignment *delta, Assignment *deltadelta) const
IntVarLocalSearchOperator(const std::vector< IntVar * > &vars, bool keep_inverse_values=false)
virtual bool MakeOneNeighbor()
MakeNextNeighbor() in a subclass of IntVarLocalSearchOperator.
IntVar * Var() override
Creates a variable from the expression.
virtual bool Contains(int64_t v) const =0
std::string DebugString() const override
~LinKernighan() override=default
LinKernighan(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, const Solver::IndexEvaluator3 &evaluator, bool topt)
LocalSearchFilterManager(std::vector< FilterEvent > filter_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
The base class for all local search operators.
virtual const LocalSearchOperator * Self() const
LocalSearchFilterManager * filter_manager() const
LocalSearchPhaseParameters(IntVar *objective, SolutionPool *const pool, LocalSearchOperator *ls_operator, DecisionBuilder *sub_decision_builder, RegularLimit *const limit, LocalSearchFilterManager *filter_manager)
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)
int64_t VariableDomainMin(VariableDomainId domain_id) const
VariableDomainId AddVariableDomain(int64_t relaxed_min, int64_t relaxed_max)
bool TightenVariableDomainMin(VariableDomainId domain_id, int64_t value)
void ChangeRelaxedVariableDomain(VariableDomainId domain_id, int64_t min, int64_t max)
void PropagateRelax(VariableDomainId domain_id)
Variable MakeVariable(VariableDomainId domain_id)
Variable MakeVariableWithRelaxedDomain(int64_t min, int64_t max)
bool TightenVariableDomainMax(VariableDomainId domain_id, int64_t value)
int64_t VariableDomainMax(VariableDomainId domain_id) const
bool PropagateTighten(VariableDomainId domain_id)
bool RelaxVariableDomain(VariableDomainId domain_id)
LocalSearchStatistics_FirstSolutionStatistics FirstSolutionStatistics
::operations_research::LocalSearchStatistics_LocalSearchOperatorStatistics *PROTOBUF_NONNULL add_local_search_operator_statistics()
::operations_research::LocalSearchStatistics_LocalSearchFilterStatistics *PROTOBUF_NONNULL add_local_search_filter_statistics()
LocalSearchStatistics_LocalSearchFilterStatistics LocalSearchFilterStatistics
LocalSearchStatistics_LocalSearchOperatorStatistics LocalSearchOperatorStatistics
::operations_research::LocalSearchStatistics_FirstSolutionStatistics *PROTOBUF_NONNULL add_first_solution_statistics()
void PushFirstSolutionDecision(DecisionBuilder *first_solution)
std::string DebugString() const override
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
Decision * Next(Solver *solver) override
MakeActiveAndRelocateOperator(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(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, NeighborAccessor get_incoming_neighbors, NeighborAccessor get_outgoing_neighbors)
bool OnSamePathAsPreviousBase(int64_t) override
it's currently way more complicated to implement.
MakeChainInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
int64_t GetBaseNodeRestartPosition(int base_index) override
std::string DebugString() const override
MakeInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
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 & operator=(const NearestNeighbors &)=delete
virtual std::string DebugString() const
NearestNeighbors(const NearestNeighbors &)=delete
NearestNeighbors(Solver::IndexEvaluator3 evaluator, const PathOperator< ignore_path_vars > &path_operator, int size)
void Initialize(absl::Span< const int > path)
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
bool HasFragments() const override
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)
~PathLns() override=default
int number_of_nexts() const
Number of next variables.
bool MakeChainInactive(int64_t before_chain, int64_t chain_end)
int64_t Prev(int64_t node) const
Returns the node before node in the current delta.
int PathClassFromStartNode(int64_t start_node) const
int64_t Path(int64_t node) const
bool MakeActive(int64_t node, int64_t destination)
Insert the inactive node after destination.
int64_t OldNext(int64_t node) const
bool SwapNodes(int64_t node1, int64_t node2)
Swaps the nodes node1 and node2.
void SetNext(int64_t from, int64_t to, int64_t path)
Sets 'to' to be the node after 'from' on the given path.
int CurrentNodePathStart(int64_t node) const
bool IsPathStart(int64_t node) const
Returns true if node is the first node on the path.
bool MoveChain(int64_t before_chain, int64_t chain_end, int64_t destination)
int64_t EndNode(int i) const
Returns the end node of the ith base node.
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 IsInactive(int64_t node) const
Returns true if node is inactive.
bool CheckChainValidity(int64_t before_chain, int64_t chain_end, int64_t exclude) const
bool ReverseChain(int64_t before_chain, int64_t after_chain, int64_t *chain_last)
virtual void SetNextBaseToIncrement(int64_t base_index)
Neighbor GetNeighborForBaseNode(int64_t base_index) const
bool MakeOneNeighbor() override
This method should not be overridden. Override MakeNeighbor() instead.
int64_t StartNode(int i) const
Returns the start node of the ith base node.
int64_t BaseNode(int i) const
Returns the ith base node of the operator.
int CurrentNodePathEnd(int64_t node) const
bool IsUncheckedSolutionLimitReached() override
Definition search.cc:4684
bool CheckWithOffset(absl::Duration offset) override
Definition search.cc:4632
void Init() override
This method is called when the search limit is initialized.
Definition search.cc:4653
RegularLimit * MakeIdenticalClone() const
Definition search.cc:4626
RelocateAndMakeActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
RelocateAndMakeInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
bool OnSamePathAsPreviousBase(int64_t) override
it's currently way more complicated to implement.
std::string DebugString() const override
~Relocate() override=default
Relocate(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, absl::string_view name, std::function< int(int64_t)> start_empty_path_class, NeighborAccessor get_incoming_neighbors, NeighborAccessor get_outgoing_neighbors, int64_t chain_length=1LL, bool single_path=false)
virtual bool AcceptDelta(Assignment *delta, Assignment *deltadelta)
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)
Example:
LocalSearchFilter * MakeVariableDomainFilter()
ABSL_MUST_USE_RESULT RegularLimit * MakeSolutionsLimit(int64_t solutions)
Definition search.cc:4758
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)
std::function< int64_t(int64_t, int64_t, int64_t)> IndexEvaluator3
LocalSearchStatistics GetLocalSearchStatistics() const
Returns detailed local search statistics.
DecisionBuilder * Compose(DecisionBuilder *db1, DecisionBuilder *db2)
Definition search.cc:635
std::string LocalSearchProfile() const
Returns local search profiling information in a human readable format.
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)
LocalSearchOperator * MakeOperator(const std::vector< IntVar * > &vars, LocalSearchOperators op, std::function< const std::vector< int > &(int, int)> get_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr)
Local Search Operators.
@ LE
Move is accepted when the current objective value <= objective.Max.
@ GE
Move is accepted when the current objective value >= objective.Min.
std::function< int64_t(int64_t, int64_t)> IndexEvaluator2
LocalSearchFilter * MakeRejectFilter()
DecisionBuilder * MakeStoreAssignment(Assignment *assignment)
LocalSearchOperator * MultiArmedBanditConcatenateOperators(const std::vector< LocalSearchOperator * > &ops, double memory_coefficient, double exploration_coefficient, bool maximize)
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.
Assignment * GetOrCreateLocalSearchState()
Returns (or creates) an assignment representing the state of local search.
bool AcceptSolution(Search *search) const
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)
SwapActiveChainOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, int max_chain_size)
bool OnSamePathAsPreviousBase(int64_t) override
it's currently way more complicated to implement.
SwapActiveOperator(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
~TSPLns() override=default
std::string DebugString() const override
bool MakeOneNeighbor() override
This method should not be overridden. Override MakeNeighbor() instead.
TSPLns(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int tsp_size)
TSPOpt(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int chain_length)
~TSPOpt() override=default
std::string DebugString() const override
bool IsIncremental() const override
void ResetIncrementalism() override
bool OnSamePathAsPreviousBase(int64_t) override
it's currently way more complicated to implement.
int64_t GetBaseNodeRestartPosition(int base_index) override
~TwoOpt() override=default
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, NeighborAccessor get_incoming_neighbors, NeighborAccessor get_outgoing_neighbors)
ABSL_FLAG(int, cp_local_search_sync_frequency, 16, "Frequency of checks for better solutions in the solution pool.")
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
Definition matchers.h:467
OR-Tools root namespace.
std::function< const std::vector< int > &(int, int)> NeighborAccessor
void DeleteLocalSearchProfiler(LocalSearchProfiler *monitor)
LocalSearchOperator * MakeExtendedSwapActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— ExtendedSwapActive --—
int64_t CapAdd(int64_t x, int64_t y)
LocalSearchOperator * RelocateAndMakeInactive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— RelocateAndMakeInactive --—
LocalSearchOperator * MakeTSPOpt(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int chain_length)
--— TSP-based operators --—
LocalSearchOperator * MakeCross(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_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr)
--— Cross --—
SubDagComputer::ArcId ArcId
LocalSearchOperator * MakeSwapActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— SwapActive --—
LocalSearchOperator * ExchangeAndMakeActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
void AcceptNeighbor(Search *search)
LocalSearchOperator * MakeExchange(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_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr)
--— Exchange --—
void AcceptUncheckedNeighbor(Search *search)
int64_t CapSub(int64_t x, int64_t y)
LocalSearchOperator * MakeActiveAndRelocate(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— MakeActiveAndRelocate --—
LocalSearchOperator * MakeTSPLns(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, Solver::IndexEvaluator3 evaluator, int tsp_size)
ClosedInterval::Iterator end(ClosedInterval interval)
LocalSearchOperator * MakeRelocate(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_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr, int64_t chain_length=1LL, bool single_path=false, const std::string &name="Relocate")
--— Relocate --—
LocalSearchOperator * RelocateAndMakeActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
-— RelocateAndMakeActive --—
LocalSearchOperator * MakePathLns(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, int number_of_chunks, int chunk_size, bool unactive_fragments)
--— Path-based Large Neighborhood Search --—
LocalSearchOperator * MakeInactive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— MakeInactive --—
LocalSearchOperator * MakeTwoOpt(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_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr)
--— 2Opt --—
LocalSearchOperator * MakeChainInactive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
--— MakeChainInactive --—
void InstallLocalSearchProfiler(LocalSearchProfiler *monitor)
int64_t CapProd(int64_t x, int64_t y)
LocalSearchProfiler * BuildLocalSearchProfiler(Solver *solver)
LocalSearchOperator * ExchangePathStartEndsAndMakeActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class)
LocalSearchOperator * MakeSwapActiveChain(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, int max_chain_size)
--— SwapActiveChain --—
LocalSearchOperator * MakeLinKernighan(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, const Solver::IndexEvaluator3 &evaluator, bool topt)
--— Lin-Kernighan --—
bool ContinueAtLocalOptimum(Search *search)
LocalSearchState::VariableDomainId VariableDomainId
ClosedInterval::Iterator begin(ClosedInterval interval)
LocalSearchOperator * MakeActive(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_incoming_neighbors=nullptr, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors=nullptr)
--— MakeActive --—
bool AcceptDelta(Search *search, Assignment *delta, Assignment *deltadelta)
SubDagComputer::NodeId NodeId
STL namespace.
bool Next()
static int input(yyscan_t yyscanner)
static const int64_t kint64max
Definition types.h:32
static const int32_t kint32max
Definition types.h:30
static const int64_t kint64min
Definition types.h:31