Open
Graph Drawing
Framework

 v. 2023.09 (Elderberry)
 

Loading...
Searching...
No Matches
Approximation.h
Go to the documentation of this file.
1
32#pragma once
33
35#include <ogdf/basic/Graph.h>
37#include <ogdf/basic/basic.h>
38#include <ogdf/basic/comparer.h>
45
46#include <iostream>
47#include <memory>
48#include <random>
49#include <utility>
50
51namespace ogdf::steiner_tree {
52template<typename T, typename ExtraDataType>
53class FullComponentWithExtraStore;
54} // namespace ogdf::steiner_tree
55
56//#define OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
57
58namespace ogdf {
59template<class E>
60class List;
61template<typename T>
62class EdgeWeightedGraph;
63
64namespace steiner_tree {
65namespace goemans {
66
68template<typename T>
74
75 const double m_eps;
76
77 std::minstd_rand m_rng;
78
80 struct TemporaryEdges : ArrayBuffer<edge> {
82 TemporaryEdges(BlowupGraph<T>& blowupGraph) : m_blowupGraph(blowupGraph) { }
83
85 edge add(node v, node w, T cost, int capacity) {
86 edge e = m_blowupGraph.newEdge(v, w, cost, capacity);
87 this->push(e);
88 return e;
89 }
90
92 ~TemporaryEdges() { m_blowupGraph.delEdges(*this); }
93
94 private:
96 };
97
99 int gammoidGetRank(const BlowupGraph<T>& blowupGraph) const {
100 MaxFlowGoldbergTarjan<int> maxFlow(blowupGraph.getGraph());
101 return maxFlow.computeValue(blowupGraph.capacities(), blowupGraph.getSource(),
102 blowupGraph.getTarget());
103 }
104
107 int findComponentAndMaxBasis(std::unique_ptr<ArrayBuffer<std::pair<node, int>>>& maxBasis,
108 BlowupGraph<T>& blowupGraph, const BlowupComponents<T>& gamma) {
109 // there should always be saturated flow to the component roots
110 // (contracted matroid)
111 EdgeArray<int> lB(blowupGraph.getGraph(), 0);
112 for (adjEntry adj : blowupGraph.getSource()->adjEntries) {
113 const edge e = adj->theEdge();
114 lB[e] = blowupGraph.getCapacity(e);
115 }
116
117 // compute weights of core edges and add source->core edges
118 TemporaryEdges sourceCoreEdges(blowupGraph);
119 EdgeArray<double> cost(blowupGraph.getGraph(), 0);
120 for (node v : blowupGraph.core()) {
121 // compute weight of core edge
122 double weight = blowupGraph.computeCoreWeight(v);
123
124 // add edges from source to core edges v
125 edge e = sourceCoreEdges.add(blowupGraph.getSource(), v, 0,
126 blowupGraph.getCoreCapacity(v));
127 cost[e] = -weight;
128 }
129
130 NodeArray<int> supply(blowupGraph.getGraph(), 0);
131 EdgeArray<int> flow(blowupGraph.getGraph());
133
134 for (int id = 1; id <= gamma.size(); ++id) {
135 /* We want to find maximum-weight basis B \in B^K_Q
136 * B_Q = minimal edge set to remove after contracting Q to obtain feasible solution (blowup graph)
137 * = bases of gammoid M_Q
138 * B^K_Q = { B \in B_Q | B \subseteq K } [K is the set of core edges]
139 * = bases of gammoid M^K_Q
140 * M^K_Q is gammoid "obtained by restricting M_Q to K"
141 * M_Q = M'_Q / X' (M'_Q contracted by X') is a gammoid
142 * M'_Q = gammoid from X \cup X' to Y in D' with arc-disjointness instead of vertex-disjointness
143 * D' is D like for separation but without source node and with interim node v_e for each edge e
144 * X = inserted nodes v_e in each edge in blowup graph
145 * X' = the (arbitrary) roots of each component
146 * Y = Q \cup {t}
147 * that means:
148 * I (subset of X) is an independent set of M_Q
149 * <=> (if X' is always an independent set in M'_Q ... which we assume here)
150 * I \cup X' is an independent set of M'_Q
151 * Restricting to K means:
152 * only put v_e in X with e \in K (that is, we only need to *generate* v_e if e \in K)
153 * Hence:
154 * - we generate D'^K (this is blowupGraph)
155 * - compute the max flow from X^K \cup X' to Q \cup {t}
156 * - ASSERT that X' is "saturated"!
157 * - check which subset of X is saturated -> these are the nodes representing the edge set we need
158 */
159 // add edges from component's terminals to target
160 TemporaryEdges Q_to_target(blowupGraph);
161 for (node t : gamma.terminals(id)) {
162 Q_to_target.add(t, blowupGraph.getTarget(), 0,
163 blowupGraph.getLCM() * blowupGraph.getY());
164 // the last value is an upper bound for the capacity
165 }
166 // TODO: we could also use static edges from all terminals to the target
167 // and just change their capacities each time; needs extra data structures
168
169 std::unique_ptr<ArrayBuffer<std::pair<node, int>>> basis(
170 new ArrayBuffer<std::pair<node, int>>);
171
172 int rank = gammoidGetRank(blowupGraph);
173 OGDF_ASSERT(rank >= blowupGraph.getY() + blowupGraph.getLCM());
174 supply[blowupGraph.getSource()] = rank;
175 supply[blowupGraph.getTarget()] = -rank;
176
177#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
178 std::cout << "Computing min-cost flow for blowup component " << id << " of "
179 << gamma.size() << std::endl;
180 std::cout << " * terminals of component are " << gamma.terminals(id) << std::endl;
181 for (node v : blowupGraph.getGraph().nodes) {
182 if (supply[v] > 0) {
183 std::cout << " * supply node " << v << " with supply " << supply[v] << std::endl;
184 }
185 if (supply[v] < 0) {
186 std::cout << " * demand node " << v << " with demand " << -supply[v] << std::endl;
187 }
188 }
189 for (edge e : blowupGraph.getGraph().edges) {
190 std::cout << " * edge " << e << " with cost " << blowupGraph.getCost(e)
191 << " and flow bounds [" << lB[e] << ", " << blowupGraph.getCapacity(e)
192 << "]" << std::endl;
193 }
194#endif
195
196 // find maximum weight basis
197#ifdef OGDF_DEBUG
198 bool feasible =
199#endif
200 mcf.call(blowupGraph.getGraph(), lB, blowupGraph.capacities(), cost, supply,
201 flow);
202 OGDF_ASSERT(feasible);
203 OGDF_ASSERT(mcf.checkComputedFlow(blowupGraph.getGraph(), lB, blowupGraph.capacities(),
204 cost, supply, flow));
205
206 double weight(0);
207 for (edge e : sourceCoreEdges) {
208 if (flow[e] > 0) {
209 basis->push(std::make_pair(e->target(), flow[e]));
210 weight -= flow[e] * cost[e];
211 }
212 }
213
214#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
215 std::cout << "Basis weight is " << weight << std::endl
216 << "Checking if " << gamma.cost(id) << "(component cost) * "
217 << blowupGraph.getLCM() << "(lcm) <= " << weight << "(basis weight)"
218 << std::endl;
219#endif
220
221 // we choose the component with max cost*N <= weight
222 if (gamma.cost(id) * blowupGraph.getLCM() <= weight + m_eps) {
223#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
224 std::cout << "Using basis because it is feasible." << std::endl;
225#endif
226 maxBasis.swap(basis);
227 return id;
228 }
229 }
230 return 0; // no component chosen, fail
231 }
232
236 std::unique_ptr<ArrayBuffer<std::pair<node, int>>>& maxBasis,
237 const BlowupGraph<T>& blowupGraph, const BlowupComponents<T>& gamma) {
238 // find cheapest component
239 int compId = 0;
240 double cost = 0;
241 for (int id = 1; id <= gamma.size(); ++id) {
242 if (gamma.cost(id) > cost) {
243 cost = gamma.cost(id);
244 compId = id;
245 }
246 }
247 // use all core edges as basis
248 maxBasis.reset(new ArrayBuffer<std::pair<node, int>>());
249 for (node v : blowupGraph.core()) {
250 maxBasis->push(std::make_pair(v, blowupGraph.getCoreCapacity(v)));
251 }
252 return compId;
253 }
254
256 void addComponent(NodeArray<bool>& isNewTerminal, const BlowupGraph<T>& blowupGraph,
257 const edge rootEdge) {
258 OGDF_ASSERT(blowupGraph.isTerminal(rootEdge->source()));
259 ArrayBuffer<node> stack;
260 stack.push(rootEdge->target());
261 while (!stack.empty()) {
262 const node v = stack.popRet();
263 if (blowupGraph.isTerminal(v)) {
264 continue;
265 }
266 const node vO = blowupGraph.getOriginal(v);
267 if (vO) {
268 isNewTerminal[vO] = true;
269 }
270 for (adjEntry adj : v->adjEntries) {
271 const node w = adj->theEdge()->target();
272 if (v != w) { // outgoing edge
273 stack.push(w);
274 }
275 }
276 }
277 }
278
280 void removeFractionalBasisAndCleanup(ArrayBuffer<std::pair<node, int>>& basis,
281 BlowupGraph<T>& blowupGraph, BlowupComponents<T>& gamma) {
282#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
283 std::cout << "Remove basis from blowup graph" << std::endl;
284#endif
285 // remove B from K (K := K \ B) and from blowup graph (X := X - B)
286 // and, while at it, remove cleanup edges from blowup graph (X := X - F)
287 // and fix components that have no incoming edges
288 ArrayBuffer<Prioritized<node, int>> fractionalCoreEdges; // we defer fractional basis elements
289 for (auto p : basis) {
290 const node v = p.first;
291 const int count = p.second;
292 int origCap = blowupGraph.getCoreCapacity(v);
293 OGDF_ASSERT(count <= origCap);
294#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
295 std::cout << " * node " << v << " with count " << count << " (of " << origCap << ")"
296 << std::endl;
297#endif
298 if (count < origCap) { // only remove a fraction?
299 fractionalCoreEdges.push(Prioritized<node, int>(v, -count));
300#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
301 std::cout << " -> deferred because fractional" << std::endl;
302#endif
303 } else {
304 // we are deleting the core edge from the whole component
305 blowupGraph.delCore(v);
306 blowupGraph.removeBasis(v);
307#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
308 std::cout << " -> done" << std::endl;
309#endif
310 }
311 }
312 fractionalCoreEdges.quicksort(); // sort decreasing by flow value
313#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
314 if (!fractionalCoreEdges.empty()) {
315 std::cout << "Deferred core edges:" << std::endl;
316 }
317#endif
318 for (auto p : fractionalCoreEdges) {
319 const node v = p.item();
320 const int count = -p.priority();
321 int origCap = blowupGraph.getCoreCapacity(v);
322#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
323 std::cout << " * node " << v << " with count " << count << " of " << origCap << std::endl;
324#endif
325 OGDF_ASSERT(count <= origCap);
326 // copy (split) the component
327 blowupGraph.copyComponent(blowupGraph.findRootEdge(v), count, origCap - count);
328 // we are deleting the core edge from the whole component
329 blowupGraph.delCore(v);
330 blowupGraph.removeBasis(v);
331 }
332 }
333
334public:
337 const NodeArray<bool>& isTerminal,
338 const FullComponentWithExtraStore<T, double>& fullCompStore,
339 const std::minstd_rand& rng, double eps = 1e-8)
340 : m_G(G)
341 , m_isTerminal(isTerminal)
342 , m_terminals(terminals)
343 , m_fullCompStore(fullCompStore)
344 , m_eps(eps)
345 , m_rng(rng) { }
346
349 void solve(NodeArray<bool>& isNewTerminal);
350};
351
352template<typename T>
354#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
355 std::cout << "Start solving based on LP solution" << std::endl;
356 int iteration = 0;
357#endif
359 BlowupGraph<T> blowupGraph(m_G, m_terminals, m_fullCompStore, cer, m_eps);
360
361 while (blowupGraph.terminals().size() > 1) { // T is not a Steiner tree
362#ifdef OGDF_STEINER_TREE_GOEMANS_APPROXIMATION_LOGGING
363 std::cout << "Iteration " << ++iteration << " with " << blowupGraph.terminals().size()
364 << " terminals" << std::endl;
365#endif
366 BlowupComponents<T> gamma(blowupGraph); // Gamma(X)
367
368 OGDF_ASSERT(isLoopFree(blowupGraph.getGraph()));
369
370 // take a component Q in Gamma(X)
371 std::unique_ptr<ArrayBuffer<std::pair<node, int>>> maxBasis;
372 int compId = blowupGraph.getY() > 0
373 ? findComponentAndMaxBasis(maxBasis, blowupGraph, gamma)
374 : findCheapestComponentAndRemainingBasis(maxBasis, blowupGraph, gamma);
375 OGDF_ASSERT(compId != 0);
376
377 // add component Q to T
378 addComponent(isNewTerminal, blowupGraph, gamma.rootEdge(compId));
379
380 // remove (maybe fractional) basis and do all the small things necessary for update
381 removeFractionalBasisAndCleanup(*maxBasis, blowupGraph, gamma);
382
383 // contract (X := X / Q)
384 blowupGraph.contract(gamma.terminals(compId));
385
386 if (blowupGraph.terminals().size() > 1) {
387 blowupGraph.updateSpecialCapacities();
388 }
389 }
390}
391
392}
393}
394}
Declaration and implementation of ArrayBuffer class.
Definition of the ogdf::steiner_tree::goemans::BlowupComponents class template.
Definition of ogdf::steiner_tree::goemans::BlowupGraph class template.
Definition of ogdf::steiner_tree::goemans::CoreEdgeRandomSpanningTree class template.
Includes declaration of graph class.
Decralation of GraphElement and GraphList classes.
Declaration and implementation of Goldberg-Tarjan max-flow algorithm with global relabeling and gap r...
Definition of ogdf::MinCostFlowReinelt class template.
Basic declarations, included by all source files.
Class for adjacency list elements.
Definition Graph_d.h:143
An array that keeps track of the number of inserted elements; also usable as an efficient stack.
Definition ArrayBuffer.h:64
E popRet()
Removes the newest element from the buffer and returns it.
void push(edge e)
Puts a new element in the buffer.
void quicksort()
Sorts buffer using Quicksort.
bool empty() const
Returns true if the buffer is empty, false otherwise.
int capacity() const
Returns the current capacity of the datastructure. Note that this value is rather irrelevant if the a...
Class for the representation of edges.
Definition Graph_d.h:364
node target() const
Returns the target node of the edge.
Definition Graph_d.h:402
node source() const
Returns the source node of the edge.
Definition Graph_d.h:399
internal::GraphObjectContainer< NodeElement > nodes
The container containing all node objects.
Definition Graph_d.h:929
internal::GraphObjectContainer< EdgeElement > edges
The container containing all edge objects.
Definition Graph_d.h:932
Doubly linked lists (maintaining the length of the list).
Definition List.h:1451
Computes a max flow via Preflow-Push (global relabeling and gap relabeling heuristic).
TCap computeValue(const EdgeArray< TCap > &cap, const node &s, const node &t)
Compute only the value of the flow.
static bool checkComputedFlow(const Graph &G, const EdgeArray< int > &lowerBound, const EdgeArray< int > &upperBound, const EdgeArray< TCost > &cost, const NodeArray< int > &supply, const EdgeArray< int > &flow, TCost &value)
checks if a computed flow is a feasible solution to the given problem instance.
Computes a min-cost flow using a network simplex method.
virtual bool call(const Graph &G, const EdgeArray< int > &lowerBound, const EdgeArray< int > &upperBound, const EdgeArray< TCost > &cost, const NodeArray< int > &supply, EdgeArray< int > &flow, NodeArray< TCost > &dual) override
Computes a min-cost flow in the directed graph G using a network simplex method.
Class for the representation of nodes.
Definition Graph_d.h:241
internal::GraphObjectContainer< AdjElement > adjEntries
The container containing all entries in the adjacency list of this node.
Definition Graph_d.h:272
Augments any data elements of type X with keys of type Priority. This class is also its own Comparer.
Definition comparer.h:295
RegisteredArray for edges of a graph, specialized for EdgeArray<edge>.
Definition Graph_d.h:717
RegisteredArray for nodes, edges and adjEntries of a graph.
Definition Graph_d.h:659
A data structure to store full components with extra data for each component.
The actual 1.39-approximation algorithm by Goemans et al. with a set of terminalized nodes as result.
int findCheapestComponentAndRemainingBasis(std::unique_ptr< ArrayBuffer< std::pair< node, int > > > &maxBasis, const BlowupGraph< T > &blowupGraph, const BlowupComponents< T > &gamma)
For the end of the algorithm: find cheapest component and choose all remaining core edges as basis.
void removeFractionalBasisAndCleanup(ArrayBuffer< std::pair< node, int > > &basis, BlowupGraph< T > &blowupGraph, BlowupComponents< T > &gamma)
Remove a given basis and cleanup, the basis may be given fractionally.
const EdgeWeightedGraph< T > & m_G
const FullComponentWithExtraStore< T, double > & m_fullCompStore
all enumerated full components, with solution
int gammoidGetRank(const BlowupGraph< T > &blowupGraph) const
Computes the rank of the gammoid (given by the blowup graph)
Approximation(const EdgeWeightedGraph< T > &G, const List< node > &terminals, const NodeArray< bool > &isTerminal, const FullComponentWithExtraStore< T, double > &fullCompStore, const std::minstd_rand &rng, double eps=1e-8)
Initialize everything.
void addComponent(NodeArray< bool > &isNewTerminal, const BlowupGraph< T > &blowupGraph, const edge rootEdge)
Add a component of the blowup graph to the final solution (by changing nonterminals to terminals)
const double m_eps
epsilon for double operations
int findComponentAndMaxBasis(std::unique_ptr< ArrayBuffer< std::pair< node, int > > > &maxBasis, BlowupGraph< T > &blowupGraph, const BlowupComponents< T > &gamma)
Finds the best component and its maximum-weight basis in the given blowup graph with given core and w...
void solve(NodeArray< bool > &isNewTerminal)
Perform the actual approximation algorithm on the LP solution.
Obtain and provides information about components in a given blowup graph.
A special-purpose blowup graph for gammoid computation: directed, with special source and target,...
Definition BlowupGraph.h:70
T getCost(edge e) const
Returns the cost of e.
void removeBasis(node v)
Removes a basis and cleans up.
const EdgeArray< int > & capacities() const
Returns a reference to the edge array containing all capacities.
void contract(node &v, node t)
Contracts node v and terminal t into v.
int getY() const
Returns the y-value of all terminals.
int getLCM() const
Returns the least common multiple of all denominators.
node getSource() const
Returns the source node.
void delCore(node e)
Remove a core edge.
node getTarget() const
Returns the target node.
void copyComponent(const edge origEdge, const int origCap, const int copyCap)
Copy a component in the blowup graph and set original capacity to origCap and capacity of copy to cop...
double computeCoreWeight(node v) const
Compute the weight of a core edge.
int getCapacity(edge e) const
Returns the capacity of e.
edge findRootEdge(node v)
Finds the root node of a component given by v, an arbitrary inner nonterminal of the component.
const List< node > & terminals() const
Returns a reference to the list containing all terminals in the blowup graph.
void updateSpecialCapacities()
Updates capacities from source to terminals and terminals to pseudotarget.
node getOriginal(node v) const
Returns the original node of v.
bool isTerminal(node v) const
Returns true if and only if v is a terminal.
T getCoreCapacity(node v) const
Get capacity of a core edge.
const List< node > & core() const
Return list of core edges (implemented by nodes)
Declarations for Comparer objects.
bool isLoopFree(const Graph &G)
Returns true iff G contains no self-loop.
#define OGDF_ASSERT(expr)
Assert condition expr. See doc/build.md for more information.
Definition basic.h:52
The namespace for all OGDF objects.
Declaration of simple graph algorithms.
Add edges into a blowup graph and delete them on destruction.
edge add(node v, node w, T cost, int capacity)
Add a temporary edge to the blowup graph.
TemporaryEdges(BlowupGraph< T > &blowupGraph)
Construct object for a specific blowupGraph.