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