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