Open
Graph Drawing
Framework

 v. 2023.09 (Elderberry)
 

Loading...
Searching...
No Matches
SpannerBerman.h
Go to the documentation of this file.
1
32#pragma once
33
36#include <ogdf/external/coin.h>
38
39#include <iomanip>
40
41namespace ogdf {
42
75template<typename TWeight>
76class SpannerBerman : public SpannerModule<TWeight> {
77public:
78 static Logger logger;
79
81 m_OPT = 0;
82 m_osi = nullptr;
83 }
84
85 virtual ~SpannerBerman() { resetLP(); }
86
88 virtual bool preconditionsOk(const GraphAttributes& GA, double stretch,
89 std::string& error) override {
90 if (!isSimple(GA.constGraph())) {
91 error = "The graph is not simple";
92 return false;
93 }
94 if (!isConnected(GA.constGraph())) {
95 error = "The graph is not connected";
96 return false;
97 }
98 if (stretch < 1.0) {
99 error = "The stretch must be >= 1.0";
100 return false;
101 }
102 return true;
103 }
104
105private:
110
111 const Graph* m_G;
117
118 // Some precalculated values that are needed all over the place.
120 double m_beta;
121 double m_sqrtlog;
123
126
129
130 // Solving the LP
132 std::vector<CoinPackedVector*> m_constraints;
133 int m_OPT;
134
136
137 inline bool isThinEdge(edge e) { return !m_isThickEdge(e); }
138
139 inline bool isThickEdge(edge e) { return m_isThickEdge(e); }
140
146 void resetLP() {
147 for (auto c : m_constraints) {
148 delete c;
149 }
150 m_constraints.clear();
151
152 delete m_osi;
153 m_osi = nullptr;
154 }
155
157 virtual void init(const GraphAttributes& GA, double stretch, GraphCopySimple& spanner,
158 EdgeArray<bool>& inSpanner) override {
159 resetLP();
161
162 m_G = &GA.constGraph();
163 m_weight.init(*m_G);
164 for (edge e : m_G->edges) {
165 m_weight[e] = getWeight(*m_GA, e);
166 }
168 m_isThickEdge.init(*m_G, false);
169
174
175 m_E1.init(*m_G, false);
176 m_E2.init(*m_G, false);
177
178 m_inDistance.init(*m_G);
179 m_outDistance.init(*m_G);
180 }
181
183 virtual typename SpannerModule<TWeight>::ReturnType execute() override {
184 if (m_G->numberOfNodes() == 0 || m_G->numberOfEdges() == 0) {
186 }
187 firstPart();
188 printStats();
189
190 logger.lout() << "\n2. Part\n" << std::endl;
191 bool ok = secondPart();
192
193 if (!ok) {
195 }
196
197 printStats(true);
198
199 // Fill m_inSpanner since only E1, E2 and m_spanner are correctly filled.
200 for (edge e : m_G->edges) {
201 (*m_inSpanner)[e] = m_E1[e] || m_E2[e];
202 }
204 }
205
209 void firstPart() {
212 for (node n : m_G->nodes) {
216 }
217
218 int max = ceil(m_beta * log(m_G->numberOfNodes())); // log is ln
219 logger.lout() << "max=" << max << std::endl;
220 for (int i = 0; i < max; i++) {
221 node v = m_G->chooseNode();
222 for (node n : m_G->nodes) {
223 edge e1 = inPredecessor[v][n];
224 if (e1) {
226 }
227
228 edge e2 = outPredecessor[v][n];
229 if (e2) {
231 }
232 }
233 }
235 logger.lout() << "thickEdgeNodeAmountLimit=" << m_thickEdgeNodeAmountLimit << std::endl;
236 printStats();
237
238 logger.lout() << "add unsettled thick edges" << std::endl;
241 }
242
246 void inArborescence(const GraphAttributes& GA, node root, NodeArray<edge>& predecessor,
249 dijkstra.callUnbound(*m_G, m_weight, root, predecessor, distance, true, true);
250 }
251
255 void outArborescence(const GraphAttributes& GA, node root, NodeArray<edge>& predecessor,
258 dijkstra.callUnbound(*m_G, m_weight, root, predecessor, distance, true, false);
259 }
260
265 if (!m_E1[e]) {
266 m_E1[e] = true;
267 m_spanner->newEdge(e);
269 }
270 }
271
272 void printStats(bool assert = false) {
273 logger.lout() << "\nStats:" << std::endl;
274 logger.lout() << m_spanner->numberOfEdges() << " covered of " << m_G->numberOfEdges()
275 << std::endl;
276
277 int E1amount = 0;
278 int E1amountThick = 0;
279 int E1amountThinNotInE2 = 0;
280 int E2amount = 0;
281 int E2amountThin = 0;
282 int E2amountThickNotInE1 = 0;
283 int edgesCovered = 0;
284 int duplicateCovered = 0;
285 for (edge e : m_G->edges) {
286 if (m_E1[e]) {
287 E1amount++;
288 if (isThickEdge(e)) {
290 } else if (!m_E2[e]) {
292 }
293 }
294 if (m_E2[e]) {
295 E2amount++;
296 if (isThinEdge(e)) {
297 E2amountThin++;
298 } else if (!m_E1[e]) {
300 }
301 }
302 if (m_E1[e] || m_E2[e]) {
303 edgesCovered++;
304 }
305 if (m_E1[e] && m_E2[e]) {
307 }
308 }
309
310 logger.lout() << "covered: " << edgesCovered << ", duplicate edges: " << duplicateCovered
311 << std::endl;
312 logger.lout() << "E1: " << E1amount << " edges, " << E1amountThick << " thick, "
313 << (E1amount - E1amountThick) << " thin, " << E1amountThinNotInE2
314 << " thin edges not in E2" << std::endl;
315 logger.lout() << "E2: " << E2amount << " edges, " << E2amountThin << " thin, "
316 << (E2amount - E2amountThin) << " thick, " << E2amountThickNotInE1
317 << " thick edges not in E1" << std::endl;
318
319#if defined(OGDF_DEBUG)
320 if (assert) {
323 }
324#endif
325
327 }
328
330 for (edge e : m_G->edges) {
332 }
333 }
334
340
342 for (node n : m_G->nodes) {
343 TWeight sd = m_outDistance[e->source()][n];
344 TWeight td = m_inDistance[e->target()][n];
345 if (sd == std::numeric_limits<TWeight>::max()
346 || td == std::numeric_limits<TWeight>::max()) {
347 continue;
348 }
349
350 OGDF_ASSERT(m_eps.geq(sd + td, static_cast<TWeight>(0)));
351 OGDF_ASSERT(m_eps.less(sd + td, std::numeric_limits<TWeight>::max()));
352
353 if (m_eps.leq((sd + td), maxDistance)) {
355 }
356
358 return true;
359 }
360 }
361 return false;
362 }
363
368
380
382 const node t, int maxLookupDist) {
383 NodeArray<TWeight> distances;
384 NodeArray<edge> predecessor;
386 dijkstra.callBound(G, weights, s, predecessor, distances, m_GA->directed(),
387 false, // arcs are not reversed
389 return distances[t];
390 }
391
393 for (edge e : m_G->edges) {
394 if (m_E1[e]) {
395 continue;
396 }
397
398 if (isThickEdge(e)) {
399 if (!isSettledEdge(e)) {
401 }
403 }
404 }
405 }
406
410 bool secondPart() {
411 logger.lout() << "Using LP-Solver: " << Configuration::whichLPSolver() << std::endl;
413 m_osi->messageHandler()->setLogLevel(0); // 0=nothing .. 4=verbose
414 m_osi->setObjSense(1); // minimize
415
416 // One column per edge (x_e)
418 EdgeArray<int> indices(*m_G);
419 int i = 0;
420 for (edge e : m_G->edges) {
421 m_osi->addCol(zero, 0, 1, 1); // vec, lower, upper, objective
422 indices[e] = i++;
423 }
424
425
427 for (edge e : m_G->edges) {
428 optConstraint->insert(indices[e], 1);
429 }
430 // sense: 'E' == 'G' >= 'L' <=
431 m_osi->addRow(*optConstraint, 'L', 0, 0); // constraint, sense, rhs (will be set below), range
432 m_constraints.push_back(optConstraint);
433
434 // Set the initial value of the OPT constraint rhs to n-1
435 setOpt(m_G->numberOfNodes() - 1);
436
437 int amountSolverCalls = 0;
438 while (true) {
439 if (amountSolverCalls == 0) {
440 m_osi->initialSolve();
441 } else {
442 m_osi->resolve();
443 }
445
447
448 logger.lout() << amountSolverCalls << ". solve... ";
449 if (m_osi->isProvenOptimal()) {
450 logger.lout() << "-> optimal (" << m_osi->getObjValue() << ")" << std::endl;
451 logger.lout() << " separation... ";
452 SeparationResult result = separation(m_osi->getColSolution(), indices);
453
454 if (result == SeparationResult::Solution) {
455 for (edge e : m_G->edges) {
456 if (m_E2[e] && !m_E1[e]) {
457 m_spanner->newEdge(e);
459 }
460 }
461 logger.lout() << "-> Found solution\n" << std::endl;
462 logger.lout() << "solver calls: " << amountSolverCalls << std::endl;
463 logger.lout() << "cols: " << m_osi->getNumCols() << std::endl;
464 logger.lout() << "rows: " << m_osi->getNumRows() << std::endl;
465 return true;
466 } else if (result == SeparationResult::NewConstraint) {
467 logger.lout() << "-> New constraint" << std::endl;
468 } else { // Fail
469 logger.lout() << "-> Failed" << std::endl;
470 if (!setOpt(m_OPT + 1)) {
471 return false;
472 }
473 }
474 } else if (m_osi->isProvenPrimalInfeasible()) {
475 logger.lout() << "-> Infeasible" << std::endl;
476 if (!setOpt(m_OPT + 1)) {
477 return false;
478 }
479 } else if (m_osi->isProvenDualInfeasible()) {
480 logger.lout() << "-> Unbounded" << std::endl;
481 return false;
482 } else {
483 logger.lout() << "-> No solution found" << std::endl;
484 return false;
485 }
486 };
487 }
488
494 bool setOpt(int opt) {
495 m_OPT = opt;
496 if (m_OPT > m_nSquared) {
497 logger.lout() << " OPT is too large. Abort." << std::endl;
498 return false;
499 }
500 float percentage = static_cast<float>(m_OPT) / m_nSquared * 100.0;
501 logger.lout() << " Set OPT to " << m_OPT << " (" << std::fixed << std::setprecision(2)
502 << percentage << "% of " << m_nSquared << ")" << std::endl;
503
504 m_osi->setRowBounds(0, 0.0,
505 m_OPT); // this is the opt constraint; it is 0 <= sum(x_e) <= m_OPT
506 return true;
507 }
508
509 SeparationResult separation(const double* solution, const EdgeArray<int>& indices) {
510 EdgeArray<bool> out(*m_G, false);
512
513 GraphCopySimple copy;
514 copy.createEmpty(*m_G);
515 for (node n : m_G->nodes) {
516 copy.newNode(n);
517 }
519 int amountCoveredEdges = 0;
520 for (edge e : m_G->edges) {
521 if (out[e]) {
523 copy.newEdge(e);
524 copyWeight[copy.copy(e)] = m_weight[e];
525 }
526 }
527
528 bool settlesAllThinEdges = true;
529 edge unsettledThinEdge = nullptr;
530 for (edge e : m_G->edges) {
531 if (isThinEdge(e) && !isSettledEdge(e, copy, copyWeight)) {
532 settlesAllThinEdges = false;
534 break;
535 }
536 }
537
539 if (amountCoveredEdges <= ceil(2.0 * m_OPT * m_sqrtlog)) {
540 m_E2 = out;
542 } else {
544 }
545 } else {
548
549 double rowValue = 0.0;
550 int i = 0;
551 for (edge e : m_G->edges) {
552 if (antispanner[e]) {
553 rowValue += solution[i];
554 }
555 i++;
556 }
557 if (m_eps.less(rowValue, 1.0)) {
558 // create new constraint
560 for (edge e : m_G->edges) {
561 if (antispanner[e]) {
562 c->insert(indices[e], 1);
563 }
564 }
565 m_osi->addRow(*c, 'G', 1, 0);
566 m_constraints.push_back(c);
568 } else {
570 }
571 }
572 }
573
575 int i = 0;
576 for (edge e : m_G->edges) {
577 double p = m_sqrtlog * fractions[i++]; // P may not be limited to 1: if it is >1 the
578 // result will always be true, which is ok.
579 out[e] = randomDouble(0.0, 1.0) <= p;
580 }
581 }
582
585 GraphCopySimple copy;
586 copy.createEmpty(*m_G);
587 for (node n : m_G->nodes) {
588 copy.newNode(n);
589 }
591
593 for (edge e : m_G->edges) {
594 // s->e->t
595 TWeight s_e = m_outDistance[unsettledThinEdge->source()][e->source()];
596 TWeight e_t = m_inDistance[unsettledThinEdge->target()][e->target()];
597 bool isInLocalSubgraph = (s_e != std::numeric_limits<TWeight>::max()
598 && e_t != std::numeric_limits<TWeight>::max()
599 && m_eps.leq(s_e + m_weight[e] + e_t, maxDistance));
600
601 // Graph to maximize is E_st\‍(E_st\E2) = E_st intersection E2
602 // intersection[e] = isInLocalSubgraph && out[e];
603 if (isInLocalSubgraph && out[e]) {
604 copy.newEdge(e);
605 copyWeight[copy.copy(e)] = m_weight[e];
606 }
607
608 // e in antispanner <=> e in E_st and not in E2 (=out)
609 antispanner[e] = isInLocalSubgraph && !out[e];
610 }
612
613 // minimize antispanner
614 bool changed;
615 do {
616 changed = false;
617 for (edge e : m_G->edges) {
618 if (!antispanner[e]) {
619 continue;
620 }
621
622 // try to remove e and check, if still no path <=stretch*max exists
623 // -> removing e from antispanner is equivalent to adding e to the graph
624 copy.newEdge(e);
625 copyWeight[copy.copy(e)] = m_weight[e];
626 antispanner[e] = false;
627
629 // we've found an unnecessary edge.
630 changed = true;
631 } else {
632 antispanner[e] = true;
633 copy.delEdge(copy.copy(e));
634 }
635 }
636
638 } while (changed);
639 }
640
647};
648
649template<typename TWeight>
651 Logger::Level::High); // do not log by default
652
653}
Basic module for spanner algorithms.
static OsiSolverInterface * createCorrectOsiSolverInterface()
Get a new solver and set its initial log level according to the level of CoinLog.
Definition coin.h:52
static constexpr LPSolver whichLPSolver()
Returns the LP-solver used by OGDF.
Definition config.h:255
Dijkstra's single source shortest path algorithm.
Definition Dijkstra.h:53
void callUnbound(const Graph &G, const EdgeArray< T > &weight, const List< node > &sources, NodeArray< edge > &predecessor, NodeArray< T > &distance, bool directed=false, bool arcsReversed=false)
Calculates, based on the graph G with corresponding edge costs and source nodes, the shortest paths a...
Definition Dijkstra.h:70
void callBound(const Graph &G, const EdgeArray< T > &weight, const List< node > &sources, NodeArray< edge > &predecessor, NodeArray< T > &distance, bool directed, bool arcsReversed, node target, T maxLength=std::numeric_limits< T >::max())
Calculates, based on the graph G with corresponding edge costs and source nodes, the shortest paths a...
Definition Dijkstra.h:138
Dynamic arrays indexed with edges.
Definition EdgeArray.h:125
void init()
Reinitializes the array. Associates the array with no graph.
Definition EdgeArray.h:292
Class for the representation of edges.
Definition Graph_d.h:300
node target() const
Returns the target node of the edge.
Definition Graph_d.h:338
node source() const
Returns the source node of the edge.
Definition Graph_d.h:335
std::enable_if< std::is_integral< T >::value, bool >::type leq(const T &x, const T &y) const
Compare if x is LEQ than y for integral types.
Definition EpsilonTest.h:83
std::enable_if< std::is_integral< T >::value, bool >::type geq(const T &x, const T &y) const
Compare if x is GEQ to y for integral types.
std::enable_if< std::is_integral< T >::value, bool >::type less(const T &x, const T &y) const
Compare if x is LESS than y for integral types.
Definition EpsilonTest.h:57
Stores additional attributes of a graph (like layout information).
bool directed() const
Returns if the graph is directed.
Copies of graphs with mapping between nodes and edges.
Definition GraphCopy.h:59
edge newEdge(edge eOrig)
Creates a new edge in the graph copy with original edge eOrig.
Definition GraphCopy.h:188
virtual void delEdge(edge e) override
Removes edge e.
node copy(node v) const
Returns the node in the graph copy corresponding to v.
Definition GraphCopy.h:124
node newNode(node vOrig)
Creates a new node in the graph copy with original node vOrig.
Definition GraphCopy.h:173
void createEmpty(const Graph &G)
Re-initializes the copy using G, but does not create any nodes or edges.
Data type for general directed graphs (adjacency list representation).
Definition Graph_d.h:521
node chooseNode(std::function< bool(node)> includeNode=[](node) { return true;}, bool isFastTest=true) const
Returns a random node.
int numberOfNodes() const
Returns the number of nodes in the graph.
Definition Graph_d.h:622
int numberOfEdges() const
Returns the number of edges in the graph.
Definition Graph_d.h:625
internal::GraphObjectContainer< NodeElement > nodes
The container containing all node objects.
Definition Graph_d.h:589
internal::GraphObjectContainer< EdgeElement > edges
The container containing all edge objects.
Definition Graph_d.h:592
Centralized global and local logging facility working on streams like std::cout.
Definition Logger.h:100
@ Log
the object is in logging mode, using its own localLogLevel
std::ostream & lout(Level level=Level::Default) const
stream for logging-output (local)
Definition Logger.h:142
ReturnType
The return type of a module.
Definition Module.h:50
Dynamic arrays indexed with nodes.
Definition NodeArray.h:125
Class for the representation of nodes.
Definition Graph_d.h:177
Approximation algorithm for calculating spanners.
void randomizedSelection(const double *fractions, EdgeArray< bool > &out)
void firstPart()
First part of the algorithm: Settle all thick edges.
static Logger logger
void printStats(bool assert=false)
bool isThickEdge(edge e)
EdgeArray< bool > m_isThickEdge
bool secondPart()
The second part: Settling all thin edges.
bool isSettledEdge(const edge e, const GraphCopySimple &_spanner, const EdgeArray< TWeight > &_spannerWeight)
bool isSettledEdge(const edge e)
Shortcut for calling isSettledEdge with m_spanner and the current spanner weights.
const Graph * m_G
const reference to the original graph
int m_OPT
The current guess for our optimal LP value.
SeparationResult
Used to indicate the result of the separation method.
EdgeArray< bool > m_E1
Holds the set E1 from the first part of the algorithm.
EdgeArray< bool > m_E2
Holds the set E2 from the second part of the algorithm.
NodeArray< NodeArray< TWeight > > m_outDistance
m_outDistance[v][w]: distance from v in a v-rooted out-arborescense to w
int m_thickEdgeNodeAmountLimit
n/beta
EdgeArray< TWeight > m_spannerWeight
weights for m_spanner Caches, if an edge is thick (or thin).
SeparationResult separation(const double *solution, const EdgeArray< int > &indices)
OsiSolverInterface * m_osi
the solver.
virtual void init(const GraphAttributes &GA, double stretch, GraphCopySimple &spanner, EdgeArray< bool > &inSpanner) override
Initializes members and create an empty spanner.
void outArborescence(const GraphAttributes &GA, node root, NodeArray< edge > &predecessor, NodeArray< TWeight > &distance)
Calculates an out-arborescense rooted at root.
virtual SpannerModule< TWeight >::ReturnType execute() override
Executes the core algorithm.
double m_beta
sqrt(n)
NodeArray< NodeArray< TWeight > > m_inDistance
m_inDistance[v][w]: distance from v in a v-rooted in-arborescense to w
void inArborescence(const GraphAttributes &GA, node root, NodeArray< edge > &predecessor, NodeArray< TWeight > &distance)
Calculates an in-arborescense rooted at root.
void addEdgeToSpanner(edge e)
Add an edge to the spanner.
virtual bool preconditionsOk(const GraphAttributes &GA, double stretch, std::string &error) override
bool setOpt(int opt)
Set a new value for m_OPT.
bool _isThickEdge(edge e)
Actually calculates whether e is thick or not.
std::vector< CoinPackedVector * > m_constraints
Holds all constraints so they can be freed at destruction.
EdgeArray< TWeight > m_weight
weights of each edge from m_G
void createAntispanner(const edge unsettledThinEdge, const EdgeArray< bool > &out, EdgeArray< bool > &antispanner)
void resetLP()
Resets the LP defining variables.
bool isThinEdge(edge e)
double m_sqrtlog
sqrt(n) * ln(n)
TWeight distance(const GraphCopySimple &G, const EdgeArray< TWeight > &weights, const node s, const node t, int maxLookupDist)
Interface for spanner algorithms.
void assertTimeLeft()
Assert, that time is left.
virtual void init(const GraphAttributes &GA, double stretch, GraphCopySimple &spanner, EdgeArray< bool > &inSpanner)
Initializes members and create an empty spanner.
const GraphAttributes * m_GA
EdgeArray< bool > * m_inSpanner
static TWeight getWeight(const GraphAttributes &GA, edge e)
GraphCopySimple * m_spanner
Definition of ogdf::CoinManager.
Declaration of extended graph algorithms.
bool isConnected(const Graph &G)
Returns true iff G is connected.
bool isSimple(const Graph &G)
Returns true iff G contains neither self-loops nor parallel edges.
#define OGDF_ASSERT(expr)
Assert condition expr. See doc/build.md for more information.
Definition basic.h:41
double randomDouble(double low, double high)
Returns a random double value from the interval [low, high).
static MultilevelBuilder * getDoubleFactoredZeroAdjustedMerger()
The namespace for all OGDF objects.
Declaration of simple graph algorithms.