Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_neighborhoods.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <utility>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/types/span.h"
24#include "ortools/base/types.h"
30
31namespace operations_research {
32using NeighborAccessor =
33 std::function<const std::vector<int>&(/*node=*/int, /*start_node=*/int)>;
34
35template <bool ignore_path_vars>
37 const std::vector<IntVar*>& vars,
38 const std::vector<IntVar*>& secondary_vars,
39 std::function<int(int64_t)> start_empty_path_class,
40 NeighborAccessor get_incoming_neighbors,
41 NeighborAccessor get_outgoing_neighbors,
42 RoutingTransitCallback2 arc_evaluator)
43 : PathOperator<ignore_path_vars>(
44 vars, secondary_vars,
45 /*number_of_base_nodes=*/
46 get_incoming_neighbors == nullptr && get_outgoing_neighbors == nullptr
47 ? 2
48 : 1,
49 /*skip_locally_optimal_paths=*/true,
50 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
51 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors)),
52 arc_evaluator_(std::move(arc_evaluator)) {}
53
54template <bool ignore_path_vars>
56 const auto do_move = [this](int64_t before_chain, int64_t destination) {
57 int64_t chain_end = this->Next(before_chain);
58 if (this->IsPathEnd(chain_end)) return false;
59 if (chain_end == destination) return false;
60 const int64_t max_arc_value = arc_evaluator_(destination, chain_end);
61 int64_t next = this->Next(chain_end);
62 while (!this->IsPathEnd(next) &&
63 arc_evaluator_(chain_end, next) <= max_arc_value) {
64 // We return false here to avoid symmetric moves. The rationale is that
65 // if destination is part of the same group as the chain, we probably want
66 // to extend the chain to contain it, which means finding another
67 // destination further down the path.
68 // TODO(user): Add a parameter to either return false or break here,
69 // depending if we want to permutate nodes within the same chain.
70 if (next == destination) return false;
71 chain_end = next;
72 next = this->Next(chain_end);
73 }
74 return MoveChainAndRepair(before_chain, chain_end, destination);
75 };
76 if (this->HasNeighbors()) {
77 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
78 if (neighbor < 0 || this->IsInactive(neighbor)) return false;
79 if (!outgoing) {
80 // TODO(user): Handle incoming neighbors by going backwards on the
81 // chain.
82 return false;
83 }
84 return do_move(/*before_chain=*/this->Prev(neighbor),
85 /*destination=*/this->BaseNode(0));
86 } else {
87 return do_move(/*before_chain=*/this->BaseNode(0),
88 /*destination=*/this->BaseNode(1));
89 }
90}
91
92template <bool ignore_path_vars>
93bool MakeRelocateNeighborsOperator<ignore_path_vars>::MoveChainAndRepair(
94 int64_t before_chain, int64_t chain_end, int64_t destination) {
95 if (this->MoveChain(before_chain, chain_end, destination)) {
96 if (!this->IsPathStart(destination)) {
97 int64_t current = this->Prev(destination);
98 int64_t last = chain_end;
99 if (current == last) { // chain was just before destination
100 current = before_chain;
101 }
102 while (last >= 0 && !this->IsPathStart(current) && current != last) {
103 last = Reposition(current, last);
104 current = this->Prev(current);
105 }
106 }
107 return true;
108 }
109 return false;
110}
111
112template <bool ignore_path_vars>
113int64_t MakeRelocateNeighborsOperator<ignore_path_vars>::Reposition(
114 int64_t before_to_move, int64_t up_to) {
115 const int64_t kNoChange = -1;
116 const int64_t to_move = this->Next(before_to_move);
117 int64_t next = this->Next(to_move);
118 if (this->Var(to_move)->Contains(next)) {
119 return kNoChange;
120 }
121 int64_t prev = next;
122 next = this->Next(next);
123 while (prev != up_to) {
124 if (this->Var(prev)->Contains(to_move) &&
125 this->Var(to_move)->Contains(next)) {
126 this->MoveChain(before_to_move, to_move, prev);
127 return up_to;
128 }
129 prev = next;
130 next = this->Next(next);
131 }
132 if (this->Var(prev)->Contains(to_move)) {
133 this->MoveChain(before_to_move, to_move, prev);
134 return to_move;
135 }
136 return kNoChange;
137}
138
140 Solver* solver, const std::vector<IntVar*>& vars,
141 const std::vector<IntVar*>& secondary_vars,
142 std::function<int(int64_t)> start_empty_path_class,
143 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
144 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
145 RoutingTransitCallback2 arc_evaluator) {
146 if (secondary_vars.empty()) {
148 vars, secondary_vars, std::move(start_empty_path_class),
149 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
150 std::move(arc_evaluator)));
151 }
153 vars, secondary_vars, std::move(start_empty_path_class),
154 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
155 std::move(arc_evaluator)));
156}
157
159 Solver* solver, const std::vector<IntVar*>& vars,
160 const std::vector<IntVar*>& secondary_vars,
161 std::function<int(int64_t)> start_empty_path_class,
162 RoutingTransitCallback2 arc_evaluator) {
163 return MakeRelocateNeighbors(solver, vars, secondary_vars,
164 std::move(start_empty_path_class), nullptr,
165 nullptr, std::move(arc_evaluator));
166}
167
169 int num_nodes, std::vector<std::vector<int64_t>> alternative_sets,
170 RoutingTransitCallback2 arc_evaluator)
171 : arc_evaluator_(std::move(arc_evaluator)),
172 alternative_sets_(std::move(alternative_sets)),
173 to_alternative_set_(num_nodes, -1),
174 path_predecessor_(num_nodes, -1),
175 touched_(num_nodes) {
176 for (int i = 0; i < alternative_sets_.size(); ++i) {
177 for (int j : alternative_sets_[i]) {
178 if (j < to_alternative_set_.size()) to_alternative_set_[j] = i;
179 }
180 }
181 for (int i = 0; i < num_nodes; ++i) {
182 if (to_alternative_set_[i] == -1) {
183 to_alternative_set_[i] = alternative_sets_.size();
184 alternative_sets_.push_back({int64_t{i}});
185 }
186 }
187}
188
190 DCHECK_LT(node, to_alternative_set_.size());
191 DCHECK_LT(to_alternative_set_[node], alternative_sets_.size());
192 return alternative_sets_[to_alternative_set_[node]].size() > 1;
193}
194
196 int64_t source, int64_t sink, absl::Span<const int64_t> chain) {
197 path_.clear();
198 if (chain.empty()) return path_;
199
200 const std::vector<int64_t> source_alternatives = {source};
201 auto prev_alternative_set = absl::Span<const int64_t>(source_alternatives);
202 std::vector<int64_t> prev_values = {0};
203
204 auto get_best_predecessor = [this, &prev_alternative_set, &prev_values](
205 int64_t node) -> std::pair<int64_t, int64_t> {
206 int64_t predecessor = -1;
207 int64_t min_value = kint64max;
208 for (int prev_alternative = 0;
209 prev_alternative < prev_alternative_set.size(); ++prev_alternative) {
210 const int64_t new_value =
211 CapAdd(prev_values[prev_alternative],
212 arc_evaluator_(prev_alternative_set[prev_alternative], node));
213 if (new_value <= min_value) {
214 min_value = new_value;
215 predecessor = prev_alternative_set[prev_alternative];
216 }
217 }
218 return {predecessor, min_value};
219 };
220
221 // Updating values "layer" by "layer" (each one is fully connected to the
222 // previous one).
223 for (const int64_t node : chain) {
224 const std::vector<int64_t>& current_alternative_set =
225 alternative_sets_[to_alternative_set_[node]];
226 current_values_.clear();
227 current_values_.reserve(current_alternative_set.size());
228 for (int alternative_node : current_alternative_set) {
229 auto [predecessor, min_value] = get_best_predecessor(alternative_node);
230 current_values_.push_back(min_value);
231 path_predecessor_[alternative_node] = predecessor;
232 }
233 prev_alternative_set = absl::MakeConstSpan(current_alternative_set);
234 prev_values.swap(current_values_);
235 }
236 // Get the predecessor in the shortest path to sink in the last layer.
237 auto [predecessor, min_value] = get_best_predecessor(sink);
238 if (predecessor == -1) return path_;
239 // Build the path from predecessors on the shortest path.
240 path_.resize(chain.size(), predecessor);
241 touched_.SparseClearAll();
242 touched_.Set(predecessor);
243 for (int rank = chain.size() - 2; rank >= 0; --rank) {
244 path_[rank] = path_predecessor_[path_[rank + 1]];
245 if (touched_[path_[rank]]) {
246 path_.clear();
247 return path_;
248 }
249 touched_.Set(path_[rank]);
250 }
251 return path_;
252}
253
254template <bool ignore_path_vars>
257 const std::vector<IntVar*>& vars,
258 const std::vector<IntVar*>& secondary_vars,
259 std::function<int(int64_t)> start_empty_path_class,
260 std::vector<std::vector<int64_t>> alternative_sets,
261 RoutingTransitCallback2 arc_evaluator)
262 : PathOperator<ignore_path_vars>(
263 vars, secondary_vars, /*number_of_base_nodes=*/2,
264 /*skip_locally_optimal_paths=*/true,
265 /*accept_path_end_base=*/true, std::move(start_empty_path_class),
266 nullptr, nullptr),
267 shortest_path_manager_(vars.size(), std::move(alternative_sets),
268 std::move(arc_evaluator)) {}
269
270template <bool ignore_path_vars>
272 DCHECK_EQ(this->StartNode(0), this->StartNode(1));
273 const int64_t before_chain = this->BaseNode(0);
274 if (this->IsPathEnd(before_chain)) {
275 ResetChainStatus();
276 return false;
277 }
278 const int64_t after_chain = this->BaseNode(1);
279 bool has_alternatives = false;
280 if (before_chain != after_chain) {
281 const int64_t prev_after_chain = this->Prev(after_chain);
282 if (prev_after_chain != before_chain &&
283 chain_status_.start == before_chain &&
284 chain_status_.end == prev_after_chain) {
285 has_alternatives =
286 chain_status_.has_alternatives ||
287 shortest_path_manager_.HasAlternatives(prev_after_chain);
288 } else {
289 // Non-incremental computation of alternative presence. The chains are
290 // small by definition.
291 for (int64_t node = this->Next(before_chain); node != after_chain;
292 node = this->Next(node)) {
293 has_alternatives |= shortest_path_manager_.HasAlternatives(node);
294 }
295 }
296 }
297 chain_status_.start = before_chain;
298 chain_status_.end = after_chain;
299 chain_status_.has_alternatives = has_alternatives;
300 if (!has_alternatives) return false;
301 int64_t chain_last;
302 if (!this->ReverseChain(before_chain, after_chain, &chain_last)) return false;
303 chain_.clear();
304 for (int64_t next = this->Next(before_chain); next != after_chain;
305 next = this->Next(next)) {
306 chain_.push_back(next);
307 }
308 // The neighbor is accepted if there were actual changes, either we reverted a
309 // chain with more than one node, or alternatives were swapped.
310 return this->SwapActiveAndInactiveChains(
311 chain_, shortest_path_manager_.GetShortestPath(
312 before_chain, after_chain, chain_)) ||
313 chain_.size() > 1;
314}
315
317 Solver* solver, const std::vector<IntVar*>& vars,
318 const std::vector<IntVar*>& secondary_vars,
319 std::function<int(int64_t)> start_empty_path_class,
320 std::vector<std::vector<int64_t>> alternative_sets,
321 RoutingTransitCallback2 arc_evaluator) {
322 if (secondary_vars.empty()) {
324 vars, secondary_vars, std::move(start_empty_path_class),
325 std::move(alternative_sets), std::move(arc_evaluator)));
326 }
328 vars, secondary_vars, std::move(start_empty_path_class),
329 std::move(alternative_sets), std::move(arc_evaluator)));
330}
331
332template <bool ignore_path_vars>
335 const std::vector<IntVar*>& vars,
336 const std::vector<IntVar*>& secondary_vars,
337 std::function<int(int64_t)> start_empty_path_class,
338 std::vector<std::vector<int64_t>> alternative_sets,
339 RoutingTransitCallback2 arc_evaluator)
340 : PathOperator<ignore_path_vars>(
341 vars, secondary_vars, /*number_of_base_nodes=*/1,
342 /*skip_locally_optimal_paths=*/true,
343 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
344 nullptr, nullptr),
345 shortest_path_manager_(vars.size(), std::move(alternative_sets),
346 std::move(arc_evaluator)) {}
347
348template <bool ignore_path_vars>
350 const int64_t before_chain = this->BaseNode(0);
351 if (!this->IsPathStart(before_chain) &&
352 shortest_path_manager_.HasAlternatives(before_chain)) {
353 return false;
354 }
355 int64_t next = this->Next(before_chain);
356 chain_.clear();
357 while (!this->IsPathEnd(next) &&
358 shortest_path_manager_.HasAlternatives(next)) {
359 chain_.push_back(next);
360 next = this->Next(next);
361 }
362 return this->SwapActiveAndInactiveChains(
363 chain_, shortest_path_manager_.GetShortestPath(
364 /*source=*/before_chain, /*sink=*/next, chain_));
365}
366
368 Solver* solver, const std::vector<IntVar*>& vars,
369 const std::vector<IntVar*>& secondary_vars,
370 std::function<int(int64_t)> start_empty_path_class,
371 std::vector<std::vector<int64_t>> alternative_sets,
372 RoutingTransitCallback2 arc_evaluator) {
373 if (secondary_vars.empty()) {
375 vars, secondary_vars, std::move(start_empty_path_class),
376 std::move(alternative_sets), std::move(arc_evaluator)));
377 }
379 vars, secondary_vars, std::move(start_empty_path_class),
380 std::move(alternative_sets), std::move(arc_evaluator)));
381}
382
383template <bool ignore_path_vars>
385 const std::vector<IntVar*>& vars,
386 const std::vector<IntVar*>& secondary_vars,
387 std::function<int(int64_t)> start_empty_path_class,
388 const std::vector<PickupDeliveryPair>& pairs)
389 : PathOperator<ignore_path_vars>(vars, secondary_vars, 2, false, true,
390 std::move(start_empty_path_class), nullptr,
391 nullptr),
392 inactive_pair_(0),
393 inactive_pair_first_index_(0),
394 inactive_pair_second_index_(0),
395 pairs_(pairs) {}
396
397template <bool ignore_path_vars>
399 while (inactive_pair_ < pairs_.size()) {
401 this->ResetPosition();
402 const auto& [pickup_alternatives, delivery_alternatives] =
403 pairs_[inactive_pair_];
404 if (inactive_pair_first_index_ < pickup_alternatives.size() - 1) {
405 ++inactive_pair_first_index_;
406 } else if (inactive_pair_second_index_ < delivery_alternatives.size() - 1) {
407 inactive_pair_first_index_ = 0;
408 ++inactive_pair_second_index_;
409 } else {
410 inactive_pair_ = FindNextInactivePair(inactive_pair_ + 1);
411 inactive_pair_first_index_ = 0;
412 inactive_pair_second_index_ = 0;
413 }
414 }
415 return false;
416}
417
418template <bool ignore_path_vars>
420 DCHECK_EQ(this->StartNode(0), this->StartNode(1));
421 // Inserting the second node of the pair before the first one which ensures
422 // that the only solutions where both nodes are next to each other have the
423 // first node before the second (the move is not symmetric and doing it this
424 // way ensures that a potential precedence constraint between the nodes of the
425 // pair is not violated).
426 const auto& [pickup_alternatives, delivery_alternatives] =
427 pairs_[inactive_pair_];
428 return this->MakeActive(delivery_alternatives[inactive_pair_second_index_],
429 this->BaseNode(1)) &&
430 this->MakeActive(pickup_alternatives[inactive_pair_first_index_],
431 this->BaseNode(0));
432}
433
434template <bool ignore_path_vars>
436 int base_index) {
437 // Base node 1 must be after base node 0 if they are both on the same path.
438 if (base_index == 0 ||
439 this->StartNode(base_index) != this->StartNode(base_index - 1)) {
440 return this->StartNode(base_index);
441 } else {
442 return this->BaseNode(base_index - 1);
443 }
444}
445
446template <bool ignore_path_vars>
447void MakePairActiveOperator<ignore_path_vars>::OnNodeInitialization() {
448 inactive_pair_ = FindNextInactivePair(0);
449 inactive_pair_first_index_ = 0;
450 inactive_pair_second_index_ = 0;
451}
452
453template <bool ignore_path_vars>
454int MakePairActiveOperator<ignore_path_vars>::FindNextInactivePair(
455 int pair_index) const {
456 for (int index = pair_index; index < pairs_.size(); ++index) {
457 if (!ContainsActiveNodes(pairs_[index].pickup_alternatives) &&
458 !ContainsActiveNodes(pairs_[index].delivery_alternatives)) {
459 return index;
460 }
461 }
462 return pairs_.size();
463}
464
465template <bool ignore_path_vars>
466bool MakePairActiveOperator<ignore_path_vars>::ContainsActiveNodes(
467 absl::Span<const int64_t> nodes) const {
468 for (int64_t node : nodes) {
469 if (!this->IsInactive(node)) return true;
470 }
471 return false;
472}
473
475 Solver* solver, const std::vector<IntVar*>& vars,
476 const std::vector<IntVar*>& secondary_vars,
477 std::function<int(int64_t)> start_empty_path_class,
478 const std::vector<PickupDeliveryPair>& pairs) {
479 if (secondary_vars.empty()) {
480 return solver->RevAlloc(new MakePairActiveOperator<true>(
481 vars, secondary_vars, std::move(start_empty_path_class), pairs));
482 }
483 return solver->RevAlloc(new MakePairActiveOperator<false>(
484 vars, secondary_vars, std::move(start_empty_path_class), pairs));
485}
486
487template <bool ignore_path_vars>
489 const std::vector<IntVar*>& vars,
490 const std::vector<IntVar*>& secondary_vars,
491 std::function<int(int64_t)> start_empty_path_class,
492 const std::vector<PickupDeliveryPair>& pairs)
493 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
494 std::move(start_empty_path_class), nullptr,
495 nullptr) {
496 this->AddPairAlternativeSets(pairs);
497}
498
499template <bool ignore_path_vars>
501 const int64_t base = this->BaseNode(0);
502 const int64_t first_index = this->Next(base);
503 const int64_t second_index = this->GetActiveAlternativeSibling(first_index);
504 if (second_index < 0) {
505 return false;
506 }
507 return this->MakeChainInactive(base, first_index) &&
508 this->MakeChainInactive(this->Prev(second_index), second_index);
509}
510
512 Solver* solver, const std::vector<IntVar*>& vars,
513 const std::vector<IntVar*>& secondary_vars,
514 std::function<int(int64_t)> start_empty_path_class,
515 const std::vector<PickupDeliveryPair>& pairs) {
516 if (secondary_vars.empty()) {
517 return solver->RevAlloc(new MakePairInactiveOperator<true>(
518 vars, secondary_vars, std::move(start_empty_path_class), pairs));
519 }
520 return solver->RevAlloc(new MakePairInactiveOperator<false>(
521 vars, secondary_vars, std::move(start_empty_path_class), pairs));
522}
523
524template <bool ignore_path_vars>
526 const std::vector<IntVar*>& vars,
527 const std::vector<IntVar*>& secondary_vars,
528 std::function<int(int64_t)> start_empty_path_class,
529 const std::vector<PickupDeliveryPair>& pairs)
530 : PathOperator<ignore_path_vars>(vars, secondary_vars, 3, true, false,
531 std::move(start_empty_path_class), nullptr,
532 nullptr) {
533 // TODO(user): Add a version where a (first_node, second_node) pair are
534 // added respectively after first_node_neighbor and second_node_neighbor.
535 // This requires a complete restructuring of the code, since we would require
536 // scanning neighbors for a non-base node (second_node is an active sibling
537 // of first_node).
538 this->AddPairAlternativeSets(pairs);
539}
540
541template <bool ignore_path_vars>
543 DCHECK_EQ(this->StartNode(1), this->StartNode(2));
544 const int64_t first_pair_node = this->BaseNode(kPairFirstNode);
545 if (this->IsPathStart(first_pair_node)) {
546 return false;
547 }
548 int64_t first_prev = this->Prev(first_pair_node);
549 const int second_pair_node =
550 this->GetActiveAlternativeSibling(first_pair_node);
551 if (second_pair_node < 0 || this->IsPathEnd(second_pair_node) ||
552 this->IsPathStart(second_pair_node)) {
553 return false;
554 }
555 const int64_t second_prev = this->Prev(second_pair_node);
556
557 const int64_t first_node_destination =
558 this->BaseNode(kPairFirstNodeDestination);
559 if (first_node_destination == second_pair_node) {
560 // The second_pair_node -> first_pair_node link is forbidden.
561 return false;
562 }
563
564 const int64_t second_node_destination =
565 this->BaseNode(kPairSecondNodeDestination);
566 if (second_prev == first_pair_node && first_node_destination == first_prev &&
567 second_node_destination == first_prev) {
568 // If the current sequence is first_prev -> first_pair_node ->
569 // second_pair_node, and both 1st and 2nd are moved both to prev, the result
570 // of the move will be first_prev -> first_pair_node -> second_pair_node,
571 // which is no move.
572 return false;
573 }
574
575 // Relocation is successful if both moves are feasible and at least one of the
576 // nodes moves.
577 if (second_pair_node == second_node_destination ||
578 first_pair_node == first_node_destination) {
579 return false;
580 }
581 const bool moved_second_pair_node =
582 this->MoveChain(second_prev, second_pair_node, second_node_destination);
583 // Explicitly calling Prev as second_pair_node might have been moved before
584 // first_pair_node.
585 const bool moved_first_pair_node = this->MoveChain(
586 this->Prev(first_pair_node), first_pair_node, first_node_destination);
587 // Swapping alternatives in.
588 this->SwapActiveAndInactive(second_pair_node,
589 this->BaseSiblingAlternativeNode(kPairFirstNode));
590 this->SwapActiveAndInactive(first_pair_node,
591 this->BaseAlternativeNode(kPairFirstNode));
592 return moved_first_pair_node || moved_second_pair_node;
593}
594
595template <bool ignore_path_vars>
597 int base_index) {
598 // Destination node of the second node of a pair must be after the
599 // destination node of the first node of a pair.
600 if (base_index == kPairSecondNodeDestination) {
601 return this->BaseNode(kPairFirstNodeDestination);
602 } else {
603 return this->StartNode(base_index);
604 }
605}
606
608 Solver* solver, const std::vector<IntVar*>& vars,
609 const std::vector<IntVar*>& secondary_vars,
610 std::function<int(int64_t)> start_empty_path_class,
611 const std::vector<PickupDeliveryPair>& pairs) {
612 if (secondary_vars.empty()) {
613 return solver->RevAlloc(new PairRelocateOperator<true>(
614 vars, secondary_vars, std::move(start_empty_path_class), pairs));
615 }
616 return solver->RevAlloc(new PairRelocateOperator<false>(
617 vars, secondary_vars, std::move(start_empty_path_class), pairs));
618}
619
620template <bool ignore_path_vars>
622 const std::vector<IntVar*>& vars,
623 const std::vector<IntVar*>& secondary_vars,
624 std::function<int(int64_t)> start_empty_path_class, NeighborAccessor,
625 NeighborAccessor get_outgoing_neighbors,
626 const std::vector<PickupDeliveryPair>& pairs)
627 : PathOperator<ignore_path_vars>(
628 vars, secondary_vars,
629 /*number_of_base_nodes=*/
630 get_outgoing_neighbors == nullptr ? 2 : 1,
631 /*skip_locally_optimal_paths=*/true,
632 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
633 nullptr, // We don't use incoming neighbors for this operator.
634 std::move(get_outgoing_neighbors)) {
635 this->AddPairAlternativeSets(pairs);
636}
637
638template <bool ignore_path_vars>
640 const auto do_move = [this](int64_t node, int64_t destination) {
641 if (this->IsPathEnd(node) || this->IsInactive(node)) return false;
642 const int64_t sibling = this->GetActiveAlternativeSibling(node);
643 if (sibling == -1) return false;
644 // Skip redundant cases.
645 if (destination == node || destination == sibling) return false;
646 const bool ok = this->MoveChain(this->Prev(node), node, destination);
647 return this->MoveChain(this->Prev(sibling), sibling, node) || ok;
648 };
649 if (this->HasNeighbors()) {
650 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
651 if (neighbor < 0) return false;
652 DCHECK(outgoing);
653 return do_move(/*node=*/neighbor, /*destination=*/this->BaseNode(0));
654 }
655 return do_move(/*node=*/this->Next(this->BaseNode(0)),
656 /*destination=*/this->BaseNode(1));
657}
658
660 Solver* solver, const std::vector<IntVar*>& vars,
661 const std::vector<IntVar*>& secondary_vars,
662 std::function<int(int64_t)> start_empty_path_class,
663 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
664 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
665 const std::vector<PickupDeliveryPair>& pairs) {
666 if (secondary_vars.empty()) {
668 vars, secondary_vars, std::move(start_empty_path_class),
669 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
670 pairs));
671 } else {
673 vars, secondary_vars, std::move(start_empty_path_class),
674 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
675 pairs));
676 }
677}
678
680 Solver* solver, const std::vector<IntVar*>& vars,
681 const std::vector<IntVar*>& secondary_vars,
682 std::function<int(int64_t)> start_empty_path_class,
683 const std::vector<PickupDeliveryPair>& pairs) {
684 return MakeGroupPairAndRelocate(solver, vars, secondary_vars,
685 std::move(start_empty_path_class), nullptr,
686 nullptr, pairs);
687}
688
689template <bool ignore_path_vars>
691 const std::vector<IntVar*>& vars,
692 const std::vector<IntVar*>& secondary_vars,
693 std::function<int(int64_t)> start_empty_path_class, NeighborAccessor,
694 NeighborAccessor get_outgoing_neighbors,
695 const std::vector<PickupDeliveryPair>& pairs,
696 std::function<bool(int64_t)> force_lifo)
697 : PathOperator<ignore_path_vars>(
698 vars, secondary_vars,
699 /*number_of_base_nodes=*/
700 get_outgoing_neighbors == nullptr ? 2 : 1,
701 /*skip_locally_optimal_paths=*/true,
702 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
703 nullptr, // Incoming neighbors not used as of 09/2024.
704 std::move(get_outgoing_neighbors)),
705 force_lifo_(std::move(force_lifo)) {
706 this->AddPairAlternativeSets(pairs);
707}
708
709template <bool ignore_path_vars>
711 const auto do_move = [this](int64_t node, int64_t destination,
712 bool destination_is_lifo) {
713 if (this->IsPathStart(node) || this->IsPathEnd(node) ||
714 this->IsInactive(node)) {
715 return false;
716 }
717 const int64_t prev = this->Prev(node);
718 if (this->IsPathEnd(node)) return false;
719 const int64_t sibling = this->GetActiveAlternativeSibling(node);
720 if (sibling == -1 || destination == sibling) return false;
721
722 // Note: MoveChain will return false if it is a no-op (moving the chain to
723 // its current position). However we want to accept the move if at least
724 // node or sibling gets moved to a new position. Therefore we want to be
725 // sure both MoveChains are called and at least one succeeds.
726
727 // Special case handling relocating the first node of a pair "before" the
728 // first node of another pair. Limiting this to relocating after the start
729 // of the path as other moves will be mostly equivalent to relocating
730 // "after".
731 // TODO(user): extend to relocating before the start of sub-tours (when
732 // all pairs have been matched).
733 if (this->IsPathStart(destination)) {
734 const bool ok = this->MoveChain(prev, node, destination);
735 const int64_t destination_sibling =
736 this->GetActiveAlternativeSibling(this->Next(node));
737 if (destination_sibling == -1) {
738 // Not inserting before a pair node: insert sibling after node.
739 return this->MoveChain(this->Prev(sibling), sibling, node) || ok;
740 } else {
741 // Depending on the lifo status of the path, insert sibling before or
742 // after destination_sibling since node is being inserted before
743 // next(destination).
744 if (!destination_is_lifo) {
745 if (this->Prev(destination_sibling) == sibling) return ok;
746 return this->MoveChain(this->Prev(sibling), sibling,
747 this->Prev(destination_sibling)) ||
748 ok;
749 } else {
750 return this->MoveChain(this->Prev(sibling), sibling,
751 destination_sibling) ||
752 ok;
753 }
754 }
755 }
756 // Relocating the first node of a pair "after" the first node of another
757 // pair.
758 const int64_t destination_sibling =
759 this->GetActiveAlternativeSibling(destination);
760 if (destination_sibling == -1) return false;
761 const bool ok = this->MoveChain(prev, node, destination);
762 if (!destination_is_lifo) {
763 return this->MoveChain(this->Prev(sibling), sibling,
764 destination_sibling) ||
765 ok;
766 } else {
767 if (this->Prev(destination_sibling) == sibling) return ok;
768 return this->MoveChain(this->Prev(sibling), sibling,
769 this->Prev(destination_sibling)) ||
770 ok;
771 }
772 };
773 if (this->HasNeighbors()) {
774 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
775 if (neighbor < 0) return false;
776 // TODO(user): Add support for incoming neighbors.
777 DCHECK(outgoing);
778 // TODO(user): Add support for lifo for neighbor-based move.
779 return do_move(/*node=*/neighbor, /*destination=*/this->BaseNode(0),
780 /*destination_is_lifo=*/false);
781 }
782 return do_move(/*node=*/this->Next(this->BaseNode(0)),
783 /*destination=*/this->BaseNode(1),
784 force_lifo_ != nullptr && force_lifo_(this->StartNode(1)));
785}
786
788 Solver* solver, const std::vector<IntVar*>& vars,
789 const std::vector<IntVar*>& secondary_vars,
790 std::function<int(int64_t)> start_empty_path_class,
791 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
792 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
793 const std::vector<PickupDeliveryPair>& pairs,
794 std::function<bool(int64_t)> force_lifo) {
795 if (secondary_vars.empty()) {
796 return solver->RevAlloc(new LightPairRelocateOperator<true>(
797 vars, secondary_vars, std::move(start_empty_path_class),
798 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
799 pairs, std::move(force_lifo)));
800 }
802 vars, secondary_vars, std::move(start_empty_path_class),
803 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
804 pairs, std::move(force_lifo)));
805}
806
808 Solver* solver, const std::vector<IntVar*>& vars,
809 const std::vector<IntVar*>& secondary_vars,
810 std::function<int(int64_t)> start_empty_path_class,
811 const std::vector<PickupDeliveryPair>& pairs,
812 std::function<bool(int64_t)> force_lifo) {
813 return MakeLightPairRelocate(solver, vars, secondary_vars,
814 std::move(start_empty_path_class), nullptr,
815 nullptr, pairs, std::move(force_lifo));
816}
817
818template <bool ignore_path_vars>
820 const std::vector<IntVar*>& vars,
821 const std::vector<IntVar*>& secondary_vars,
822 std::function<int(int64_t)> start_empty_path_class,
823 NeighborAccessor get_incoming_neighbors,
824 NeighborAccessor get_outgoing_neighbors,
825 const std::vector<PickupDeliveryPair>& pairs)
826 : PathOperator<ignore_path_vars>(
827 vars, secondary_vars,
828 /*number_of_base_nodes=*/
829 get_incoming_neighbors == nullptr && get_outgoing_neighbors == nullptr
830 ? 2
831 : 1,
832 /*skip_locally_optimal_paths=*/true,
833 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
834 std::move(get_incoming_neighbors),
835 std::move(get_outgoing_neighbors)) {
836 this->AddPairAlternativeSets(pairs);
837}
838
839template <bool ignore_path_vars>
841 const int64_t node1 = this->BaseNode(0);
842 int64_t prev1, sibling1, sibling_prev1 = -1;
843 if (!GetPreviousAndSibling(node1, &prev1, &sibling1, &sibling_prev1)) {
844 return false;
845 }
846 int64_t node2 = -1;
847 if (!this->HasNeighbors()) {
848 node2 = this->BaseNode(1);
849 } else {
850 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
851 if (neighbor < 0 || this->IsInactive(neighbor)) return false;
852 if (outgoing) {
853 if (this->IsPathStart(neighbor)) return false;
854 } else if (this->IsPathEnd(neighbor)) {
855 return false;
856 }
857 node2 = outgoing ? this->Prev(neighbor) : this->Next(neighbor);
858 if (this->IsPathEnd(node2)) return false;
859 }
860 int64_t prev2, sibling2, sibling_prev2 = -1;
861 if (!GetPreviousAndSibling(node2, &prev2, &sibling2, &sibling_prev2)) {
862 return false;
863 }
864 bool status = true;
865 // Exchanging node1 and node2.
866 if (node1 == prev2) {
867 status = this->MoveChain(prev2, node2, prev1);
868 if (sibling_prev1 == node2) sibling_prev1 = node1;
869 if (sibling_prev2 == node2) sibling_prev2 = node1;
870 } else if (node2 == prev1) {
871 status = this->MoveChain(prev1, node1, prev2);
872 if (sibling_prev1 == node1) sibling_prev1 = node2;
873 if (sibling_prev2 == node1) sibling_prev2 = node2;
874 } else {
875 status = this->MoveChain(prev1, node1, node2) &&
876 this->MoveChain(prev2, node2, prev1);
877 if (sibling_prev1 == node1) {
878 sibling_prev1 = node2;
879 } else if (sibling_prev1 == node2) {
880 sibling_prev1 = node1;
881 }
882 if (sibling_prev2 == node1) {
883 sibling_prev2 = node2;
884 } else if (sibling_prev2 == node2) {
885 sibling_prev2 = node1;
886 }
887 }
888 if (!status) return false;
889 // Exchanging sibling1 and sibling2.
890 if (sibling1 == sibling_prev2) {
891 status = this->MoveChain(sibling_prev2, sibling2, sibling_prev1);
892 } else if (sibling2 == sibling_prev1) {
893 status = this->MoveChain(sibling_prev1, sibling1, sibling_prev2);
894 } else {
895 status = this->MoveChain(sibling_prev1, sibling1, sibling2) &&
896 this->MoveChain(sibling_prev2, sibling2, sibling_prev1);
897 }
898 // Swapping alternatives in.
899 this->SwapActiveAndInactive(sibling1, this->BaseSiblingAlternativeNode(0));
900 this->SwapActiveAndInactive(node1, this->BaseAlternativeNode(0));
901 if (!this->HasNeighbors()) {
902 // TODO(user): Support alternatives with neighbors.
903 this->SwapActiveAndInactive(sibling2, this->BaseSiblingAlternativeNode(1));
904 this->SwapActiveAndInactive(node2, this->BaseAlternativeNode(1));
905 }
906 return status;
907}
908
909template <bool ignore_path_vars>
910bool PairExchangeOperator<ignore_path_vars>::GetPreviousAndSibling(
911 int64_t node, int64_t* previous, int64_t* sibling,
912 int64_t* sibling_previous) const {
913 if (this->IsPathStart(node)) return false;
914 *previous = this->Prev(node);
915 *sibling = this->GetActiveAlternativeSibling(node);
916 *sibling_previous = *sibling >= 0 ? this->Prev(*sibling) : -1;
917 return *sibling_previous >= 0;
918}
919
921 Solver* solver, const std::vector<IntVar*>& vars,
922 const std::vector<IntVar*>& secondary_vars,
923 std::function<int(int64_t)> start_empty_path_class,
924 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
925 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
926 const std::vector<PickupDeliveryPair>& pairs) {
927 if (secondary_vars.empty()) {
928 return solver->RevAlloc(new PairExchangeOperator<true>(
929 vars, secondary_vars, std::move(start_empty_path_class),
930 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
931 pairs));
932 }
933 return solver->RevAlloc(new PairExchangeOperator<false>(
934 vars, secondary_vars, std::move(start_empty_path_class),
935 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
936 pairs));
937}
938
940 Solver* solver, const std::vector<IntVar*>& vars,
941 const std::vector<IntVar*>& secondary_vars,
942 std::function<int(int64_t)> start_empty_path_class,
943 const std::vector<PickupDeliveryPair>& pairs) {
944 return MakePairExchange(solver, vars, secondary_vars,
945 std::move(start_empty_path_class), nullptr, nullptr,
946 pairs);
947}
948
949template <bool ignore_path_vars>
951 const std::vector<IntVar*>& vars,
952 const std::vector<IntVar*>& secondary_vars,
953 std::function<int(int64_t)> start_empty_path_class,
954 const std::vector<PickupDeliveryPair>& pairs)
955 : PathOperator<ignore_path_vars>(vars, secondary_vars, 6, true, false,
956 std::move(start_empty_path_class), nullptr,
957 nullptr) {
958 this->AddPairAlternativeSets(pairs);
959}
960
961template <bool ignore_path_vars>
963 DCHECK_EQ(this->StartNode(kSecondPairFirstNodeDestination),
964 this->StartNode(kSecondPairSecondNodeDestination));
965 DCHECK_EQ(this->StartNode(kSecondPairFirstNode),
966 this->StartNode(kFirstPairFirstNodeDestination));
967 DCHECK_EQ(this->StartNode(kSecondPairFirstNode),
968 this->StartNode(kFirstPairSecondNodeDestination));
969
970 if (this->StartNode(kFirstPairFirstNode) ==
971 this->StartNode(kSecondPairFirstNode)) {
972 this->SetNextBaseToIncrement(kSecondPairFirstNode);
973 return false;
974 }
975 // Through this method, <base>[X][Y] represent the <base> variable for the
976 // node Y of pair X. <base> is in node, prev, dest.
977 int64_t nodes[2][2];
978 int64_t prev[2][2];
979 int64_t dest[2][2];
980 nodes[0][0] = this->BaseNode(kFirstPairFirstNode);
981 nodes[1][0] = this->BaseNode(kSecondPairFirstNode);
982 if (nodes[1][0] <= nodes[0][0]) {
983 // Exchange is symmetric.
984 this->SetNextBaseToIncrement(kSecondPairFirstNode);
985 return false;
986 }
987 if (!this->GetPreviousAndSibling(nodes[0][0], &prev[0][0], &nodes[0][1],
988 &prev[0][1])) {
989 this->SetNextBaseToIncrement(kFirstPairFirstNode);
990 return false;
991 }
992 if (!this->GetPreviousAndSibling(nodes[1][0], &prev[1][0], &nodes[1][1],
993 &prev[1][1])) {
994 this->SetNextBaseToIncrement(kSecondPairFirstNode);
995 return false;
996 }
997
998 if (!this->LoadAndCheckDest(0, 0, kFirstPairFirstNodeDestination, nodes,
999 dest)) {
1000 this->SetNextBaseToIncrement(kFirstPairFirstNodeDestination);
1001 return false;
1002 }
1003 if (!this->LoadAndCheckDest(0, 1, kFirstPairSecondNodeDestination, nodes,
1004 dest)) {
1005 this->SetNextBaseToIncrement(kFirstPairSecondNodeDestination);
1006 return false;
1007 }
1008 if (this->StartNode(kSecondPairFirstNodeDestination) !=
1009 this->StartNode(kFirstPairFirstNode) ||
1010 !this->LoadAndCheckDest(1, 0, kSecondPairFirstNodeDestination, nodes,
1011 dest)) {
1012 this->SetNextBaseToIncrement(kSecondPairFirstNodeDestination);
1013 return false;
1014 }
1015 if (!this->LoadAndCheckDest(1, 1, kSecondPairSecondNodeDestination, nodes,
1016 dest)) {
1017 this->SetNextBaseToIncrement(kSecondPairSecondNodeDestination);
1018 return false;
1019 }
1020
1021 if (!this->MoveNode(0, 1, nodes, dest, prev)) {
1022 this->SetNextBaseToIncrement(kFirstPairSecondNodeDestination);
1023 return false;
1024 }
1025 if (!this->MoveNode(0, 0, nodes, dest, prev)) {
1026 this->SetNextBaseToIncrement(kFirstPairSecondNodeDestination);
1027 return false;
1028 }
1029 if (!this->MoveNode(1, 1, nodes, dest, prev)) {
1030 return false;
1031 }
1032 if (!this->MoveNode(1, 0, nodes, dest, prev)) {
1033 return false;
1034 }
1035 return true;
1036}
1037
1038template <bool ignore_path_vars>
1039bool PairExchangeRelocateOperator<ignore_path_vars>::MoveNode(
1040 int pair, int node, int64_t nodes[2][2], int64_t dest[2][2],
1041 int64_t prev[2][2]) {
1042 if (!this->MoveChain(prev[pair][node], nodes[pair][node], dest[pair][node])) {
1043 return false;
1044 }
1045 // Update the other pair if needed.
1046 if (prev[1 - pair][0] == dest[pair][node]) {
1047 prev[1 - pair][0] = nodes[pair][node];
1048 }
1049 if (prev[1 - pair][1] == dest[pair][node]) {
1050 prev[1 - pair][1] = nodes[pair][node];
1051 }
1052 return true;
1053}
1054
1055template <bool ignore_path_vars>
1056bool PairExchangeRelocateOperator<ignore_path_vars>::LoadAndCheckDest(
1057 int pair, int node, int64_t base_node, int64_t nodes[2][2],
1058 int64_t dest[2][2]) const {
1059 dest[pair][node] = this->BaseNode(base_node);
1060 // A destination cannot be a node that will be moved.
1061 return !(nodes[0][0] == dest[pair][node] || nodes[0][1] == dest[pair][node] ||
1062 nodes[1][0] == dest[pair][node] || nodes[1][1] == dest[pair][node]);
1063}
1064
1065template <bool ignore_path_vars>
1067 int64_t base_index) {
1068 // Ensuring the destination of the first pair is on the route of the second.
1069 // pair.
1070 // Ensuring that destination of both nodes of a pair are on the same route.
1071 return base_index == kFirstPairFirstNodeDestination ||
1072 base_index == kFirstPairSecondNodeDestination ||
1073 base_index == kSecondPairSecondNodeDestination;
1074}
1075
1076template <bool ignore_path_vars>
1077int64_t
1079 int base_index) {
1080 if (base_index == kFirstPairSecondNodeDestination ||
1081 base_index == kSecondPairSecondNodeDestination) {
1082 return this->BaseNode(base_index - 1);
1083 } else {
1084 return this->StartNode(base_index);
1085 }
1086}
1087
1088template <bool ignore_path_vars>
1089bool PairExchangeRelocateOperator<ignore_path_vars>::GetPreviousAndSibling(
1090 int64_t node, int64_t* previous, int64_t* sibling,
1091 int64_t* sibling_previous) const {
1092 if (this->IsPathStart(node)) return false;
1093 *previous = this->Prev(node);
1094 *sibling = this->GetActiveAlternativeSibling(node);
1095 *sibling_previous = *sibling >= 0 ? this->Prev(*sibling) : -1;
1096 return *sibling_previous >= 0;
1097}
1098
1100 Solver* solver, const std::vector<IntVar*>& vars,
1101 const std::vector<IntVar*>& secondary_vars,
1102 std::function<int(int64_t)> start_empty_path_class,
1103 const std::vector<PickupDeliveryPair>& pairs) {
1104 if (secondary_vars.empty()) {
1106 vars, secondary_vars, std::move(start_empty_path_class), pairs));
1107 }
1109 vars, secondary_vars, std::move(start_empty_path_class), pairs));
1110}
1111
1113 const std::vector<IntVar*>& vars, const std::vector<IntVar*>& path_vars,
1114 const std::vector<PickupDeliveryPair>& pairs)
1116 pairs_(pairs),
1117 pair_index_(0),
1118 first_index_(0),
1119 second_index_(0),
1120 number_of_nexts_(vars.size()),
1121 ignore_path_vars_(path_vars.empty()) {
1122 if (!ignore_path_vars_) {
1123 AddVars(path_vars);
1124 }
1125}
1126
1128 Assignment* deltadelta) {
1129 const int64_t kNoPath = -1;
1130 CHECK(delta != nullptr);
1131 while (true) {
1132 RevertChanges(true);
1133
1134 if (pair_index_ >= pairs_.size()) return false;
1135 const int64_t path =
1136 ignore_path_vars_ ? 0LL : Value(first_active_ + number_of_nexts_);
1137 const int64_t prev_first = prevs_[first_active_];
1138 const int64_t next_first = Value(first_active_);
1139 // Making current active "pickup" unperformed.
1140 SetNext(first_active_, first_active_, kNoPath);
1141 // Inserting "pickup" alternative at the same position.
1142 const auto& [pickup_alternatives, delivery_alternatives] =
1143 pairs_[pair_index_];
1144 const int64_t insert_first = pickup_alternatives[first_index_];
1145 SetNext(prev_first, insert_first, path);
1146 SetNext(insert_first, next_first, path);
1147 int64_t prev_second = prevs_[second_active_];
1148 if (prev_second == first_active_) {
1149 prev_second = insert_first;
1150 }
1151 DCHECK_EQ(path, ignore_path_vars_
1152 ? int64_t{0}
1153 : Value(second_active_ + number_of_nexts_));
1154 const int64_t next_second = Value(second_active_);
1155 // Making current active "delivery" unperformed.
1156 SetNext(second_active_, second_active_, kNoPath);
1157 // Inserting "delivery" alternative at the same position.
1158 const int64_t insert_second = delivery_alternatives[second_index_];
1159 SetNext(prev_second, insert_second, path);
1160 SetNext(insert_second, next_second, path);
1161 // Move to next "pickup/delivery" alternative.
1162 ++second_index_;
1163 if (second_index_ >= delivery_alternatives.size()) {
1164 second_index_ = 0;
1165 ++first_index_;
1166 if (first_index_ >= pickup_alternatives.size()) {
1167 first_index_ = 0;
1168 while (true) {
1169 ++pair_index_;
1170 if (!UpdateActiveNodes()) break;
1171 if (first_active_ != -1 && second_active_ != -1) {
1172 break;
1173 }
1174 }
1175 }
1176 }
1177
1178 if (ApplyChanges(delta, deltadelta)) return true;
1179 }
1180 return false;
1181}
1182
1184 prevs_.resize(number_of_nexts_, -1);
1185 for (int index = 0; index < number_of_nexts_; ++index) {
1186 const int64_t next = Value(index);
1187 if (next >= prevs_.size()) prevs_.resize(next + 1, -1);
1188 prevs_[next] = index;
1189 }
1190 pair_index_ = 0;
1191 first_index_ = 0;
1192 second_index_ = 0;
1193 first_active_ = -1;
1194 second_active_ = -1;
1195 while (true) {
1196 if (!UpdateActiveNodes()) break;
1197 if (first_active_ != -1 && second_active_ != -1) {
1198 break;
1199 }
1200 ++pair_index_;
1201 }
1202}
1203
1204bool SwapIndexPairOperator::UpdateActiveNodes() {
1205 if (pair_index_ < pairs_.size()) {
1206 const auto& [pickup_alternatives, delivery_alternatives] =
1207 pairs_[pair_index_];
1208 first_active_ = -1;
1209 second_active_ = -1;
1210 if (pickup_alternatives.size() == 1 && delivery_alternatives.size() == 1) {
1211 // When there are no alternatives, the pair should be ignored whether
1212 // there are active nodes or not.
1213 return true;
1214 }
1215 for (const int64_t first : pickup_alternatives) {
1216 if (Value(first) != first) {
1217 first_active_ = first;
1218 break;
1219 }
1220 }
1221 for (const int64_t second : delivery_alternatives) {
1222 if (Value(second) != second) {
1223 second_active_ = second;
1224 break;
1225 }
1226 }
1227 return true;
1228 }
1229 return false;
1230}
1231
1232template <bool ignore_path_vars>
1234 const std::vector<IntVar*>& vars,
1235 const std::vector<IntVar*>& secondary_vars,
1236 std::function<int(int64_t)> start_empty_path_class,
1237 const std::vector<PickupDeliveryPair>& pairs)
1238 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, true, false,
1239 std::move(start_empty_path_class), nullptr,
1240 nullptr),
1241 inactive_node_(0) {
1242 this->AddPairAlternativeSets(pairs);
1243}
1244
1245template <bool ignore_path_vars>
1247 Assignment* delta, Assignment* deltadelta) {
1248 while (inactive_node_ < this->Size()) {
1249 if (!this->IsInactive(inactive_node_) ||
1251 this->ResetPosition();
1252 ++inactive_node_;
1253 } else {
1254 return true;
1255 }
1256 }
1257 return false;
1258}
1259
1260template <bool ignore_path_vars>
1262 const int64_t base = this->BaseNode(0);
1263 const int64_t next = this->Next(base);
1264 const int64_t other = this->GetActiveAlternativeSibling(next);
1265 if (other != -1) {
1266 return this->MakeChainInactive(this->Prev(other), other) &&
1267 this->MakeChainInactive(base, next) &&
1268 this->MakeActive(inactive_node_, base);
1269 }
1270 return false;
1271}
1272
1273template <bool ignore_path_vars>
1274void IndexPairSwapActiveOperator<ignore_path_vars>::OnNodeInitialization() {
1276 for (int i = 0; i < this->Size(); ++i) {
1277 if (this->IsInactive(i)) {
1278 inactive_node_ = i;
1279 return;
1280 }
1281 }
1282 inactive_node_ = this->Size();
1283}
1284
1286 Solver* solver, const std::vector<IntVar*>& vars,
1287 const std::vector<IntVar*>& secondary_vars,
1288 std::function<int(int64_t)> start_empty_path_class,
1289 const std::vector<PickupDeliveryPair>& pairs) {
1290 if (secondary_vars.empty()) {
1292 vars, secondary_vars, std::move(start_empty_path_class), pairs));
1293 }
1295 vars, secondary_vars, std::move(start_empty_path_class), pairs));
1296}
1297
1298template <bool ignore_path_vars>
1300 const std::vector<IntVar*>& vars,
1301 const std::vector<IntVar*>& secondary_vars,
1302 std::function<int(int64_t)> start_empty_path_class,
1303 int num_arcs_to_consider,
1304 std::function<int64_t(int64_t, int64_t, int64_t)> arc_cost_for_path_start)
1305 : PathOperator<ignore_path_vars>(vars, secondary_vars, 1, false, false,
1306 std::move(start_empty_path_class), nullptr,
1307 nullptr),
1308 num_arcs_to_consider_(num_arcs_to_consider),
1309 current_path_(0),
1310 current_expensive_arc_indices_({-1, -1}),
1311 arc_cost_for_path_start_(std::move(arc_cost_for_path_start)),
1312 end_path_(0),
1313 has_non_empty_paths_to_explore_(false) {
1314 DCHECK_GE(num_arcs_to_consider_, 2);
1315}
1316
1317template <bool ignore_path_vars>
1319 // TODO(user): Consider node neighbors? The operator would no longer be
1320 // a path operator though, because we would no longer have any base nodes.
1321 const int first_arc_index = current_expensive_arc_indices_.first;
1322 const int second_arc_index = current_expensive_arc_indices_.second;
1323 DCHECK_LE(0, first_arc_index);
1324 DCHECK_LT(first_arc_index, second_arc_index);
1325 DCHECK_LT(second_arc_index, most_expensive_arc_starts_and_ranks_.size());
1326
1327 const std::pair<int, int>& first_start_and_rank =
1328 most_expensive_arc_starts_and_ranks_[first_arc_index];
1329 const std::pair<int, int>& second_start_and_rank =
1330 most_expensive_arc_starts_and_ranks_[second_arc_index];
1331 if (first_start_and_rank.second < second_start_and_rank.second) {
1332 return this->CheckChainValidity(first_start_and_rank.first,
1333 second_start_and_rank.first,
1334 this->BaseNode(0)) &&
1335 this->MoveChain(first_start_and_rank.first,
1336 second_start_and_rank.first, this->BaseNode(0));
1337 }
1338 return this->CheckChainValidity(second_start_and_rank.first,
1339 first_start_and_rank.first,
1340 this->BaseNode(0)) &&
1341 this->MoveChain(second_start_and_rank.first,
1342 first_start_and_rank.first, this->BaseNode(0));
1343}
1344
1345template <bool ignore_path_vars>
1347 while (has_non_empty_paths_to_explore_) {
1349 this->ResetPosition();
1350 // Move on to the next expensive arcs on the same path.
1351 if (IncrementCurrentArcIndices()) {
1352 continue;
1353 }
1354 // Move on to the next non_empty path.
1355 IncrementCurrentPath();
1356 has_non_empty_paths_to_explore_ =
1357 current_path_ != end_path_ &&
1358 FindMostExpensiveChainsOnRemainingPaths();
1359 } else {
1360 return true;
1361 }
1362 }
1363 return false;
1364}
1365
1366template <bool ignore_path_vars>
1367void RelocateExpensiveChain<ignore_path_vars>::OnNodeInitialization() {
1368 if (current_path_ >= this->path_starts().size()) {
1369 // current_path_ was made empty by last move (and it was the last non-empty
1370 // path), restart from 0.
1371 current_path_ = 0;
1372 }
1373 end_path_ = current_path_;
1374 has_non_empty_paths_to_explore_ = FindMostExpensiveChainsOnRemainingPaths();
1375}
1376
1377template <bool ignore_path_vars>
1378void RelocateExpensiveChain<ignore_path_vars>::IncrementCurrentPath() {
1379 const int num_paths = this->path_starts().size();
1380 if (++current_path_ == num_paths) {
1381 current_path_ = 0;
1382 }
1383}
1384
1385template <bool ignore_path_vars>
1386bool RelocateExpensiveChain<ignore_path_vars>::IncrementCurrentArcIndices() {
1387 int& second_index = current_expensive_arc_indices_.second;
1388 if (++second_index < most_expensive_arc_starts_and_ranks_.size()) {
1389 return true;
1390 }
1391 int& first_index = current_expensive_arc_indices_.first;
1392 if (first_index + 2 < most_expensive_arc_starts_and_ranks_.size()) {
1393 first_index++;
1394 second_index = first_index + 1;
1395 return true;
1396 }
1397 return false;
1398}
1399
1400template <bool ignore_path_vars>
1402 ignore_path_vars>::FindMostExpensiveChainsOnRemainingPaths() {
1403 do {
1405 num_arcs_to_consider_, this->path_starts()[current_path_],
1406 [this](int64_t i) { return this->OldNext(i); },
1407 [this](int64_t node) { return this->IsPathEnd(node); },
1408 arc_cost_for_path_start_, &most_expensive_arc_starts_and_ranks_,
1409 &current_expensive_arc_indices_)) {
1410 return true;
1411 }
1412 IncrementCurrentPath();
1413 } while (current_path_ != end_path_);
1414 return false;
1415}
1416
1418 Solver* solver, const std::vector<IntVar*>& vars,
1419 const std::vector<IntVar*>& secondary_vars,
1420 std::function<int(int64_t)> start_empty_path_class,
1421 int num_arcs_to_consider,
1422 std::function<int64_t(int64_t, int64_t, int64_t)> arc_cost_for_path_start) {
1423 if (secondary_vars.empty()) {
1424 return solver->RevAlloc(new RelocateExpensiveChain<true>(
1425 vars, secondary_vars, std::move(start_empty_path_class),
1426 num_arcs_to_consider, std::move(arc_cost_for_path_start)));
1427 }
1428 return solver->RevAlloc(new RelocateExpensiveChain<false>(
1429 vars, secondary_vars, std::move(start_empty_path_class),
1430 num_arcs_to_consider, std::move(arc_cost_for_path_start)));
1431}
1432
1434 int num_nodes, absl::Span<const PickupDeliveryPair> pairs)
1435 : is_pickup_node_(num_nodes, false),
1436 is_delivery_node_(num_nodes, false),
1437 pair_of_node_(num_nodes, -1) {
1438 for (int pair_index = 0; pair_index < pairs.size(); ++pair_index) {
1439 for (const int node : pairs[pair_index].pickup_alternatives) {
1440 is_pickup_node_[node] = true;
1441 pair_of_node_[node] = pair_index;
1442 }
1443 for (const int node : pairs[pair_index].delivery_alternatives) {
1444 is_delivery_node_[node] = true;
1445 pair_of_node_[node] = pair_index;
1446 }
1447 }
1448}
1449
1450template <bool ignore_path_vars>
1452 const std::vector<IntVar*>& vars,
1453 const std::vector<IntVar*>& secondary_vars,
1454 std::function<int(int64_t)> start_empty_path_class, NeighborAccessor,
1455 NeighborAccessor get_outgoing_neighbors,
1456 absl::Span<const PickupDeliveryPair> pairs)
1457 : PathOperator<ignore_path_vars>(
1458 vars, secondary_vars,
1459 /*number_of_base_nodes=*/
1460 get_outgoing_neighbors == nullptr ? 2 : 1,
1461 /*skip_locally_optimal_paths=*/true,
1462 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
1463 nullptr, // Incoming neighbors aren't supported as of 09/2024.
1464 std::move(get_outgoing_neighbors)),
1465 pd_data_(this->number_of_nexts_, pairs) {
1466 opened_pairs_set_.resize(pairs.size(), false);
1467}
1468
1469template <bool ignore_path_vars>
1470void RelocateSubtrip<ignore_path_vars>::SetPath(absl::Span<const int64_t> path,
1471 int path_id) {
1472 for (int i = 1; i < path.size(); ++i) {
1473 this->SetNext(path[i - 1], path[i], path_id);
1474 }
1475}
1476
1477template <bool ignore_path_vars>
1478bool RelocateSubtrip<ignore_path_vars>::RelocateSubTripFromPickup(
1479 const int64_t chain_first_node, const int64_t insertion_node) {
1480 if (this->IsPathEnd(insertion_node)) return false;
1481 if (this->Prev(chain_first_node) == insertion_node)
1482 return false; // Skip null move.
1483
1484 int num_opened_pairs = 0;
1485 // Split chain into subtrip and rejected nodes.
1486 rejected_nodes_ = {this->Prev(chain_first_node)};
1487 subtrip_nodes_ = {insertion_node};
1488 int current = chain_first_node;
1489 do {
1490 if (current == insertion_node) {
1491 // opened_pairs_set_ must be all false when we leave this function.
1492 opened_pairs_set_.assign(opened_pairs_set_.size(), false);
1493 return false;
1494 }
1495 const int pair = pd_data_.GetPairOfNode(current);
1496 if (pd_data_.IsDeliveryNode(current) && !opened_pairs_set_[pair]) {
1497 rejected_nodes_.push_back(current);
1498 } else {
1499 subtrip_nodes_.push_back(current);
1500 if (pd_data_.IsPickupNode(current)) {
1501 ++num_opened_pairs;
1502 opened_pairs_set_[pair] = true;
1503 } else if (pd_data_.IsDeliveryNode(current)) {
1504 --num_opened_pairs;
1505 opened_pairs_set_[pair] = false;
1506 }
1507 }
1508 current = this->Next(current);
1509 } while (num_opened_pairs != 0 && !this->IsPathEnd(current));
1510 DCHECK_EQ(num_opened_pairs, 0);
1511 rejected_nodes_.push_back(current);
1512 subtrip_nodes_.push_back(this->Next(insertion_node));
1513
1514 // Set new paths.
1515 SetPath(rejected_nodes_, this->Path(chain_first_node));
1516 SetPath(subtrip_nodes_, this->Path(insertion_node));
1517 return true;
1518}
1519
1520template <bool ignore_path_vars>
1521bool RelocateSubtrip<ignore_path_vars>::RelocateSubTripFromDelivery(
1522 const int64_t chain_last_node, const int64_t insertion_node) {
1523 if (this->IsPathEnd(insertion_node)) return false;
1524
1525 // opened_pairs_set_ should be all false.
1526 DCHECK(std::none_of(opened_pairs_set_.begin(), opened_pairs_set_.end(),
1527 [](bool value) { return value; }));
1528 int num_opened_pairs = 0;
1529 // Split chain into subtrip and rejected nodes. Store nodes in reverse order.
1530 rejected_nodes_ = {this->Next(chain_last_node)};
1531 subtrip_nodes_ = {this->Next(insertion_node)};
1532 int current = chain_last_node;
1533 do {
1534 if (current == insertion_node) {
1535 opened_pairs_set_.assign(opened_pairs_set_.size(), false);
1536 return false;
1537 }
1538 const int pair = pd_data_.GetPairOfNode(current);
1539 if (pd_data_.IsPickupNode(current) && !opened_pairs_set_[pair]) {
1540 rejected_nodes_.push_back(current);
1541 } else {
1542 subtrip_nodes_.push_back(current);
1543 if (pd_data_.IsDeliveryNode(current)) {
1544 ++num_opened_pairs;
1545 opened_pairs_set_[pair] = true;
1546 } else if (pd_data_.IsPickupNode(current)) {
1547 --num_opened_pairs;
1548 opened_pairs_set_[pair] = false;
1549 }
1550 }
1551 current = this->Prev(current);
1552 } while (num_opened_pairs != 0 && !this->IsPathStart(current));
1553 DCHECK_EQ(num_opened_pairs, 0);
1554 if (current == insertion_node) return false; // Skip null move.
1555 rejected_nodes_.push_back(current);
1556 subtrip_nodes_.push_back(insertion_node);
1557
1558 // TODO(user): either remove those std::reverse() and adapt the loops
1559 // below, or refactor the loops into a function that also DCHECKs the path.
1560 std::reverse(rejected_nodes_.begin(), rejected_nodes_.end());
1561 std::reverse(subtrip_nodes_.begin(), subtrip_nodes_.end());
1562
1563 // Set new paths.
1564 SetPath(rejected_nodes_, this->Path(chain_last_node));
1565 SetPath(subtrip_nodes_, this->Path(insertion_node));
1566 return true;
1567}
1568
1569template <bool ignore_path_vars>
1571 const auto do_move = [this](int64_t node, int64_t insertion_node) {
1572 if (this->IsInactive(node)) return false;
1573 if (pd_data_.IsPickupNode(node)) {
1574 return RelocateSubTripFromPickup(node, insertion_node);
1575 } else if (pd_data_.IsDeliveryNode(node)) {
1576 return RelocateSubTripFromDelivery(node, insertion_node);
1577 } else {
1578 return false;
1579 }
1580 };
1581 if (this->HasNeighbors()) {
1582 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
1583 if (neighbor < 0) return false;
1584 DCHECK(outgoing);
1585 if (this->IsInactive(neighbor)) return false;
1586 return do_move(/*node=*/neighbor, /*insertion_node=*/this->BaseNode(0));
1587 }
1588 return do_move(/*node=*/this->BaseNode(0),
1589 /*insertion_node=*/this->BaseNode(1));
1590}
1591
1593 Solver* solver, const std::vector<IntVar*>& vars,
1594 const std::vector<IntVar*>& secondary_vars,
1595 std::function<int(int64_t)> start_empty_path_class,
1596 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
1597 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
1598 absl::Span<const PickupDeliveryPair> pairs) {
1599 if (secondary_vars.empty()) {
1600 return solver->RevAlloc(new RelocateSubtrip<true>(
1601 vars, secondary_vars, std::move(start_empty_path_class),
1602 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
1603 pairs));
1604 }
1605 return solver->RevAlloc(new RelocateSubtrip<false>(
1606 vars, secondary_vars, std::move(start_empty_path_class),
1607 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
1608 pairs));
1609}
1610
1612 Solver* solver, const std::vector<IntVar*>& vars,
1613 const std::vector<IntVar*>& secondary_vars,
1614 std::function<int(int64_t)> start_empty_path_class,
1615 absl::Span<const PickupDeliveryPair> pairs) {
1616 return MakeRelocateSubtrip(solver, vars, secondary_vars,
1617 std::move(start_empty_path_class), nullptr,
1618 nullptr, pairs);
1619}
1620
1621template <bool ignore_path_vars>
1623 const std::vector<IntVar*>& vars,
1624 const std::vector<IntVar*>& secondary_vars,
1625 std::function<int(int64_t)> start_empty_path_class, NeighborAccessor,
1626 NeighborAccessor get_outgoing_neighbors,
1627 absl::Span<const PickupDeliveryPair> pairs)
1628 : PathOperator<ignore_path_vars>(
1629 vars, secondary_vars,
1630 /*number_of_base_nodes=*/
1631 get_outgoing_neighbors == nullptr ? 2 : 1,
1632 /*skip_locally_optimal_paths=*/true,
1633 /*accept_path_end_base=*/false, std::move(start_empty_path_class),
1634 nullptr, // Incoming neighbors aren't supported as of 09/2024.
1635 std::move(get_outgoing_neighbors)),
1636 pd_data_(this->number_of_nexts_, pairs) {
1637 opened_pairs_set_.resize(pairs.size(), false);
1638}
1639
1640template <bool ignore_path_vars>
1641void ExchangeSubtrip<ignore_path_vars>::SetPath(absl::Span<const int64_t> path,
1642 int path_id) {
1643 for (int i = 1; i < path.size(); ++i) {
1644 this->SetNext(path[i - 1], path[i], path_id);
1645 }
1646}
1647
1648namespace {
1649bool VectorContains(absl::Span<const int64_t> values, int64_t target) {
1650 return std::find(values.begin(), values.end(), target) != values.end();
1651}
1652} // namespace
1653
1654template <bool ignore_path_vars>
1656 int64_t node0 = -1;
1657 int64_t node1 = -1;
1658 if (this->HasNeighbors()) {
1659 const int64_t node = this->BaseNode(0);
1660 const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
1661 if (neighbor < 0) return false;
1662 DCHECK(outgoing);
1663 if (this->IsInactive(neighbor)) return false;
1664 if (pd_data_.IsDeliveryNode(node) &&
1665 pd_data_.IsDeliveryNode(this->Prev(neighbor))) {
1666 node0 = node;
1667 node1 = this->Prev(neighbor);
1668 } else if (pd_data_.IsPickupNode(neighbor) &&
1669 !this->IsPathEnd(this->Next(node)) &&
1670 pd_data_.IsPickupNode(this->Next(node))) {
1671 node0 = this->Next(node);
1672 node1 = neighbor;
1673 } else {
1674 return false;
1675 }
1676 } else {
1677 node0 = this->BaseNode(0);
1678 node1 = this->BaseNode(1);
1679 }
1680
1681 if (pd_data_.GetPairOfNode(node0) == -1) return false;
1682 if (pd_data_.GetPairOfNode(node1) == -1) return false;
1683 // Break symmetry: a move generated from (node0, node1) is the
1684 // same as from (node1, node0): no need to do it twice.
1685 if (node0 >= node1) return false;
1686 rejects0_.clear();
1687 subtrip0_.clear();
1688 if (!ExtractChainsAndCheckCanonical(node0, &rejects0_, &subtrip0_)) {
1689 return false;
1690 }
1691 rejects1_.clear();
1692 subtrip1_.clear();
1693 if (!ExtractChainsAndCheckCanonical(node1, &rejects1_, &subtrip1_)) {
1694 return false;
1695 }
1696
1697 // If paths intersect, skip the move.
1698 if (this->HasNeighbors() || this->StartNode(0) == this->StartNode(1)) {
1699 if (VectorContains(rejects0_, subtrip1_.front())) return false;
1700 if (VectorContains(rejects1_, subtrip0_.front())) return false;
1701 if (VectorContains(subtrip0_, subtrip1_.front())) return false;
1702 if (VectorContains(subtrip1_, subtrip0_.front())) return false;
1703 }
1704
1705 // Assemble the new paths.
1706 path0_ = {this->Prev(subtrip0_.front())};
1707 path1_ = {this->Prev(subtrip1_.front())};
1708 const int64_t last0 = this->Next(subtrip0_.back());
1709 const int64_t last1 = this->Next(subtrip1_.back());
1710 const bool concatenated01 = last0 == subtrip1_.front();
1711 const bool concatenated10 = last1 == subtrip0_.front();
1712
1713 if (pd_data_.IsDeliveryNode(node0)) std::swap(subtrip1_, rejects0_);
1714 path0_.insert(path0_.end(), subtrip1_.begin(), subtrip1_.end());
1715 path0_.insert(path0_.end(), rejects0_.begin(), rejects0_.end());
1716 path0_.push_back(last0);
1717
1718 if (pd_data_.IsDeliveryNode(node1)) std::swap(subtrip0_, rejects1_);
1719 path1_.insert(path1_.end(), subtrip0_.begin(), subtrip0_.end());
1720 path1_.insert(path1_.end(), rejects1_.begin(), rejects1_.end());
1721 path1_.push_back(last1);
1722
1723 // When the trips are concatenated, bypass the regular extremities.
1724 if (concatenated01) {
1725 path0_.pop_back();
1726 path1_.front() = path0_.back();
1727 } else if (concatenated10) {
1728 path1_.pop_back();
1729 path0_.front() = path1_.back();
1730 }
1731
1732 // Change the paths. Since SetNext() modifies Path() values,
1733 // record path_id0 and path_id11 before calling SetPath();
1734 const int64_t path0_id = this->Path(node0);
1735 const int64_t path1_id = this->Path(node1);
1736 SetPath(path0_, path0_id);
1737 SetPath(path1_, path1_id);
1738 return true;
1739}
1740
1741template <bool ignore_path_vars>
1742bool ExchangeSubtrip<ignore_path_vars>::ExtractChainsAndCheckCanonical(
1743 int64_t base_node, std::vector<int64_t>* rejects,
1744 std::vector<int64_t>* subtrip) {
1745 const bool extracted =
1746 pd_data_.IsPickupNode(base_node)
1747 ? ExtractChainsFromPickup(base_node, rejects, subtrip)
1748 : ExtractChainsFromDelivery(base_node, rejects, subtrip);
1749 if (!extracted) return false;
1750 // Check canonicality.
1751 return !pd_data_.IsDeliveryNode(base_node) ||
1752 pd_data_.GetPairOfNode(subtrip->front()) !=
1753 pd_data_.GetPairOfNode(subtrip->back()) ||
1754 !rejects->empty();
1755}
1756
1757template <bool ignore_path_vars>
1758bool ExchangeSubtrip<ignore_path_vars>::ExtractChainsFromPickup(
1759 int64_t base_node, std::vector<int64_t>* rejects,
1760 std::vector<int64_t>* subtrip) {
1761 DCHECK(pd_data_.IsPickupNode(base_node));
1762 DCHECK(rejects->empty());
1763 DCHECK(subtrip->empty());
1764 // Iterate from base_node forwards while maintaining the set of opened pairs.
1765 // A pair is opened by a pickup, closed with the corresponding delivery.
1766 opened_pairs_set_.assign(opened_pairs_set_.size(), false);
1767 int num_opened_pairs = 0;
1768 int current = base_node;
1769 do {
1770 const int pair = pd_data_.GetPairOfNode(current);
1771 if (pd_data_.IsDeliveryNode(current) && !opened_pairs_set_[pair]) {
1772 rejects->push_back(current);
1773 } else {
1774 subtrip->push_back(current);
1775 if (pd_data_.IsPickupNode(current)) {
1776 ++num_opened_pairs;
1777 opened_pairs_set_[pair] = true;
1778 } else if (pd_data_.IsDeliveryNode(current)) {
1779 --num_opened_pairs;
1780 opened_pairs_set_[pair] = false;
1781 }
1782 }
1783 current = this->Next(current);
1784 } while (num_opened_pairs != 0 && !this->IsPathEnd(current));
1785 return num_opened_pairs == 0;
1786}
1787
1788template <bool ignore_path_vars>
1789bool ExchangeSubtrip<ignore_path_vars>::ExtractChainsFromDelivery(
1790 int64_t base_node, std::vector<int64_t>* rejects,
1791 std::vector<int64_t>* subtrip) {
1792 DCHECK(pd_data_.IsDeliveryNode(base_node));
1793 DCHECK(rejects->empty());
1794 DCHECK(subtrip->empty());
1795 // Iterate from base_node backwards while maintaining the set of opened pairs.
1796 // A pair is opened by a delivery, closed with the corresponding pickup.
1797 opened_pairs_set_.assign(opened_pairs_set_.size(), false);
1798 int num_opened_pairs = 0;
1799 int current = base_node;
1800 do {
1801 const int pair = pd_data_.GetPairOfNode(current);
1802 if (pd_data_.IsPickupNode(current) && !opened_pairs_set_[pair]) {
1803 rejects->push_back(current);
1804 } else {
1805 subtrip->push_back(current);
1806 if (pd_data_.IsDeliveryNode(current)) {
1807 ++num_opened_pairs;
1808 opened_pairs_set_[pair] = true;
1809 } else if (pd_data_.IsPickupNode(current)) {
1810 --num_opened_pairs;
1811 opened_pairs_set_[pair] = false;
1812 }
1813 }
1814 current = this->Prev(current);
1815 } while (num_opened_pairs != 0 && !this->IsPathStart(current));
1816 if (num_opened_pairs != 0) return false;
1817 std::reverse(rejects->begin(), rejects->end());
1818 std::reverse(subtrip->begin(), subtrip->end());
1819 return true;
1820}
1821
1823 Solver* solver, const std::vector<IntVar*>& vars,
1824 const std::vector<IntVar*>& secondary_vars,
1825 std::function<int(int64_t)> start_empty_path_class,
1826 std::function<const std::vector<int>&(int, int)> get_incoming_neighbors,
1827 std::function<const std::vector<int>&(int, int)> get_outgoing_neighbors,
1828 absl::Span<const PickupDeliveryPair> pairs) {
1829 if (secondary_vars.empty()) {
1830 return solver->RevAlloc(new ExchangeSubtrip<true>(
1831 vars, secondary_vars, std::move(start_empty_path_class),
1832 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
1833 pairs));
1834 }
1835 return solver->RevAlloc(new ExchangeSubtrip<false>(
1836 vars, secondary_vars, std::move(start_empty_path_class),
1837 std::move(get_incoming_neighbors), std::move(get_outgoing_neighbors),
1838 pairs));
1839}
1840
1842 Solver* solver, const std::vector<IntVar*>& vars,
1843 const std::vector<IntVar*>& secondary_vars,
1844 std::function<int(int64_t)> start_empty_path_class,
1845 absl::Span<const PickupDeliveryPair> pairs) {
1846 return MakeExchangeSubtrip(solver, vars, secondary_vars,
1847 std::move(start_empty_path_class), nullptr,
1848 nullptr, pairs);
1849}
1850
1851} // namespace operations_research
ExchangeSubtrip(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, absl::Span< const PickupDeliveryPair > pairs)
GroupPairAndRelocateOperator(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs)
IndexPairSwapActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta) override
void RevertChanges(bool change_was_incremental)
void AddVars(const std::vector< IntVar * > &vars)
bool ApplyChanges(Assignment *delta, Assignment *deltadelta) const
IntVarLocalSearchOperator(const std::vector< IntVar * > &vars, bool keep_inverse_values=false)
LightPairRelocateOperator(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs, std::function< bool(int64_t)> force_lifo=nullptr)
virtual bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta)=0
MakePairActiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
int64_t GetBaseNodeRestartPosition(int base_index) override
Operator which makes pairs of active nodes inactive.
MakePairInactiveOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
MakeRelocateNeighborsOperator(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, RoutingTransitCallback2 arc_evaluator)
PairExchangeOperator(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs)
PairExchangeRelocateOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
PairRelocateOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
int64_t GetBaseNodeRestartPosition(int base_index) override
int64_t Prev(int64_t node) const
Returns the node before node in the current delta.
bool SwapActiveAndInactive(int64_t active, int64_t inactive)
Replaces active by inactive in the current path, making active inactive.
int64_t Path(int64_t node) const
void AddPairAlternativeSets(const std::vector< PairType > &pair_alternative_sets)
int64_t BaseSiblingAlternativeNode(int i) const
Returns the alternative node for the sibling of the ith base node.
int64_t GetActiveAlternativeSibling(int 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)
bool SwapActiveAndInactiveChains(absl::Span< const int64_t > active_chain, absl::Span< const int64_t > inactive_chain)
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
int64_t BaseAlternativeNode(int i) const
Returns the alternative node for the ith base node.
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.
PickupAndDeliveryData(int num_nodes, absl::Span< const PickupDeliveryPair > pairs)
RelocateExpensiveChain(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, int num_arcs_to_consider, std::function< int64_t(int64_t, int64_t, int64_t)> arc_cost_for_path_start)
bool MakeOneNeighbor() override
This method should not be overridden. Override MakeNeighbor() instead.
RelocateSubtrip(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, absl::Span< const PickupDeliveryPair > pairs)
absl::Span< const int64_t > GetShortestPath(int64_t source, int64_t sink, absl::Span< const int64_t > chain)
ShortestPathOnAlternatives(int num_nodes, std::vector< std::vector< int64_t > > alternative_sets, RoutingTransitCallback2 arc_evaluator)
For the time being, Solver is neither MT_SAFE nor MT_HOT.
SwapActiveToShortestPathOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::vector< std::vector< int64_t > > alternative_sets, RoutingTransitCallback2 arc_evaluator)
SwapIndexPairOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &path_vars, const std::vector< PickupDeliveryPair > &pairs)
bool MakeNextNeighbor(Assignment *delta, Assignment *deltadelta) override
--— Base operator class for operators manipulating IntVars --—
TwoOptWithShortestPathOperator(const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::vector< std::vector< int64_t > > alternative_sets, RoutingTransitCallback2 arc_evaluator)
In SWIG mode, we don't want anything besides these top-level includes.
std::function< const std::vector< int > &(int, int)> NeighborAccessor
--— Path-based Operators --—
int64_t CapAdd(int64_t x, int64_t y)
LocalSearchOperator * MakeExchangeSubtrip(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, absl::Span< const PickupDeliveryPair > pairs)
LocalSearchOperator * MakePairActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
LocalSearchOperator * MakeLightPairRelocate(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs, std::function< bool(int64_t)> force_lifo)
LocalSearchOperator * MakePairExchangeRelocate(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
LocalSearchOperator * MakeGroupPairAndRelocate(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs)
LocalSearchOperator * MakePairInactive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
LocalSearchOperator * MakePairRelocate(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
LocalSearchOperator * MakeSwapActiveToShortestPath(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::vector< std::vector< int64_t > > alternative_sets, RoutingTransitCallback2 arc_evaluator)
LocalSearchOperator * MakeRelocateExpensiveChain(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, int num_arcs_to_consider, std::function< int64_t(int64_t, int64_t, int64_t)> arc_cost_for_path_start)
LocalSearchOperator * MakeIndexPairSwapActive(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, const std::vector< PickupDeliveryPair > &pairs)
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 --—
LocalSearchOperator * MakeRelocateSubtrip(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, absl::Span< const PickupDeliveryPair > pairs)
bool FindMostExpensiveArcsOnRoute(int num_arcs, int64_t start, const std::function< int64_t(int64_t)> &next_accessor, const std::function< bool(int64_t)> &is_end, const std::function< int64_t(int64_t, int64_t, int64_t)> &arc_cost_for_route_start, std::vector< std::pair< int64_t, int > > *most_expensive_arc_starts_and_ranks, std::pair< int, int > *first_expensive_arc_indices)
LocalSearchOperator * MakeTwoOptWithShortestPath(Solver *solver, const std::vector< IntVar * > &vars, const std::vector< IntVar * > &secondary_vars, std::function< int(int64_t)> start_empty_path_class, std::vector< std::vector< int64_t > > alternative_sets, RoutingTransitCallback2 arc_evaluator)
std::function< int64_t(int64_t, int64_t)> RoutingTransitCallback2
LocalSearchOperator * MakeRelocateNeighbors(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, RoutingTransitCallback2 arc_evaluator)
LocalSearchOperator * MakePairExchange(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, std::function< const std::vector< int > &(int, int)> get_outgoing_neighbors, const std::vector< PickupDeliveryPair > &pairs)
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 --—
STL namespace.
bool Next()
static const int64_t kint64max
Definition types.h:32