XNetwork 1.7.8
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 // -------------------------------------------------------------------
85 template <typename Node, typename WeightFunc>
86 auto build_dual(const std::vector<std::vector<Node>>& faces, WeightFunc weight)
87 -> std::vector<std::vector<DualEdge<Node>>> {
88 const auto n_face = faces.size();
89
90 std::map<std::pair<Node, Node>, std::vector<int>> edge_face_map;
91 for (size_t fi = 0; fi < n_face; ++fi) {
92 const auto& f = faces[fi];
93 const auto sz = f.size();
94 for (size_t i = 0; i < sz; ++i) {
95 auto u = f[i];
96 auto v = f[(i + 1) % sz];
97 if (u > v) std::swap(u, v);
98 edge_face_map[{u, v}].push_back(static_cast<int>(fi));
99 }
100 }
101
102 std::map<std::pair<int, int>, std::pair<int, std::pair<Node, Node>>> best;
103 for (const auto& [primal_key, face_ids] : edge_face_map) {
104 if (face_ids.size() < 2) continue;
105 const auto& [u, v] = primal_key;
106 const int w = weight(u, v);
107 for (size_t a = 0; a < face_ids.size(); ++a) {
108 for (size_t b = a + 1; b < face_ids.size(); ++b) {
109 int fi = face_ids[a];
110 int fj = face_ids[b];
111 if (fi > fj) std::swap(fi, fj);
112 auto it = best.find({fi, fj});
113 if (it == best.end() || w < it->second.first) {
114 best[{fi, fj}] = {w, {u, v}};
115 }
116 }
117 }
118 }
119
120 std::vector<std::vector<DualEdge<Node>>> dual(n_face);
121 auto* dual_raw = dual.data();
122 for (const auto& [key, info] : best) {
123 const auto& [fi, fj] = key;
124 const auto& [w, primal] = info;
125 dual_raw[fi].push_back({fj, w, primal});
126 dual_raw[fj].push_back({fi, w, primal});
127 }
128 return dual;
129 }
130
131 // -------------------------------------------------------------------
132 // Dijkstra shortest path
133 // -------------------------------------------------------------------
134 template <typename Node>
135 auto dijkstra(const std::vector<std::vector<DualEdge<Node>>>& dual, int src)
136 -> std::pair<std::vector<int>, std::vector<int>>;
137
138 inline auto reconstruct_path(const std::vector<int>& prev, int src, int dst)
139 -> std::vector<int> {
140 std::vector<int> path;
141 const auto* p = prev.data();
142 for (int v = dst; v != -1 && v != src; v = p[v]) {
143 path.push_back(v);
144 }
145 if (path.empty() && src != dst) return {};
146 path.push_back(src);
147 std::reverse(path.begin(), path.end());
148 return path;
149 }
150
151 // -------------------------------------------------------------------
152 // Minimum Weight Perfect Matching
153 // -------------------------------------------------------------------
154
158 template <typename Node>
159 auto greedy_mwpm(const std::vector<int>& odd_faces, const std::vector<std::vector<int>>& dist)
160 -> std::vector<std::pair<int, int>>;
161
165 template <typename Node>
166 auto exact_mwpm(const std::vector<int>& odd_faces, const std::vector<std::vector<int>>& dist)
167 -> std::vector<std::pair<int, int>>;
168
172 template <typename Node>
173 auto min_weight_perfect_matching(const std::vector<int>& odd_faces,
174 const std::vector<std::vector<int>>& dist)
175 -> std::vector<std::pair<int, int>> {
176 const int n = static_cast<int>(odd_faces.size());
177 if (n <= 18) {
179 }
181 }
182
183 // -------------------------------------------------------------------
184 // Biconnected Component Decomposition (for MAX-CUT speedup)
185 // -------------------------------------------------------------------
186
204 template <typename Graph> auto biconnected_components(const Graph& G)
205 -> std::vector<py::set<typename Graph::node_t>>;
206
207 // -------------------------------------------------------------------
208 // Core Hadlock solver (single component)
209 // -------------------------------------------------------------------
210
240 template <typename Graph, typename WeightFunc>
241 auto solve_hadlock_component(const Graph& G, WeightFunc weight,
242 const std::vector<std::vector<typename Graph::node_t>>& faces)
244 using node_t = typename Graph::node_t;
245
246 // (1) Build the dual graph
247 const auto dual = build_dual<node_t>(faces, weight);
248
249 // (2) Identify odd-degree faces
250 std::vector<int> odd_faces;
251 for (size_t i = 0; i < faces.size(); ++i) {
252 if (faces[i].size() % 2 == 1) {
253 odd_faces.push_back(static_cast<int>(i));
254 }
255 }
256
257 // No odd faces -> graph is bipartite -> all edges are in the cut
258 if (odd_faces.size() < 2) {
260 for (const auto& e : G.edges()) {
261 all_edges.insert(e);
262 }
263 return all_edges;
264 }
265
266 // Odd number of odd faces -> drop one (planar graphs have even #odd faces)
267 if (odd_faces.size() % 2 != 0) {
268 odd_faces.pop_back();
269 if (odd_faces.size() < 2) {
271 for (const auto& e : G.edges()) {
272 all_edges.insert(e);
273 }
274 return all_edges;
275 }
276 }
277
278 const auto n_odd = odd_faces.size();
279
280 // odd_faces[k] -> k lookup
281 std::map<int, size_t> odd_idx;
282 for (size_t k = 0; k < n_odd; ++k) {
283 odd_idx[odd_faces[k]] = k;
284 }
285
286 // (3) All-pairs shortest paths between odd faces
287 std::vector<std::vector<int>> dist_mat(n_odd);
288 std::vector<std::vector<std::vector<int>>> path_mat(n_odd,
289 std::vector<std::vector<int>>(n_odd));
290
291 for (size_t i = 0; i < n_odd; ++i) {
293 dist_mat[i] = std::move(dist);
294 for (size_t j = 0; j < n_odd; ++j) {
295 if (i != j) {
297 }
298 }
299 }
300
301 // (4) Minimum weight perfect matching on odd-face distances
303
304 // (5) Collect primal edges excluded from the cut
305 // Build dual-edge -> primal-edge lookup
306 std::map<std::pair<int, int>, std::pair<node_t, node_t>> dedge_primal;
307 for (size_t fi = 0; fi < dual.size(); ++fi) {
308 for (const auto& e : dual[fi]) {
309 int a = static_cast<int>(fi);
310 int b = e.neighbor;
311 if (a > b) std::swap(a, b);
312 dedge_primal[{a, b}] = e.primal;
313 }
314 }
315
316 // Walk each matched-pair path in the dual to find primal edges to exclude
317 std::set<std::pair<node_t, node_t>> excluded;
318 for (const auto& [u_face, v_face] : matching) {
319 auto ui = odd_idx.find(u_face);
320 auto vi = odd_idx.find(v_face);
321 if (ui == odd_idx.end() || vi == odd_idx.end()) continue;
322
323 const auto& path = path_mat[ui->second][vi->second];
324 for (size_t k = 0; k + 1 < path.size(); ++k) {
325 int a = path[k];
326 int b = path[k + 1];
327 if (a > b) std::swap(a, b);
328 auto it = dedge_primal.find({a, b});
329 if (it != dedge_primal.end()) {
330 auto [pu, pv] = it->second;
331 if (pu > pv) std::swap(pu, pv);
332 excluded.insert({pu, pv});
333 }
334 }
335 }
336
337 // (6) Max-cut = all edges not excluded
339 for (const auto& e : G.edges()) {
340 auto [u, v] = e;
341 if (u > v) std::swap(u, v);
342 if (!excluded.count({u, v})) {
343 cut_edges.insert(e);
344 }
345 }
346 return cut_edges;
347 }
348
349} // namespace detail
350
351// ===================================================================
352// Public API
353// ===================================================================
354
365template <typename Graph, typename WeightFunc>
366auto solve_hadlock_max_cut(const Graph& G, WeightFunc weight,
367 const std::vector<std::vector<typename Graph::node_t>>& faces)
369 return detail::solve_hadlock_component(G, weight, faces);
370}
371
388template <typename Graph, typename WeightFunc> auto solve_hadlock_max_cut(
389 const Graph& G, WeightFunc weight,
390 const std::vector<std::vector<std::vector<typename Graph::node_t>>>& component_faces)
392 using node_t = typename Graph::node_t;
393 using edge_t = std::pair<node_t, node_t>;
394
395 // Decompose into biconnected components
397
398 const auto n_comps = std::min(comp_nodes.size(), component_faces.size());
399
400 // Components are independent (edges are disjoint), so no
401 // synchronization is needed beyond collecting results.
403 std::vector<std::future<py::set<edge_t>>> futures;
404 futures.reserve(n_comps);
405
406 for (size_t i = 0; i < n_comps; ++i) {
407 futures.push_back(pool.enqueue([&, i]() -> py::set<edge_t> {
408 const auto& comp_faces = component_faces[i];
409
410 auto comp_weight = [&weight](node_t u, node_t v) -> int { return weight(u, v); };
411
412 // Extract edges belonging to this component by scanning faces
413 std::vector<std::pair<node_t, node_t>> comp_edges;
414 for (const auto& face : comp_faces) {
415 for (size_t j = 0; j < face.size(); ++j) {
416 auto u = face[j];
417 auto v = face[(j + 1) % face.size()];
418 if (u > v) std::swap(u, v);
419 edge_t e{u, v};
420 if (std::find(comp_edges.begin(), comp_edges.end(), e) == comp_edges.end()) {
421 comp_edges.push_back(e);
422 }
423 }
424 }
425
426 struct CompGraph {
427 using node_t = typename std::remove_reference<
428 decltype(comp_edges)>::type::value_type::first_type;
429 const std::vector<std::pair<node_t, node_t>>& edge_list;
430 auto edges() const { return edge_list; }
431 };
432
435 }));
436 }
437
439 for (auto& f : futures) {
440 auto comp_cut = f.get();
441 for (const auto& e : comp_cut) {
442 cut_edges.insert(e);
443 }
444 }
445
446 return cut_edges;
447}
448
449template <typename Graph, typename WeightFunc> auto validate_max_cut(
450 [[maybe_unused]] const Graph& G,
451 const py::set<std::pair<typename Graph::node_t, typename Graph::node_t>>& cut_edges,
452 WeightFunc weight) -> std::pair<bool, int> {
453 using node_t = typename Graph::node_t;
454
455 std::map<node_t, std::vector<node_t>> cut_adj;
456 int total_weight = 0;
457 for (const auto& e : cut_edges) {
458 cut_adj[e.first].push_back(e.second);
459 cut_adj[e.second].push_back(e.first);
460 total_weight += weight(e.first, e.second);
461 }
462
463 std::map<node_t, int> colour;
464 bool is_bipartite = true;
465 for (const auto& [start, _] : cut_adj) {
466 if (colour.count(start)) continue;
467 colour[start] = 1;
468 std::queue<node_t> q;
469 q.push(start);
470 while (!q.empty() && is_bipartite) {
471 auto u = q.front();
472 q.pop();
473 for (const auto& v : cut_adj[u]) {
474 if (!colour.count(v)) {
475 colour[v] = (colour[u] == 1) ? 2 : 1;
476 q.push(v);
477 } else if (colour[v] == colour[u]) {
478 is_bipartite = false;
479 break;
480 }
481 }
482 }
483 }
484 return {is_bipartite, total_weight};
485}
486
487template <typename Graph> auto validate_max_cut(
488 [[maybe_unused]] const Graph& G,
489 const py::set<std::pair<typename Graph::node_t, typename Graph::node_t>>& cut_edges)
490 -> std::pair<bool, int> {
491 return validate_max_cut(G, cut_edges, [](auto, auto) { return 1; });
492}
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
Command pattern: tasks are encapsulated as command objects (std::function<void()>) placed in a shared...
Definition thread_pool.hpp:42
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:449
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:366
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:138
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:241
auto build_dual(const std::vector< std::vector< Node > > &faces, WeightFunc weight) -> std::vector< std::vector< DualEdge< Node > > >
Build the planar dual graph: faces become vertices, shared primal edges become edges with equal weigh...
Definition hadlock.hpp:86
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:173
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