Open
Graph Drawing
Framework

 v. 2023.09 (Elderberry)
 

Loading...
Searching...
No Matches
LPRelaxationSER.h
Go to the documentation of this file.
1
32#pragma once
33
34#include <ogdf/basic/Array.h>
37#include <ogdf/basic/Graph.h>
40#include <ogdf/basic/List.h>
42#include <ogdf/basic/basic.h>
45
46#include <ogdf/external/coin.h>
47
48#include <cstdio>
49#include <iostream>
50#include <limits>
51
52namespace ogdf::steiner_tree {
53template<typename T, typename ExtraDataType>
54class FullComponentWithExtraStore;
55} // namespace ogdf::steiner_tree
56
57namespace ogdf {
58template<typename T>
59class EdgeWeightedGraph;
60} // namespace ogdf
61
62//#define OGDF_STEINERTREE_LPRELAXATIONSER_LOGGING
63//#define OGDF_STEINERTREE_LPRELAXATIONSER_OUTPUT_LP
64#define OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_CONNECTED_COMPONENTS // this is faster
65#define OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_YVAR_CONSTRAINTS // if not defined: generate all yvar constraints in the beginning
66
67namespace ogdf {
68namespace steiner_tree {
69
72template<typename T>
78
79 OsiSolverInterface* m_osiSolver;
80 CoinPackedMatrix* m_matrix;
81 double* m_objective;
84
85 const T m_upperBound;
87#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_CONNECTED_COMPONENTS
89#endif
90
91 const double m_eps;
92
94 void generateProblem();
100 void addYConstraint(const node t);
101
104 bool separateConnected(const ArrayBuffer<int>& activeComponents);
105
108 bool separateMinCut(const ArrayBuffer<int>& activeComponents);
109
112 bool separateCycles(const ArrayBuffer<int>& activeComponents);
113
116 bool separate();
117
120 double generateMinCutSeparationGraph(const ArrayBuffer<int>& activeComponents, node& source,
121 node& target, GraphCopy& G, EdgeArray<double>& capacity, int& cutsFound) {
122 G.setOriginalGraph(m_G);
123 capacity.init(G);
124 source = G.newNode();
125 for (node t : m_terminals) { // generate all terminals
126 G.newNode(t);
127 }
128 target = G.newNode();
129 for (int j = 0; j < activeComponents.size(); ++j) {
130 const int i = activeComponents[j];
131 const double cap = m_fullCompStore.extra(i);
132 const Array<node>& terminals = m_fullCompStore.terminals(i);
133 // take the first terminal as root
134 // XXX: we may generate parallel edges but it's ok
135 const auto it0 = terminals.begin();
136 const node rC = G.copy(*it0);
137 capacity[G.newEdge(source, rC)] = cap;
138 if (terminals.size() > 2) {
139 const node v = G.newNode();
140 capacity[G.newEdge(rC, v)] = cap;
141 for (auto it = it0 + 1; it != terminals.end(); ++it) {
142 const node w = G.copy(*it);
143 capacity[G.newEdge(v, w)] = cap;
144 }
145 } else { // exactly two terminals: we do not need the inner Steiner node
146 capacity[G.newEdge(rC, G.copy(*terminals.rbegin()))] = cap;
147 }
148 }
149 double y_R = 0;
150 // TODO: perhaps better to compute y_v before
151 // add edges to target and compute y_R
152 for (node t : m_terminals) {
153 const node v = G.copy(t);
154 OGDF_ASSERT(v);
155 // compute y_v, simply the sum of all x_C where C contains v and then - 1
156 double y_v(-1);
157 for (adjEntry adj : v->adjEntries) {
158 if (adj->twinNode() != source) {
159 y_v += capacity[adj->theEdge()];
160 }
161 }
162
163#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_YVAR_CONSTRAINTS
164 if (y_v < -m_eps) {
166 ++cutsFound;
167 } else
168#endif
169 if (y_v > 0) {
170 capacity[G.newEdge(v, target)] = y_v;
171 y_R += y_v;
172 }
173 }
174#if 0
175 // just for output of blow-up graph
176 for (edge e : G.edges) {
177 if (G.original(e->source())) {
178 std::cout << " T:" << G.original(e->source());
179 } else {
180 std::cout << " " << e->source();
181 }
182 std::cout << " -> ";
183 if (G.original(e->target())) {
184 std::cout << "T:" << G.original(e->target());
185 } else {
186 std::cout << e->target();
187 }
188 std::cout << " " << capacity[e] << "\n";
189 }
190#endif
191
192 return y_R;
193 }
194
195public:
207 const NodeArray<bool>& isTerminal, FullComponentWithExtraStore<T, double>& fullCompStore,
208 T upperBound = 0, int cliqueSize = 0, double eps = 1e-8)
209 : m_G(G)
210 , m_isTerminal(isTerminal)
211 , m_terminals(terminals)
212 , m_fullCompStore(fullCompStore)
213 , m_osiSolver(CoinManager::createCorrectOsiSolverInterface())
214 , m_matrix(new CoinPackedMatrix)
215 , m_objective(new double[m_fullCompStore.size()])
216 , m_lowerBounds(new double[m_fullCompStore.size()])
217 , m_upperBounds(new double[m_fullCompStore.size()])
218 , m_upperBound(upperBound)
219 , m_separateCliqueSize(cliqueSize)
222#endif
223 , m_eps(eps) {
226
227#ifndef OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_YVAR_CONSTRAINTS
228 for (node t : m_terminals) {
230 }
231#endif
232 }
233
235 delete[] m_objective;
236 delete m_matrix;
237 delete[] m_lowerBounds;
238 delete[] m_upperBounds;
239 delete m_osiSolver;
240 }
241
244 bool solve();
245};
246
247template<typename T>
249 const int n = m_fullCompStore.size();
250
251 m_matrix->setDimensions(0, n);
252 for (int i = 0; i < n; ++i) {
253 m_lowerBounds[i] = 0;
254 m_upperBounds[i] = 1;
255 }
256 for (int i = 0; i < n; ++i) {
257 m_objective[i] = m_fullCompStore.cost(i);
258 }
259 m_osiSolver->loadProblem(*m_matrix, m_lowerBounds, m_upperBounds, m_objective, nullptr, nullptr);
260
261 if (m_upperBound > 0) { // add upper bound
262 CoinPackedVector row;
263 row.setFull(m_fullCompStore.size(), m_objective);
264 m_osiSolver->addRow(row, 0, m_upperBound);
265 }
266}
267
268template<typename T>
270 CoinPackedVector row;
271
272 for (int i = 0; i < m_fullCompStore.size(); ++i) {
273 if (m_fullCompStore.isTerminal(i, t)) { // component spans terminal
274 row.insert(i, 1);
275 }
276 }
277
278 m_osiSolver->addRow(row, 1, m_osiSolver->getInfinity());
279}
280
281// add constraint if necessary and return if necessary
282template<typename T>
284 CoinPackedVector row;
285 double test = 0;
286
287 for (int i = 0; i < m_fullCompStore.size(); ++i) {
288 // compute the intersection cardinality (linear time because terminal sets are sorted by index)
289 int intersectionCard = 0;
290 auto terminals = m_fullCompStore.terminals(i);
291 node* it1 = terminals.begin();
292 auto it2 = subset.begin();
293 while (it1 != terminals.end() && it2 != subset.end()) {
294 if ((*it1)->index() < (*it2)->index()) {
295 ++it1;
296 } else if ((*it1)->index() > (*it2)->index()) {
297 ++it2;
298 } else { // ==
299 ++intersectionCard;
300 ++it1;
301 ++it2;
302 }
303 }
304 // and use it as a coefficient
305 if (intersectionCard > 1) {
306 row.insert(i, intersectionCard - 1);
307 test += (intersectionCard - 1) * m_fullCompStore.extra(i);
308 }
309 }
310 if (test > subset.size() - 1) {
311 m_osiSolver->addRow(row, 0, subset.size() - 1);
312 return true;
313 }
314 return false;
315}
316
317template<typename T>
319 // we use the sum over all components C of (|C| - 1) * x_C = |R| - 1 constraint
320 // from the paper
321 CoinPackedVector row;
322
323 for (int i = 0; i < m_fullCompStore.size(); ++i) {
324 row.insert(i, m_fullCompStore.terminals(i).size() - 1);
325 }
326
327 int value = m_terminals.size() - 1;
328 m_osiSolver->addRow(row, value, value);
329}
330
331template<typename T>
333 bool initialIteration = true;
334
335 do {
336 if (initialIteration) {
337 m_osiSolver->initialSolve();
338#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_LOGGING
339 std::cout << "Objective value " << m_osiSolver->getObjValue() << " of initial solution."
340 << std::endl;
341#endif
342 initialIteration = false;
343 } else {
344 m_osiSolver->resolve();
345#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_LOGGING
346 std::cout << "Objective value " << m_osiSolver->getObjValue() << " after resolve."
347 << std::endl;
348#endif
349 }
350
351 if (!m_osiSolver->isProvenOptimal()) {
352 if (m_upperBound > 0) { // failed due to better upper bound
353 return false;
354 } else { // failed due to infeasibility
355 std::cerr << "Failed to optimize LP!" << std::endl;
356 throw(-1);
357 }
358 }
359 } while (separate());
360
361 const double* constSol = m_osiSolver->getColSolution();
362 const int numberOfColumns = m_osiSolver->getNumCols();
363
364 for (int i = 0; i < numberOfColumns; ++i) {
365 m_fullCompStore.extra(i) = constSol[i];
366 }
367
368#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_OUTPUT_LP
369 m_osiSolver->writeLp(stderr);
370#endif
371
372 return true;
373}
374
375template<typename T>
377 const double* constSol = m_osiSolver->getColSolution();
378
379 ArrayBuffer<int> activeComponents;
380 for (int i = 0; i < m_fullCompStore.size(); ++i) {
381 m_fullCompStore.extra(i) = constSol[i];
382 if (m_fullCompStore.extra(i) > m_eps) {
383 activeComponents.push(i);
384 }
385 }
386
387#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_CONNECTED_COMPONENTS
388 if (!m_separationStage) {
389 if (separateConnected(activeComponents)) {
390 return true;
391 }
392 m_separationStage = 1;
393 }
394#endif
395 if (separateMinCut(activeComponents)) {
396 return true;
397 }
398 return m_separateCliqueSize > 2 ? separateCycles(activeComponents) : false;
399}
400
401template<typename T>
403 NodeArray<int> setID(m_G, -1); // XXX: NodeArray over terminals only would be better
404 DisjointSets<> uf(m_terminals.size());
405 for (node t : m_terminals) {
406 setID[t] = uf.makeSet();
407 }
408
409 // union all nodes within one component
410 for (int j = 0; j < activeComponents.size(); ++j) {
411 auto terminals = m_fullCompStore.terminals(activeComponents[j]);
412 auto it = terminals.begin();
413 const int s1 = setID[*it];
414 for (++it; it != terminals.end(); ++it) {
415 uf.link(uf.find(s1), uf.find(setID[*it]));
416 }
417 }
418
419 if (uf.getNumberOfSets() == 1) { // solution is connected
420 return false;
421 }
422
423 Array<ArrayBuffer<node>> components(m_terminals.size());
424 ArrayBuffer<int> usedComp;
425 for (node t : m_terminals) {
426 const int k = uf.find(setID[t]);
427 if (components[k].empty()) {
428 usedComp.push(k);
429 }
430 components[k].push(t);
431 }
432 int cutsFound = 0;
433 for (const int k : usedComp) {
434 cutsFound += addSubsetCoverConstraint(components[k]);
435 }
436 return true;
437}
438
439template<typename T>
441 int cutsFound = 0;
442 node source;
443 node pseudotarget;
444 GraphCopy auxG;
445 EdgeArray<double> capacity;
446 double y_R = generateMinCutSeparationGraph(activeComponents, source, pseudotarget, auxG,
447 capacity, cutsFound);
448
449#ifdef OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_YVAR_CONSTRAINTS
450 if (cutsFound > 0) {
451 return true;
452 }
453#endif
454
455 node target = auxG.newNode();
456 capacity[auxG.newEdge(pseudotarget, target)] = y_R;
457
461 for (node t : m_terminals) {
462 const node v = auxG.copy(t);
463
464 edge v_to_target = auxG.newEdge(v, target);
465 capacity[v_to_target] = std::numeric_limits<double>::max(); // XXX: smaller is better
466
467 maxFlow.init(auxG, &flow);
468
469 const double cutVal = maxFlow.computeValue(capacity, source, target);
470 if (cutVal - y_R < 1 - m_eps) {
471 minSTCut.call(auxG, capacity, flow, source, target);
472 ArrayBuffer<node> subset;
473 for (node tOrig : m_terminals) {
474 const node tCopy = auxG.copy(tOrig);
475 if (tCopy && minSTCut.isInBackCut(tCopy)) {
476 subset.push(tOrig);
477 }
478 }
479
480 cutsFound += addSubsetCoverConstraint(subset);
481 }
482
483 auxG.delEdge(v_to_target);
484 }
485 return cutsFound != 0;
486}
487
488template<typename T>
490 int count = 0;
491
492 // generate auxiliary graph
493 Graph G;
494 NodeArray<int> id(G);
495 for (int i : activeComponents) {
496 id[G.newNode()] = i;
497 }
498 for (node u1 : G.nodes) {
499 const int i1 = id[u1];
500 const Array<node>& terminals1 = m_fullCompStore.terminals(i1);
501 for (node u2 = u1->succ(); u2; u2 = u2->succ()) {
502 const int i2 = id[u2];
503 const Array<node>& terminals2 = m_fullCompStore.terminals(i2);
504 // compute intersection cardinality (linear time because terminal sets are sorted by index)
505 int intersectionCard = 0;
506 const node* it1 = terminals1.begin();
507 const node* it2 = terminals2.begin();
508 while (it1 != terminals1.end() && it2 != terminals2.end()) {
509 if ((*it1)->index() < (*it2)->index()) {
510 ++it1;
511 } else if ((*it1)->index() > (*it2)->index()) {
512 ++it2;
513 } else { // ==
514 ++intersectionCard;
515 if (intersectionCard == 2) {
516 G.newEdge(u1, u2);
517 break;
518 }
519 ++it1;
520 ++it2;
521 }
522 }
523 }
524 }
525
526 if (G.numberOfEdges() == 0) {
527 return false;
528 }
529
530 // now find cliques
531 Array<List<node>> degrees(m_separateCliqueSize);
532 for (node v : G.nodes) {
533 int idx = v->degree();
534 if (idx == 0) { // ignore isolated nodes
535 continue;
536 }
537 if (idx >= m_separateCliqueSize) {
538 idx = m_separateCliqueSize - 1;
539 }
540 --idx;
541 degrees[idx].pushBack(v);
542 }
543 NodeArray<bool> test(G, false);
544 for (int k = degrees.size(); k >= 2; --k) {
545 degrees[k - 2].conc(degrees[k - 1]);
546 if (degrees[k - 2].size() >= k) {
547 SubsetEnumerator<node> nodeSubset(degrees[k - 2]);
548 for (nodeSubset.begin(k); nodeSubset.valid(); nodeSubset.next()) {
549 int countEdges = (k * (k - 1)) / 2;
550 for (int j = 0; j < nodeSubset.size(); ++j) {
551 test[nodeSubset[j]] = true;
552 }
553 for (edge e : G.edges) {
554 if (test[e->source()] && test[e->target()]) {
555 countEdges -= 1;
556 }
557 }
558 OGDF_ASSERT(countEdges >= 0);
559 if (countEdges == 0) {
560 // found clique, add constraint
561 double val(0);
562 CoinPackedVector row;
563
564 for (int j = 0; j < nodeSubset.size(); ++j) {
565 int i = id[nodeSubset[j]];
566 val += m_fullCompStore.extra(i);
567 row.insert(i, 1);
568 }
569 if (val >= 1 + m_eps) {
570 m_osiSolver->addRow(row, 0, 1);
571 ++count;
572 }
573 }
574 for (int j = 0; j < nodeSubset.size(); ++j) {
575 test[nodeSubset[j]] = false;
576 }
577 }
578 }
579 }
580 return count > 0;
581}
582
583}
584}
Declaration and implementation of Array class and Array algorithms.
Declaration and implementation of ArrayBuffer class.
Implementation of disjoint sets data structures (union-find functionality).
Includes declaration of graph class.
Declaration of graph copy classes.
Decralation of GraphElement and GraphList classes.
#define OGDF_STEINERTREE_LPRELAXATIONSER_SEPARATE_CONNECTED_COMPONENTS
Declaration of doubly linked lists and iterators.
Declaration and implementation of Goldberg-Tarjan max-flow algorithm with global relabeling and gap r...
Declaration of min-st-cut algorithms parameterized by max-flow alorithm.
A class that allows to enumerate k-subsets.
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
iterator end()
Returns an iterator to one past the last element.
void push(E e)
Puts a new element in the buffer.
iterator begin()
Returns an iterator to the first element.
INDEX size() const
Returns number of elements in the buffer.
The parameterized class Array implements dynamic arrays of type E.
Definition Array.h:219
iterator begin()
Returns an iterator to the first element.
Definition Array.h:329
reverse_iterator rbegin()
Returns an reverse iterator to the last element.
Definition Array.h:347
iterator end()
Returns an iterator to one past the last element.
Definition Array.h:338
INDEX size() const
Returns the size (number of elements) of the array.
Definition Array.h:302
If you use COIN-OR, you should use this class.
Definition coin.h:46
A Union/Find data structure for maintaining disjoint sets.
int makeSet()
Initializes a singleton set.
int getNumberOfSets()
Returns the current number of disjoint sets.
int find(disjoint_sets::CompressionOption< CompressionOptions::PathCompression >, int set)
int link(disjoint_sets::LinkOption< LinkOptions::Naive >, int set1, int set2)
Class for the representation of edges.
Definition Graph_d.h:364
node newNode(node vOrig)
Creates a new node in the graph copy with original node vOrig.
Definition GraphCopy.h:191
Copies of graphs supporting edge splitting.
Definition GraphCopy.h:390
edge copy(edge e) const override
Returns the first edge in the list of edges corresponding to edge e.
Definition GraphCopy.h:463
void delEdge(edge e) override
Removes edge e and clears the list of edges corresponding to e's original edge.
edge newEdge(edge eOrig)
Creates a new edge (v,w) with original edge eOrig.
Data type for general directed graphs (adjacency list representation).
Definition Graph_d.h:866
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.
virtual void init(const Graph &graph, EdgeArray< T > *flow=nullptr)
Initialize the problem with a graph and optional flow array. If no flow array is given,...
Min-st-cut algorithm, that calculates the cut via maxflow.
bool isInBackCut(const node v) const
Returns whether this node is part of the back cut.
virtual bool call(const Graph &graph, const EdgeArray< TCost > &weight, node s, node t, List< edge > &edgeList, edge e_st=nullptr) override
The actual algorithm call.
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
node succ() const
Returns the successor in the list of all nodes.
Definition Graph_d.h:293
void init(const Base *base=nullptr)
Reinitializes the array. Associates the array with the matching registry of base.
Enumerator for k-subsets of a given type.
void begin(int low, int high)
Initializes the SubsetEnumerator to enumerate subsets of cardinalities from low to high.
int size() const
Returns the cardinality of the subset.
bool valid() const
Checks if the current subset is valid. If not, the subset is either not initialized or all subsets ha...
void next()
Obtains the next subset if possible. The result should be checked using the valid() method.
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
const Array< node > & terminals(int id) const
Returns the list of terminals in the full component with given id.
A data structure to store full components with extra data for each component.
ExtraDataType & extra(int i)
Returns a reference to the extra data of this full component.
Class managing the component-based subtour elimination LP relaxation for the Steiner tree problem and...
bool separate()
Perform all available separation algorithms.
bool separateConnected(const ArrayBuffer< int > &activeComponents)
Separate to ensure that the solution is connected.
LPRelaxationSER(const EdgeWeightedGraph< T > &G, const List< node > &terminals, const NodeArray< bool > &isTerminal, FullComponentWithExtraStore< T, double > &fullCompStore, T upperBound=0, int cliqueSize=0, double eps=1e-8)
Initialize the LP.
bool solve()
Solve the LP. The solution will be written to the extra data of the full component store.
double generateMinCutSeparationGraph(const ArrayBuffer< int > &activeComponents, node &source, node &target, GraphCopy &G, EdgeArray< double > &capacity, int &cutsFound)
Generates an auxiliary multi-graph for separation (during LP solving): directed, with special source ...
bool separateCycles(const ArrayBuffer< int > &activeComponents)
Perform the separation algorithm for cycle constraints (to obtain stronger LP solution)
const double m_eps
epsilon for double operations
void generateProblem()
Generate the basic LP model.
bool addSubsetCoverConstraint(const ArrayBuffer< node > &subset)
Add subset cover constraints to the LP for a given subset of terminals.
const EdgeWeightedGraph< T > & m_G
const NodeArray< bool > & m_isTerminal
bool separateMinCut(const ArrayBuffer< int > &activeComponents)
Perform the general cut-based separation algorithm.
void addYConstraint(const node t)
Add constraint that the sum of x_C over all components C spanning terminal t is at least 1 to ensure ...
const List< node > & m_terminals
List of terminals.
FullComponentWithExtraStore< T, double > & m_fullCompStore
all enumerated full components, with solution
void addTerminalCoverConstraint()
Add terminal cover constraints to the LP.
Definition of ogdf::CoinManager.
#define OGDF_ASSERT(expr)
Assert condition expr. See doc/build.md for more information.
Definition basic.h:52
The namespace for all OGDF objects.