1 #include "partitioner/dinic_max_flow.hpp"
2 #include "util/integer_range.hpp"
3
4 #include <algorithm>
5 #include <limits>
6 #include <numeric>
7 #include <queue>
8 #include <set>
9 #include <stack>
10
11 namespace osrm
12 {
13 namespace partitioner
14 {
15
16 namespace
17 {
18
19 const auto constexpr INVALID_LEVEL = std::numeric_limits<DinicMaxFlow::Level>::max();
20
makeHasNeighborNotInCheck(const DinicMaxFlow::SourceSinkNodes & set,const BisectionGraphView & view)21 auto makeHasNeighborNotInCheck(const DinicMaxFlow::SourceSinkNodes &set,
22 const BisectionGraphView &view)
23 {
24 return [&](const NodeID nid) {
25 const auto is_not_contained = [&set](const BisectionEdge &edge) {
26 return set.count(edge.target) == 0;
27 };
28 return view.EndEdges(nid) !=
29 std::find_if(view.BeginEdges(nid), view.EndEdges(nid), is_not_contained);
30 };
31 }
32
33 } // end namespace
34
operator ()(const BisectionGraphView & view,const SourceSinkNodes & source_nodes,const SourceSinkNodes & sink_nodes) const35 DinicMaxFlow::MinCut DinicMaxFlow::operator()(const BisectionGraphView &view,
36 const SourceSinkNodes &source_nodes,
37 const SourceSinkNodes &sink_nodes) const
38 {
39 BOOST_ASSERT(Validate(view, source_nodes, sink_nodes));
40 // for the inertial flow algorithm, we use quite a large set of nodes as source/sink nodes. Only
41 // a few of them can be part of the process, since they are grouped together. A standard
42 // parameterisation would be 25% sink/source nodes. This already includes 50% of the graph. By
43 // only focussing on a small set on the outside of the source/sink blob, we can save quite some
44 // overhead in initialisation/search cost.
45
46 std::vector<NodeID> border_source_nodes;
47 border_source_nodes.reserve(0.01 * source_nodes.size());
48
49 std::copy_if(source_nodes.begin(),
50 source_nodes.end(),
51 std::back_inserter(border_source_nodes),
52 makeHasNeighborNotInCheck(source_nodes, view));
53
54 std::vector<NodeID> border_sink_nodes;
55 border_sink_nodes.reserve(0.01 * sink_nodes.size());
56 std::copy_if(sink_nodes.begin(),
57 sink_nodes.end(),
58 std::back_inserter(border_sink_nodes),
59 makeHasNeighborNotInCheck(sink_nodes, view));
60
61 // edges in current flow that have capacity
62 // The graph (V,E) contains undirected edges for all (u,v) \in V x V. We describe the flow as a
63 // set of vertices (s,t) with flow set to `true`. Since flow can be either from `s` to `t` or
64 // from `t` to `s`, we can remove `(s,t)` from the flow, if we send flow back the first time,
65 // and insert `(t,s)` only if we send flow again.
66
67 // allocate storage for the flow
68 FlowEdges flow(view.NumberOfNodes());
69 std::size_t flow_value = 0;
70 do
71 {
72 auto levels = ComputeLevelGraph(view, border_source_nodes, source_nodes, sink_nodes, flow);
73
74 // check if the sink can be reached from the source, it's enough to check the border
75 const auto separated = std::find_if(border_sink_nodes.begin(),
76 border_sink_nodes.end(),
77 [&levels](const auto node) {
78 return levels[node] != INVALID_LEVEL;
79 }) == border_sink_nodes.end();
80
81 if (!separated)
82 {
83 flow_value += BlockingFlow(flow, levels, view, source_nodes, border_sink_nodes);
84 }
85 else
86 {
87 // mark levels for all sources to not confuse make-cut (due to the border nodes
88 // heuristic)
89 for (auto s : source_nodes)
90 levels[s] = 0;
91 const auto cut = MakeCut(view, levels, flow_value);
92 return cut;
93 }
94 } while (true);
95 }
96
MakeCut(const BisectionGraphView & view,const LevelGraph & levels,const std::size_t flow_value) const97 DinicMaxFlow::MinCut DinicMaxFlow::MakeCut(const BisectionGraphView &view,
98 const LevelGraph &levels,
99 const std::size_t flow_value) const
100 {
101 const auto is_valid_level = [](const Level level) { return level != INVALID_LEVEL; };
102
103 // all elements within `levels` are on the source side
104 // This part should opt to find the most balanced cut, which is not necessarily the case right
105 // now. There is potential for optimisation here.
106 std::vector<bool> result(view.NumberOfNodes());
107
108 BOOST_ASSERT(view.NumberOfNodes() == levels.size());
109 std::size_t source_side_count = std::count_if(levels.begin(), levels.end(), is_valid_level);
110 std::transform(levels.begin(), levels.end(), result.begin(), is_valid_level);
111
112 return {source_side_count, flow_value, std::move(result)};
113 }
114
115 DinicMaxFlow::LevelGraph
ComputeLevelGraph(const BisectionGraphView & view,const std::vector<NodeID> & border_source_nodes,const SourceSinkNodes & source_nodes,const SourceSinkNodes & sink_nodes,const FlowEdges & flow) const116 DinicMaxFlow::ComputeLevelGraph(const BisectionGraphView &view,
117 const std::vector<NodeID> &border_source_nodes,
118 const SourceSinkNodes &source_nodes,
119 const SourceSinkNodes &sink_nodes,
120 const FlowEdges &flow) const
121 {
122 LevelGraph levels(view.NumberOfNodes(), INVALID_LEVEL);
123 std::queue<NodeID> level_queue;
124
125 // set the front of the source nodes to zero and add them to the BFS queue. In addition, set all
126 // neighbors to zero as well (which allows direct usage of the levels to see what we visited,
127 // and still don't go back into the hughe set of sources)
128 for (const auto node_id : border_source_nodes)
129 {
130 levels[node_id] = 0;
131 level_queue.push(node_id);
132 for (const auto &edge : view.Edges(node_id))
133 if (source_nodes.count(edge.target))
134 levels[edge.target] = 0;
135 }
136 // check if there is flow present on an edge
137 const auto has_flow = [&](const NodeID from, const NodeID to) {
138 return flow[from].find(to) != flow[from].end();
139 };
140
141 // perform a relaxation step in the BFS algorithm
142 const auto relax_node = [&](const NodeID node_id) {
143 // don't relax sink nodes
144 if (sink_nodes.count(node_id))
145 return;
146
147 const auto level = levels[node_id] + 1;
148 for (const auto &edge : view.Edges(node_id))
149 {
150 const auto target = edge.target;
151 // don't relax edges with flow on them
152 if (has_flow(node_id, target))
153 continue;
154
155 // don't go back, only follow edges to new nodes
156 if (levels[target] > level)
157 {
158 level_queue.push(target);
159 levels[target] = level;
160 }
161 }
162 };
163
164 // compute the levels of level graph using BFS
165 while (!level_queue.empty())
166 {
167 relax_node(level_queue.front());
168 level_queue.pop();
169 }
170
171 return levels;
172 }
173
BlockingFlow(FlowEdges & flow,LevelGraph & levels,const BisectionGraphView & view,const SourceSinkNodes & source_nodes,const std::vector<NodeID> & border_sink_nodes) const174 std::size_t DinicMaxFlow::BlockingFlow(FlowEdges &flow,
175 LevelGraph &levels,
176 const BisectionGraphView &view,
177 const SourceSinkNodes &source_nodes,
178 const std::vector<NodeID> &border_sink_nodes) const
179 {
180 // track the number of augmenting paths (which in sum will equal the number of unique border
181 // edges) (since our graph is undirected)
182 std::size_t flow_increase = 0;
183
184 // augment the flow along a path in the level graph
185 const auto augment_flow = [&flow](const std::vector<NodeID> &path) {
186 // add/remove flow edges from the current residual graph
187 const auto augment_one = [&flow](const NodeID from, const NodeID to) {
188 // check if there is flow in the opposite direction
189 auto existing_edge = flow[to].find(from);
190 if (existing_edge != flow[to].end())
191 flow[to].erase(existing_edge); // remove flow from reverse edges first
192 else
193 flow[from].insert(to); // only add flow if no opposite flow exists
194
195 // do augmentation on all pairs, never stop early:
196 return false;
197 };
198
199 // augment all adjacent edges
200 std::adjacent_find(path.begin(), path.end(), augment_one);
201 };
202
203 const auto augment_all_paths = [&](const NodeID sink_node_id) {
204 // only augment sinks
205 if (levels[sink_node_id] == INVALID_LEVEL)
206 return;
207
208 while (true)
209 {
210 // as long as there are augmenting paths from the sink, add them
211 const auto path = GetAugmentingPath(levels, sink_node_id, view, flow, source_nodes);
212 if (path.empty())
213 break;
214 else
215 {
216 augment_flow(path);
217 ++flow_increase;
218 }
219 }
220 };
221
222 std::for_each(border_sink_nodes.begin(), border_sink_nodes.end(), augment_all_paths);
223 BOOST_ASSERT(flow_increase > 0);
224 return flow_increase;
225 }
226
227 // performs a dfs in the level graph, by adjusting levels that don't offer any further paths to
228 // INVALID_LEVEL and by following the level graph, this looks at every edge at most `c` times (O(E))
GetAugmentingPath(LevelGraph & levels,const NodeID node_id,const BisectionGraphView & view,const FlowEdges & flow,const SourceSinkNodes & source_nodes) const229 std::vector<NodeID> DinicMaxFlow::GetAugmentingPath(LevelGraph &levels,
230 const NodeID node_id,
231 const BisectionGraphView &view,
232 const FlowEdges &flow,
233 const SourceSinkNodes &source_nodes) const
234 {
235 std::vector<NodeID> path;
236 BOOST_ASSERT(source_nodes.find(node_id) == source_nodes.end());
237
238 // Keeps the local state of the DFS in forms of the iterators
239 struct DFSState
240 {
241 BisectionGraph::ConstEdgeIterator edge_iterator;
242 const BisectionGraph::ConstEdgeIterator end_iterator;
243 };
244
245 std::stack<DFSState> dfs_stack;
246 DFSState initial_state = {view.BeginEdges(node_id), view.EndEdges(node_id)};
247 dfs_stack.push(std::move(initial_state));
248 path.push_back(node_id);
249
250 while (!dfs_stack.empty())
251 {
252 // the dfs_stack and the path have to be kept in sync
253 BOOST_ASSERT(dfs_stack.size() == path.size());
254
255 while (dfs_stack.top().edge_iterator != dfs_stack.top().end_iterator)
256 {
257 const auto target = dfs_stack.top().edge_iterator->target;
258
259 // look at every edge only once, so advance the state of the current node (last in
260 // path)
261 dfs_stack.top().edge_iterator++;
262
263 // check if the edge is valid
264 const auto has_capacity = flow[target].count(path.back()) == 0;
265 const auto descends_level_graph = levels[target] + 1 == levels[path.back()];
266
267 if (has_capacity && descends_level_graph)
268 {
269 // recurse
270 path.push_back(target);
271
272 // termination
273 if (source_nodes.find(target) != source_nodes.end())
274 {
275 std::reverse(path.begin(), path.end());
276 return path;
277 }
278
279 // start next iteration
280 dfs_stack.push({view.BeginEdges(target), view.EndEdges(target)});
281 }
282 }
283
284 // backtrack - mark that there is no way to the target
285 levels[path.back()] = -1;
286 path.pop_back();
287 dfs_stack.pop();
288 }
289 BOOST_ASSERT(path.empty());
290 return path;
291 }
292
Validate(const BisectionGraphView & view,const SourceSinkNodes & source_nodes,const SourceSinkNodes & sink_nodes) const293 bool DinicMaxFlow::Validate(const BisectionGraphView &view,
294 const SourceSinkNodes &source_nodes,
295 const SourceSinkNodes &sink_nodes) const
296 {
297 // sink and source cannot share a common node
298 const auto separated =
299 std::find_if(source_nodes.begin(), source_nodes.end(), [&sink_nodes](const auto node) {
300 return sink_nodes.count(node);
301 }) == source_nodes.end();
302
303 const auto invalid_id = [&view](const NodeID nid) { return nid >= view.NumberOfNodes(); };
304 const auto in_range_source =
305 std::find_if(source_nodes.begin(), source_nodes.end(), invalid_id) == source_nodes.end();
306 const auto in_range_sink =
307 std::find_if(sink_nodes.begin(), sink_nodes.end(), invalid_id) == sink_nodes.end();
308
309 return separated && in_range_source && in_range_sink;
310 }
311
312 } // namespace partitioner
313 } // namespace osrm
314