Open
Graph Drawing
Framework

 v. 2023.09 (Elderberry)
 

Loading...
Searching...
No Matches
MatchingBlossom.h
Go to the documentation of this file.
1
32#pragma once
33
34#include <ogdf/basic/Graph.h>
37#include <ogdf/basic/Logger.h>
38#include <ogdf/basic/basic.h>
45
46#include <cstddef>
47#include <iostream>
48#include <memory>
49#include <tuple>
50#include <unordered_map>
51#include <unordered_set>
52#include <vector>
53
54namespace ogdf {
55
62template<typename TWeight>
63class MatchingBlossom : public MatchingModule<TWeight> {
64 using Logger::lout;
65
68
70 std::unique_ptr<Graph::HiddenEdgeSet> m_nonEqualityEdgesHiddenSet;
71
73 std::unordered_set<edge> m_nonEqualityEdges;
74
76 std::unordered_set<node> m_graphNodes;
77
79 std::unordered_set<node> m_unmatchedNodes;
80
82 std::unordered_set<node> m_pseudonodes;
83
85 std::unique_ptr<AlternatingTree<TWeight>> m_tree;
86
87#ifdef OGDF_HEAVY_DEBUG
89 void assertConsistency() {
90 if (m_unmatchedNodes.empty()) {
91 return;
92 }
93 // graph nodes & euqality edges
94 int matchedNodes = 0;
95 for (node v : m_graphNodes) {
96 if (m_helper.matching(v)) {
97 matchedNodes++;
98 }
99 OGDF_ASSERT(v->graphOf() == &m_helper.graph());
100 }
102 for (edge e : m_helper.graph().edges) {
103 bool isGraphNode = m_graphNodes.find(e->source()) != m_graphNodes.end();
104 OGDF_ASSERT(isGraphNode == (m_graphNodes.find(e->target()) != m_graphNodes.end()));
105 if (isGraphNode) {
107 == m_helper.isEqualityEdge(e));
108 }
109 }
110 // tree structure
111 for (node v : m_tree->evenNodes) {
112 // all even nodes except the root have a corresponding matching edge
113 if (v != m_tree->root()) {
114 OGDF_ASSERT(m_helper.matching(v) == m_tree->evenBackEdge(v));
115 }
116 // and no back edge in the tree
117 OGDF_ASSERT(!m_tree->isOdd(v));
118 }
119 for (node v : m_tree->oddNodes) {
120 OGDF_ASSERT(!m_tree->isEven(v));
121 // all odd nodes have both a back edge in the tree and a forward matching
122 // edge, since the matching edges are always defined for both ends
123 node w = m_helper.matching(v)->opposite(v);
124 OGDF_ASSERT(m_tree->isEven(w));
125 }
126 // assert that all matching edges are correctly defined on both ends
127 for (node v : m_helper.graph().nodes) {
128 if (edge matchingEdge = m_helper.matching(v)) {
129 OGDF_ASSERT(matchingEdge->isIncident(v));
130 OGDF_ASSERT(m_helper.matching(matchingEdge->opposite(v)) == matchingEdge);
131 }
132 }
133 // matching
134 long unsigned int hiddenNodes = 0;
135 for (auto entry : m_helper.pseudonodes()) {
136 auto pseudonode = entry.second;
137 hiddenNodes += pseudonode->cycle->nodes().size();
138 for (node v : pseudonode->cycle->nodes()) {
139 OGDF_ASSERT(m_helper.matching(v) == nullptr);
140 }
141 }
142 OGDF_ASSERT(matchedNodes + m_unmatchedNodes.size() + hiddenNodes
143 == (size_t)m_helper.graph().numberOfNodes());
144 if (m_tree->hasRoot()) {
145 OGDF_ASSERT(m_unmatchedNodes.find(m_tree->root()) != m_unmatchedNodes.end());
146 }
147 }
148#endif
149
152 for (edge e : m_nonEqualityEdges) {
154 }
155 }
156
159
167 node getNewRoot() { return *m_unmatchedNodes.begin(); }
168
169public:
175 MatchingBlossom(bool greedyInit = true) : m_helper(greedyInit) { }
176
177private:
178 bool doCall(const Graph& G, const EdgeArray<TWeight>& weights,
179 std::unordered_set<edge>& matching) {
180 return _doCall(G, weights, matching);
181 }
182
183 bool doCall(const GraphAttributes& GA, std::unordered_set<edge>& matching) {
184 return _doCall(GA.constGraph(), GA, matching);
185 }
186
188 template<class WeightContainer>
189 bool _doCall(const Graph& G, const WeightContainer& weights, std::unordered_set<edge>& matching) {
190 // init
191 lout() << std::endl;
192 if (!m_helper.init(G, weights)) {
193 return false;
194 }
196 m_nonEqualityEdges.clear();
197 for (edge e : m_helper.graph().edges) {
198 // hide all non-equality edges
199 if (!m_helper.isEqualityEdge(e)) {
200 m_nonEqualityEdges.insert(e);
201 }
202 }
204 m_graphNodes.clear();
205 m_unmatchedNodes.clear();
206 for (node v : m_helper.graph().nodes) {
207 m_graphNodes.insert(v);
208 if (!m_helper.matching(v)) {
209 m_unmatchedNodes.insert(v);
210 }
211 }
213
214 return findMatching(matching);
215 }
216
223 bool findMatching(std::unordered_set<edge>& matching) {
224 // main loop
225 while (!m_unmatchedNodes.empty()) {
226#ifdef OGDF_HEAVY_DEBUG
227 assertConsistency();
228#endif
229 if (!m_tree->hasRoot()) {
230 // we need to generate a new root for the tree
231 m_tree->reset(getNewRoot());
232 lout() << "new root: " << m_tree->root() << std::endl;
233 }
234 if (!primalChange() && !dualChange()) {
235 return false;
236 }
237 }
238 // matching found
239 m_helper.getOriginalMatching(matching);
240 return true;
241 }
242
245#ifdef OGDF_HEAVY_DEBUG
246 std::unordered_map<edge, TWeight> edgeWeights;
247 m_helper.getReducedEdgeWeights(edgeWeights);
248#endif
251#ifdef OGDF_HEAVY_DEBUG
252 m_helper.assertConsistentEdgeWeights(edgeWeights);
253#endif
254 return result;
255 }
256
263 for (node u : m_tree->evenNodes) {
264 for (adjEntry adj : u->adjEntries) {
265 node v = adj->twinNode();
266 if (!m_helper.matching(v) && v != m_tree->root()) {
267 // found free edge, augment matching
268 m_tree->augmentMatching(adj->theEdge());
269 m_unmatchedNodes.erase(m_tree->root());
270 m_unmatchedNodes.erase(v);
271 m_tree->reset();
272 lout() << "augmented matching with " << adj->theEdge() << std::endl;
273 return true;
274 }
275 }
276 }
277 return false;
278 }
279
286 for (node u : m_tree->evenNodes) {
287 for (adjEntry adj : u->adjEntries) {
288 node v = adj->twinNode();
289 edge matchingEdge = m_helper.matching(v);
290 if (matchingEdge && !m_tree->contains(v)) {
291 m_tree->grow(u, adj->theEdge(), matchingEdge);
292 lout() << "augmented tree with " << adj->theEdge() << std::endl;
293 return true;
294 }
295 }
296 }
297 return false;
298 }
299
306 for (node u : m_tree->evenNodes) {
307 for (adjEntry adj : u->adjEntries) {
308 node v = adj->twinNode();
309 if (m_tree->isEven(v)) {
310 // found an odd cycle: shrink it
311 shrink(adj->theEdge());
312 return true;
313 }
314 }
315 }
316 return false;
317 }
318
325 for (node v : m_pseudonodes) {
326 if (m_tree->isOdd(v) && m_helper.isZeroCostNode(v)) {
327 expand(m_helper.pseudonode(v));
328 return true;
329 }
330 }
331 return false;
332 }
333
335 bool dualChange() {
336 TWeight delta = infinity<TWeight>();
337 // restore all edges to find potential candidates
339 // find ++/+0 edges
340 for (node u : m_tree->evenNodes) {
341 for (adjEntry adj : u->adjEntries) {
342 node v = adj->twinNode();
343 TWeight potentialChange = m_helper.getReducedWeight(adj->theEdge());
344 if (m_tree->isEven(v)) {
345 // ++ edge: max dual change has to be halfed
346 potentialChange *= 0.5;
347 } else if (m_tree->isOdd(v)) {
348 // +- edge: ignore
349 continue;
350 }
351 // (modified) reduced weight gives an upper bound to the dual change
352 delta = std::min(delta, potentialChange);
353 }
354 }
355 // find - pseudonodes
356 for (node v : m_pseudonodes) {
357 if (m_tree->isOdd(v)) {
358 delta = std::min(delta, m_helper.y(v));
359 }
360 }
361 // no dual change possible, so no matching can be found
362 if (delta == infinity<TWeight>()) {
363 return false;
364 }
365 OGDF_ASSERT(delta > 0);
366
367 // do update of dual variables
368 for (node v : m_tree->evenNodes) {
369 m_helper.y(v) += delta;
370 }
371 for (node v : m_tree->oddNodes) {
372 m_helper.y(v) -= delta;
373 }
374 // update non-equality edges
375 for (node v : m_tree->evenNodes) {
376 for (adjEntry adj : v->adjEntries) {
377 edge e = adj->theEdge();
378 if (m_helper.isEqualityEdge(e)) {
379 auto it = m_nonEqualityEdges.find(e);
380 if (it != m_nonEqualityEdges.end()) {
381 m_nonEqualityEdges.erase(it);
382 }
383 }
384 }
385 }
386 for (node u : m_tree->oddNodes) {
387 // no adjacent edge to a node outside of the current tree can be an equality edge anymore
388 for (adjEntry adj : u->adjEntries) {
389 node v = adj->twinNode();
390 edge e = adj->theEdge();
391 if (!m_tree->isEven(v)) {
392 m_nonEqualityEdges.insert(e);
393 }
394 }
395 }
397 lout() << "dual change: " << delta << std::endl;
398 return true;
399 }
400
402 void shrink(edge cycleEdge) {
404 Cycle* cycle = m_tree->getCycle(cycleEdge);
405 std::vector<std::tuple<edge, bool>> selfLoops;
406 Pseudonode* pseudonode = m_tree->shrink(cycle, selfLoops);
407 node newNode = pseudonode->graphNode;
408
409 for (node u : pseudonode->cycle->nodes()) {
410 m_unmatchedNodes.erase(u);
411 m_graphNodes.erase(u);
412 }
413 for (node u : pseudonode->cycle->nodes()) {
414 for (auto adj : u->adjEntries) {
415 edge e = adj->theEdge();
416 auto it = m_nonEqualityEdges.find(e);
417 if (it != m_nonEqualityEdges.end()) {
418 m_nonEqualityEdges.erase(it);
419 }
420 }
421 }
422 m_graphNodes.insert(newNode);
423 m_pseudonodes.insert(newNode);
424 if (m_helper.matching(newNode) == nullptr) {
425 m_unmatchedNodes.insert(newNode);
426 }
427 lout() << "shrunk odd cycle of length " << cycle->nodes().size() << " at " << cycleEdge
428 << " into pseudonode " << pseudonode->graphNode << std::endl;
430 }
431
433 void expand(Pseudonode* pseudonode) {
434 node graphNode = pseudonode->graphNode;
435 m_pseudonodes.erase(graphNode);
436 m_graphNodes.erase(graphNode);
437 OGDF_ASSERT(m_unmatchedNodes.empty() || m_helper.isZeroCostNode(graphNode));
439 int pseudonodeIndex = graphNode->index();
440 m_tree->expand(pseudonode);
441 for (node u : pseudonode->cycle->nodes()) {
442 m_graphNodes.insert(u);
443 }
444 for (node u : pseudonode->cycle->nodes()) {
445 for (auto adj : u->adjEntries) {
446 edge e = adj->theEdge();
447 if (!m_helper.isEqualityEdge(e)) {
448 m_nonEqualityEdges.insert(e);
449 }
450 }
451 }
452 delete pseudonode;
454 lout() << "expanded pseudonode " << pseudonodeIndex << std::endl;
455 }
456};
457
458}
Implementation of an Alternating Tree helper structure for the Blossom algorithm.
Utility class for the Blossom algorithm, providiing access to all important data structures.
Utility class representing a cycle in the Blossom algorithm.
Includes declaration of graph class.
Declaration of class GraphAttributes which extends a Graph by additional attributes.
Decralation of GraphElement and GraphList classes.
Contains logging functionality.
Declaration of interface for matching algorithms.
Helper structure representing a pseudonode in the Blossom algorithm.
Basic declarations, included by all source files.
Class for adjacency list elements.
Definition Graph_d.h:143
Class for the representation of edges.
Definition Graph_d.h:364
Functionality for temporarily hiding edges in constant time.
Definition Graph_d.h:1222
Stores additional attributes of a graph (like layout information).
const Graph & constGraph() const
Returns a reference to the associated graph.
Data type for general directed graphs (adjacency list representation).
Definition Graph_d.h:866
std::ostream & lout(Level level=Level::Default, bool indent=true) const
stream for logging-output (local)
Definition Logger.h:160
Implementation of the Blossom-I algorithm by Edmonds (1965) for Minimum Weight Perfect Matching.
bool findShrinkableCycle()
Finds and shrinks an odd cycle, if possible.
bool dualChange()
Executes a dual change step.
MatchingBlossom(bool greedyInit=true)
Construct a MatchingBlossom instance.
bool findMatchingAugmentation()
Finds and executes a matching augmentation, if possible.
bool findExpandablePseudonode()
Finds and expands an odd pseudonode, if possible.
bool doCall(const Graph &G, const EdgeArray< TWeight > &weights, std::unordered_set< edge > &matching)
Main call function for the algorithm with an EdgeArray as weight container.
void expand(Pseudonode *pseudonode)
Expand the pseudonode pseudonode.
std::unordered_set< edge > m_nonEqualityEdges
Set of all non-equality edges.
std::unordered_set< node > m_unmatchedNodes
All nodes which are not part of the matching yet.
void shrink(edge cycleEdge)
Shrink the odd cycle induced by the current tree together with cycleEdge.
bool findTreeAugmentation()
Finds and executes a tree augmentation, if possible.
void hideNonEqualityEdges()
Helper function to hide all non-equality edges.
std::ostream & lout(Level level=Level::Default, bool indent=true) const
stream for logging-output (local)
Definition Logger.h:160
std::unique_ptr< Graph::HiddenEdgeSet > m_nonEqualityEdgesHiddenSet
Pointer to the HiddenEdgeSet containing all non-equality edges.
bool primalChange()
Executes a primal change step.
bool doCall(const GraphAttributes &GA, std::unordered_set< edge > &matching)
Main call function for the algorithm with GraphAttrubtes as weight container.
BlossomHelper< TWeight > m_helper
Helper class to store the current state of the algorithm.
std::unordered_set< node > m_graphNodes
All nodes currently present in the graph.
std::unique_ptr< AlternatingTree< TWeight > > m_tree
The alternating tree.
std::unordered_set< node > m_pseudonodes
All top-level pseudonodes.
bool findMatching(std::unordered_set< edge > &matching)
Main function of the algorithm.
node getNewRoot()
Helper function to get a new root for the tree.
bool _doCall(const Graph &G, const WeightContainer &weights, std::unordered_set< edge > &matching)
Helper for the main call function since abstract functions cannot be templated.
void restoreNonEqualityEdges()
Helper function to restore all non-equality edges.
Class for the representation of nodes.
Definition Graph_d.h:241
int index() const
Returns the (unique) node index.
Definition Graph_d.h:275
RegisteredArray for edges of a graph, specialized for EdgeArray<edge>.
Definition Graph_d.h:717
Helper class for the blossom matching algorithms.
const std::unordered_set< node > & nodes()
Helper class representing a pseudonode in the Blossom algorithm.
Definition Pseudonode.h:50
Cycle * cycle
The odd-length cycle that this pseudonode represents.
Definition Pseudonode.h:98
node graphNode
The node in the graph that this pseudonode is linked to.
Definition Pseudonode.h:95
Utility functions and classes regarding map access and iteration.
#define OGDF_ASSERT(expr)
Assert condition expr. See doc/build.md for more information.
Definition basic.h:52
The namespace for all OGDF objects.