Google OR-Tools v9.15
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// cs/ortools/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 ORTOOLS_GRAPH_LINEAR_ASSIGNMENT_H_
194#define ORTOOLS_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 // ORTOOLS_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
CostValueCycleHandler & operator=(const CostValueCycleHandler &)=delete
void SetTempFromIndex(ArcIndexType source) override
void SetIndexFromTemp(ArcIndexType destination) const override
CostValueCycleHandler(std::vector< CostValue > *cost)
void SetIndexFromIndex(ArcIndexType source, ArcIndexType destination) const override
void SetArcCost(ArcIndex arc, CostValue cost)
CostValue GetAssignmentCost(NodeIndex node) const
operations_research::PermutationCycleHandler< typename GraphType::ArcIndex > * ArcAnnotationCycleHandler()
ArcIndex GetAssignmentArc(NodeIndex left_node) const
NodeIndex GetMate(NodeIndex left_node) const
::util::IntegerRange< NodeIndex > BipartiteLeftNodes() const
LinearSumAssignment(const LinearSumAssignment &)=delete
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)
OR-Tools root namespace.
BlossomGraph::CostValue CostValue
BlossomGraph::NodeIndex NodeIndex