Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
perfect_matching.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 <cstdlib>
19#include <limits>
20#include <memory>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/base/log_severity.h"
26#include "absl/log/check.h"
27#include "absl/log/log.h"
28#include "absl/strings/str_cat.h"
29#include "absl/strings/str_join.h"
31
32namespace operations_research {
33
34void MinCostPerfectMatching::Reset(int num_nodes) {
35 graph_ = std::make_unique<BlossomGraph>(num_nodes);
36 optimal_cost_ = 0;
37 matches_.assign(num_nodes, -1);
38}
39
40void MinCostPerfectMatching::AddEdgeWithCost(int tail, int head, int64_t cost) {
41 CHECK_GE(cost, 0) << "Not supported for now, just shift your costs.";
42 if (tail == head) {
43 VLOG(1) << "Ignoring self-arc: " << tail << " <-> " << head
44 << " cost: " << cost;
45 return;
46 }
47 maximum_edge_cost_ = std::max(maximum_edge_cost_, cost);
48 graph_->AddEdge(BlossomGraph::NodeIndex(tail), BlossomGraph::NodeIndex(head),
49 BlossomGraph::CostValue(cost));
50}
51
53 optimal_solution_found_ = false;
54
55 // We want all dual and all slack value to never overflow. After Initialize()
56 // they are both bounded by the 2 * maximum cost. And we track an upper bound
57 // on these quantities. The factor two is because of the re-scaling we do
58 // internally since all our dual values are actually multiple of 1/2.
59 //
60 // Note that since the whole code in BlossomGraph assumes that dual/slack have
61 // a magnitude that is always lower than kMaxCostValue it is important to use
62 // it here since there is no reason it cannot be smaller than kint64max.
63 //
64 // TODO(user): Improve the overflow detection if needed. The current one seems
65 // ok though.
66 int64_t overflow_detection = CapAdd(maximum_edge_cost_, maximum_edge_cost_);
67 if (overflow_detection >= BlossomGraph::kMaxCostValue) {
69 }
70
71 const int num_nodes = matches_.size();
72 if (!graph_->Initialize()) return Status::INFEASIBLE;
73 VLOG(2) << graph_->DebugString();
74 VLOG(1) << "num_unmatched: " << num_nodes - graph_->NumMatched()
75 << " dual_objective: " << graph_->DualObjective();
76
77 while (graph_->NumMatched() != num_nodes) {
78 graph_->PrimalUpdates();
79 if (DEBUG_MODE) {
80 graph_->DebugCheckNoPossiblePrimalUpdates();
81 }
82
83 VLOG(1) << "num_unmatched: " << num_nodes - graph_->NumMatched()
84 << " dual_objective: " << graph_->DualObjective();
85 if (graph_->NumMatched() == num_nodes) break;
86
87 const BlossomGraph::CostValue delta =
88 graph_->ComputeMaxCommonTreeDualDeltaAndResetPrimalEdgeQueue();
89 overflow_detection = CapAdd(overflow_detection, std::abs(delta.value()));
90 if (overflow_detection >= BlossomGraph::kMaxCostValue) {
92 }
93
94 if (delta == 0) break; // Infeasible!
95 graph_->UpdateAllTrees(delta);
96 }
97
98 VLOG(1) << "End: " << graph_->NumMatched() << " / " << num_nodes;
99 graph_->DisplayStats();
100 if (graph_->NumMatched() < num_nodes) {
101 return Status::INFEASIBLE;
102 }
103 VLOG(2) << graph_->DebugString();
104 CHECK(graph_->DebugDualsAreFeasible());
105
106 // TODO(user): Maybe there is a faster/better way to recover the mapping
107 // in the presence of blossoms.
108 graph_->ExpandAllBlossoms();
109 for (int i = 0; i < num_nodes; ++i) {
110 matches_[i] = graph_->Match(BlossomGraph::NodeIndex(i)).value();
111 }
112
113 optimal_solution_found_ = true;
114 optimal_cost_ = graph_->DualObjective().value();
115 if (optimal_cost_ == std::numeric_limits<int64_t>::max())
117 return Status::OPTIMAL;
118}
119
120using NodeIndex = BlossomGraph::NodeIndex;
121using CostValue = BlossomGraph::CostValue;
122
123const BlossomGraph::NodeIndex BlossomGraph::kNoNodeIndex =
124 BlossomGraph::NodeIndex(-1);
125const BlossomGraph::EdgeIndex BlossomGraph::kNoEdgeIndex =
126 BlossomGraph::EdgeIndex(-1);
127const BlossomGraph::CostValue BlossomGraph::kMaxCostValue =
128 BlossomGraph::CostValue(std::numeric_limits<int64_t>::max());
129
131 graph_.resize(num_nodes);
132 nodes_.reserve(num_nodes);
133 root_blossom_node_.resize(num_nodes);
134 for (NodeIndex n(0); n < num_nodes; ++n) {
135 root_blossom_node_[n] = n;
136 nodes_.push_back(Node(n));
137 }
138}
139
141 DCHECK_GE(tail, 0);
142 DCHECK_LT(tail, nodes_.size());
143 DCHECK_GE(head, 0);
144 DCHECK_LT(head, nodes_.size());
145 DCHECK_GE(cost, 0);
146 DCHECK(!is_initialized_);
147 const EdgeIndex index(edges_.size());
148 edges_.push_back(Edge(tail, head, cost));
149 graph_[tail].push_back(index);
150 graph_[head].push_back(index);
151}
152
153// TODO(user): Code the more advanced "Fractional matching initialization"
154// heuristic.
155//
156// TODO(user): Add a preprocessing step that performs the 'forced' matches?
158 CHECK(!is_initialized_);
159 is_initialized_ = true;
160
161 for (NodeIndex n(0); n < nodes_.size(); ++n) {
162 if (graph_[n].empty()) return false; // INFEASIBLE.
163 CostValue min_cost = kMaxCostValue;
164
165 // Initialize the dual of each nodes to min_cost / 2.
166 //
167 // TODO(user): We might be able to do better for odd min_cost, but then
168 // we might need to scale by 4? think about it.
169 for (const EdgeIndex e : graph_[n]) {
170 min_cost = std::min(min_cost, edges_[e].pseudo_slack);
171 }
172 DCHECK_NE(min_cost, kMaxCostValue);
173 nodes_[n].pseudo_dual = min_cost / 2;
174
175 // Starts with all nodes as tree roots.
176 nodes_[n].type = 1;
177 }
178
179 // Update the slack of each edges now that nodes might have non-zero duals.
180 // Note that we made sure that all updated slacks are non-negative.
181 for (EdgeIndex e(0); e < edges_.size(); ++e) {
182 Edge& mutable_edge = edges_[e];
183 mutable_edge.pseudo_slack -= nodes_[mutable_edge.tail].pseudo_dual +
184 nodes_[mutable_edge.head].pseudo_dual;
185 DCHECK_GE(mutable_edge.pseudo_slack, 0);
186 }
187
188 for (NodeIndex n(0); n < nodes_.size(); ++n) {
189 if (NodeIsMatched(n)) continue;
190
191 // After this greedy update, there will be at least an edge with a
192 // slack of zero.
193 CostValue min_slack = kMaxCostValue;
194 for (const EdgeIndex e : graph_[n]) {
195 min_slack = std::min(min_slack, edges_[e].pseudo_slack);
196 }
197 DCHECK_NE(min_slack, kMaxCostValue);
198 if (min_slack > 0) {
199 nodes_[n].pseudo_dual += min_slack;
200 for (const EdgeIndex e : graph_[n]) {
201 edges_[e].pseudo_slack -= min_slack;
202 }
203 DebugUpdateNodeDual(n, min_slack);
204 }
205
206 // Match this node if possible.
207 //
208 // TODO(user): Optimize by merging this loop with the one above?
209 for (const EdgeIndex e : graph_[n]) {
210 const Edge& edge = edges_[e];
211 if (edge.pseudo_slack != 0) continue;
212 if (!NodeIsMatched(edge.OtherEnd(n))) {
213 nodes_[edge.tail].type = 0;
214 nodes_[edge.tail].match = edge.head;
215 nodes_[edge.head].type = 0;
216 nodes_[edge.head].match = edge.tail;
217 break;
218 }
219 }
220 }
221
222 // Initialize unmatched_nodes_.
223 for (NodeIndex n(0); n < nodes_.size(); ++n) {
224 if (NodeIsMatched(n)) continue;
225 unmatched_nodes_.push_back(n);
226 }
227
228 // Scale everything by 2 and update the dual cost. Note that we made sure that
229 // there cannot be an integer overflow at the beginning of Solve().
230 //
231 // This scaling allows to only have integer weights during the algorithm
232 // because the slack of [+] -- [+] edges will always stay even.
233 //
234 // TODO(user): Reduce the number of loops we do in the initialization. We
235 // could likely just scale the edge cost as we fill them.
236 for (NodeIndex n(0); n < nodes_.size(); ++n) {
237 DCHECK_LE(nodes_[n].pseudo_dual, kMaxCostValue / 2);
238 nodes_[n].pseudo_dual *= 2;
239 AddToDualObjective(nodes_[n].pseudo_dual);
240#ifndef NDEBUG
241 nodes_[n].dual = nodes_[n].pseudo_dual;
242#endif
243 }
244 for (EdgeIndex e(0); e < edges_.size(); ++e) {
245 DCHECK_LE(edges_[e].pseudo_slack, kMaxCostValue / 2);
246 edges_[e].pseudo_slack *= 2;
247#ifndef NDEBUG
248 edges_[e].slack = edges_[e].pseudo_slack;
249#endif
250 }
251
252 // Initialize the edge priority queues and the primal update queue.
253 // We only need to do that if we have unmatched nodes.
254 if (!unmatched_nodes_.empty()) {
255 primal_update_edge_queue_.clear();
256 for (EdgeIndex e(0); e < edges_.size(); ++e) {
257 Edge& edge = edges_[e];
258 const bool tail_is_plus = nodes_[edge.tail].IsPlus();
259 const bool head_is_plus = nodes_[edge.head].IsPlus();
260 if (tail_is_plus && head_is_plus) {
261 plus_plus_pq_.Add(&edge);
262 if (edge.pseudo_slack == 0) primal_update_edge_queue_.push_back(e);
263 } else if (tail_is_plus || head_is_plus) {
264 plus_free_pq_.Add(&edge);
265 if (edge.pseudo_slack == 0) primal_update_edge_queue_.push_back(e);
266 }
267 }
268 }
269
270 return true;
271}
272
274 // TODO(user): Avoid this linear loop.
275 CostValue best_update = kMaxCostValue;
276 for (NodeIndex n(0); n < nodes_.size(); ++n) {
277 const Node& node = nodes_[n];
278 if (node.IsBlossom() && node.IsMinus()) {
279 best_update = std::min(best_update, Dual(node));
280 }
281 }
282
283 // This code only works because all tree_dual_delta are the same.
284 CHECK(!unmatched_nodes_.empty());
285 const CostValue tree_delta = nodes_[unmatched_nodes_.front()].tree_dual_delta;
286 CostValue plus_plus_slack = kMaxCostValue;
287 if (!plus_plus_pq_.IsEmpty()) {
288 DCHECK_EQ(plus_plus_pq_.Top()->pseudo_slack % 2, 0) << "Non integer bound!";
289 plus_plus_slack = plus_plus_pq_.Top()->pseudo_slack / 2 - tree_delta;
290 best_update = std::min(best_update, plus_plus_slack);
291 }
292 CostValue plus_free_slack = kMaxCostValue;
293 if (!plus_free_pq_.IsEmpty()) {
294 plus_free_slack = plus_free_pq_.Top()->pseudo_slack - tree_delta;
295 best_update = std::min(best_update, plus_free_slack);
296 }
297
298 // This means infeasible, and returning zero will abort the search.
299 if (best_update == kMaxCostValue) return CostValue(0);
300
301 // Initialize primal_update_edge_queue_ with all the edges that will have a
302 // slack of zero once we apply the update.
303 //
304 // NOTE(user): If we want more "determinism" and be independent on the PQ
305 // algorithm, we could std::sort() the primal_update_edge_queue_ here.
306 primal_update_edge_queue_.clear();
307 if (plus_plus_slack == best_update) {
308 plus_plus_pq_.AllTop(&tmp_all_tops_);
309 for (const Edge* pt : tmp_all_tops_) {
310 primal_update_edge_queue_.push_back(EdgeIndex(pt - &edges_.front()));
311 }
312 }
313 if (plus_free_slack == best_update) {
314 plus_free_pq_.AllTop(&tmp_all_tops_);
315 for (const Edge* pt : tmp_all_tops_) {
316 primal_update_edge_queue_.push_back(EdgeIndex(pt - &edges_.front()));
317 }
318 }
319
320 return best_update;
321}
322
324 ++num_dual_updates_;
325
326 // Reminder: the tree roots are exactly the unmatched nodes.
327 CHECK_GE(delta, 0);
328 for (const NodeIndex n : unmatched_nodes_) {
329 CHECK(!NodeIsMatched(n));
330 AddToDualObjective(delta);
331 nodes_[n].tree_dual_delta += delta;
332 }
333
334 if (DEBUG_MODE) {
335 for (NodeIndex n(0); n < nodes_.size(); ++n) {
336 const Node& node = nodes_[n];
337 if (node.IsPlus()) DebugUpdateNodeDual(n, delta);
338 if (node.IsMinus()) DebugUpdateNodeDual(n, -delta);
339 }
340 }
341}
342
344 // An unmatched node must be a tree root.
345 const Node& node = nodes_[n];
346 CHECK(node.match != n || (node.root == n && node.IsPlus()));
347 return node.match != n;
348}
349
351 const Node& node = nodes_[n];
352 if (DEBUG_MODE) {
353 if (node.IsMinus()) CHECK_EQ(node.parent, node.match);
354 if (node.IsPlus()) CHECK_EQ(n, node.match);
355 }
356 return node.match;
357}
358
359// Meant to only be used in DEBUG to make sure our queue in PrimalUpdates()
360// do not miss any potential edges.
362 for (EdgeIndex e(0); e < edges_.size(); ++e) {
363 const Edge& edge = edges_[e];
364 if (Head(edge) == Tail(edge)) continue;
365
366 CHECK(!nodes_[Tail(edge)].is_internal);
367 CHECK(!nodes_[Head(edge)].is_internal);
368 if (Slack(edge) != 0) continue;
369
370 // Make sure tail is a plus node if possible.
371 NodeIndex tail = Tail(edge);
372 NodeIndex head = Head(edge);
373 if (!nodes_[tail].IsPlus()) std::swap(tail, head);
374 if (!nodes_[tail].IsPlus()) continue;
375
376 if (nodes_[head].IsFree()) {
377 VLOG(2) << DebugString();
378 LOG(FATAL) << "Possible Grow! " << tail << " " << head;
379 }
380 if (nodes_[head].IsPlus()) {
381 if (nodes_[tail].root == nodes_[head].root) {
382 LOG(FATAL) << "Possible Shrink!";
383 } else {
384 LOG(FATAL) << "Possible augment!";
385 }
386 }
387 }
388 for (const Node& node : nodes_) {
389 if (node.IsMinus() && node.IsBlossom() && Dual(node) == 0) {
390 LOG(FATAL) << "Possible expand!";
391 }
392 }
393}
394
396 // Any Grow/Augment/Shrink/Expand operation can add new tight edges that need
397 // to be explored again.
398 //
399 // TODO(user): avoid adding duplicates?
400 while (true) {
401 possible_shrink_.clear();
402
403 // First, we Grow/Augment as much as possible.
404 while (!primal_update_edge_queue_.empty()) {
405 const EdgeIndex e = primal_update_edge_queue_.back();
406 primal_update_edge_queue_.pop_back();
407
408 // Because of the Expand() operation, the edge may have become un-tight
409 // since it has been inserted in the tight edges queue. It's cheaper to
410 // detect it here and skip it than it would be to dynamically update the
411 // queue to only keep actually tight edges at all times.
412 const Edge& edge = edges_[e];
413 if (Slack(edge) != 0) continue;
414
415 NodeIndex tail = Tail(edge);
416 NodeIndex head = Head(edge);
417 if (!nodes_[tail].IsPlus()) std::swap(tail, head);
418 if (!nodes_[tail].IsPlus()) continue;
419
420 if (nodes_[head].IsFree()) {
421 Grow(e, tail, head);
422 } else if (nodes_[head].IsPlus()) {
423 if (nodes_[tail].root != nodes_[head].root) {
424 Augment(e);
425 } else {
426 possible_shrink_.push_back(e);
427 }
428 }
429 }
430
431 // Shrink all potential Blossom.
432 for (const EdgeIndex e : possible_shrink_) {
433 const Edge& edge = edges_[e];
434 const NodeIndex tail = Tail(edge);
435 const NodeIndex head = Head(edge);
436 const Node& tail_node = nodes_[tail];
437 const Node& head_node = nodes_[head];
438 if (tail_node.IsPlus() && head_node.IsPlus() &&
439 tail_node.root == head_node.root && tail != head) {
440 Shrink(e);
441 }
442 }
443
444 // Delay expand if any blossom was created.
445 if (!primal_update_edge_queue_.empty()) continue;
446
447 // Expand Blossom if any.
448 //
449 // TODO(user): Avoid doing a O(num_nodes). Also expand all blossom
450 // recursively? I am not sure it is a good heuristic to expand all possible
451 // blossom before trying the other operations though.
452 int num_expands = 0;
453 for (NodeIndex n(0); n < nodes_.size(); ++n) {
454 const Node& node = nodes_[n];
455 if (node.IsMinus() && node.IsBlossom() && Dual(node) == 0) {
456 ++num_expands;
457 Expand(n);
458 }
459 }
460 if (num_expands == 0) break;
461 }
462}
463
465 // The slack of all edge must be non-negative.
466 for (const Edge& edge : edges_) {
467 if (Slack(edge) < 0) return false;
468 }
469
470 // The dual of all Blossom must be non-negative.
471 for (const Node& node : nodes_) {
472 if (node.IsBlossom() && Dual(node) < 0) return false;
473 }
474 return true;
475}
476
478 if (Tail(edge) == Head(edge)) return false;
479 if (nodes_[Tail(edge)].IsInternal()) return false;
480 if (nodes_[Head(edge)].IsInternal()) return false;
481 return Slack(edge) == 0;
482}
483
484void BlossomGraph::Grow(EdgeIndex e, NodeIndex tail, NodeIndex head) {
485 ++num_grows_;
486 VLOG(2) << "Grow " << tail << " -> " << head << " === " << Match(head);
487
488 DCHECK(DebugEdgeIsTightAndExternal(edges_[e]));
489 DCHECK(nodes_[tail].IsPlus());
490 DCHECK(nodes_[head].IsFree());
491 DCHECK(NodeIsMatched(head));
492
493 const NodeIndex root = nodes_[tail].root;
494 const NodeIndex leaf = Match(head);
495
496 Node& head_node = nodes_[head];
497 head_node.root = root;
498 head_node.parent = tail;
499 head_node.type = -1;
500
501 // head was free and is now a [-] node.
502 const CostValue tree_dual = nodes_[root].tree_dual_delta;
503 head_node.pseudo_dual += tree_dual;
504 for (const NodeIndex subnode : SubNodes(head)) {
505 for (const EdgeIndex e : graph_[subnode]) {
506 Edge& edge = edges_[e];
507 const NodeIndex other_end = OtherEnd(edge, subnode);
508 if (other_end == head) continue;
509 edge.pseudo_slack -= tree_dual;
510 if (plus_free_pq_.Contains(&edge)) plus_free_pq_.Remove(&edge);
511 }
512 }
513
514 Node& leaf_node = nodes_[leaf];
515 leaf_node.root = root;
516 leaf_node.parent = head;
517 leaf_node.type = +1;
518
519 // leaf was free and is now a [+] node.
520 leaf_node.pseudo_dual -= tree_dual;
521 for (const NodeIndex subnode : SubNodes(leaf)) {
522 for (const EdgeIndex e : graph_[subnode]) {
523 Edge& edge = edges_[e];
524 const NodeIndex other_end = OtherEnd(edge, subnode);
525 if (other_end == leaf) continue;
526 edge.pseudo_slack += tree_dual;
527 const Node& other_node = nodes_[other_end];
528 if (other_node.IsPlus()) {
529 // The edge switch from [+] -- [0] to [+] -- [+].
530 DCHECK(plus_free_pq_.Contains(&edge));
531 DCHECK(!plus_plus_pq_.Contains(&edge));
532 plus_free_pq_.Remove(&edge);
533 plus_plus_pq_.Add(&edge);
534 if (edge.pseudo_slack == 2 * tree_dual) {
535 DCHECK_EQ(Slack(edge), 0);
536 primal_update_edge_queue_.push_back(e);
537 }
538 } else if (other_node.IsFree()) {
539 // We have a new [+] -- [0] edge.
540 DCHECK(!plus_free_pq_.Contains(&edge));
541 DCHECK(!plus_plus_pq_.Contains(&edge));
542 plus_free_pq_.Add(&edge);
543 if (edge.pseudo_slack == tree_dual) {
544 DCHECK_EQ(Slack(edge), 0);
545 primal_update_edge_queue_.push_back(e);
546 }
547 }
548 }
549 }
550}
551
552void BlossomGraph::AppendNodePathToRoot(NodeIndex n,
553 std::vector<NodeIndex>* path) const {
554 while (true) {
555 path->push_back(n);
556 n = nodes_[n].parent;
557 if (n == path->back()) break;
558 }
559}
560
561void BlossomGraph::Augment(EdgeIndex e) {
562 ++num_augments_;
563
564 const Edge& edge = edges_[e];
565 VLOG(2) << "Augment " << Tail(edge) << " -> " << Head(edge);
566 DCHECK(DebugEdgeIsTightAndExternal(edge));
567 DCHECK(nodes_[Tail(edge)].IsPlus());
568 DCHECK(nodes_[Head(edge)].IsPlus());
569
570 const NodeIndex root_a = nodes_[Tail(edge)].root;
571 const NodeIndex root_b = nodes_[Head(edge)].root;
572 DCHECK_NE(root_a, root_b);
573
574 // Compute the path from root_a to root_b.
575 std::vector<NodeIndex> node_path;
576 AppendNodePathToRoot(Tail(edge), &node_path);
577 std::reverse(node_path.begin(), node_path.end());
578 AppendNodePathToRoot(Head(edge), &node_path);
579
580 // TODO(user): Check all dual/slack same after primal op?
581 const CostValue delta_a = nodes_[root_a].tree_dual_delta;
582 const CostValue delta_b = nodes_[root_b].tree_dual_delta;
583 nodes_[root_a].tree_dual_delta = 0;
584 nodes_[root_b].tree_dual_delta = 0;
585
586 // Make all the nodes from both trees free while keeping the
587 // current matching.
588 //
589 // TODO(user): It seems that we may waste some computation since the part of
590 // the tree not in the path between roots can lead to the same Grow()
591 // operations later when one of its node is ratched to a new root.
592 //
593 // TODO(user): Reduce this O(num_nodes) complexity. We might be able to
594 // even do O(num_node_in_path) with lazy updates. Note that this operation
595 // will only be performed at most num_initial_unmatched_nodes / 2 times
596 // though.
597 for (NodeIndex n(0); n < nodes_.size(); ++n) {
598 Node& node = nodes_[n];
599 if (node.IsInternal()) continue;
600 const NodeIndex root = node.root;
601 if (root != root_a && root != root_b) continue;
602
603 const CostValue delta = node.type * (root == root_a ? delta_a : delta_b);
604 node.pseudo_dual += delta;
605 for (const NodeIndex subnode : SubNodes(n)) {
606 for (const EdgeIndex e : graph_[subnode]) {
607 Edge& edge = edges_[e];
608 const NodeIndex other_end = OtherEnd(edge, subnode);
609 if (other_end == n) continue;
610 edge.pseudo_slack -= delta;
611
612 // If the other end is not in one of the two trees, and it is a plus
613 // node, we add it the plus_free queue. All previous [+]--[0] and
614 // [+]--[+] edges need to be removed from the queues.
615 const Node& other_node = nodes_[other_end];
616 if (other_node.root != root_a && other_node.root != root_b &&
617 other_node.IsPlus()) {
618 if (plus_plus_pq_.Contains(&edge)) plus_plus_pq_.Remove(&edge);
619 DCHECK(!plus_free_pq_.Contains(&edge));
620 plus_free_pq_.Add(&edge);
621 if (Slack(edge) == 0) primal_update_edge_queue_.push_back(e);
622 } else {
623 if (plus_plus_pq_.Contains(&edge)) plus_plus_pq_.Remove(&edge);
624 if (plus_free_pq_.Contains(&edge)) plus_free_pq_.Remove(&edge);
625 }
626 }
627 }
628
629 node.type = 0;
630 node.parent = node.root = n;
631 }
632
633 // Change the matching of nodes along node_path.
634 CHECK_EQ(node_path.size() % 2, 0);
635 for (int i = 0; i < node_path.size(); i += 2) {
636 nodes_[node_path[i]].match = node_path[i + 1];
637 nodes_[node_path[i + 1]].match = node_path[i];
638 }
639
640 // Update unmatched_nodes_.
641 //
642 // TODO(user): This could probably be optimized if needed. But we do usually
643 // iterate a lot more over it than we update it. Note that as long as we use
644 // the same delta for all trees, this is not even needed.
645 int new_size = 0;
646 for (const NodeIndex n : unmatched_nodes_) {
647 if (!NodeIsMatched(n)) unmatched_nodes_[new_size++] = n;
648 }
649 CHECK_EQ(unmatched_nodes_.size(), new_size + 2);
650 unmatched_nodes_.resize(new_size);
651}
652
653int BlossomGraph::GetDepth(NodeIndex n) const {
654 int depth = 0;
655 while (true) {
656 const NodeIndex parent = nodes_[n].parent;
657 if (parent == n) break;
658 ++depth;
659 n = parent;
660 }
661 return depth;
662}
663
664void BlossomGraph::Shrink(EdgeIndex e) {
665 ++num_shrinks_;
666
667 const Edge& edge = edges_[e];
668 DCHECK(DebugEdgeIsTightAndExternal(edge));
669 DCHECK(nodes_[Tail(edge)].IsPlus());
670 DCHECK(nodes_[Head(edge)].IsPlus());
671 DCHECK_EQ(nodes_[Tail(edge)].root, nodes_[Head(edge)].root);
672
673 CHECK_NE(Tail(edge), Head(edge)) << e;
674
675 // Find lowest common ancestor and the two node paths to reach it. Note that
676 // we do not add it to the paths.
677 NodeIndex lca_index = kNoNodeIndex;
678 std::vector<NodeIndex> tail_path;
679 std::vector<NodeIndex> head_path;
680 {
681 NodeIndex tail = Tail(edge);
682 NodeIndex head = Head(edge);
683 int tail_depth = GetDepth(tail);
684 int head_depth = GetDepth(head);
685 if (tail_depth > head_depth) {
686 std::swap(tail, head);
687 std::swap(tail_depth, head_depth);
688 }
689 VLOG(2) << "Shrink " << tail << " <-> " << head;
690
691 while (head_depth > tail_depth) {
692 head_path.push_back(head);
693 head = nodes_[head].parent;
694 --head_depth;
695 }
696 while (tail != head) {
697 DCHECK_EQ(tail_depth, head_depth);
698 DCHECK_GE(tail_depth, 0);
699 if (DEBUG_MODE) {
700 --tail_depth;
701 --head_depth;
702 }
703
704 tail_path.push_back(tail);
705 tail = nodes_[tail].parent;
706
707 head_path.push_back(head);
708 head = nodes_[head].parent;
709 }
710 lca_index = tail;
711 VLOG(2) << "LCA " << lca_index;
712 }
713 Node& lca = nodes_[lca_index];
714 DCHECK(lca.IsPlus());
715
716 // Fill the cycle.
717 std::vector<NodeIndex> blossom = {lca_index};
718 std::reverse(head_path.begin(), head_path.end());
719 blossom.insert(blossom.end(), head_path.begin(), head_path.end());
720 blossom.insert(blossom.end(), tail_path.begin(), tail_path.end());
721 CHECK_EQ(blossom.size() % 2, 1);
722
723 const CostValue tree_dual = nodes_[lca.root].tree_dual_delta;
724
725 // Save all values that will be needed if we expand this Blossom later.
726 CHECK_GT(blossom.size(), 1);
727 Node& backup_node = nodes_[blossom[1]];
728#ifndef NDEBUG
729 backup_node.saved_dual = lca.dual;
730#endif
731 backup_node.saved_pseudo_dual = lca.pseudo_dual + tree_dual;
732
733 // Set the new dual of the node to zero.
734#ifndef NDEBUG
735 lca.dual = 0;
736#endif
737 lca.pseudo_dual = -tree_dual;
738 CHECK_EQ(Dual(lca), 0);
739
740 // Mark node as internal, but do not change their type to zero yet.
741 // We need to do that first to properly detect edges between two internal
742 // nodes in the second loop below.
743 for (const NodeIndex n : blossom) {
744 VLOG(2) << "blossom-node: " << NodeDebugString(n);
745 if (n != lca_index) {
746 nodes_[n].is_internal = true;
747 }
748 }
749
750 // Update the dual of all edges and the priority queueus.
751 for (const NodeIndex n : blossom) {
752 Node& mutable_node = nodes_[n];
753 const bool was_minus = mutable_node.IsMinus();
754 const CostValue slack_adjust =
755 mutable_node.IsMinus() ? tree_dual : -tree_dual;
756 if (n != lca_index) {
757 mutable_node.pseudo_dual -= slack_adjust;
758#ifndef NDEBUG
759 DCHECK_EQ(mutable_node.dual, mutable_node.pseudo_dual);
760#endif
761 mutable_node.type = 0;
762 }
763 for (const NodeIndex subnode : SubNodes(n)) {
764 // Subtle: We update root_blossom_node_ while we loop, so for new internal
765 // edges, depending if an edge "other end" appear after or before, it will
766 // not be updated. We use this to only process internal edges once.
767 root_blossom_node_[subnode] = lca_index;
768
769 for (const EdgeIndex e : graph_[subnode]) {
770 Edge& edge = edges_[e];
771 const NodeIndex other_end = OtherEnd(edge, subnode);
772
773 // Skip edge that are already internal.
774 if (other_end == n) continue;
775
776 // This internal edge was already processed from its other end, so we
777 // can just skip it.
778 if (other_end == lca_index) {
779#ifndef NDEBUG
780 DCHECK_EQ(edge.slack, Slack(edge));
781#endif
782 continue;
783 }
784
785 // This is a new-internal edge that we didn't process yet.
786 //
787 // TODO(user): It would be nicer to not to have to read the memory of
788 // the other node at all. It might be possible once we store the
789 // parent edge instead of the parent node since then we will only need
790 // to know if this edges point to a new-internal node or not.
791 Node& mutable_other_node = nodes_[other_end];
792 if (mutable_other_node.is_internal) {
793 DCHECK(!plus_free_pq_.Contains(&edge));
794 if (plus_plus_pq_.Contains(&edge)) plus_plus_pq_.Remove(&edge);
795 edge.pseudo_slack += slack_adjust;
796 edge.pseudo_slack +=
797 mutable_other_node.IsMinus() ? tree_dual : -tree_dual;
798 continue;
799 }
800
801 // Replace the parent of any child of n by lca_index.
802 if (mutable_other_node.parent == n) {
803 mutable_other_node.parent = lca_index;
804 }
805
806 // Adjust when the edge used to be connected to a [-] node now that we
807 // attach it to a [+] node. Note that if the node was [+] then the
808 // non-internal incident edges slack and type do not change.
809 if (was_minus) {
810 edge.pseudo_slack += 2 * tree_dual;
811
812 // Add it to the correct PQ.
813 DCHECK(!plus_plus_pq_.Contains(&edge));
814 DCHECK(!plus_free_pq_.Contains(&edge));
815 if (mutable_other_node.IsPlus()) {
816 plus_plus_pq_.Add(&edge);
817 if (edge.pseudo_slack == 2 * tree_dual) {
818 primal_update_edge_queue_.push_back(e);
819 }
820 } else if (mutable_other_node.IsFree()) {
821 plus_free_pq_.Add(&edge);
822 if (edge.pseudo_slack == tree_dual) {
823 primal_update_edge_queue_.push_back(e);
824 }
825 }
826 }
827
828#ifndef NDEBUG
829 DCHECK_EQ(edge.slack, Slack(edge));
830#endif
831 }
832 }
833 }
834
835 DCHECK(backup_node.saved_blossom.empty());
836 backup_node.saved_blossom = std::move(lca.blossom);
837 lca.blossom = std::move(blossom);
838
839 VLOG(2) << "S result " << NodeDebugString(lca_index);
840}
841
842BlossomGraph::EdgeIndex BlossomGraph::FindTightExternalEdgeBetweenNodes(
843 NodeIndex tail, NodeIndex head) {
844 DCHECK_NE(tail, head);
845 DCHECK_EQ(tail, root_blossom_node_[tail]);
846 DCHECK_EQ(head, root_blossom_node_[head]);
847 for (const NodeIndex subnode : SubNodes(tail)) {
848 for (const EdgeIndex e : graph_[subnode]) {
849 const Edge& edge = edges_[e];
850 const NodeIndex other_end = OtherEnd(edge, subnode);
851 if (other_end == head && Slack(edge) == 0) {
852 return e;
853 }
854 }
855 }
856 return kNoEdgeIndex;
857}
858
860 ++num_expands_;
861 VLOG(2) << "Expand " << to_expand;
862
863 Node& node_to_expand = nodes_[to_expand];
864 DCHECK(node_to_expand.IsBlossom());
865 DCHECK(node_to_expand.IsMinus());
866 DCHECK_EQ(Dual(node_to_expand), 0);
867
868 const EdgeIndex match_edge_index =
869 FindTightExternalEdgeBetweenNodes(to_expand, node_to_expand.match);
870 const EdgeIndex parent_edge_index =
871 FindTightExternalEdgeBetweenNodes(to_expand, node_to_expand.parent);
872
873 // First, restore the saved fields.
874 Node& backup_node = nodes_[node_to_expand.blossom[1]];
875#ifndef NDEBUG
876 node_to_expand.dual = backup_node.saved_dual;
877#endif
878 node_to_expand.pseudo_dual = backup_node.saved_pseudo_dual;
879 std::vector<NodeIndex> blossom = std::move(node_to_expand.blossom);
880 node_to_expand.blossom = std::move(backup_node.saved_blossom);
881 backup_node.saved_blossom.clear();
882
883 // Restore the edges Head()/Tail().
884 for (const NodeIndex n : blossom) {
885 for (const NodeIndex subnode : SubNodes(n)) {
886 root_blossom_node_[subnode] = n;
887 }
888 }
889
890 // Now we try to find a 'blossom path' that will replace the blossom node in
891 // the alternating tree: the blossom's parent [+] node in the tree will be
892 // attached to a blossom subnode (the "path start"), the blossom's child in
893 // the tree will be attached to a blossom subnode (the "path end", which could
894 // be the same subnode or a different one), and, using the blossom cycle,
895 // we'll get a path with an odd number of blossom subnodes to connect the two
896 // (since the cycle is odd, one of the two paths will be odd too). The other
897 // subnodes of the blossom will then be made free nodes matched pairwise.
898 int blossom_path_start = -1;
899 int blossom_path_end = -1;
900 const NodeIndex start_node = OtherEndFromExternalNode(
901 edges_[parent_edge_index], node_to_expand.parent);
902 const NodeIndex end_node =
903 OtherEndFromExternalNode(edges_[match_edge_index], node_to_expand.match);
904 for (int i = 0; i < blossom.size(); ++i) {
905 if (blossom[i] == start_node) blossom_path_start = i;
906 if (blossom[i] == end_node) blossom_path_end = i;
907 }
908
909 // Split the cycle in two halves: nodes in [start..end] in path1, and
910 // nodes in [end..start] in path2. Note the inclusive intervals.
911 const std::vector<NodeIndex>& cycle = blossom;
912 std::vector<NodeIndex> path1;
913 std::vector<NodeIndex> path2;
914 {
915 const int end_offset =
916 (blossom_path_end + cycle.size() - blossom_path_start) % cycle.size();
917 for (int offset = 0; offset <= /*or equal*/ cycle.size(); ++offset) {
918 const NodeIndex node =
919 cycle[(blossom_path_start + offset) % cycle.size()];
920 if (offset <= end_offset) path1.push_back(node);
921 if (offset >= end_offset) path2.push_back(node);
922 }
923 }
924
925 // Reverse path2 to also make it go from start to end.
926 std::reverse(path2.begin(), path2.end());
927
928 // Swap if necessary so that path1 is the odd-length one.
929 if (path1.size() % 2 == 0) path1.swap(path2);
930
931 // Use better aliases than 'path1' and 'path2' in the code below.
932 std::vector<NodeIndex>& path_in_tree = path1;
933 const std::vector<NodeIndex>& free_pairs = path2;
934
935 // Strip path2 from the start and end, which aren't needed.
936 path2.erase(path2.begin());
937 path2.pop_back();
938
939 const NodeIndex blossom_matched_node = node_to_expand.match;
940 VLOG(2) << "Path ["
941 << absl::StrJoin(path_in_tree, ", ", absl::StreamFormatter())
942 << "] === " << blossom_matched_node;
943 VLOG(2) << "Pairs ["
944 << absl::StrJoin(free_pairs, ", ", absl::StreamFormatter()) << "]";
945
946 // Restore the path in the tree, note that we append the blossom_matched_node
947 // to simplify the code:
948 // <---- Blossom ---->
949 // [-] === [+] --- [-] === [+]
950 path_in_tree.push_back(blossom_matched_node);
951 CHECK_EQ(path_in_tree.size() % 2, 0);
952 const CostValue tree_dual = nodes_[node_to_expand.root].tree_dual_delta;
953 for (int i = 0; i < path_in_tree.size(); ++i) {
954 const NodeIndex n = path_in_tree[i];
955 const bool node_is_plus = i % 2;
956
957 // Update the parent.
958 if (i == 0) {
959 // This is the path start and its parent is either itself or the parent of
960 // to_expand if there was one.
961 DCHECK(node_to_expand.parent != to_expand || n == to_expand);
962 nodes_[n].parent = node_to_expand.parent;
963 } else {
964 nodes_[n].parent = path_in_tree[i - 1];
965 }
966
967 // Update the types and matches.
968 nodes_[n].root = node_to_expand.root;
969 nodes_[n].type = node_is_plus ? 1 : -1;
970 nodes_[n].match = path_in_tree[node_is_plus ? i - 1 : i + 1];
971
972 // Ignore the blossom_matched_node for the code below.
973 if (i + 1 == path_in_tree.size()) continue;
974
975 // Update the duals, depending on whether we have a new [+] or [-] node.
976 // Note that this is also needed for the 'root' blossom node (i=0), because
977 // we've restored its pseudo-dual from its old saved value above.
978 const CostValue adjust = node_is_plus ? -tree_dual : tree_dual;
979 nodes_[n].pseudo_dual += adjust;
980 for (const NodeIndex subnode : SubNodes(n)) {
981 for (const EdgeIndex e : graph_[subnode]) {
982 Edge& edge = edges_[e];
983 const NodeIndex other_end = OtherEnd(edge, subnode);
984 if (other_end == n) continue;
985
986 edge.pseudo_slack -= adjust;
987
988 // non-internal edges used to be attached to the [-] node_to_expand,
989 // so we adjust their dual.
990 if (other_end != to_expand && !nodes_[other_end].is_internal) {
991 edge.pseudo_slack += tree_dual;
992 } else {
993 // This was an internal edges. For the PQ code below to be correct, we
994 // wait for its other end to have been processed by this loop already.
995 // We detect that using the fact that the type of unprocessed internal
996 // node is still zero.
997 if (nodes_[other_end].type == 0) continue;
998 }
999
1000 // Update edge queues.
1001 if (node_is_plus) {
1002 const Node& other_node = nodes_[other_end];
1003 DCHECK(!plus_plus_pq_.Contains(&edge));
1004 DCHECK(!plus_free_pq_.Contains(&edge));
1005 if (other_node.IsPlus()) {
1006 plus_plus_pq_.Add(&edge);
1007 if (edge.pseudo_slack == 2 * tree_dual) {
1008 primal_update_edge_queue_.push_back(e);
1009 }
1010 } else if (other_node.IsFree()) {
1011 plus_free_pq_.Add(&edge);
1012 if (edge.pseudo_slack == tree_dual) {
1013 primal_update_edge_queue_.push_back(e);
1014 }
1015 }
1016 }
1017 }
1018 }
1019 }
1020
1021 // Update free nodes.
1022 for (const NodeIndex n : free_pairs) {
1023 nodes_[n].type = 0;
1024 nodes_[n].parent = n;
1025 nodes_[n].root = n;
1026
1027 // Update edges slack and priority queue for the adjacent edges.
1028 for (const NodeIndex subnode : SubNodes(n)) {
1029 for (const EdgeIndex e : graph_[subnode]) {
1030 Edge& edge = edges_[e];
1031 const NodeIndex other_end = OtherEnd(edge, subnode);
1032 if (other_end == n) continue;
1033
1034 // non-internal edges used to be attached to the [-] node_to_expand,
1035 // so we adjust their dual.
1036 if (other_end != to_expand && !nodes_[other_end].is_internal) {
1037 edge.pseudo_slack += tree_dual;
1038 }
1039
1040 // Update PQ. Note that since this was attached to a [-] node it cannot
1041 // be in any queue. We will also never process twice the same edge here.
1042 DCHECK(!plus_plus_pq_.Contains(&edge));
1043 DCHECK(!plus_free_pq_.Contains(&edge));
1044 if (nodes_[other_end].IsPlus()) {
1045 plus_free_pq_.Add(&edge);
1046 if (edge.pseudo_slack == tree_dual) {
1047 primal_update_edge_queue_.push_back(e);
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054 // Matches the free pair together.
1055 CHECK_EQ(free_pairs.size() % 2, 0);
1056 for (int i = 0; i < free_pairs.size(); i += 2) {
1057 nodes_[free_pairs[i]].match = free_pairs[i + 1];
1058 nodes_[free_pairs[i + 1]].match = free_pairs[i];
1059 }
1060
1061 // Mark all node as external. We do that last so we could easily detect old
1062 // internal edges that are now external.
1063 for (const NodeIndex n : blossom) {
1064 nodes_[n].is_internal = false;
1065 }
1066}
1067
1069 // Queue of blossoms to expand.
1070 std::vector<NodeIndex> queue;
1071 for (NodeIndex n(0); n < nodes_.size(); ++n) {
1072 Node& node = nodes_[n];
1073 if (node.IsInternal()) continue;
1074
1075 // When this is called, there should be no more trees.
1076 CHECK(node.IsFree());
1077
1078 if (node.IsBlossom()) queue.push_back(n);
1079 }
1080
1081 // TODO(user): remove duplication with expand?
1082 while (!queue.empty()) {
1083 const NodeIndex to_expand = queue.back();
1084 queue.pop_back();
1085
1086 Node& node_to_expand = nodes_[to_expand];
1087 DCHECK(node_to_expand.IsBlossom());
1088
1089 // Find the edge used to match to_expand with Match(to_expand).
1090 const EdgeIndex match_edge_index =
1091 FindTightExternalEdgeBetweenNodes(to_expand, node_to_expand.match);
1092
1093 // Restore the saved data.
1094 Node& backup_node = nodes_[node_to_expand.blossom[1]];
1095#ifndef NDEBUG
1096 node_to_expand.dual = backup_node.saved_dual;
1097#endif
1098 node_to_expand.pseudo_dual = backup_node.saved_pseudo_dual;
1099
1100 std::vector<NodeIndex> blossom = std::move(node_to_expand.blossom);
1101 node_to_expand.blossom = std::move(backup_node.saved_blossom);
1102 backup_node.saved_blossom.clear();
1103
1104 // Restore the edges Head()/Tail().
1105 for (const NodeIndex n : blossom) {
1106 for (const NodeIndex subnode : SubNodes(n)) {
1107 root_blossom_node_[subnode] = n;
1108 }
1109 }
1110
1111 // Find the index of matched_node in the blossom list.
1112 int internal_matched_index = -1;
1113 const NodeIndex matched_node = OtherEndFromExternalNode(
1114 edges_[match_edge_index], node_to_expand.match);
1115 const int size = blossom.size();
1116 for (int i = 0; i < size; ++i) {
1117 if (blossom[i] == matched_node) {
1118 internal_matched_index = i;
1119 break;
1120 }
1121 }
1122 CHECK_NE(internal_matched_index, -1);
1123
1124 // Amongst the node_to_expand.blossom nodes, internal_matched_index is
1125 // matched with external_matched_node and the other are matched together.
1126 std::vector<NodeIndex> free_pairs;
1127 for (int i = (internal_matched_index + 1) % size;
1128 i != internal_matched_index; i = (i + 1) % size) {
1129 free_pairs.push_back(blossom[i]);
1130 }
1131
1132 // Clear root/parent/type of all internal nodes.
1133 for (const NodeIndex to_clear : blossom) {
1134 nodes_[to_clear].type = 0;
1135 nodes_[to_clear].is_internal = false;
1136 nodes_[to_clear].parent = to_clear;
1137 nodes_[to_clear].root = to_clear;
1138 }
1139
1140 // Matches the internal node with external one.
1141 const NodeIndex external_matched_node = node_to_expand.match;
1142 const NodeIndex internal_matched_node = blossom[internal_matched_index];
1143 nodes_[internal_matched_node].match = external_matched_node;
1144 nodes_[external_matched_node].match = internal_matched_node;
1145
1146 // Matches the free pair together.
1147 CHECK_EQ(free_pairs.size() % 2, 0);
1148 for (int i = 0; i < free_pairs.size(); i += 2) {
1149 nodes_[free_pairs[i]].match = free_pairs[i + 1];
1150 nodes_[free_pairs[i + 1]].match = free_pairs[i];
1151 }
1152
1153 // Now that the expansion is done, add to the queue any sub-blossoms.
1154 for (const NodeIndex n : blossom) {
1155 if (nodes_[n].IsBlossom()) queue.push_back(n);
1156 }
1157 }
1158}
1159
1160const std::vector<NodeIndex>& BlossomGraph::SubNodes(NodeIndex n) {
1161 // This should be only called on an external node. However, in Shrink() we
1162 // mark the node as internal early, so we just make sure the node as no saved
1163 // blossom field here.
1164 DCHECK(nodes_[n].saved_blossom.empty());
1165
1166 // Expand all the inner nodes under the node n. This will not be n iff node is
1167 // is in fact a blossom.
1168 subnodes_ = {n};
1169 for (int i = 0; i < subnodes_.size(); ++i) {
1170 const Node& node = nodes_[subnodes_[i]];
1171
1172 // Since the first node in each list is always the node above, we just
1173 // skip it to avoid listing twice the nodes.
1174 if (!node.blossom.empty()) {
1175 subnodes_.insert(subnodes_.end(), node.blossom.begin() + 1,
1176 node.blossom.end());
1177 }
1178
1179 // We also need to recursively expand the sub-blossom nodes, which are (if
1180 // any) in the "saved_blossom" field of the first internal node of each
1181 // blossom. Since we iterate on all internal nodes here, we simply consult
1182 // the "saved_blossom" field of all subnodes, and it works the same.
1183 if (!node.saved_blossom.empty()) {
1184 subnodes_.insert(subnodes_.end(), node.saved_blossom.begin() + 1,
1185 node.saved_blossom.end());
1186 }
1187 }
1188 return subnodes_;
1189}
1190
1192 const Node& node = nodes_[n];
1193 if (node.is_internal) {
1194 return absl::StrCat("[I] #", n.value());
1195 }
1196 const std::string type = !NodeIsMatched(n) ? "[*]"
1197 : node.type == 1 ? "[+]"
1198 : node.type == -1 ? "[-]"
1199 : "[0]";
1200 return absl::StrCat(
1201 type, " #", n.value(), " dual: ", Dual(node).value(),
1202 " parent: ", node.parent.value(), " match: ", node.match.value(),
1203 " blossom: [", absl::StrJoin(node.blossom, ", ", absl::StreamFormatter()),
1204 "]");
1205}
1206
1207std::string BlossomGraph::EdgeDebugString(EdgeIndex e) const {
1208 const Edge& edge = edges_[e];
1209 if (nodes_[Tail(edge)].is_internal || nodes_[Head(edge)].is_internal) {
1210 return absl::StrCat(Tail(edge).value(), "<->", Head(edge).value(),
1211 " internal ");
1212 }
1213 return absl::StrCat(Tail(edge).value(), "<->", Head(edge).value(),
1214 " slack: ", Slack(edge).value());
1215}
1216
1217std::string BlossomGraph::DebugString() const {
1218 std::string result = "Graph:\n";
1219 for (NodeIndex n(0); n < nodes_.size(); ++n) {
1220 absl::StrAppend(&result, NodeDebugString(n), "\n");
1221 }
1222 for (EdgeIndex e(0); e < edges_.size(); ++e) {
1223 absl::StrAppend(&result, EdgeDebugString(e), "\n");
1224 }
1225 return result;
1226}
1227
1229#ifndef NDEBUG
1230 nodes_[n].dual += delta;
1231 for (const NodeIndex subnode : SubNodes(n)) {
1232 for (const EdgeIndex e : graph_[subnode]) {
1233 Edge& edge = edges_[e];
1234 const NodeIndex other_end = OtherEnd(edge, subnode);
1235 if (other_end == n) continue;
1236 edges_[e].slack -= delta;
1237 }
1238 }
1239#endif
1240}
1241
1243 const Node& tail_node = nodes_[Tail(edge)];
1244 const Node& head_node = nodes_[Head(edge)];
1245 CostValue slack = edge.pseudo_slack;
1246 if (Tail(edge) == Head(edge)) return slack; // Internal...
1247
1248 if (!tail_node.is_internal && !head_node.is_internal) {
1249 slack -= tail_node.type * nodes_[tail_node.root].tree_dual_delta +
1250 head_node.type * nodes_[head_node.root].tree_dual_delta;
1251 }
1252#ifndef NDEBUG
1253 DCHECK_EQ(slack, edge.slack) << tail_node.type << " " << head_node.type
1254 << " " << Tail(edge) << "<->" << Head(edge);
1255#endif
1256 return slack;
1257}
1258
1259// Returns the dual value of the given node (which might be a pseudo-node).
1261 const CostValue dual =
1262 node.pseudo_dual + node.type * nodes_[node.root].tree_dual_delta;
1263#ifndef NDEBUG
1264 DCHECK_EQ(dual, node.dual);
1265#endif
1266 return dual;
1267}
1268
1270 if (dual_objective_ == std::numeric_limits<int64_t>::max())
1271 return CostValue(std::numeric_limits<int64_t>::max());
1272 CHECK_EQ(dual_objective_ % 2, 0);
1273 return dual_objective_ / 2;
1274}
1275
1276void BlossomGraph::AddToDualObjective(CostValue delta) {
1277 CHECK_GE(delta, 0);
1278 dual_objective_ = CostValue(CapAdd(dual_objective_.value(), delta.value()));
1279}
1280
1282 VLOG(1) << "num_grows: " << num_grows_;
1283 VLOG(1) << "num_augments: " << num_augments_;
1284 VLOG(1) << "num_shrinks: " << num_shrinks_;
1285 VLOG(1) << "num_expands: " << num_expands_;
1286 VLOG(1) << "num_dual_updates: " << num_dual_updates_;
1287}
1288
1289} // namespace operations_research
void AddEdge(NodeIndex tail, NodeIndex head, CostValue cost)
CostValue ComputeMaxCommonTreeDualDeltaAndResetPrimalEdgeQueue()
CostValue Slack(const Edge &edge) const
NodeIndex Match(NodeIndex n) const
void DebugUpdateNodeDual(NodeIndex n, CostValue delta)
std::string NodeDebugString(NodeIndex n) const
static const EdgeIndex kNoEdgeIndex
std::string EdgeDebugString(EdgeIndex e) const
bool DebugEdgeIsTightAndExternal(const Edge &edge) const
static const NodeIndex kNoNodeIndex
CostValue Dual(const Node &node) const
void Expand(NodeIndex to_expand)
bool NodeIsMatched(NodeIndex n) const
ABSL_MUST_USE_RESULT bool Initialize()
void Grow(EdgeIndex e, NodeIndex tail, NodeIndex head)
static const CostValue kMaxCostValue
void AddEdgeWithCost(int tail, int head, int64_t cost)
OR-Tools root namespace.
int64_t CapAdd(int64_t x, int64_t y)
BlossomGraph::CostValue CostValue
BlossomGraph::NodeIndex NodeIndex
const bool DEBUG_MODE
Definition radix_sort.h:266
NodeIndex OtherEnd(NodeIndex n) const