Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_assignment.h
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//
15// An implementation of a cost-scaling push-relabel algorithm for the
16// assignment problem (minimum-cost perfect bipartite matching), from
17// the paper of Goldberg and Kennedy (1995).
18//
19//
20// This implementation finds the minimum-cost perfect assignment in
21// the given graph with integral edge weights set through the
22// SetArcCost method.
23//
24// The running time is O(n*m*log(nC)) where n is the number of nodes,
25// m is the number of edges, and C is the largest magnitude of an edge cost.
26// In principle it can be worse than the Hungarian algorithm but we don't know
27// of any class of problems where that actually happens. An additional sqrt(n)
28// factor could be shaved off the running time bound using the technique
29// described in http://dx.doi.org/10.1137/S0895480194281185
30// (see also http://theory.stanford.edu/~robert/papers/glob_upd.ps).
31//
32// Example usage:
33//
34// #include "ortools/graph/graph.h"
35// #include "ortools/graph/linear_assignment.h"
36//
37// // Choose a graph implementation (we recommend StaticGraph<>).
38// typedef util::StaticGraph<> Graph;
39//
40// // Define a num_nodes / 2 by num_nodes / 2 assignment problem:
41// const int num_nodes = ...
42// const int num_arcs = ...
43// const int num_left_nodes = num_nodes / 2;
44// Graph graph(num_nodes, num_arcs);
45// std::vector<operations_research::CostValue> arc_costs(num_arcs);
46// for (int arc = 0; arc < num_arcs; ++arc) {
47// const int arc_tail = ... // must be in [0, num_left_nodes)
48// const int arc_head = ... // must be in [num_left_nodes, num_nodes)
49// graph.AddArc(arc_tail, arc_head);
50// arc_costs[arc] = ...
51// }
52//
53// // Build the StaticGraph. You can skip this step by using a ListGraph<>
54// // instead, but then the ComputeAssignment() below will be slower. It is
55// // okay if your graph is small and performance is not critical though.
56// {
57// std::vector<Graph::ArcIndex> arc_permutation;
58// graph.Build(&arc_permutation);
59// util::Permute(arc_permutation, &arc_costs);
60// }
61//
62// // Construct the LinearSumAssignment.
63// ::operations_research::LinearSumAssignment<Graph> a(graph, num_left_nodes);
64// for (int arc = 0; arc < num_arcs; ++arc) {
65// // You can also replace 'arc_costs[arc]' by something like
66// // ComputeArcCost(permutation.empty() ? arc : permutation[arc])
67// // if you don't want to store the costs in arc_costs to save memory.
68// a.SetArcCost(arc, arc_costs[arc]);
69// }
70//
71// // Compute the optimum assignment.
72// bool success = a.ComputeAssignment();
73// // Retrieve the cost of the optimum assignment.
74// operations_research::CostValue optimum_cost = a.GetCost();
75// // Retrieve the node-node correspondence of the optimum assignment and the
76// // cost of each node pairing.
77// for (int left_node = 0; left_node < num_left_nodes; ++left_node) {
78// const int right_node = a.GetMate(left_node);
79// operations_research::CostValue node_pair_cost =
80// a.GetAssignmentCost(left_node);
81// ...
82// }
83//
84// In the following, we consider a bipartite graph
85// G = (V = X union Y, E subset XxY),
86// where V denotes the set of nodes (vertices) in the graph, E denotes
87// the set of arcs (edges), n = |V| denotes the number of nodes in the
88// graph, and m = |E| denotes the number of arcs in the graph.
89//
90// The set of nodes is divided into two parts, X and Y, and every arc
91// must go between a node of X and a node of Y. With each arc is
92// associated a cost c(v, w). A matching M is a subset of E with the
93// property that no two arcs in M have a head or tail node in common,
94// and a perfect matching is a matching that touches every node in the
95// graph. The cost of a matching M is the sum of the costs of all the
96// arcs in M.
97//
98// The assignment problem is to find a perfect matching of minimum
99// cost in the given bipartite graph. The present algorithm reduces
100// the assignment problem to an instance of the minimum-cost flow
101// problem and takes advantage of special properties of the resulting
102// minimum-cost flow problem to solve it efficiently using a
103// push-relabel method. For more information about minimum-cost flow
104// see ortools/graph/min_cost_flow.h
105//
106// The method used here is the cost-scaling approach for the
107// minimum-cost circulation problem as described in [Goldberg and
108// Tarjan] with some technical modifications:
109// 1. For efficiency, we solve a transportation problem instead of
110// minimum-cost circulation. We might revisit this decision if it
111// is important to handle problems in which no perfect matching
112// exists.
113// 2. We use a modified "asymmetric" notion of epsilon-optimality in
114// which left-to-right residual arcs are required to have reduced
115// cost bounded below by zero and right-to-left residual arcs are
116// required to have reduced cost bounded below by -epsilon. For
117// each residual arc direction, the reduced-cost threshold for
118// admissibility is epsilon/2 above the threshold for epsilon
119// optimality.
120// 3. We do not limit the applicability of the relabeling operation to
121// nodes with excess. Instead we use the double-push operation
122// (discussed in the Goldberg and Kennedy CSA paper and Kennedy's
123// thesis) which relabels right-side nodes just *after* they have
124// been discharged.
125// The above differences are explained in detail in [Kennedy's thesis]
126// and explained not quite as cleanly in [Goldberg and Kennedy's CSA
127// paper]. But note that the thesis explanation uses a value of
128// epsilon that's double what we use here.
129//
130// Some definitions:
131// Active: A node is called active when it has excess. It is
132// eligible to be pushed from. In this implementation, every active
133// node is on the left side of the graph where prices are determined
134// implicitly, so no left-side relabeling is necessary before
135// pushing from an active node. We do, however, need to compute
136// the implications for price changes on the affected right-side
137// nodes.
138// Admissible: A residual arc (one that can carry more flow) is
139// called admissible when its reduced cost is small enough. We can
140// push additional flow along such an arc without violating
141// epsilon-optimality. In the case of a left-to-right residual
142// arc, the reduced cost must be at most epsilon/2. In the case of
143// a right-to-left residual arc, the reduced cost must be at
144// most -epsilon/2. The careful reader will note that these thresholds
145// are not used explicitly anywhere in this implementation, and
146// the reason is the implicit pricing of left-side nodes.
147// Reduced cost: Essentially an arc's reduced cost is its
148// complementary slackness. In push-relabel algorithms this is
149// c_p(v, w) = p(v) + c(v, w) - p(w),
150// where p() is the node price function and c(v, w) is the cost of
151// the arc from v to w. See min_cost_flow.h for more details.
152// Partial reduced cost: We maintain prices implicitly for left-side
153// nodes in this implementation, so instead of reduced costs we
154// work with partial reduced costs, defined as
155// c'_p(v, w) = c(v, w) - p(w).
156//
157// We check at initialization time for the possibility of arithmetic
158// overflow and warn if the given costs are too large. In many cases
159// the bound we use to trigger the warning is pessimistic so the given
160// problem can often be solved even if we warn that overflow is
161// possible.
162//
163// We don't use the interface from
164// operations_research/algorithms/hungarian.h because we want to be
165// able to express sparse problems efficiently.
166//
167// When asked to solve the given assignment problem we return a
168// boolean to indicate whether the given problem was feasible.
169//
170// References:
171// [ Goldberg and Kennedy's CSA paper ] A. V. Goldberg and R. Kennedy,
172// "An Efficient Cost Scaling Algorithm for the Assignment Problem."
173// Mathematical Programming, Vol. 71, pages 153-178, December 1995.
174//
175// [ Goldberg and Tarjan ] A. V. Goldberg and R. E. Tarjan, "Finding
176// Minimum-Cost Circulations by Successive Approximation." Mathematics
177// of Operations Research, Vol. 15, No. 3, pages 430-466, August 1990.
178//
179// [ Kennedy's thesis ] J. R. Kennedy, Jr., "Solving Unweighted and
180// Weighted Bipartite Matching Problems in Theory and Practice."
181// Stanford University Doctoral Dissertation, Department of Computer
182// Science, 1995.
183//
184// [ Burkard et al. ] R. Burkard, M. Dell'Amico, S. Martello, "Assignment
185// Problems", SIAM, 2009, ISBN: 978-0898716634,
186// http://www.amazon.com/dp/0898716632/
187//
188// [ Ahuja et al. ] R. K. Ahuja, T. L. Magnanti, J. B. Orlin, "Network Flows:
189// Theory, Algorithms, and Applications," Prentice Hall, 1993,
190// ISBN: 978-0136175490, http://www.amazon.com/dp/013617549X.
191//
192// Keywords: linear sum assignment problem, Hungarian method, Goldberg, Kennedy.
193
194#ifndef OR_TOOLS_GRAPH_LINEAR_ASSIGNMENT_H_
195#define OR_TOOLS_GRAPH_LINEAR_ASSIGNMENT_H_
196
197#include <algorithm>
198#include <cstdint>
199#include <cstdlib>
200#include <deque>
201#include <limits>
202#include <memory>
203#include <string>
204#include <utility>
205#include <vector>
206
207#include "absl/flags/declare.h"
208#include "absl/flags/flag.h"
209#include "absl/strings/str_format.h"
210#include "ortools/base/logging.h"
214#include "ortools/util/zvector.h"
215
216#ifndef SWIG
217OR_DLL ABSL_DECLARE_FLAG(int64_t, assignment_alpha);
218OR_DLL ABSL_DECLARE_FLAG(int, assignment_progress_logging_period);
219OR_DLL ABSL_DECLARE_FLAG(bool, assignment_stack_order);
220#endif
221
222namespace operations_research {
223
224// This class does not take ownership of its underlying graph.
225template <typename GraphType, typename CostValue = int64_t>
227 public:
228 typedef typename GraphType::NodeIndex NodeIndex;
229 typedef typename GraphType::ArcIndex ArcIndex;
231
232 // Constructor for the case in which we will build the graph
233 // incrementally as we discover arc costs, as might be done with any
234 // of the dynamic graph representations such as `ReverseArcListGraph`.
235 LinearSumAssignment(const GraphType& graph, NodeIndex num_left_nodes);
236
237 // Constructor for the case in which the underlying graph cannot be built
238 // until after all the arc costs are known, as is the case with `StaticGraph`.
239 // In this case, the graph is passed to us later via the SetGraph() method,
240 // below.
241 LinearSumAssignment(NodeIndex num_left_nodes, ArcIndex num_arcs);
242
243 // This type is neither copyable nor movable.
246
248
249 // Sets the graph used by the `LinearSumAssignment` instance, for use when the
250 // graph layout can be determined only after arc costs are set. This happens,
251 // for example, when we use a `StaticGraph`.
252 void SetGraph(const GraphType* graph) {
253 DCHECK(graph_ == nullptr);
254 graph_ = graph;
255 }
256
257 // Sets the cost-scaling divisor, i.e., the amount by which we
258 // divide the scaling parameter on each iteration.
259 void SetCostScalingDivisor(CostValue factor) { alpha_ = factor; }
260
261 // Returns a permutation cycle handler that can be passed to the
262 // TransformToForwardStaticGraph method so that arc costs get
263 // permuted along with arcs themselves.
264 //
265 // Passes ownership of the cycle handler to the caller.
266 //
269
270 // Allows tests, iterators, etc., to inspect our underlying graph.
271 inline const GraphType& Graph() const { return *graph_; }
272
273 // These handy member functions make the code more compact, and we
274 // expose them to clients so that client code that doesn't have
275 // direct access to the graph can learn about the optimum assignment
276 // once it is computed.
277 inline NodeIndex Head(ArcIndex arc) const { return graph_->Head(arc); }
278
279 // Returns the original arc cost for use by a client that's
280 // iterating over the optimum assignment.
282 DCHECK_EQ(0, scaled_arc_cost_[arc] % cost_scaling_factor_);
283 return scaled_arc_cost_[arc] / cost_scaling_factor_;
284 }
285
286 // Sets the cost of an arc already present in the given graph.
287 void SetArcCost(ArcIndex arc, CostValue cost);
288
289 // Completes initialization after the problem is fully specified.
290 // Returns true if we successfully prove that arithmetic
291 // calculations are guaranteed not to overflow. ComputeAssignment()
292 // calls this method itself, so only clients that care about
293 // obtaining a warning about the possibility of arithmetic precision
294 // problems need to call this method explicitly.
295 //
296 // Separate from ComputeAssignment() for white-box testing and for
297 // clients that need to react to the possibility that arithmetic
298 // overflow is not ruled out.
299 //
300 // FinalizeSetup() is idempotent.
301 bool FinalizeSetup();
302
303 // Computes the optimum assignment. Returns true on success. Return
304 // value of false implies the given problem is infeasible.
305 bool ComputeAssignment();
306
307 // Returns the cost of the minimum-cost perfect matching.
308 // Precondition: success_ == true, signifying that we computed the
309 // optimum assignment for a feasible problem.
310 CostValue GetCost() const;
311
312 // Returns the total number of nodes in the given problem.
314 if (graph_ == nullptr) {
315 // Return a guess that must be true if ultimately we are given a
316 // feasible problem to solve.
317 return 2 * NumLeftNodes();
318 } else {
319 return graph_->num_nodes();
320 }
321 }
322
323 // Returns the number of nodes on the left side of the given
324 // problem.
325 NodeIndex NumLeftNodes() const { return num_left_nodes_; }
326
327 // Returns the arc through which the given node is matched.
328 inline ArcIndex GetAssignmentArc(NodeIndex left_node) const {
329 DCHECK_LT(left_node, num_left_nodes_);
330 return matched_arc_[left_node];
331 }
332
333 // Returns the cost of the assignment arc incident to the given
334 // node.
336 return ArcCost(GetAssignmentArc(node));
337 }
338
339 // Returns the node to which the given node is matched.
340 inline NodeIndex GetMate(NodeIndex left_node) const {
341 DCHECK_LT(left_node, num_left_nodes_);
342 ArcIndex matching_arc = GetAssignmentArc(left_node);
343 DCHECK_NE(GraphType::kNilArc, matching_arc);
344 return Head(matching_arc);
345 }
346
347 std::string StatsString() const { return total_stats_.StatsString(); }
348
349 // Returns the range of valid left node indices.
351 return ::util::IntegerRange<NodeIndex>(0, num_left_nodes_);
352 }
353
354 // Returns true if and only if the current pseudoflow is
355 // epsilon-optimal. To be used in a DCHECK.
356 //
357 // Visible for testing.
358 bool EpsilonOptimal() const;
359
360 private:
361 struct Stats {
362 Stats() : pushes_(0), double_pushes_(0), relabelings_(0), refinements_(0) {}
363 void Clear() {
364 pushes_ = 0;
365 double_pushes_ = 0;
366 relabelings_ = 0;
367 refinements_ = 0;
368 }
369 void Add(const Stats& that) {
370 pushes_ += that.pushes_;
371 double_pushes_ += that.double_pushes_;
372 relabelings_ += that.relabelings_;
373 refinements_ += that.refinements_;
374 }
375 std::string StatsString() const {
376 return absl::StrFormat(
377 "%d refinements; %d relabelings; "
378 "%d double pushes; %d pushes",
379 refinements_, relabelings_, double_pushes_, pushes_);
380 }
381 int64_t pushes_;
382 int64_t double_pushes_;
383 int64_t relabelings_;
384 int64_t refinements_;
385 };
386
387#ifndef SWIG
388 class ActiveNodeContainerInterface {
389 public:
390 virtual ~ActiveNodeContainerInterface() {}
391 virtual bool Empty() const = 0;
392 virtual void Add(NodeIndex node) = 0;
393 virtual NodeIndex Get() = 0;
394 };
395
396 class ActiveNodeStack : public ActiveNodeContainerInterface {
397 public:
398 ~ActiveNodeStack() override {}
399
400 bool Empty() const override { return v_.empty(); }
401
402 void Add(NodeIndex node) override { v_.push_back(node); }
403
404 NodeIndex Get() override {
405 DCHECK(!Empty());
406 NodeIndex result = v_.back();
407 v_.pop_back();
408 return result;
409 }
410
411 private:
412 std::vector<NodeIndex> v_;
413 };
414
415 class ActiveNodeQueue : public ActiveNodeContainerInterface {
416 public:
417 ~ActiveNodeQueue() override {}
418
419 bool Empty() const override { return q_.empty(); }
420
421 void Add(NodeIndex node) override { q_.push_front(node); }
422
423 NodeIndex Get() override {
424 DCHECK(!Empty());
425 NodeIndex result = q_.back();
426 q_.pop_back();
427 return result;
428 }
429
430 private:
431 std::deque<NodeIndex> q_;
432 };
433#endif
434
435 // Type definition for a pair
436 // (arc index, reduced cost gap)
437 // giving the arc along which we will push from a given left-side
438 // node and the gap between that arc's partial reduced cost and the
439 // reduced cost of the next-best (necessarily residual) arc out of
440 // the node. This information helps us efficiently relabel
441 // right-side nodes during DoublePush operations.
442 typedef std::pair<ArcIndex, CostValue> ImplicitPriceSummary;
443
444 // Checks that all nodes are matched.
445 // To be used in a DCHECK.
446 bool AllMatched() const;
447
448 // Calculates the implicit price of the given node.
449 // Only for debugging, for use in EpsilonOptimal().
450 inline CostValue ImplicitPrice(NodeIndex left_node) const;
451
452 // For use by DoublePush()
453 inline ImplicitPriceSummary BestArcAndGap(NodeIndex left_node) const;
454
455 // Accumulates stats between iterations and reports them if the
456 // verbosity level is high enough.
457 void ReportAndAccumulateStats();
458
459 // Utility function to compute the next error parameter value. This
460 // is used to ensure that the same sequence of error parameter
461 // values is used for computation of price bounds as is used for
462 // computing the optimum assignment.
463 CostValue NewEpsilon(CostValue current_epsilon) const;
464
465 // Advances internal state to prepare for the next scaling
466 // iteration. Returns false if infeasibility is detected, true
467 // otherwise.
468 bool UpdateEpsilon();
469
470 // Indicates whether the given left_node has positive excess. Called
471 // only for nodes on the left side.
472 inline bool IsActive(NodeIndex left_node) const;
473
474 // Indicates whether the given node has nonzero excess. The idea
475 // here is the same as the IsActive method above, but that method
476 // contains a safety DCHECK() that its argument is a left-side node,
477 // while this method is usable for any node.
478 // To be used in a DCHECK.
479 inline bool IsActiveForDebugging(NodeIndex node) const;
480
481 // Performs the push/relabel work for one scaling iteration.
482 bool Refine();
483
484 // Puts all left-side nodes in the active set in preparation for the
485 // first scaling iteration.
486 void InitializeActiveNodeContainer();
487
488 // Saturates all negative-reduced-cost arcs at the beginning of each
489 // scaling iteration. Note that according to the asymmetric
490 // definition of admissibility, this action is different from
491 // saturating all admissible arcs (which we never do). All negative
492 // arcs are admissible, but not all admissible arcs are negative. It
493 // is always enough to saturate only the negative ones.
494 void SaturateNegativeArcs();
495
496 // Performs an optimized sequence of pushing a unit of excess out of
497 // the left-side node v and back to another left-side node if no
498 // deficit is cancelled with the first push.
499 bool DoublePush(NodeIndex source);
500
501 // Returns the partial reduced cost of the given arc.
502 inline CostValue PartialReducedCost(ArcIndex arc) const {
503 return scaled_arc_cost_[arc] - price_[Head(arc)];
504 }
505
506 // The graph underlying the problem definition we are given. Not
507 // owned by *this.
508 const GraphType* graph_;
509
510 // The number of nodes on the left side of the graph we are given.
511 NodeIndex num_left_nodes_;
512
513 // A flag indicating, after FinalizeSetup() has run, whether the
514 // arc-incidence precondition required by BestArcAndGap() is
515 // satisfied by every left-side node. If not, the problem is
516 // infeasible.
517 bool incidence_precondition_satisfied_;
518
519 // A flag indicating that an optimal perfect matching has been computed.
520 bool success_;
521
522 // The value by which we multiply all the arc costs we are given in
523 // order to be able to use integer arithmetic in all our
524 // computations. In order to establish optimality of the final
525 // matching we compute, we need that
526 // (cost_scaling_factor_ / kMinEpsilon) > graph_->num_nodes().
527 const CostValue cost_scaling_factor_;
528
529 // Scaling divisor.
530 CostValue alpha_;
531
532 // Minimum value of epsilon. When a flow is epsilon-optimal for
533 // epsilon == kMinEpsilon, the flow is optimal.
534 static constexpr CostValue kMinEpsilon = 1;
535
536 // Current value of epsilon, the cost scaling parameter.
537 CostValue epsilon_;
538
539 // The following two data members, price_lower_bound_ and
540 // slack_relabeling_price_, have to do with bounds on the amount by
541 // which node prices can change during execution of the algorithm.
542 // We need some detailed discussion of this topic because we violate
543 // several simplifying assumptions typically made in the theoretical
544 // literature. In particular, we use integer arithmetic, we use a
545 // reduction to the transportation problem rather than min-cost
546 // circulation, we provide detection of infeasible problems rather
547 // than assume feasibility, we detect when our computations might
548 // exceed the range of representable cost values, and we use the
549 // double-push heuristic which relabels nodes that do not have
550 // excess.
551 //
552 // In the following discussion, we prove the following propositions:
553 // Proposition 1. [Fidelity of arithmetic precision guarantee] If
554 // FinalizeSetup() returns true, no arithmetic
555 // overflow occurs during ComputeAssignment().
556 // Proposition 2. [Fidelity of feasibility detection] If no
557 // arithmetic overflow occurs during
558 // ComputeAssignment(), the return value of
559 // ComputeAssignment() faithfully indicates whether
560 // the given problem is feasible.
561 //
562 // We begin with some general discussion.
563 //
564 // The ideas used to prove our two propositions are essentially
565 // those that appear in [Goldberg and Tarjan], but several details
566 // are different: [Goldberg and Tarjan] assumes a feasible problem,
567 // uses a symmetric notion of epsilon-optimality, considers only
568 // nodes with excess eligible for relabeling, and does not treat the
569 // question of arithmetic overflow. This implementation, on the
570 // other hand, detects and reports infeasible problems, uses
571 // asymmetric epsilon-optimality, relabels nodes with no excess in
572 // the course of the double-push operation, and gives a reasonably
573 // tight guarantee of arithmetic precision. No fundamentally new
574 // ideas are involved, but the details are a bit tricky so they are
575 // explained here.
576 //
577 // We have two intertwined needs that lead us to compute bounds on
578 // the prices nodes can have during the assignment computation, on
579 // the assumption that the given problem is feasible:
580 // 1. Infeasibility detection: Infeasibility is detected by
581 // observing that some node's price has been reduced too much by
582 // relabeling operations (see [Goldberg and Tarjan] for the
583 // argument -- duplicated in modified form below -- bounding the
584 // running time of the push/relabel min-cost flow algorithm for
585 // feasible problems); and
586 // 2. Aggressively relabeling nodes and arcs whose matching is
587 // forced: When a left-side node is incident to only one arc a,
588 // any feasible solution must include a, and reducing the price
589 // of Head(a) by any nonnegative amount preserves epsilon-
590 // optimality. Because of this freedom, we'll call this sort of
591 // relabeling (i.e., a relabeling of a right-side node that is
592 // the only neighbor of the left-side node to which it has been
593 // matched in the present double-push operation) a "slack"
594 // relabeling. Relabelings that are not slack relabelings are
595 // called "confined" relabelings. By relabeling Head(a) to have
596 // p(Head(a))=-infinity, we could guarantee that a never becomes
597 // unmatched during the current iteration, and this would prevent
598 // our wasting time repeatedly unmatching and rematching a. But
599 // there are some details we need to handle:
600 // a. The CostValue type cannot represent -infinity;
601 // b. Low node prices are precisely the signal we use to detect
602 // infeasibility (see (1)), so we must be careful not to
603 // falsely conclude that the problem is infeasible as a result
604 // of the low price we gave Head(a); and
605 // c. We need to indicate accurately to the client when our best
606 // understanding indicates that we can't rule out arithmetic
607 // overflow in our calculations. Most importantly, if we don't
608 // warn the client, we must be certain to avoid overflow. This
609 // means our slack relabelings must not be so aggressive as to
610 // create the possibility of unforeseen overflow. Although we
611 // will not achieve this in practice, slack relabelings would
612 // ideally not introduce overflow unless overflow was
613 // inevitable were even the smallest reasonable price change
614 // (== epsilon) used for slack relabelings.
615 // Using the analysis below, we choose a finite amount of price
616 // change for slack relabelings aggressive enough that we don't
617 // waste time doing repeated slack relabelings in a single
618 // iteration, yet modest enough that we keep a good handle on
619 // arithmetic precision and our ability to detect infeasible
620 // problems.
621 //
622 // To provide faithful detection of infeasibility, a dependable
623 // guarantee of arithmetic precision whenever possible, and good
624 // performance by aggressively relabeling nodes whose matching is
625 // forced, we exploit these facts:
626 // 1. Beyond the first iteration, infeasibility detection isn't needed
627 // because a problem is feasible in some iteration if and only if
628 // it's feasible in all others. Therefore we are free to use an
629 // infeasibility detection mechanism that might work in just one
630 // iteration and switch it off in all other iterations.
631 // 2. When we do a slack relabeling, we must choose the amount of
632 // price reduction to use. We choose an amount large enough to
633 // guarantee putting the node's matching to rest, yet (although
634 // we don't bother to prove this explicitly) small enough that
635 // the node's price obeys the overall lower bound that holds if
636 // the slack relabeling amount is small.
637 //
638 // We will establish Propositions (1) and (2) above according to the
639 // following steps:
640 // First, we prove Lemma 1, which is a modified form of lemma 5.8 of
641 // [Goldberg and Tarjan] giving a bound on the difference in price
642 // between the end nodes of certain paths in the residual graph.
643 // Second, we prove Lemma 2, which is technical lemma to establish
644 // reachability of certain "anchor" nodes in the residual graph from
645 // any node where a relabeling takes place.
646 // Third, we apply the first two lemmas to prove Lemma 3 and Lemma
647 // 4, which give two similar bounds that hold whenever the given
648 // problem is feasible: (for feasibility detection) a bound on the
649 // price of any node we relabel during any iteration (and the first
650 // iteration in particular), and (for arithmetic precision) a bound
651 // on the price of any node we relabel during the entire algorithm.
652 //
653 // Finally, we note that if the whole-algorithm price bound can be
654 // represented precisely by the CostValue type, arithmetic overflow
655 // cannot occur (establishing Proposition 1), and assuming no
656 // overflow occurs during the first iteration, any violation of the
657 // first-iteration price bound establishes infeasibility
658 // (Proposition 2).
659 //
660 // The statement of Lemma 1 is perhaps easier to understand when the
661 // reader knows how it will be used. To wit: In this lemma, f' and
662 // e_0 are the flow and error parameter (epsilon) at the beginning
663 // of the current iteration, while f and e_1 are the current
664 // pseudoflow and error parameter when a relabeling of interest
665 // occurs. Without loss of generality, c is the reduced cost
666 // function at the beginning of the current iteration and p is the
667 // change in prices that has taken place in the current iteration.
668 //
669 // Lemma 1 (a variant of lemma 5.8 from [Goldberg and Tarjan]): Let
670 // f be a pseudoflow and let f' be a flow. Suppose P is a simple
671 // path from right-side node v to right-side node w such that P is
672 // residual with respect to f and reverse(P) is residual with
673 // respect to f'. Further, suppose c is an arc cost function with
674 // respect to which f' is e_0-optimal with the zero price function
675 // and p is a price function with respect to which f is e_1-optimal
676 // with respect to p. Then
677 // p(v) - p(w) >= -(e_0 + e_1) * (n-2)/2. (***)
678 //
679 // Proof: We have c_p(P) = p(v) + c(P) - p(w) and hence
680 // p(v) - p(w) = c_p(P) - c(P).
681 // So we seek a bound on c_p(P) - c(P).
682 // p(v) = c_p(P) - c(P).
683 // Let arc a lie on P, which implies that a is residual with respect
684 // to f and reverse(a) is residual with respect to f'.
685 // Case 1: a is a forward arc. Then by e_1-optimality of f with
686 // respect to p, c_p(a) >= 0 and reverse(a) is residual with
687 // respect to f'. By e_0-optimality of f', c(a) <= e_0. So
688 // c_p(a) - c(a) >= -e_0.
689 // Case 2: a is a reverse arc. Then by e_1-optimality of f with
690 // respect to p, c_p(a) >= -e_1 and reverse(a) is residual
691 // with respect to f'. By e_0-optimality of f', c(a) <= 0.
692 // So
693 // c_p(a) - c(a) >= -e_1.
694 // We assumed v and w are both right-side nodes, so there are at
695 // most n - 2 arcs on the path P, of which at most (n-2)/2 are
696 // forward arcs and at most (n-2)/2 are reverse arcs, so
697 // p(v) - p(w) = c_p(P) - c(P)
698 // >= -(e_0 + e_1) * (n-2)/2. (***)
699 //
700 // Some of the rest of our argument is given as a sketch, omitting
701 // several details. Also elided here are some minor technical issues
702 // related to the first iteration, inasmuch as our arguments assume
703 // on the surface a "previous iteration" that doesn't exist in that
704 // case. The issues are not substantial, just a bit messy.
705 //
706 // Lemma 2 is analogous to lemma 5.7 of [Goldberg and Tarjan], where
707 // they have only relabelings that take place at nodes with excess
708 // while we have only relabelings that take place as part of the
709 // double-push operation at nodes without excess.
710 //
711 // Lemma 2: If the problem is feasible, for any node v with excess,
712 // there exists a path P from v to a node w with deficit such that P
713 // is residual with respect to the current pseudoflow, and
714 // reverse(P) is residual with respect to the flow at the beginning
715 // of the current iteration. (Note that such a path exactly
716 // satisfies the conditions of Lemma 1.)
717 //
718 // Let the bound from Lemma 1 with p(w) = 0 be called B(e_0, e_1),
719 // and let us say that when a slack relabeling of a node v occurs,
720 // we will change the price of v by B(e_0, e_1) such that v tightly
721 // satisfies the bound of Lemma 1. Explicitly, we define
722 // B(e_0, e_1) = -(e_0 + e_1) * (n-2)/2.
723 //
724 // Lemma 1 and Lemma 2 combine to bound the price change during an
725 // iteration for any node with excess. Viewed a different way, Lemma
726 // 1 and Lemma 2 tell us that if epsilon-optimality can be preserved
727 // by changing the price of a node by B(e_0, e_1), that node will
728 // never have excess again during the current iteration unless the
729 // problem is infeasible. This insight gives us an approach to
730 // detect infeasibility (by observing prices on nodes with excess
731 // that violate this bound) and to relabel nodes aggressively enough
732 // to avoid unnecessary future work while we also avoid falsely
733 // concluding the problem is infeasible.
734 //
735 // From Lemma 1 and Lemma 2, and taking into account our knowledge
736 // of the slack relabeling amount, we have Lemma 3.
737 //
738 // Lemma 3: During any iteration, if the given problem is feasible
739 // the price of any node is reduced by less than
740 // -2 * B(e_0, e_1) = (e_0 + e_1) * (n-2).
741 //
742 // Proof: Straightforward, omitted for expedience.
743 //
744 // In the case where e_0 = e_1 * alpha, we can express the bound
745 // just in terms of e_1, the current iteration's value of epsilon_:
746 // B(e_1) = B(e_1 * alpha, e_1) = -(1 + alpha) * e_1 * (n-2)/2,
747 // so we have that p(v) is reduced by less than 2 * B(e_1).
748 //
749 // Because we use truncating division to compute each iteration's error
750 // parameter from that of the previous iteration, it isn't exactly
751 // the case that e_0 = e_1 * alpha as we just assumed. To patch this
752 // up, we can use the observation that
753 // e_1 = floor(e_0 / alpha),
754 // which implies
755 // -e_0 > -(e_1 + 1) * alpha
756 // to rewrite from (***):
757 // p(v) > 2 * B(e_0, e_1) > 2 * B((e_1 + 1) * alpha, e_1)
758 // = 2 * -((e_1 + 1) * alpha + e_1) * (n-2)/2
759 // = 2 * -(1 + alpha) * e_1 * (n-2)/2 - alpha * (n-2)
760 // = 2 * B(e_1) - alpha * (n-2)
761 // = -((1 + alpha) * e_1 + alpha) * (n-2).
762 //
763 // We sum up the bounds for all the iterations to get Lemma 4:
764 //
765 // Lemma 4: If the given problem is feasible, after k iterations the
766 // price of any node is always greater than
767 // -((1 + alpha) * C + (k * alpha)) * (n-2)
768 //
769 // Proof: Suppose the price decrease of every node in the iteration
770 // with epsilon_ == x is bounded by B(x) which is proportional to x
771 // (not surprisingly, this will be the same function B() as
772 // above). Assume for simplicity that C, the largest cost magnitude,
773 // is a power of alpha. Then the price of each node, tallied across
774 // all iterations is bounded
775 // p(v) > 2 * B(C/alpha) + 2 * B(C/alpha^2) + ... + 2 * B(kMinEpsilon)
776 // == 2 * B(C/alpha) * alpha / (alpha - 1)
777 // == 2 * B(C) / (alpha - 1).
778 // As above, this needs some patching up to handle the fact that we
779 // use truncating arithmetic. We saw that each iteration effectively
780 // reduces the price bound by alpha * (n-2), hence if there are k
781 // iterations, the bound is
782 // p(v) > 2 * B(C) / (alpha - 1) - k * alpha * (n-2)
783 // = -(1 + alpha) * C * (n-2) / (alpha - 1) - k * alpha * (n-2)
784 // = (n-2) * (C * (1 + alpha) / (1 - alpha) - k * alpha).
785 //
786 // The bound of lemma 4 can be used to warn for possible overflow of
787 // arithmetic precision. But because it involves the number of
788 // iterations, k, we might as well count through the iterations
789 // simply adding up the bounds given by Lemma 3 to get a tighter
790 // result. This is what the implementation does.
791
792 // A lower bound on the price of any node at any time throughout the
793 // computation. A price below this level proves infeasibility; this
794 // value is used for feasibility detection. We use this value also
795 // to rule out the possibility of arithmetic overflow or warn the
796 // client that we have not been able to rule out that possibility.
797 //
798 // We can use the value implied by Lemma 4 here, but note that that
799 // value includes k, the number of iterations. It's plenty fast if
800 // we count through the iterations to compute that value, but if
801 // we're going to count through the iterations, we might as well use
802 // the two-parameter bound from Lemma 3, summing up as we go. This
803 // gives us a tighter bound and more comprehensible code.
804 //
805 // While computing this bound, if we find the value justified by the
806 // theory lies outside the representable range of CostValue, we
807 // conclude that the given arc costs have magnitudes so large that
808 // we cannot guarantee our calculations don't overflow. If the value
809 // justified by the theory lies inside the representable range of
810 // CostValue, we commit that our calculation will not overflow. This
811 // commitment means we need to be careful with the amount by which
812 // we relabel right-side nodes that are incident to any node with
813 // only one neighbor.
814 CostValue price_lower_bound_;
815
816 // A bound on the amount by which a node's price can be reduced
817 // during the current iteration, used only for slack
818 // relabelings. Where epsilon is the first iteration's error
819 // parameter and C is the largest magnitude of an arc cost, we set
820 // slack_relabeling_price_ = -B(C, epsilon)
821 // = (C + epsilon) * (n-2)/2.
822 //
823 // We could use slack_relabeling_price_ for feasibility detection
824 // but the feasibility threshold is double the slack relabeling
825 // amount and we judge it not to be worth having to multiply by two
826 // gratuitously to check feasibility in each double push
827 // operation. Instead we settle for feasibility detection using
828 // price_lower_bound_ instead, which is somewhat slower in the
829 // infeasible case because more relabelings will be required for
830 // some node price to attain the looser bound.
831 CostValue slack_relabeling_price_;
832
833 // Computes the value of the bound on price reduction for an
834 // iteration, given the old and new values of epsilon_. Because the
835 // expression computed here is used in at least one place where we
836 // want an additional factor in the denominator, we take that factor
837 // as an argument. If extra_divisor == 1, this function computes of
838 // the function B() discussed above.
839 //
840 // Avoids overflow in computing the bound, and sets *in_range =
841 // false if the value of the bound doesn't fit in CostValue.
842 inline CostValue PriceChangeBound(CostValue old_epsilon,
843 CostValue new_epsilon,
844 bool* in_range) const {
845 const CostValue n = graph_->num_nodes();
846 // We work in double-precision floating point to determine whether
847 // we'll overflow the integral CostValue type's range of
848 // representation. Switching between integer and double is a
849 // rather expensive operation, but we do this only twice per
850 // scaling iteration, so we can afford it rather than resort to
851 // complex and subtle tricks within the bounds of integer
852 // arithmetic.
853 //
854 // You will want to read the comments above about
855 // price_lower_bound_ and slack_relabeling_price_, and have a
856 // pencil handy. :-)
857 const double result =
858 static_cast<double>(std::max<CostValue>(1, n / 2 - 1)) *
859 (static_cast<double>(old_epsilon) + static_cast<double>(new_epsilon));
860 const double limit =
861 static_cast<double>(std::numeric_limits<CostValue>::max());
862 if (result > limit) {
863 // Our integer computations could overflow.
864 if (in_range != nullptr) *in_range = false;
865 return std::numeric_limits<CostValue>::max();
866 } else {
867 // Don't touch *in_range; other computations could already have
868 // set it to false and we don't want to overwrite that result.
869 return static_cast<CostValue>(result);
870 }
871 }
872
873 // A scaled record of the largest arc-cost magnitude we've been
874 // given during problem setup. This is used to set the initial value
875 // of epsilon_, which in turn is used not only as the error
876 // parameter but also to determine whether we risk arithmetic
877 // overflow during the algorithm.
878 //
879 // Note: Our treatment of arithmetic overflow assumes the following
880 // property of CostValue:
881 // -std::numeric_limits<CostValue>::max() is a representable
882 // CostValue.
883 // That property is satisfied if CostValue uses a two's-complement
884 // representation.
885 CostValue largest_scaled_cost_magnitude_;
886
887 // The total excess in the graph. Given our asymmetric definition of
888 // epsilon-optimality and our use of the double-push operation, this
889 // equals the number of unmatched left-side nodes.
890 NodeIndex total_excess_;
891
892 // Indexed by node index, the price_ values are maintained only for
893 // right-side nodes.
894 //
895 // Note: We use a ZVector to only allocate a vector of size num_left_nodes_
896 // instead of 2*num_left_nodes_ since the right-side node indices start at
897 // num_left_nodes_.
898 ZVector<CostValue> price_;
899
900 // Indexed by left-side node index, the matched_arc_ array gives the
901 // arc index of the arc matching any given left-side node, or
902 // GraphType::kNilArc if the node is unmatched.
903 std::vector<ArcIndex> matched_arc_;
904
905 // Indexed by right-side node index, the matched_node_ array gives
906 // the node index of the left-side node matching any given
907 // right-side node, or GraphType::kNilNode if the right-side node is
908 // unmatched.
909 //
910 // Note: We use a ZVector for the same reason as for price_.
911 ZVector<NodeIndex> matched_node_;
912
913 // The array of arc costs as given in the problem definition, except
914 // that they are scaled up by the number of nodes in the graph so we
915 // can use integer arithmetic throughout.
916 std::vector<CostValue> scaled_arc_cost_;
917
918 // The container of active nodes (i.e., unmatched nodes). This can
919 // be switched easily between ActiveNodeStack and ActiveNodeQueue
920 // for experimentation.
921 std::unique_ptr<ActiveNodeContainerInterface> active_nodes_;
922
923 // Statistics giving the overall numbers of various operations the
924 // algorithm performs.
925 Stats total_stats_;
926
927 // Statistics giving the numbers of various operations the algorithm
928 // has performed in the current iteration.
929 Stats iteration_stats_;
930};
931
932// Implementation of out-of-line LinearSumAssignment template member
933// functions.
934
935template <typename GraphType, typename CostValue>
937 const GraphType& graph, const NodeIndex num_left_nodes)
938 : graph_(&graph),
939 num_left_nodes_(num_left_nodes),
940 success_(false),
941 cost_scaling_factor_(1 + num_left_nodes),
942 alpha_(absl::GetFlag(FLAGS_assignment_alpha)),
943 epsilon_(0),
944 price_lower_bound_(0),
945 slack_relabeling_price_(0),
946 largest_scaled_cost_magnitude_(0),
947 total_excess_(0),
948 price_(num_left_nodes, 2 * num_left_nodes - 1),
949 matched_arc_(num_left_nodes, 0),
950 matched_node_(num_left_nodes, 2 * num_left_nodes - 1),
951 scaled_arc_cost_(graph.arc_capacity(), 0),
952 active_nodes_(absl::GetFlag(FLAGS_assignment_stack_order)
953 ? static_cast<ActiveNodeContainerInterface*>(
954 new ActiveNodeStack())
955 : static_cast<ActiveNodeContainerInterface*>(
956 new ActiveNodeQueue())) {}
957
958template <typename GraphType, typename CostValue>
960 const NodeIndex num_left_nodes, const ArcIndex num_arcs)
961 : graph_(nullptr),
962 num_left_nodes_(num_left_nodes),
963 success_(false),
964 cost_scaling_factor_(1 + num_left_nodes),
965 alpha_(absl::GetFlag(FLAGS_assignment_alpha)),
966 epsilon_(0),
967 price_lower_bound_(0),
968 slack_relabeling_price_(0),
969 largest_scaled_cost_magnitude_(0),
970 total_excess_(0),
971 price_(num_left_nodes, 2 * num_left_nodes - 1),
972 matched_arc_(num_left_nodes, 0),
973 matched_node_(num_left_nodes, 2 * num_left_nodes - 1),
974 scaled_arc_cost_(num_arcs, 0),
975 active_nodes_(absl::GetFlag(FLAGS_assignment_stack_order)
976 ? static_cast<ActiveNodeContainerInterface*>(
977 new ActiveNodeStack())
978 : static_cast<ActiveNodeContainerInterface*>(
979 new ActiveNodeQueue())) {}
980
981template <typename GraphType, typename CostValue>
983 CostValue cost) {
984 if (graph_ != nullptr) {
985 DCHECK_GE(arc, 0);
986 DCHECK_LT(arc, graph_->num_arcs());
987 NodeIndex head = Head(arc);
988 DCHECK_LE(num_left_nodes_, head);
989 }
990 cost *= cost_scaling_factor_;
991 const CostValue cost_magnitude = std::abs(cost);
992 largest_scaled_cost_magnitude_ =
993 std::max(largest_scaled_cost_magnitude_, cost_magnitude);
994 scaled_arc_cost_[arc] = cost;
995}
996
997template <typename ArcIndexType, typename CostValue>
998class CostValueCycleHandler : public PermutationCycleHandler<ArcIndexType> {
999 public:
1000 explicit CostValueCycleHandler(std::vector<CostValue>* cost)
1001 : temp_(0), cost_(cost) {}
1002
1003 // This type is neither copyable nor movable.
1006
1007 void SetTempFromIndex(ArcIndexType source) override {
1008 temp_ = (*cost_)[source];
1009 }
1010
1011 void SetIndexFromIndex(ArcIndexType source,
1012 ArcIndexType destination) const override {
1013 (*cost_)[destination] = (*cost_)[source];
1014 }
1015
1016 void SetIndexFromTemp(ArcIndexType destination) const override {
1017 (*cost_)[destination] = temp_;
1018 }
1019
1021
1022 private:
1023 CostValue temp_;
1024 std::vector<CostValue>* const cost_;
1025};
1026
1027// Logically this class should be defined inside OptimizeGraphLayout,
1028// but compilation fails if we do that because C++98 doesn't allow
1029// instantiation of member templates with function-scoped types as
1030// template parameters, which in turn is because those function-scoped
1031// types lack linkage.
1032template <typename GraphType>
1034 public:
1035 explicit ArcIndexOrderingByTailNode(const GraphType& graph) : graph_(graph) {}
1036
1037 // Says ArcIndex a is less than ArcIndex b if arc a's tail is less
1038 // than arc b's tail. If their tails are equal, orders according to
1039 // heads.
1040 bool operator()(typename GraphType::ArcIndex a,
1041 typename GraphType::ArcIndex b) const {
1042 return ((graph_.Tail(a) < graph_.Tail(b)) ||
1043 ((graph_.Tail(a) == graph_.Tail(b)) &&
1044 (graph_.Head(a) < graph_.Head(b))));
1045 }
1046
1047 private:
1048 const GraphType& graph_;
1049
1050 // Copy and assign are allowed; they have to be for STL to work
1051 // with this functor, although it seems like a bug for STL to be
1052 // written that way.
1053};
1054
1055// Passes ownership of the cycle handler to the caller.
1056template <typename GraphType, typename CostValue>
1057PermutationCycleHandler<typename GraphType::ArcIndex>*
1062
1063template <typename GraphType, typename CostValue>
1064CostValue LinearSumAssignment<GraphType, CostValue>::NewEpsilon(
1065 const CostValue current_epsilon) const {
1066 return std::max(current_epsilon / alpha_, kMinEpsilon);
1067}
1068
1069template <typename GraphType, typename CostValue>
1070bool LinearSumAssignment<GraphType, CostValue>::UpdateEpsilon() {
1071 CostValue new_epsilon = NewEpsilon(epsilon_);
1072 slack_relabeling_price_ = PriceChangeBound(epsilon_, new_epsilon, nullptr);
1073 epsilon_ = new_epsilon;
1074 VLOG(3) << "Updated: epsilon_ == " << epsilon_;
1075 VLOG(4) << "slack_relabeling_price_ == " << slack_relabeling_price_;
1076 DCHECK_GT(slack_relabeling_price_, 0);
1077 // For today we always return true; in the future updating epsilon
1078 // in sophisticated ways could conceivably detect infeasibility
1079 // before the first iteration of Refine().
1080 return true;
1081}
1082
1083// For production code that checks whether a left-side node is active.
1084template <typename GraphType, typename CostValue>
1085inline bool LinearSumAssignment<GraphType, CostValue>::IsActive(
1086 NodeIndex left_node) const {
1087 DCHECK_LT(left_node, num_left_nodes_);
1088 return matched_arc_[left_node] == GraphType::kNilArc;
1089}
1090
1091// Only for debugging. Separate from the production IsActive() method
1092// so that method can assert that its argument is a left-side node,
1093// while for debugging we need to be able to test any node.
1094template <typename GraphType, typename CostValue>
1095inline bool LinearSumAssignment<GraphType, CostValue>::IsActiveForDebugging(
1096 NodeIndex node) const {
1097 if (node < num_left_nodes_) {
1098 return IsActive(node);
1099 } else {
1100 return matched_node_[node] == GraphType::kNilNode;
1101 }
1102}
1103
1104template <typename GraphType, typename CostValue>
1105void LinearSumAssignment<GraphType,
1106 CostValue>::InitializeActiveNodeContainer() {
1107 DCHECK(active_nodes_->Empty());
1108 for (const NodeIndex node : BipartiteLeftNodes()) {
1109 if (IsActive(node)) {
1110 active_nodes_->Add(node);
1111 }
1112 }
1113}
1114
1115// There exists a price function such that the admissible arcs at the
1116// beginning of an iteration are exactly the reverse arcs of all
1117// matching arcs. Saturating all admissible arcs with respect to that
1118// price function therefore means simply unmatching every matched
1119// node.
1120//
1121// In the future we will price out arcs, which will reduce the set of
1122// nodes we unmatch here. If a matching arc is priced out, we will not
1123// unmatch its endpoints since that element of the matching is
1124// guaranteed not to change.
1125template <typename GraphType, typename CostValue>
1126void LinearSumAssignment<GraphType, CostValue>::SaturateNegativeArcs() {
1127 total_excess_ = 0;
1128 for (const NodeIndex node : BipartiteLeftNodes()) {
1129 if (IsActive(node)) {
1130 // This can happen in the first iteration when nothing is
1131 // matched yet.
1132 total_excess_ += 1;
1133 } else {
1134 // We're about to create a unit of excess by unmatching these nodes.
1135 total_excess_ += 1;
1136 const NodeIndex mate = GetMate(node);
1137 matched_arc_[node] = GraphType::kNilArc;
1138 matched_node_[mate] = GraphType::kNilNode;
1139 }
1140 }
1141}
1142
1143// Returns true for success, false for infeasible.
1144template <typename GraphType, typename CostValue>
1145bool LinearSumAssignment<GraphType, CostValue>::DoublePush(NodeIndex source) {
1146 DCHECK_GT(num_left_nodes_, source);
1147 DCHECK(IsActive(source)) << "Node " << source
1148 << "must be active (unmatched)!";
1149 ImplicitPriceSummary summary = BestArcAndGap(source);
1150 const ArcIndex best_arc = summary.first;
1151 const CostValue gap = summary.second;
1152 // Now we have the best arc incident to source, i.e., the one with
1153 // minimum reduced cost. Match that arc, unmatching its head if
1154 // necessary.
1155 if (best_arc == GraphType::kNilArc) {
1156 return false;
1157 }
1158 const NodeIndex new_mate = Head(best_arc);
1159 const NodeIndex to_unmatch = matched_node_[new_mate];
1160 if (to_unmatch != GraphType::kNilNode) {
1161 // Unmatch new_mate from its current mate, pushing the unit of
1162 // flow back to a node on the left side as a unit of excess.
1163 matched_arc_[to_unmatch] = GraphType::kNilArc;
1164 active_nodes_->Add(to_unmatch);
1165 // This counts as a double push.
1166 iteration_stats_.double_pushes_ += 1;
1167 } else {
1168 // We are about to increase the cardinality of the matching.
1169 total_excess_ -= 1;
1170 // This counts as a single push.
1171 iteration_stats_.pushes_ += 1;
1172 }
1173 matched_arc_[source] = best_arc;
1174 matched_node_[new_mate] = source;
1175 // Finally, relabel new_mate.
1176 iteration_stats_.relabelings_ += 1;
1177 const CostValue new_price = price_[new_mate] - gap - epsilon_;
1178 price_[new_mate] = new_price;
1179 return new_price >= price_lower_bound_;
1180}
1181
1182template <typename GraphType, typename CostValue>
1183bool LinearSumAssignment<GraphType, CostValue>::Refine() {
1184 SaturateNegativeArcs();
1185 InitializeActiveNodeContainer();
1186 while (total_excess_ > 0) {
1187 // Get an active node (i.e., one with excess == 1) and discharge
1188 // it using DoublePush.
1189 const NodeIndex node = active_nodes_->Get();
1190 if (!DoublePush(node)) {
1191 // Infeasibility detected.
1192 //
1193 // If infeasibility is detected after the first iteration, we
1194 // have a bug. We don't crash production code in this case but
1195 // we know we're returning a wrong answer so we we leave a
1196 // message in the logs to increase our hope of chasing down the
1197 // problem.
1198 LOG_IF(DFATAL, total_stats_.refinements_ > 0)
1199 << "Infeasibility detection triggered after first iteration found "
1200 << "a feasible assignment!";
1201 return false;
1202 }
1203 }
1204 DCHECK(active_nodes_->Empty());
1205 iteration_stats_.refinements_ += 1;
1206 return true;
1207}
1208
1209// Computes best_arc, the minimum reduced-cost arc incident to
1210// left_node and admissibility_gap, the amount by which the reduced
1211// cost of best_arc must be increased to make it equal in reduced cost
1212// to another residual arc incident to left_node.
1213//
1214// Precondition: left_node is unmatched and has at least one incident
1215// arc. This allows us to simplify the code. The debug-only
1216// counterpart to this routine is LinearSumAssignment::ImplicitPrice()
1217// and it assumes there is an incident arc but does not assume the
1218// node is unmatched. The condition that each left node has at least
1219// one incident arc is explicitly computed during FinalizeSetup().
1220//
1221// This function is large enough that our suggestion that the compiler
1222// inline it might be pointless.
1223template <typename GraphType, typename CostValue>
1224inline typename LinearSumAssignment<GraphType, CostValue>::ImplicitPriceSummary
1225LinearSumAssignment<GraphType, CostValue>::BestArcAndGap(
1226 NodeIndex left_node) const {
1227 DCHECK(IsActive(left_node))
1228 << "Node " << left_node << " must be active (unmatched)!";
1229 DCHECK_GT(epsilon_, 0);
1230 typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
1231 ArcIndex best_arc = arc_it.Index();
1232 CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
1233 // We choose second_min_partial_reduced_cost so that in the case of
1234 // the largest possible gap (which results from a left-side node
1235 // with only a single incident residual arc), the corresponding
1236 // right-side node will be relabeled by an amount that exactly
1237 // matches slack_relabeling_price_.
1238 const CostValue max_gap = slack_relabeling_price_ - epsilon_;
1239 CostValue second_min_partial_reduced_cost =
1240 min_partial_reduced_cost + max_gap;
1241 for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
1242 const ArcIndex arc = arc_it.Index();
1243 const CostValue partial_reduced_cost = PartialReducedCost(arc);
1244 if (partial_reduced_cost < second_min_partial_reduced_cost) {
1245 if (partial_reduced_cost < min_partial_reduced_cost) {
1246 best_arc = arc;
1247 second_min_partial_reduced_cost = min_partial_reduced_cost;
1248 min_partial_reduced_cost = partial_reduced_cost;
1249 } else {
1250 second_min_partial_reduced_cost = partial_reduced_cost;
1251 }
1252 }
1253 }
1254 const CostValue gap = std::min<CostValue>(
1255 second_min_partial_reduced_cost - min_partial_reduced_cost, max_gap);
1256 DCHECK_GE(gap, 0);
1257 return std::make_pair(best_arc, gap);
1258}
1259
1260// Only for debugging.
1261//
1262// Requires the precondition, explicitly computed in FinalizeSetup(),
1263// that every left-side node has at least one incident arc.
1264template <typename GraphType, typename CostValue>
1265inline CostValue LinearSumAssignment<GraphType, CostValue>::ImplicitPrice(
1266 NodeIndex left_node) const {
1267 DCHECK_GT(num_left_nodes_, left_node);
1268 DCHECK_GT(epsilon_, 0);
1269 typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
1270 // We must not execute this method if left_node has no incident arc.
1271 DCHECK(arc_it.Ok());
1272 ArcIndex best_arc = arc_it.Index();
1273 if (best_arc == matched_arc_[left_node]) {
1274 arc_it.Next();
1275 if (arc_it.Ok()) {
1276 best_arc = arc_it.Index();
1277 }
1278 }
1279 CostValue min_partial_reduced_cost = PartialReducedCost(best_arc);
1280 if (!arc_it.Ok()) {
1281 // Only one arc is incident to left_node, and the node is
1282 // currently matched along that arc, which must be the case in any
1283 // feasible solution. Therefore we implicitly price this node so
1284 // low that we will never consider unmatching it.
1285 return -(min_partial_reduced_cost + slack_relabeling_price_);
1286 }
1287 for (arc_it.Next(); arc_it.Ok(); arc_it.Next()) {
1288 const ArcIndex arc = arc_it.Index();
1289 if (arc != matched_arc_[left_node]) {
1290 const CostValue partial_reduced_cost = PartialReducedCost(arc);
1291 if (partial_reduced_cost < min_partial_reduced_cost) {
1292 min_partial_reduced_cost = partial_reduced_cost;
1293 }
1294 }
1295 }
1296 return -min_partial_reduced_cost;
1297}
1298
1299// Only for debugging.
1300template <typename GraphType, typename CostValue>
1301bool LinearSumAssignment<GraphType, CostValue>::AllMatched() const {
1302 for (NodeIndex node = 0; node < graph_->num_nodes(); ++node) {
1303 if (IsActiveForDebugging(node)) {
1304 return false;
1305 }
1306 }
1307 return true;
1308}
1309
1310// Only for debugging.
1311template <typename GraphType, typename CostValue>
1313 for (const NodeIndex left_node : BipartiteLeftNodes()) {
1314 // Get the implicit price of left_node and make sure the reduced
1315 // costs of left_node's incident arcs are in bounds.
1316 CostValue left_node_price = ImplicitPrice(left_node);
1317 for (typename GraphType::OutgoingArcIterator arc_it(*graph_, left_node);
1318 arc_it.Ok(); arc_it.Next()) {
1319 const ArcIndex arc = arc_it.Index();
1320 const CostValue reduced_cost = left_node_price + PartialReducedCost(arc);
1321 // Note the asymmetric definition of epsilon-optimality that we
1322 // use because it means we can saturate all admissible arcs in
1323 // the beginning of Refine() just by unmatching all matched
1324 // nodes.
1325 if (matched_arc_[left_node] == arc) {
1326 // The reverse arc is residual. Epsilon-optimality requires
1327 // that the reduced cost of the forward arc be at most
1328 // epsilon_.
1329 if (reduced_cost > epsilon_) {
1330 return false;
1331 }
1332 } else {
1333 // The forward arc is residual. Epsilon-optimality requires
1334 // that the reduced cost of the forward arc be at least zero.
1335 if (reduced_cost < 0) {
1336 return false;
1337 }
1338 }
1339 }
1340 }
1341 return true;
1342}
1343
1344template <typename GraphType, typename CostValue>
1346 incidence_precondition_satisfied_ = true;
1347 // epsilon_ must be greater than kMinEpsilon so that in the case
1348 // where the largest arc cost is zero, we still do a Refine()
1349 // iteration.
1350 epsilon_ = std::max(largest_scaled_cost_magnitude_, kMinEpsilon + 1);
1351 VLOG(2) << "Largest given cost magnitude: "
1352 << largest_scaled_cost_magnitude_ / cost_scaling_factor_;
1353 // Initialize left-side node-indexed arrays and check incidence
1354 // precondition.
1355 for (NodeIndex node = 0; node < num_left_nodes_; ++node) {
1356 matched_arc_[node] = GraphType::kNilArc;
1357 typename GraphType::OutgoingArcIterator arc_it(*graph_, node);
1358 if (!arc_it.Ok()) {
1359 incidence_precondition_satisfied_ = false;
1360 }
1361 }
1362 // Initialize right-side node-indexed arrays. Example: prices are
1363 // stored only for right-side nodes.
1364 for (NodeIndex node = num_left_nodes_; node < graph_->num_nodes(); ++node) {
1365 price_[node] = 0;
1366 matched_node_[node] = GraphType::kNilNode;
1367 }
1368 bool in_range = true;
1369 double double_price_lower_bound = 0.0;
1370 CostValue new_error_parameter;
1371 CostValue old_error_parameter = epsilon_;
1372 do {
1373 new_error_parameter = NewEpsilon(old_error_parameter);
1374 double_price_lower_bound -=
1375 2.0 * static_cast<double>(PriceChangeBound(
1376 old_error_parameter, new_error_parameter, &in_range));
1377 old_error_parameter = new_error_parameter;
1378 } while (new_error_parameter != kMinEpsilon);
1379 const double limit =
1380 -static_cast<double>(std::numeric_limits<CostValue>::max());
1381 if (double_price_lower_bound < limit) {
1382 in_range = false;
1383 price_lower_bound_ = -std::numeric_limits<CostValue>::max();
1384 } else {
1385 price_lower_bound_ = static_cast<CostValue>(double_price_lower_bound);
1386 }
1387 VLOG(4) << "price_lower_bound_ == " << price_lower_bound_;
1388 DCHECK_LE(price_lower_bound_, 0);
1389 if (!in_range) {
1390 LOG(WARNING) << "Price change bound exceeds range of representable "
1391 << "costs; arithmetic overflow is not ruled out and "
1392 << "infeasibility might go undetected.";
1393 }
1394 return in_range;
1395}
1396
1397template <typename GraphType, typename CostValue>
1398void LinearSumAssignment<GraphType, CostValue>::ReportAndAccumulateStats() {
1399 total_stats_.Add(iteration_stats_);
1400 VLOG(3) << "Iteration stats: " << iteration_stats_.StatsString();
1401 iteration_stats_.Clear();
1402}
1403
1404template <typename GraphType, typename CostValue>
1406 CHECK(graph_ != nullptr);
1407 bool ok = graph_->num_nodes() == 2 * num_left_nodes_;
1408 if (!ok) return false;
1409 // Note: FinalizeSetup() might have been called already by white-box
1410 // test code or by a client that wants to react to the possibility
1411 // of overflow before solving the given problem, but FinalizeSetup()
1412 // is idempotent and reasonably fast, so we call it unconditionally
1413 // here.
1414 FinalizeSetup();
1415 ok = ok && incidence_precondition_satisfied_;
1416 DCHECK(!ok || EpsilonOptimal());
1417 while (ok && epsilon_ > kMinEpsilon) {
1418 ok = ok && UpdateEpsilon();
1419 ok = ok && Refine();
1420 ReportAndAccumulateStats();
1421 DCHECK(!ok || EpsilonOptimal());
1422 DCHECK(!ok || AllMatched());
1423 }
1424 success_ = ok;
1425 VLOG(1) << "Overall stats: " << total_stats_.StatsString();
1426 return ok;
1427}
1428
1429template <typename GraphType, typename CostValue>
1431 // It is illegal to call this method unless we successfully computed
1432 // an optimum assignment.
1433 DCHECK(success_);
1434 CostValue cost = 0;
1435 for (const NodeIndex node : BipartiteLeftNodes()) {
1436 cost += GetAssignmentCost(node);
1437 }
1438 return cost;
1439}
1440
1441} // namespace operations_research
1442
1443#endif // OR_TOOLS_GRAPH_LINEAR_ASSIGNMENT_H_
#define OR_DLL
Definition base_export.h:27
bool operator()(typename GraphType::ArcIndex a, typename GraphType::ArcIndex b) const
CostValueCycleHandler(const CostValueCycleHandler &)=delete
This type is neither copyable nor movable.
CostValueCycleHandler & operator=(const CostValueCycleHandler &)=delete
void SetTempFromIndex(ArcIndexType source) override
void SetIndexFromTemp(ArcIndexType destination) const override
Sets a data element from the temporary.
CostValueCycleHandler(std::vector< CostValue > *cost)
void SetIndexFromIndex(ArcIndexType source, ArcIndexType destination) const override
Moves a data element one step along its cycle.
This class does not take ownership of its underlying graph.
void SetArcCost(ArcIndex arc, CostValue cost)
Sets the cost of an arc already present in the given graph.
CostValue GetAssignmentCost(NodeIndex node) const
operations_research::PermutationCycleHandler< typename GraphType::ArcIndex > * ArcAnnotationCycleHandler()
Passes ownership of the cycle handler to the caller.
NodeIndex NumNodes() const
Returns the total number of nodes in the given problem.
ArcIndex GetAssignmentArc(NodeIndex left_node) const
Returns the arc through which the given node is matched.
const GraphType & Graph() const
Allows tests, iterators, etc., to inspect our underlying graph.
NodeIndex GetMate(NodeIndex left_node) const
Returns the node to which the given node is matched.
bool EpsilonOptimal() const
Only for debugging.
::util::IntegerRange< NodeIndex > BipartiteLeftNodes() const
Returns the range of valid left node indices.
LinearSumAssignment(const LinearSumAssignment &)=delete
This type is neither copyable nor movable.
CostValue ArcCost(ArcIndex arc) const
LinearSumAssignment(const GraphType &graph, NodeIndex num_left_nodes)
LinearSumAssignment & operator=(const LinearSumAssignment &)=delete
PermutationCycleHandler(const PermutationCycleHandler &)=delete
OR_DLL ABSL_DECLARE_FLAG(int64_t, assignment_alpha)
In SWIG mode, we don't want anything besides these top-level includes.