MLIR  22.0.0git
RootOrdering.cpp
Go to the documentation of this file.
1 //===- RootOrdering.cpp - Optimal root ordering ---------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // An implementation of Edmonds' optimal branching algorithm. This is a
10 // directed analogue of the minimum spanning tree problem for a given root.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "RootOrdering.h"
15 
16 #include "llvm/ADT/DenseMap.h"
17 #include "llvm/ADT/SmallVector.h"
18 #include <utility>
19 
20 using namespace mlir;
21 using namespace mlir::pdl_to_pdl_interp;
22 
23 /// Returns the cycle implied by the specified parent relation, starting at the
24 /// given node.
26  Value rep) {
27  SmallVector<Value> cycle;
28  Value node = rep;
29  do {
30  cycle.push_back(node);
31  node = parents.lookup(node);
32  assert(node && "got an empty value in the cycle");
33  } while (node != rep);
34  return cycle;
35 }
36 
37 /// Contracts the specified cycle in the given graph in-place.
38 /// The parentsCost map specifies, for each node in the cycle, the lowest cost
39 /// among the edges entering that node. Then, the nodes in the cycle C are
40 /// replaced with a single node v_C (the first node in the cycle). All edges
41 /// (u, v) entering the cycle, v \in C, are replaced with a single edge
42 /// (u, v_C) with an appropriately chosen cost, and the selected node v is
43 /// marked in the output map actualTarget[u]. All edges (u, v) leaving the
44 /// cycle, u \in C, are replaced with a single edge (v_C, v), and the selected
45 /// node u is marked in the ouptut map actualSource[v].
46 static void contract(RootOrderingGraph &graph, ArrayRef<Value> cycle,
47  const DenseMap<Value, unsigned> &parentDepths,
48  DenseMap<Value, Value> &actualSource,
49  DenseMap<Value, Value> &actualTarget) {
50  Value rep = cycle.front();
51  DenseSet<Value> cycleSet(cycle.begin(), cycle.end());
52 
53  // Now, contract the cycle, marking the actual sources and targets.
55  for (auto outer = graph.begin(), e = graph.end(); outer != e; ++outer) {
56  Value target = outer->first;
57  if (cycleSet.contains(target)) {
58  // Target in the cycle => edges incoming to the cycle or within the cycle.
59  unsigned parentDepth = parentDepths.lookup(target);
60  for (const auto &inner : outer->second) {
61  Value source = inner.first;
62  // Ignore edges within the cycle.
63  if (cycleSet.contains(source))
64  continue;
65 
66  // Edge incoming to the cycle.
67  std::pair<unsigned, unsigned> cost = inner.second.cost;
68  assert(parentDepth <= cost.first && "invalid parent depth");
69 
70  // Subtract the cost of the parent within the cycle from the cost of
71  // the edge incoming to the cycle. This update ensures that the cost
72  // of the minimum-weight spanning arborescence of the entire graph is
73  // the cost of arborescence for the contracted graph plus the cost of
74  // the cycle, no matter which edge in the cycle we choose to drop.
75  cost.first -= parentDepth;
76  auto it = repEntries.find(source);
77  if (it == repEntries.end() || it->second.cost > cost) {
78  actualTarget[source] = target;
79  // Do not bother populating the connector (the connector is only
80  // relevant for the final traversal, not for the optimal branching).
81  repEntries[source].cost = cost;
82  }
83  }
84  // Erase the node in the cycle.
85  graph.erase(outer);
86  } else {
87  // Target not in cycle => edges going away from or unrelated to the cycle.
88  DenseMap<Value, RootOrderingEntry> &entries = outer->second;
89  Value bestSource;
90  std::pair<unsigned, unsigned> bestCost;
91  auto inner = entries.begin(), innerE = entries.end();
92  while (inner != innerE) {
93  Value source = inner->first;
94  if (cycleSet.contains(source)) {
95  // Going-away edge => get its cost and erase it.
96  if (!bestSource || bestCost > inner->second.cost) {
97  bestSource = source;
98  bestCost = inner->second.cost;
99  }
100  entries.erase(inner++);
101  } else {
102  ++inner;
103  }
104  }
105 
106  // There were going-away edges, contract them.
107  if (bestSource) {
108  entries[rep].cost = bestCost;
109  actualSource[target] = bestSource;
110  }
111  }
112  }
113 
114  // Store the edges to the representative.
115  graph[rep] = std::move(repEntries);
116 }
117 
119  : graph(std::move(graph)), root(root) {}
120 
122  // Initialize the parents and total cost.
123  parents.clear();
124  parents[root] = Value();
125  unsigned totalCost = 0;
126 
127  // A map that stores the cost of the optimal local choice for each node
128  // in a directed cycle. This map is cleared every time we seed the search.
129  DenseMap<Value, unsigned> parentDepths;
130  parentDepths.reserve(graph.size());
131 
132  // Determine if the optimal local choice results in an acyclic graph. This is
133  // done by computing the optimal local choice and traversing up the computed
134  // parents. On success, `parents` will contain the parent of each node.
135  for (const auto &outer : graph) {
136  Value node = outer.first;
137  if (parents.count(node)) // already visited
138  continue;
139 
140  // Follow the trail of best sources until we reach an already visited node.
141  // The code will assert if we cannot reach an already visited node, i.e.,
142  // the graph is not strongly connected.
143  parentDepths.clear();
144  do {
145  auto it = graph.find(node);
146  assert(it != graph.end() && "the graph is not strongly connected");
147 
148  // Find the best local parent, taking into account both the depth and the
149  // tie breaking rules.
150  Value &bestSource = parents[node];
151  std::pair<unsigned, unsigned> bestCost;
152  for (const auto &inner : it->second) {
153  const RootOrderingEntry &entry = inner.second;
154  if (!bestSource /* initial */ || bestCost > entry.cost) {
155  bestSource = inner.first;
156  bestCost = entry.cost;
157  }
158  }
159  assert(bestSource && "the graph is not strongly connected");
160  parentDepths[node] = bestCost.first;
161  node = bestSource;
162  totalCost += bestCost.first;
163  } while (!parents.count(node));
164 
165  // If we reached a non-root node, we have a cycle.
166  if (parentDepths.count(node)) {
167  // Determine the cycle starting at the representative node.
168  SmallVector<Value> cycle = getCycle(parents, node);
169 
170  // The following maps disambiguate the source / target of the edges
171  // going out of / into the cycle.
172  DenseMap<Value, Value> actualSource, actualTarget;
173 
174  // Contract the cycle and recurse.
175  contract(graph, cycle, parentDepths, actualSource, actualTarget);
176  totalCost = solve();
177 
178  // Redirect the going-away edges.
179  for (auto &p : parents)
180  if (p.second == node)
181  // The parent is the node representating the cycle; replace it
182  // with the actual (best) source in the cycle.
183  p.second = actualSource.lookup(p.first);
184 
185  // Redirect the unique incoming edge and copy the cycle.
186  Value parent = parents.lookup(node);
187  Value entry = actualTarget.lookup(parent);
188  cycle.push_back(node); // complete the cycle
189  for (size_t i = 0, e = cycle.size() - 1; i < e; ++i) {
190  totalCost += parentDepths.lookup(cycle[i]);
191  if (cycle[i] == entry)
192  parents[cycle[i]] = parent; // break the cycle
193  else
194  parents[cycle[i]] = cycle[i + 1];
195  }
196 
197  // `parents` has a complete solution.
198  break;
199  }
200  }
201 
202  return totalCost;
203 }
204 
207  // Invert the parent mapping.
209  for (Value node : nodes) {
210  if (node != root) {
211  Value parent = parents.lookup(node);
212  assert(parent && "invalid parent");
213  children[parent].push_back(node);
214  }
215  }
216 
217  // The result which simultaneously acts as a queue.
218  EdgeList result;
219  result.reserve(nodes.size());
220  result.emplace_back(root, Value());
221 
222  // Perform a BFS, pushing into the queue.
223  for (size_t i = 0; i < result.size(); ++i) {
224  Value node = result[i].first;
225  for (Value child : children[node])
226  result.emplace_back(child, node);
227  }
228 
229  return result;
230 }
static void contract(RootOrderingGraph &graph, ArrayRef< Value > cycle, const DenseMap< Value, unsigned > &parentDepths, DenseMap< Value, Value > &actualSource, DenseMap< Value, Value > &actualTarget)
Contracts the specified cycle in the given graph in-place.
static SmallVector< Value > getCycle(const DenseMap< Value, Value > &parents, Value rep)
Returns the cycle implied by the specified parent relation, starting at the given node.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
unsigned solve()
Runs the Edmonds' algorithm for the current graph, returning the total cost of the minimum-weight spa...
std::vector< std::pair< Value, Value > > EdgeList
A list of edges (child, parent).
Definition: RootOrdering.h:93
OptimalBranching(RootOrderingGraph graph, Value root)
Constructs the solver for the given graph and root value.
EdgeList preOrderTraversal(ArrayRef< Value > nodes) const
Returns the computed edges as visited in the preorder traversal.
Include the generated interface declarations.
The information associated with an edge in the cost graph.
Definition: RootOrdering.h:59
std::pair< unsigned, unsigned > cost
The depth of the connector Value w.r.t.
Definition: RootOrdering.h:65