discrete-modulus (C++)
Reference implementations of discrete modulus algorithms (C++)
Loading...
Searching...
No Matches
cunningham.hpp
Go to the documentation of this file.
1
33#pragma once
34
35#include <algorithm>
36#include <cassert>
37#include <iomanip>
38#include <iostream>
39#include <map>
40#include <set>
41#include <utility>
42#include <vector>
43
44#include <boost/graph/push_relabel_max_flow.hpp>
45#include <boost/rational.hpp>
46
47#include "graphs.hpp"
48#include "solver_trace.hpp"
49
51
52using namespace boost;
53
68 std::vector<FlowEdge> forward;
69 std::vector<FlowEdge> backward;
70 std::vector<FlowEdge> to_target;
71 std::vector<FlowEdge> from_source;
72};
73
74namespace detail {
75
86inline FlowEdge add_capacity_edge(FlowGraph& fg, FlowVertex u, FlowVertex v, long capacity) {
87 FlowEdge fe = add_edge(u, v, fg).first;
88 FlowEdge fe_r = add_edge(v, u, fg).first;
89 put(edge_capacity, fg, fe, capacity);
90 put(edge_capacity, fg, fe_r, 0);
91 put(edge_reverse, fg, fe, fe_r);
92 put(edge_reverse, fg, fe_r, fe);
93 return fe;
94}
95
107inline std::pair<long, std::set<Edge>> solve_subproblem(Graph& g, FlowContext& ctx, const Edge& e, long q) {
108 const long n = static_cast<long>(num_vertices(g));
109 Vertex u = source(e, g), v = target(e, g);
110
111 // capacities between "regular" nodes
112 EdgeIterator ei, ei_end;
113 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
114 int idx = get(edge_index, g, *ei);
115 long w = get(edge_weight, g, *ei);
116 put(edge_capacity, ctx.graph, ctx.forward[idx], w);
117 put(edge_capacity, ctx.graph, ctx.backward[idx], w);
118 }
119
120 // fixed capacities to the target
121 VertexIterator vi, vi_end;
122 for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) {
123 put(edge_capacity, ctx.graph, ctx.to_target[*vi], 2 * q);
124 }
125
126 // capacities from the source. Vertices incident to e get an
127 // "unlimited" capacity so the min cut never has to pass through
128 // them; the total capacity into the target is 2*q*n, so anything
129 // larger than that can never be the bottleneck. Using a finite bound
130 // (rather than an arbitrary huge constant) keeps everything well
131 // inside `long` range.
132 const long unlimited = 2 * q * n + 1;
133 for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) {
134 long capacity;
135 if (*vi == u || *vi == v) {
136 capacity = unlimited;
137 } else {
138 capacity = 0;
139 graph_traits<Graph>::out_edge_iterator oei, oei_end;
140 for (boost::tie(oei, oei_end) = out_edges(*vi, g); oei != oei_end; ++oei) {
141 capacity += get(edge_weight, g, *oei);
142 }
143 }
144 put(edge_capacity, ctx.graph, ctx.from_source[*vi], capacity);
145 }
146
147 // run max flow
148 long flow = push_relabel_max_flow(ctx.graph, ctx.src, ctx.tgt);
149 assert(flow % 2 == 0);
150
151 long eps = flow / 2 - q;
152 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
153 eps -= get(edge_weight, g, *ei);
154 }
155
156 // the critical set is made of the vertices reachable from the source
157 // via positive-residual-capacity edges
158 std::set<Vertex> C;
159 std::vector<FlowVertex> process{ctx.src};
160 while (!process.empty()) {
161 FlowVertex w = process.back();
162 process.pop_back();
163 C.insert(static_cast<Vertex>(w));
164
165 graph_traits<FlowGraph>::out_edge_iterator fei, fei_end;
166 for (boost::tie(fei, fei_end) = out_edges(w, ctx.graph); fei != fei_end; ++fei) {
167 if (get(edge_residual_capacity, ctx.graph, *fei) > 0) {
168 FlowVertex w2 = target(*fei, ctx.graph);
169 if (C.count(static_cast<Vertex>(w2)) == 0) {
170 process.push_back(w2);
171 }
172 }
173 }
174 }
175 C.erase(static_cast<Vertex>(ctx.src));
176
177 std::set<Edge> crit_edges;
178 for (Vertex a : C) {
179 graph_traits<Graph>::out_edge_iterator oei, oei_end;
180 for (boost::tie(oei, oei_end) = out_edges(a, g); oei != oei_end; ++oei) {
181 if (C.count(target(*oei, g)) > 0) {
182 crit_edges.insert(*oei);
183 }
184 }
185 }
186
187 return {eps, crit_edges};
188}
189
190} // namespace detail
191
201 const long n = static_cast<long>(num_vertices(g));
202
203 FlowContext ctx;
204 ctx.graph = FlowGraph(static_cast<std::size_t>(n + 2));
205 ctx.src = static_cast<FlowVertex>(n);
206 ctx.tgt = static_cast<FlowVertex>(n + 1);
207
208 const std::size_t m = num_edges(g);
209 ctx.forward.resize(m);
210 ctx.backward.resize(m);
211 ctx.to_target.resize(static_cast<std::size_t>(n));
212 ctx.from_source.resize(static_cast<std::size_t>(n));
213
214 std::size_t i = 0;
215 EdgeIterator ei, ei_end;
216 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei, ++i) {
217 put(edge_index, g, *ei, static_cast<int>(i));
218 Vertex u = source(*ei, g), v = target(*ei, g);
219 ctx.forward[i] = detail::add_capacity_edge(ctx.graph, u, v, 0);
220 ctx.backward[i] = detail::add_capacity_edge(ctx.graph, v, u, 0);
221 }
222
223 VertexIterator vi, vi_end;
224 for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) {
225 ctx.to_target[*vi] = detail::add_capacity_edge(ctx.graph, *vi, ctx.tgt, 0);
226 ctx.from_source[*vi] = detail::add_capacity_edge(ctx.graph, ctx.src, *vi, 0);
227 }
228
229 return ctx;
230}
231
246inline std::pair<long, std::set<Edge>> cunningham_min(Graph& g, FlowContext& ctx, long p, long q) {
247 std::set<Edge> A;
248
249 EdgeIterator ei, ei_end;
250 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
251 put(edge_weight, g, *ei, 0);
252 }
253
254 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
255 if (A.count(*ei) > 0) {
256 continue;
257 }
258
259 auto [eps, crit_edges] = detail::solve_subproblem(g, ctx, *ei, q);
260
261 long w = get(edge_weight, g, *ei);
262 if (eps < p - w) {
263 A.insert(crit_edges.begin(), crit_edges.end());
264 } else {
265 eps = p - w;
266 }
267 put(edge_weight, g, *ei, w + eps);
268 }
269
270 long total = 0;
271 for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
272 total += get(edge_weight, g, *ei);
273 }
274
275 return {total, A};
276}
277
291inline std::pair<rational<long>, std::set<Edge>> graph_vulnerability(
292 Graph& g, FlowContext& ctx, rational<long> ubound = rational<long>(1, 1)) {
293 const long m = static_cast<long>(num_edges(g));
294 const long n = static_cast<long>(num_vertices(g));
295
296 std::set<rational<long>> theta_set;
297 for (long q = 1; q <= m; ++q) {
298 for (long p = 1; p <= std::min(n - 1, q); ++p) {
299 rational<long> val(p, q);
300 if (val <= ubound) {
301 theta_set.insert(val);
302 }
303 }
304 }
305 std::vector<rational<long>> Theta(theta_set.begin(), theta_set.end());
306
307 std::set<Edge> crit_set;
308 std::size_t lb = 0, ub = Theta.size();
309
310 while (lb < ub) {
311 std::size_t mid = (ub + lb) / 2;
312 long p = Theta[mid].numerator(), q = Theta[mid].denominator();
313
314 auto [xE, J] = cunningham_min(g, ctx, p, q);
315
316 if (xE >= q * (n - 1)) {
317 ub = mid;
318 crit_set = J;
319 } else {
320 lb = mid + 1;
321 }
322 }
323
324 return {Theta[lb], crit_set};
325}
326
341inline std::map<Edge, rational<long>> spanning_tree_modulus(Graph& g, bool verbose = false,
342 SolverTrace* trace = nullptr) {
343 assert(is_simple_graph(g) &&
344 "spanning_tree_modulus assumes a simple graph (no self-loops, no parallel edges) -- "
345 "see is_simple_graph's docs in graphs.hpp for why a multigraph input isn't just "
346 "unsupported but silently wrong");
347
348 std::map<Edge, rational<long>> eta_star;
349 long remain = static_cast<long>(num_edges(g));
350
351 // memorize vertex names so results from subgraphs (after splitting
352 // into connected components) can be mapped back to g
353 VertexIterator vi, vi_end;
354 for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) {
355 put(vertex_name, g, *vi, *vi);
356 }
357
358 // each entry pairs a component with an upper bound on its own theta
359 // (inherited from the component it was split from, since splitting
360 // can never increase vulnerability) -- restricting graph_vulnerability's
361 // search range this way matters a great deal on inputs that
362 // decompose into many components (see the file-level docs above)
363 std::vector<std::pair<Graph, rational<long>>> process;
364 for (auto& comp : connected_component_graphs(g)) {
365 process.emplace_back(std::move(comp), rational<long>(1, 1));
366 }
367
368 if (verbose) {
369 std::cout << "| " << std::setw(12) << "eta" << " | " << std::setw(12) << "num edge" << " | "
370 << std::setw(12) << "edge_remain" << " | " << std::setw(12) << "comp remain" << " |" << std::endl;
371 }
372
373 while (!process.empty()) {
374 Graph h = std::move(process.back().first);
375 rational<long> parent_theta = process.back().second;
376 process.pop_back();
377
378 if (num_edges(h) == 0) {
379 continue;
380 }
381
383 auto [theta, J] = graph_vulnerability(h, ctx, parent_theta);
384
385 const long m = static_cast<long>(num_edges(h));
386 long p = theta.numerator(), q = theta.denominator();
387 if (static_cast<long>(J.size()) == m) {
388 // the whole edge set came back tight; rerun with a slightly
389 // smaller p/q so the complement isn't empty
390 long pp = p * m * m - q;
391 long qq = q * m * m;
392 J = cunningham_min(h, ctx, pp, qq).second;
393 }
394
395 // complement of the tight set: these edges get eta* = theta
396 std::set<Edge> crit_set;
397 EdgeIterator ei, ei_end;
398 for (boost::tie(ei, ei_end) = edges(h); ei != ei_end; ++ei) {
399 if (J.count(*ei) == 0) {
400 crit_set.insert(*ei);
401 }
402 }
403
404 TraceRound trace_round;
405 if (trace != nullptr) {
406 VertexIterator hvi, hvi_end;
407 for (boost::tie(hvi, hvi_end) = vertices(h); hvi != hvi_end; ++hvi) {
408 trace_round.vertices.push_back(get(vertex_name, h, *hvi));
409 }
410 trace_round.theta = theta;
411 }
412
413 for (const Edge& e : crit_set) {
414 Vertex uu = get(vertex_name, h, source(e, h));
415 Vertex vv = get(vertex_name, h, target(e, h));
416 eta_star[edge(uu, vv, g).first] = theta;
417 if (trace != nullptr) {
418 trace_round.crit_set.emplace_back(uu, vv);
419 }
420 }
421
422 if (trace != nullptr) {
423 trace->rounds.push_back(std::move(trace_round));
424 }
425
426 // split into connected components after removing crit_set (via a
427 // non-mutating filtered view -- removing edges one at a time
428 // from a vecS-based adjacency_list can invalidate other stored
429 // edge descriptors, so h itself is never modified)
430 std::vector<Graph> comps = induced_components(h, crit_set);
431 assert(theta == rational<long>(static_cast<long>(comps.size()) - 1, static_cast<long>(crit_set.size())));
432
433 for (auto& comp : comps) {
434 process.emplace_back(std::move(comp), theta);
435 }
436
437 remain -= static_cast<long>(crit_set.size());
438 if (verbose) {
439 std::cout << "| " << std::setw(12) << theta << " | " << std::setw(12) << crit_set.size() << " | "
440 << std::setw(12) << remain << " | " << std::setw(12) << process.size() << " |" << std::endl;
441 }
442 }
443
444 return eta_star;
445}
446
447} // namespace discrete_modulus
Graph/flow-graph type aliases, subgraph/component helpers, and demo graph generators used by cunningh...
FlowEdge add_capacity_edge(FlowGraph &fg, FlowVertex u, FlowVertex v, long capacity)
Adds a directed capacity edge u -> v to fg, along with its required zero-capacity reverse-residual co...
Definition cunningham.hpp:86
std::pair< long, std::set< Edge > > solve_subproblem(Graph &g, FlowContext &ctx, const Edge &e, long q)
One max-flow step of Cunningham's algorithm: how far can x be increased on edge e without leaving the...
Definition cunningham.hpp:107
Definition cunningham.hpp:50
adjacency_list< vecS, vecS, undirectedS, property< vertex_name_t, Traits::vertex_descriptor >, property< edge_index_t, int, property< edge_weight_t, long > > > Graph
The undirected graph type used throughout this library.
Definition graphs.hpp:43
bool is_simple_graph(const G &g)
Checks whether g is a simple graph: no self-loops, no parallel edges between the same pair of vertice...
Definition graphs.hpp:166
adjacency_list< vecS, vecS, directedS, no_property, property< edge_capacity_t, long, property< edge_residual_capacity_t, long, property< edge_reverse_t, FlowTraits::edge_descriptor > > > > FlowGraph
The directed, capacitated graph type used for the max-flow subproblem in Cunningham's algorithm (see ...
Definition graphs.hpp:69
std::vector< Graph > connected_component_graphs(const G &g)
Splits g into one Graph per connected component.
Definition graphs.hpp:109
std::vector< Graph > induced_components(Graph &g, const std::set< Edge > &A)
Splits g into connected components after removing the critical edge set A.
Definition graphs.hpp:141
std::pair< long, std::set< Edge > > cunningham_min(Graph &g, FlowContext &ctx, long p, long q)
Finds a P(qf)-basis for the constant function p, along with a tight set, using Cunningham's greedy al...
Definition cunningham.hpp:246
std::map< Edge, rational< long > > spanning_tree_modulus(Graph &g, bool verbose=false, SolverTrace *trace=nullptr)
Computes the exact spanning tree modulus of g using Cunningham's algorithm.
Definition cunningham.hpp:341
FlowContext create_flow_graph(Graph &g)
Builds the reusable max-flow network for g.
Definition cunningham.hpp:200
FlowTraits::vertex_descriptor FlowVertex
Definition graphs.hpp:56
graph_traits< Graph >::edge_descriptor Edge
Definition graphs.hpp:46
FlowTraits::edge_descriptor FlowEdge
Definition graphs.hpp:57
graph_traits< Graph >::vertex_iterator VertexIterator
Definition graphs.hpp:47
graph_traits< Graph >::vertex_descriptor Vertex
Definition graphs.hpp:45
graph_traits< Graph >::edge_iterator EdgeIterator
Definition graphs.hpp:48
std::pair< rational< long >, std::set< Edge > > graph_vulnerability(Graph &g, FlowContext &ctx, rational< long > ubound=rational< long >(1, 1))
Finds the vulnerability theta(G) of a graph by binary search, along with an optimal tight set.
Definition cunningham.hpp:291
Opt-in per-round trace recording for spanning_tree_modulus, and a versioned JSON writer for the recor...
The reusable max-flow network for cunningham_min / graph_vulnerability / spanning_tree_modulus,...
Definition cunningham.hpp:64
std::vector< FlowEdge > to_target
indexed by vertex: v -> target
Definition cunningham.hpp:70
FlowVertex src
Definition cunningham.hpp:66
std::vector< FlowEdge > forward
indexed by edge_index: source(e,g) -> target(e,g)
Definition cunningham.hpp:68
FlowGraph graph
Definition cunningham.hpp:65
std::vector< FlowEdge > backward
indexed by edge_index: target(e,g) -> source(e,g)
Definition cunningham.hpp:69
FlowVertex tgt
Definition cunningham.hpp:67
std::vector< FlowEdge > from_source
indexed by vertex: source -> v
Definition cunningham.hpp:71
The full recorded trace of a spanning_tree_modulus run.
Definition solver_trace.hpp:48
One round of spanning_tree_modulus's main loop: the component it was carved from, the edge set dispat...
Definition solver_trace.hpp:41
rational< long > theta
Definition solver_trace.hpp:42
std::vector< std::pair< Vertex, Vertex > > crit_set
the dispatched edges
Definition solver_trace.hpp:44
std::vector< Vertex > vertices
the component's vertex set
Definition solver_trace.hpp:43