XNetwork 1.7.5; VERSION ${PROJECT_VERSION}
Loading...
Searching...
No Matches
hadlock.hpp
Go to the documentation of this file.
1#pragma once
2
18#include <algorithm>
19#include <cassert>
20#include <functional>
21#include <limits>
22#include <map>
23#include <py2cpp/dict.hpp>
24#include <py2cpp/set.hpp>
25#include <queue>
26#include <set>
27#include <utility>
28#include <vector>
30
31// Hash for std::pair - needed by py::set<std::pair<...>> (backed by unordered_set)
32namespace std {
33 template <typename T1, typename T2> struct hash<pair<T1, T2>> {
34 size_t operator()(const pair<T1, T2>& p) const {
35 return hash<T1>{}(p.first) ^ (hash<T2>{}(p.second) << 1);
36 }
37 };
38} // namespace std
39
40namespace detail {
41
42 constexpr int INF = std::numeric_limits<int>::max() / 2;
43
44 template <typename Node> struct DualEdge {
46 int weight;
47 std::pair<Node, Node> primal;
48 };
49
50 // -------------------------------------------------------------------
51 // Dual graph construction
52 //
53 // Each face -> dual vertex. Two dual vertices connect if faces share a
54 // primal edge. Parallel edges -> only the minimum-weight edge is kept.
55 // -------------------------------------------------------------------
56 template <typename Node, typename WeightFunc>
57 auto build_dual(const std::vector<std::vector<Node>>& faces, WeightFunc weight)
58 -> std::vector<std::vector<DualEdge<Node>>> {
59 const auto n_face = static_cast<int>(faces.size());
60
61 std::map<std::pair<Node, Node>, std::vector<int>> edge_face_map;
62 for (int fi = 0; fi < n_face; ++fi) {
63 const auto& f = faces[fi];
64 const auto sz = static_cast<int>(f.size());
65 for (int i = 0; i < sz; ++i) {
66 auto u = f[i];
67 auto v = f[(i + 1) % sz];
68 if (u > v) std::swap(u, v);
69 edge_face_map[{u, v}].push_back(fi);
70 }
71 }
72
73 std::map<std::pair<int, int>, std::pair<int, std::pair<Node, Node>>> best;
74 for (const auto& [primal_key, face_ids] : edge_face_map) {
75 if (face_ids.size() < 2) continue;
76 const auto& [u, v] = primal_key;
77 const int w = weight(u, v);
78 for (size_t a = 0; a < face_ids.size(); ++a) {
79 for (size_t b = a + 1; b < face_ids.size(); ++b) {
80 int fi = face_ids[a];
81 int fj = face_ids[b];
82 if (fi > fj) std::swap(fi, fj);
83 auto it = best.find({fi, fj});
84 if (it == best.end() || w < it->second.first) {
85 best[{fi, fj}] = {w, {u, v}};
86 }
87 }
88 }
89 }
90
91 std::vector<std::vector<DualEdge<Node>>> dual(n_face);
92 for (const auto& [key, info] : best) {
93 const auto& [fi, fj] = key;
94 const auto& [w, primal] = info;
95 dual[fi].push_back({fj, w, primal});
96 dual[fj].push_back({fi, w, primal});
97 }
98 return dual;
99 }
100
101 // -------------------------------------------------------------------
102 // Dijkstra shortest path
103 // -------------------------------------------------------------------
104 template <typename Node>
105 auto dijkstra(const std::vector<std::vector<DualEdge<Node>>>& dual, int src)
106 -> std::pair<std::vector<int>, std::vector<int>>;
107
108 inline auto reconstruct_path(const std::vector<int>& prev, int src, int dst)
109 -> std::vector<int> {
110 std::vector<int> path;
111 for (int v = dst; v != -1 && v != src; v = prev[v]) {
112 path.push_back(v);
113 }
114 if (path.empty() && src != dst) return {};
115 path.push_back(src);
116 std::reverse(path.begin(), path.end());
117 return path;
118 }
119
120 // -------------------------------------------------------------------
121 // Minimum Weight Perfect Matching
122 // -------------------------------------------------------------------
123
127 template <typename Node>
128 auto greedy_mwpm(const std::vector<int>& odd_faces, const std::vector<std::vector<int>>& dist)
129 -> std::vector<std::pair<int, int>>;
130
134 template <typename Node>
135 auto exact_mwpm(const std::vector<int>& odd_faces, const std::vector<std::vector<int>>& dist)
136 -> std::vector<std::pair<int, int>>;
137
141 template <typename Node>
142 auto min_weight_perfect_matching(const std::vector<int>& odd_faces,
143 const std::vector<std::vector<int>>& dist)
144 -> std::vector<std::pair<int, int>> {
145 const int n = static_cast<int>(odd_faces.size());
146 if (n <= 18) {
148 }
150 }
151
152 // -------------------------------------------------------------------
153 // Biconnected Component Decomposition (for MAX-CUT speedup)
154 // -------------------------------------------------------------------
155
173 template <typename Graph> auto biconnected_components(const Graph& G)
174 -> std::vector<py::set<typename Graph::node_t>>;
175
176 // -------------------------------------------------------------------
177 // Core Hadlock solver (single component)
178 // -------------------------------------------------------------------
179
194 template <typename Graph, typename WeightFunc>
195 auto solve_hadlock_component(const Graph& G, WeightFunc weight,
196 const std::vector<std::vector<typename Graph::node_t>>& faces)
198 using node_t = typename Graph::node_t;
199
200 // (1) Build the dual graph
201 const auto dual = build_dual<node_t>(faces, weight);
202
203 // (2) Identify odd-degree faces
204 std::vector<int> odd_faces;
205 for (int i = 0; i < static_cast<int>(faces.size()); ++i) {
206 if (faces[i].size() % 2 == 1) {
207 odd_faces.push_back(i);
208 }
209 }
210
211 // No odd faces -> graph is bipartite -> all edges are in the cut
212 if (odd_faces.size() < 2) {
214 for (const auto& e : G.edges()) {
215 all_edges.insert(e);
216 }
217 return all_edges;
218 }
219
220 // Odd number of odd faces -> drop one (planar graphs have even #odd faces)
221 if (odd_faces.size() % 2 != 0) {
222 odd_faces.pop_back();
223 if (odd_faces.size() < 2) {
225 for (const auto& e : G.edges()) {
226 all_edges.insert(e);
227 }
228 return all_edges;
229 }
230 }
231
232 const int n_odd = static_cast<int>(odd_faces.size());
233
234 // odd_faces[k] -> k lookup
235 std::map<int, int> odd_idx;
236 for (int k = 0; k < n_odd; ++k) {
237 odd_idx[odd_faces[k]] = k;
238 }
239
240 // (3) All-pairs shortest paths between odd faces
241 std::vector<std::vector<int>> dist_mat(n_odd);
242 std::vector<std::vector<std::vector<int>>> path_mat(n_odd,
243 std::vector<std::vector<int>>(n_odd));
244
245 for (int i = 0; i < n_odd; ++i) {
247 dist_mat[i] = std::move(dist);
248 for (int j = 0; j < n_odd; ++j) {
249 if (i != j) {
251 }
252 }
253 }
254
255 // (4) Minimum weight perfect matching on odd-face distances
257
258 // (5) Collect primal edges excluded from the cut
259 // Build dual-edge -> primal-edge lookup
260 std::map<std::pair<int, int>, std::pair<node_t, node_t>> dedge_primal;
261 for (int fi = 0; fi < static_cast<int>(dual.size()); ++fi) {
262 for (const auto& e : dual[fi]) {
263 int a = fi;
264 int b = e.neighbor;
265 if (a > b) std::swap(a, b);
266 dedge_primal[{a, b}] = e.primal;
267 }
268 }
269
270 // Walk each matched-pair path in the dual to find primal edges to exclude
271 std::set<std::pair<node_t, node_t>> excluded;
272 for (const auto& [u_face, v_face] : matching) {
273 auto ui = odd_idx.find(u_face);
274 auto vi = odd_idx.find(v_face);
275 if (ui == odd_idx.end() || vi == odd_idx.end()) continue;
276
277 const auto& path = path_mat[ui->second][vi->second];
278 for (size_t k = 0; k + 1 < path.size(); ++k) {
279 int a = path[k];
280 int b = path[k + 1];
281 if (a > b) std::swap(a, b);
282 auto it = dedge_primal.find({a, b});
283 if (it != dedge_primal.end()) {
284 auto [pu, pv] = it->second;
285 if (pu > pv) std::swap(pu, pv);
286 excluded.insert({pu, pv});
287 }
288 }
289 }
290
291 // (6) Max-cut = all edges not excluded
293 for (const auto& e : G.edges()) {
294 auto [u, v] = e;
295 if (u > v) std::swap(u, v);
296 if (!excluded.count({u, v})) {
297 cut_edges.insert(e);
298 }
299 }
300 return cut_edges;
301 }
302
303} // namespace detail
304
305// ===================================================================
306// Public API
307// ===================================================================
308
319template <typename Graph, typename WeightFunc>
320auto solve_hadlock_max_cut(const Graph& G, WeightFunc weight,
321 const std::vector<std::vector<typename Graph::node_t>>& faces)
323 return detail::solve_hadlock_component(G, weight, faces);
324}
325
342template <typename Graph, typename WeightFunc> auto solve_hadlock_max_cut(
343 const Graph& G, WeightFunc weight,
344 const std::vector<std::vector<std::vector<typename Graph::node_t>>>& component_faces)
346 using node_t = typename Graph::node_t;
347 using edge_t = std::pair<node_t, node_t>;
348
349 // Decompose into biconnected components
351
352 const auto n_comps = std::min(comp_nodes.size(), component_faces.size());
353
354 // Components are independent (edges are disjoint), so no
355 // synchronization is needed beyond collecting results.
357 std::vector<std::future<py::set<edge_t>>> futures;
358 futures.reserve(n_comps);
359
360 for (size_t i = 0; i < n_comps; ++i) {
361 futures.push_back(pool.enqueue([&, i]() -> py::set<edge_t> {
362 const auto& comp_faces = component_faces[i];
363
364 auto comp_weight = [&weight](node_t u, node_t v) -> int { return weight(u, v); };
365
366 // Extract edges belonging to this component by scanning faces
367 std::vector<std::pair<node_t, node_t>> comp_edges;
368 for (const auto& face : comp_faces) {
369 for (size_t j = 0; j < face.size(); ++j) {
370 auto u = face[j];
371 auto v = face[(j + 1) % face.size()];
372 if (u > v) std::swap(u, v);
373 edge_t e{u, v};
374 if (std::find(comp_edges.begin(), comp_edges.end(), e) == comp_edges.end()) {
375 comp_edges.push_back(e);
376 }
377 }
378 }
379
380 struct CompGraph {
381 using node_t = typename std::remove_reference<
382 decltype(comp_edges)>::type::value_type::first_type;
383 const std::vector<std::pair<node_t, node_t>>& edge_list;
384 auto edges() const { return edge_list; }
385 };
386
389 }));
390 }
391
393 for (auto& f : futures) {
394 auto comp_cut = f.get();
395 for (const auto& e : comp_cut) {
396 cut_edges.insert(e);
397 }
398 }
399
400 return cut_edges;
401}
402
403template <typename Graph, typename WeightFunc> auto validate_max_cut(
404 [[maybe_unused]] const Graph& G,
405 const py::set<std::pair<typename Graph::node_t, typename Graph::node_t>>& cut_edges,
406 WeightFunc weight) -> std::pair<bool, int> {
407 using node_t = typename Graph::node_t;
408
409 std::map<node_t, std::vector<node_t>> cut_adj;
410 int total_weight = 0;
411 for (const auto& e : cut_edges) {
412 cut_adj[e.first].push_back(e.second);
413 cut_adj[e.second].push_back(e.first);
414 total_weight += weight(e.first, e.second);
415 }
416
417 std::map<node_t, int> colour;
418 bool is_bipartite = true;
419 for (const auto& [start, _] : cut_adj) {
420 if (colour.count(start)) continue;
421 colour[start] = 1;
422 std::queue<node_t> q;
423 q.push(start);
424 while (!q.empty() && is_bipartite) {
425 auto u = q.front();
426 q.pop();
427 for (const auto& v : cut_adj[u]) {
428 if (!colour.count(v)) {
429 colour[v] = (colour[u] == 1) ? 2 : 1;
430 q.push(v);
431 } else if (colour[v] == colour[u]) {
432 is_bipartite = false;
433 break;
434 }
435 }
436 }
437 }
438 return {is_bipartite, total_weight};
439}
440
441template <typename Graph> auto validate_max_cut(
442 [[maybe_unused]] const Graph& G,
443 const py::set<std::pair<typename Graph::node_t, typename Graph::node_t>>& cut_edges)
444 -> std::pair<bool, int> {
445 return validate_max_cut(G, cut_edges, [](auto, auto) { return 1; });
446}
Read-only map of maps of maps (view into a dict-of-dict-of-dict structure)
Definition coreviews.hpp:109
AdjacencyView(Atlas &d)
Construct an AdjacencyView from an Atlas container.
Definition coreviews.hpp:115
auto begin() const
Get iterator to the beginning of the view.
Definition coreviews.hpp:55
auto size() const -> size_t
Get the number of elements in the view.
Definition coreviews.hpp:49
auto end() const
Get iterator to the end of the view.
Definition coreviews.hpp:61
Definition thread_pool.hpp:35
auto validate_max_cut(const Graph &G, const py::set< std::pair< typename Graph::node_t, typename Graph::node_t > > &cut_edges, WeightFunc weight) -> std::pair< bool, int >
Definition hadlock.hpp:403
auto solve_hadlock_max_cut(const Graph &G, WeightFunc weight, const std::vector< std::vector< typename Graph::node_t > > &faces) -> py::set< std::pair< typename Graph::node_t, typename Graph::node_t > >
Solve MAX-CUT for a planar graph using Hadlock's algorithm.
Definition hadlock.hpp:320
Definition hadlock.hpp:40
auto dijkstra(const std::vector< std::vector< DualEdge< Node > > > &dual, int src) -> std::pair< std::vector< int >, std::vector< int > >
auto reconstruct_path(const std::vector< int > &prev, int src, int dst) -> std::vector< int >
Definition hadlock.hpp:108
auto biconnected_components(const Graph &G) -> std::vector< py::set< typename Graph::node_t > >
Decompose a graph into its biconnected components (blocks).
auto exact_mwpm(const std::vector< int > &odd_faces, const std::vector< std::vector< int > > &dist) -> std::vector< std::pair< int, int > >
auto solve_hadlock_component(const Graph &G, WeightFunc weight, const std::vector< std::vector< typename Graph::node_t > > &faces) -> py::set< std::pair< typename Graph::node_t, typename Graph::node_t > >
Solve MAX-CUT for a single planar biconnected component.
Definition hadlock.hpp:195
auto build_dual(const std::vector< std::vector< Node > > &faces, WeightFunc weight) -> std::vector< std::vector< DualEdge< Node > > >
Definition hadlock.hpp:57
auto min_weight_perfect_matching(const std::vector< int > &odd_faces, const std::vector< std::vector< int > > &dist) -> std::vector< std::pair< int, int > >
Definition hadlock.hpp:142
auto greedy_mwpm(const std::vector< int > &odd_faces, const std::vector< std::vector< int > > &dist) -> std::vector< std::pair< int, int > >
constexpr int INF
Definition hadlock.hpp:42
Definition hadlock.hpp:32
Definition hadlock.hpp:44
std::pair< Node, Node > primal
Definition hadlock.hpp:47
int weight
Definition hadlock.hpp:46
int neighbor
Definition hadlock.hpp:45
size_t operator()(const pair< T1, T2 > &p) const
Definition hadlock.hpp:34