OR-Tools  9.6
perfect_matching.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 // Implementation of the Blossom V min-cost perfect matching algorithm. The
15 // main source for the algo is the paper: "Blossom V: A new implementation
16 // of a minimum cost perfect matching algorithm", Vladimir Kolmogorov.
17 //
18 // The Algorithm is a primal-dual algorithm. It always maintains a dual-feasible
19 // solution. We recall some notations here, but see the paper for more details
20 // as it is well written.
21 //
22 // TODO(user): This is a work in progress. The algo is not fully implemented
23 // yet. The initial version is closer to Blossom IV since we update the dual
24 // values for all trees at once with the same delta.
25 
26 #ifndef OR_TOOLS_GRAPH_PERFECT_MATCHING_H_
27 #define OR_TOOLS_GRAPH_PERFECT_MATCHING_H_
28 
29 #include <cstdint>
30 #include <functional>
31 #include <limits>
32 #include <memory>
33 #include <string>
34 #include <vector>
35 
36 #include "absl/base/attributes.h"
37 #include "absl/strings/str_cat.h"
38 #include "absl/strings/str_join.h"
41 #include "ortools/base/int_type.h"
43 #include "ortools/base/logging.h"
44 #include "ortools/base/macros.h"
46 
47 namespace operations_research {
48 
49 class BlossomGraph;
50 
51 // Given an undirected graph with costs on each edges, this class allows to
52 // compute a perfect matching with minimum cost. A matching is a set of disjoint
53 // pairs of nodes connected by an edge. The matching is perfect if all nodes are
54 // matched to each others.
56  public:
57  // TODO(user): For now we ask the number of nodes at construction, but we
58  // could automatically infer it from the added edges if needed.
60  explicit MinCostPerfectMatching(int num_nodes) { Reset(num_nodes); }
61 
62  // Resets the class for a new graph.
63  //
64  // TODO(user): Eventually, we may support incremental Solves(). Or at least
65  // memory reuse if one wants to solve many problems in a row.
66  void Reset(int num_nodes);
67 
68  // Adds an undirected edges between the two given nodes.
69  //
70  // For now we only accept non-negative cost.
71  // TODO(user): We can easily shift all costs if negative costs are needed.
72  //
73  // Important: The algorithm supports multi-edges, but it will be slower. So it
74  // is better to only add one edge with a minimum cost between two nodes. In
75  // particular, do not add both AddEdge(a, b, cost) and AddEdge(b, a, cost).
76  // TODO(user): We could just presolve them away.
77  void AddEdgeWithCost(int tail, int head, int64_t cost);
78 
79  // Solves the min-cost perfect matching problem on the given graph.
80  //
81  // NOTE(user): If needed we could support a time limit. Aborting early will
82  // not provide a perfect matching, but the algorithm does maintain a valid
83  // lower bound on the optimal cost that gets better and better during
84  // execution until it reaches the optimal value. Similarly, it is easy to
85  // support an early stop if this bound crosses a preset threshold.
86  enum Status {
87  // A perfect matching with min-cost has been found.
88  OPTIMAL = 0,
89 
90  // There is no perfect matching in this graph.
92 
93  // The costs are too large and caused an overflow during the algorithm
94  // execution.
96 
97  // Advanced usage: the matching is OPTIMAL and was computed without
98  // overflow, but its OptimalCost() does not fit on an int64_t. Note that
99  // Match() still work and you can re-compute the cost in double for
100  // instance.
102  };
103  ABSL_MUST_USE_RESULT Status Solve();
104 
105  // Returns the cost of the perfect matching. Only valid when the last solve
106  // status was OPTIMAL.
107  int64_t OptimalCost() const {
108  DCHECK(optimal_solution_found_);
109  return optimal_cost_;
110  }
111 
112  // Returns the node matched to the given node. In a perfect matching all nodes
113  // have a match. Only valid when the last solve status was OPTIMAL.
114  int Match(int node) const {
115  DCHECK(optimal_solution_found_);
116  return matches_[node];
117  }
118  const std::vector<int>& Matches() const {
119  DCHECK(optimal_solution_found_);
120  return matches_;
121  }
122 
123  private:
124  std::unique_ptr<BlossomGraph> graph_;
125 
126  // Fields used to report the optimal solution. Most of it could be read on
127  // the fly from BlossomGraph, but we prefer to copy them here. This allows to
128  // reclaim the memory of graph_ early or allows to still query the last
129  // solution if we later allow re-solve with incremental changes to the graph.
130  bool optimal_solution_found_ = false;
131  int64_t optimal_cost_ = 0;
132  int64_t maximum_edge_cost_ = 0;
133  std::vector<int> matches_;
134 };
135 
136 // Class containing the main data structure used by the Blossom algorithm.
137 //
138 // At the core is the original undirected graph. During the algorithm execution
139 // we might collapse nodes into so-called Blossoms. A Blossom is a cycle of
140 // external nodes (which can be blossom nodes) of odd length (>= 3). The edges
141 // of the cycle are called blossom-forming eges and will always be tight
142 // (i.e. have a slack of zero). Once a Blossom is created, its nodes become
143 // "internal" and are basically considered merged into the blossom node for the
144 // rest of the algorithm (except if we later re-expand the blossom).
145 //
146 // Moreover, external nodes of the graph will have 3 possible types ([+], [-]
147 // and free [0]). Free nodes will always be matched together in pairs. Nodes of
148 // type [+] and [-] are arranged in a forest of alternating [+]/[-] disjoint
149 // trees. Each unmatched node is the root of a tree, and of type [+]. Nodes [-]
150 // will always have exactly one child to witch they are matched. [+] nodes can
151 // have any number of [-] children, to which they are not matched. All the edges
152 // of the trees will always be tight. Some examples below, double edges are used
153 // for matched nodes:
154 //
155 // A matched pair of free nodes: [0] === [0]
156 //
157 // A possible rooted tree: [+] -- [-] ==== [+]
158 // \
159 // [-] ==== [+] ---- [-] === [+]
160 // \
161 // [-] === [+]
162 //
163 // A single unmatched node is also a tree: [+]
164 //
165 // TODO(user): For now this class does not maintain a second graph of edges
166 // between the trees nor does it maintains priority queue of edges.
167 //
168 // TODO(user): For now we use CHECKs in many places to facilitate development.
169 // Switch them to DCHECKs for speed once the code is more stable.
171  public:
172  // Typed index used by this class.
174  DEFINE_INT_TYPE(EdgeIndex, int);
176 
177  // Basic constants.
178  // NOTE(user): Those can't be constexpr because of the or-tools export,
179  // which complains for constexpr DEFINE_INT_TYPE.
180  static const NodeIndex kNoNodeIndex;
181  static const EdgeIndex kNoEdgeIndex;
182  static const CostValue kMaxCostValue;
183 
184  // Node related data.
185  // We store the edges incident to a node separately in the graph_ member.
186  struct Node {
187  explicit Node(NodeIndex n) : parent(n), match(n), root(n) {}
188 
189  // A node can be in one of these 4 exclusive states. Internal nodes are part
190  // of a Blossom and should be ignored until this Blossom is expanded. All
191  // the other nodes are "external". A free node is always matched to another
192  // free node. All the other external node are in alternating [+]/[-] trees
193  // rooted at the only unmatched node of the tree (always of type [+]).
194  bool IsInternal() const {
195  DCHECK(!is_internal || type == 0);
196  return is_internal;
197  }
198  bool IsFree() const { return type == 0 && !is_internal; }
199  bool IsPlus() const { return type == 1; }
200  bool IsMinus() const { return type == -1; }
201 
202  // Is this node a blossom? if yes, it was formed by merging the node.blossom
203  // nodes together. Note that we reuse the index of node.blossom[0] for this
204  // blossom node. A blossom node can be of any type.
205  bool IsBlossom() const { return !blossom.empty(); }
206 
207  // The type of this node. We use an int for convenience in the update
208  // formulas. This is 1 for [+] nodes, -1 for [-] nodes and 0 for all the
209  // others.
210  //
211  // Internal node also have a type of zero so the dual formula are correct.
212  int type = 0;
213 
214  // Whether this node is part of a blossom.
215  bool is_internal = false;
216 
217  // The parent of this node in its tree or itself otherwise.
218  // Unused for internal nodes.
220 
221  // Itself if not matched, or this node match otherwise.
222  // Unused for internal nodes.
224 
225  // The root of this tree which never changes until a tree is disassambled by
226  // an Augment(). Unused for internal nodes.
228 
229  // The "delta" to apply to get the dual for nodes of this tree.
230  // This is only filled for root nodes (i.e unmatched nodes).
232 
233  // See the formula in Dual() used to derive the true dual of this node.
234  // This is the equal to the "true" dual for free exterior node and internal
235  // node.
237 
238 #ifndef NDEBUG
239  // The true dual of this node. We only maintain this in debug mode.
241 #endif
242 
243  // Non-empty for Blossom only. The odd-cycle of blossom nodes that form this
244  // blossom. The first element should always be the current blossom node, and
245  // all the other nodes are internal nodes.
246  std::vector<NodeIndex> blossom;
247 
248  // This allows to store information about a new blossom node created by
249  // Shrink() so that we can properly restore it on Expand(). Note that we
250  // store the saved information on the second node of a blossom cycle (and
251  // not the blossom node itself) because that node will be "hidden" until the
252  // blossom is expanded so this way, we do not need more than one set of
253  // saved information per node.
254 #ifndef NDEBUG
256 #endif
258  std::vector<NodeIndex> saved_blossom;
259  };
260 
261  // An undirected edge between two nodes: tail <-> head.
262  struct Edge {
264  : pseudo_slack(c),
265 #ifndef NDEBUG
266  slack(c),
267 #endif
268  tail(t),
269  head(h) {
270  }
271 
272  // Returns the "other" end of this edge.
274  DCHECK(n == tail || n == head);
275  return NodeIndex(tail.value() ^ head.value() ^ n.value());
276  }
277 
278  // AdjustablePriorityQueue interface. Note that we use std::greater<> in
279  // our queues since we want the lowest pseudo_slack first.
281  int GetHeapIndex() const { return pq_position; }
282  bool operator>(const Edge& other) const {
283  return pseudo_slack > other.pseudo_slack;
284  }
285 
286  // See the formula is Slack() used to derive the true slack of this edge.
288 
289 #ifndef NDEBUG
290  // We only maintain this in debug mode.
292 #endif
293 
294  // These are the current tail/head of this edges. These are changed when
295  // creating or expanding blossoms. The order do not matter.
296  //
297  // TODO(user): Consider using node_a/node_b instead to remove the "directed"
298  // meaning. I do need to think a bit more about it though.
301 
302  // Position of this Edge in the underlying std::vector<> used to encode the
303  // heap of one priority queue. An edge can be in at most one priority queue
304  // which allow us to share this amongst queues.
305  int pq_position = -1;
306  };
307 
308  // Creates a BlossomGraph on the given number of nodes.
309  explicit BlossomGraph(int num_nodes);
310 
311  // Same comment as MinCostPerfectMatching::AddEdgeWithCost() applies.
313 
314  // Heuristic to start with a dual-feasible solution and some matched edges.
315  // To be called once all edges are added. Returns false if the problem is
316  // detected to be INFEASIBLE.
317  ABSL_MUST_USE_RESULT bool Initialize();
318 
319  // Enters a loop that perform one of Grow()/Augment()/Shrink()/Expand() until
320  // a fixed point is reached.
321  void PrimalUpdates();
322 
323  // Computes the maximum possible delta for UpdateAllTrees() that keeps the
324  // dual feasibility. Dual update approach (2) from the paper. This also fills
325  // primal_update_edge_queue_.
327 
328  // Applies the same dual delta to all trees. Dual update approach (2) from the
329  // paper.
331 
332  // Returns true iff this node is matched and is thus not a tree root.
333  // This cannot live in the Node class because we need to know the NodeIndex.
334  bool NodeIsMatched(NodeIndex n) const;
335 
336  // Returns the node matched to the given one, or n if this node is not
337  // currently matched.
338  NodeIndex Match(NodeIndex n) const;
339 
340  // Adds to the tree of tail the free matched pair(head, Match(head)).
341  // The edge is only used in DCHECKs. We duplicate tail/head because the
342  // order matter here.
343  void Grow(EdgeIndex e, NodeIndex tail, NodeIndex head);
344 
345  // Merges two tree and augment the number of matched nodes by 1. This is
346  // the only functions that change the current matching.
347  void Augment(EdgeIndex e);
348 
349  // Creates a Blossom using the given [+] -- [+] edge between two nodes of the
350  // same tree.
351  void Shrink(EdgeIndex e);
352 
353  // Expands a Blossom into its component.
354  void Expand(NodeIndex to_expand);
355 
356  // Returns the current number of matched nodes.
357  int NumMatched() const { return nodes_.size() - unmatched_nodes_.size(); }
358 
359  // Returns the current dual objective which is always a valid lower-bound on
360  // the min-cost matching. Note that this is capped to kint64max in case of
361  // overflow. Because all of our cost are positive, this starts at zero.
362  CostValue DualObjective() const;
363 
364  // This must be called at the end of the algorithm to recover the matching.
365  void ExpandAllBlossoms();
366 
367  // Return the "slack" of the given edge.
368  CostValue Slack(const Edge& edge) const;
369 
370  // Returns the dual value of the given node (which might be a pseudo-node).
371  CostValue Dual(const Node& node) const;
372 
373  // Display to VLOG(1) some statistic about the solve.
374  void DisplayStats() const;
375 
376  // Checks that there is no possible primal update in the current
377  // configuration.
379 
380  // Tests that the dual values are currently feasible.
381  // This should ALWAYS be the case.
382  bool DebugDualsAreFeasible() const;
383 
384  // In debug mode, we maintain the real slack of each edges and the real dual
385  // of each node via this function. Both Slack() and Dual() checks in debug
386  // mode that the value computed is the correct one.
388 
389  // Returns true iff this is an external edge with a slack of zero.
390  // An external edge is an edge between two external nodes.
391  bool DebugEdgeIsTightAndExternal(const Edge& edge) const;
392 
393  // Getters to access node/edges from outside the class.
394  // Only used in tests.
395  const Edge& GetEdge(int e) const { return edges_[EdgeIndex(e)]; }
396  const Node& GetNode(int n) const { return nodes_[NodeIndex(n)]; }
397 
398  // Display information for debugging.
399  std::string NodeDebugString(NodeIndex n) const;
400  std::string EdgeDebugString(EdgeIndex e) const;
401  std::string DebugString() const;
402 
403  private:
404  // Returns the index of a tight edge between the two given external nodes.
405  // Returns kNoEdgeIndex if none could be found.
406  //
407  // TODO(user): Store edges for match/parent/blossom instead and remove the
408  // need for this function that can take around 10% of the running time on
409  // some problems.
410  EdgeIndex FindTightExternalEdgeBetweenNodes(NodeIndex tail, NodeIndex head);
411 
412  // Appends the path from n to the root of its tree. Used by Augment().
413  void AppendNodePathToRoot(NodeIndex n, std::vector<NodeIndex>* path) const;
414 
415  // Returns the depth of a node in its tree. Used by Shrink().
416  int GetDepth(NodeIndex n) const;
417 
418  // Adds positive delta to dual_objective_ and cap at kint64max on overflow.
419  void AddToDualObjective(CostValue delta);
420 
421  // In the presence of blossoms, the original tail/head of an arc might not be
422  // up to date anymore. It is important to use these functions instead in all
423  // the places where this can happen. That is basically everywhere except in
424  // the initialization.
425  NodeIndex Tail(const Edge& edge) const {
426  return root_blossom_node_[edge.tail];
427  }
428  NodeIndex Head(const Edge& edge) const {
429  return root_blossom_node_[edge.head];
430  }
431 
432  // Returns the Head() or Tail() that does not correspond to node. Node that
433  // node must be one of the original index in the given edge, this is DCHECKed
434  // by edge.OtherEnd().
435  NodeIndex OtherEnd(const Edge& edge, NodeIndex node) const {
436  return root_blossom_node_[edge.OtherEnd(node)];
437  }
438 
439  // Same as OtherEnd() but the given node should either be Tail(edge) or
440  // Head(edge) and do not need to be one of the original node of this edge.
441  NodeIndex OtherEndFromExternalNode(const Edge& edge, NodeIndex node) const {
442  const NodeIndex head = Head(edge);
443  if (head != node) {
444  DCHECK_EQ(node, Tail(edge));
445  return head;
446  }
447  return Tail(edge);
448  }
449 
450  // Returns the given node and if this node is a blossom, all its internal
451  // nodes (recursively). Note that any call to SubNodes() invalidate the
452  // previously returned reference.
453  const std::vector<NodeIndex>& SubNodes(NodeIndex n);
454 
455  // Just used to check that initialized is called exactly once.
456  bool is_initialized_ = false;
457 
458  // The set of all edges/nodes of the graph.
461 
462  // Identity for a non-blossom node, and its top blossom node (in case of many
463  // nested blossom) for an internal node.
464  absl::StrongVector<NodeIndex, NodeIndex> root_blossom_node_;
465 
466  // The current graph incidence. Note that one EdgeIndex should appear in
467  // exactly two places (on its tail and head incidence list).
469 
470  // Used by SubNodes().
471  std::vector<NodeIndex> subnodes_;
472 
473  // The unmatched nodes are exactly the root of the trees. After
474  // initialization, this is only modified by Augment() which removes two nodes
475  // from this list each time. Note that during Shrink()/Expand() we never
476  // change the indexing of the root nodes.
477  std::vector<NodeIndex> unmatched_nodes_;
478 
479  // List of tight_edges and possible shrink to check in PrimalUpdates().
480  std::vector<EdgeIndex> primal_update_edge_queue_;
481  std::vector<EdgeIndex> possible_shrink_;
482 
483  // Priority queues of edges of a certain types.
486  std::vector<Edge*> tmp_all_tops_;
487 
488  // The dual objective. Increase as the algorithm progress. This is a lower
489  // bound on the min-cost of a perfect matching.
490  CostValue dual_objective_ = CostValue(0);
491 
492  // Statistics on the main operations.
493  int64_t num_grows_ = 0;
494  int64_t num_augments_ = 0;
495  int64_t num_shrinks_ = 0;
496  int64_t num_expands_ = 0;
497  int64_t num_dual_updates_ = 0;
498 };
499 
500 } // namespace operations_research
501 
502 #endif // OR_TOOLS_GRAPH_PERFECT_MATCHING_H_
size_type size() const
DEFINE_INT_TYPE(CostValue, int64_t)
ABSL_MUST_USE_RESULT bool Initialize()
void Grow(EdgeIndex e, NodeIndex tail, NodeIndex head)
CostValue ComputeMaxCommonTreeDualDeltaAndResetPrimalEdgeQueue()
bool DebugEdgeIsTightAndExternal(const Edge &edge) const
static const CostValue kMaxCostValue
NodeIndex Match(NodeIndex n) const
void AddEdge(NodeIndex tail, NodeIndex head, CostValue cost)
bool NodeIsMatched(NodeIndex n) const
CostValue Slack(const Edge &edge) const
const Node & GetNode(int n) const
std::string NodeDebugString(NodeIndex n) const
std::string EdgeDebugString(EdgeIndex e) const
CostValue Dual(const Node &node) const
static const EdgeIndex kNoEdgeIndex
static const NodeIndex kNoNodeIndex
void Expand(NodeIndex to_expand)
const Edge & GetEdge(int e) const
void UpdateAllTrees(CostValue delta)
void DebugUpdateNodeDual(NodeIndex n, CostValue delta)
int64_t tail() const
Definition: simple_graph.h:37
void AddEdgeWithCost(int tail, int head, int64_t cost)
const std::vector< int > & Matches() const
int index
Collection of objects used to extend the Constraint Solver library.
int64_t delta
Definition: resource.cc:1695
int64_t tail
int64_t cost
int64_t head
Edge(NodeIndex t, NodeIndex h, CostValue c)
bool operator>(const Edge &other) const
NodeIndex OtherEnd(NodeIndex n) const