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