1 | /** \file |
2 | * \brief Tests for Steiner Tree approximation algorithm helpers |
3 | * |
4 | * \author Stephan Beyer |
5 | * |
6 | * \par License: |
7 | * This file is part of the Open Graph Drawing Framework (OGDF). |
8 | * |
9 | * \par |
10 | * Copyright (C)<br> |
11 | * See README.md in the OGDF root directory for details. |
12 | * |
13 | * \par |
14 | * This program is free software; you can redistribute it and/or |
15 | * modify it under the terms of the GNU General Public License |
16 | * Version 2 or 3 as published by the Free Software Foundation; |
17 | * see the file LICENSE.txt included in the packaging of this file |
18 | * for details. |
19 | * |
20 | * \par |
21 | * This program is distributed in the hope that it will be useful, |
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24 | * GNU General Public License for more details. |
25 | * |
26 | * \par |
27 | * You should have received a copy of the GNU General Public |
28 | * License along with this program; if not, see |
29 | * http://www.gnu.org/copyleft/gpl.html |
30 | */ |
31 | |
32 | #include <set> |
33 | #include <ogdf/basic/EpsilonTest.h> |
34 | #include <ogdf/module/MinSteinerTreeModule.h> |
35 | #include <ogdf/graphalg/steiner_tree/FullComponentStore.h> |
36 | #include <ogdf/graphalg/steiner_tree/Full2ComponentGenerator.h> |
37 | #include <ogdf/graphalg/steiner_tree/Full3ComponentGeneratorVoronoi.h> |
38 | #include <ogdf/graphalg/steiner_tree/Full3ComponentGeneratorEnumeration.h> |
39 | #include <ogdf/graphalg/steiner_tree/FullComponentGeneratorDreyfusWagner.h> |
40 | #include <testing.h> |
41 | |
42 | using namespace std::placeholders; |
43 | |
44 | static EpsilonTest et(1e-6); |
45 | |
46 | template<typename T> |
47 | struct EdgeData { |
48 | int source; |
49 | int target; |
50 | T cost; |
51 | }; |
52 | |
53 | //! Predefined instances that can be used by using their index |
54 | template<typename T> |
55 | static std::pair<std::vector<int>, std::vector<EdgeData<T>>> predefinedInstanceData(int i) { |
56 | switch (i) { |
57 | case 1: |
58 | // an instance with two terminal nodes |
59 | return {{0, 3}, |
60 | {{0, 2, 3}, |
61 | {1, 0, 1}, |
62 | {3, 2, 4}, |
63 | {2, 1, 1}, |
64 | {1, 3, 6}}}; |
65 | case 2: |
66 | // a simple instance with all nodes being terminals |
67 | return {{0, 1, 2}, {{0, 1, 2}, {0, 2, 2}}}; |
68 | case 3: |
69 | // an instance to check heuristics avoiding terminals |
70 | return {{0, 1, 2}, |
71 | {{0, 3, 1}, |
72 | {0, 1, 2}, |
73 | {1, 3, 1}, |
74 | {2, 3, 5}, |
75 | {2, 1, 2}}}; |
76 | case 4: |
77 | // an instance to check heuristics preferring terminals |
78 | return {{0, 1, 2}, |
79 | {{0, 3, 1}, |
80 | {0, 1, 2}, |
81 | {1, 3, 1}, |
82 | {2, 5, 1}, |
83 | {5, 4, 1}, |
84 | {4, 3, 1}, |
85 | {2, 1, 2}}}; |
86 | } |
87 | return {{}, {}}; |
88 | }; |
89 | |
90 | //! An auxiliary class for nicer tests |
91 | template<typename T> |
92 | struct Instance { |
93 | EdgeWeightedGraph<T> graph; |
94 | List<node> terminals; |
95 | NodeArray<bool> isTerminal; |
96 | Array<node> v; |
97 | |
98 | //! Constructs a custom instance |
99 | Instance(std::vector<int> terminalList, std::vector<EdgeData<T>> edges) { |
100 | int n = 0; |
101 | for (auto e : edges) { |
102 | Math::updateMax(n, e.source + 1); |
103 | Math::updateMax(n, e.target + 1); |
104 | } |
105 | |
106 | v.init(n); |
107 | for (int i = 0; i < n; ++i) { |
108 | v[i] = graph.newNode(); |
109 | } |
110 | |
111 | for (auto e : edges) { |
112 | graph.newEdge(v[e.source], v[e.target], e.cost); |
113 | } |
114 | |
115 | setTerminals(terminalList); |
116 | } |
117 | |
118 | //! Constructs a predefined instance with index \p i |
119 | explicit Instance(int i) |
120 | : Instance(predefinedInstanceData<T>(i).first, predefinedInstanceData<T>(i).second) |
121 | {} |
122 | |
123 | void setTerminals(std::vector<int> terminalList) { |
124 | isTerminal.init(graph, false); |
125 | for (auto t : terminalList) { |
126 | OGDF_ASSERT(t <= v.high()); |
127 | isTerminal[v[t]] = true; |
128 | } |
129 | terminals.clear(); |
130 | MinSteinerTreeModule<T>::getTerminals(terminals, graph, isTerminal); |
131 | } |
132 | }; |
133 | |
134 | template<typename T> |
135 | struct Arguments { |
136 | NodeArray<NodeArray<T>> distance; |
137 | NodeArray<NodeArray<edge>> pred; |
138 | }; |
139 | |
140 | template<typename T> |
141 | struct ModifiedShortestPathAlgorithmsTest { |
142 | //! Assert something when considering a shortest path tree from a start node |
143 | struct AssertFrom { |
144 | int start; |
145 | std::function<void(const Instance<T> &, const NodeArray<T> &, const NodeArray<edge> &)> doAssert; |
146 | }; |
147 | |
148 | //! Test a predefined instance |
149 | static void testIt(std::string &&title, int instance, |
150 | std::function<void(const Instance<T> &, Arguments<T> &)> doAPSP, |
151 | std::initializer_list<AssertFrom> list) { |
152 | it(title, [&] { |
153 | Instance<T> S(instance); |
154 | Arguments<T> arg; |
155 | |
156 | doAPSP(S, arg); |
157 | |
158 | for (auto af : list) { |
159 | af.doAssert(S, arg.distance[S.v[af.start]], arg.pred[S.v[af.start]]); |
160 | } |
161 | }); |
162 | } |
163 | |
164 | //! Assert that \p nodes have no predecessor |
165 | static void assertHasNoPred(const Instance<T> &S, const NodeArray<edge> &pred, std::initializer_list<int> nodes) { |
166 | for (int i : nodes) { |
167 | AssertThat(pred[S.v[i]], IsNull()); |
168 | } |
169 | } |
170 | |
171 | //! For each pair (\a u, \a d) in \p nodeDistancePairs, assert that |
172 | //! the distance to \a u equals \a d. |
173 | static void assertDistanceTo(const Instance<T> &S, const NodeArray<T> &distance, std::initializer_list<std::pair<int, T>> nodeDistancePairs) { |
174 | for (auto p : nodeDistancePairs) { |
175 | AssertThat(et.equal(distance[S.v[p.first]], T(p.second)), IsTrue()); |
176 | } |
177 | } |
178 | |
179 | //! For each pair (\a u, \a v) in \p nodePredPairs, assert that the predecessor of \a u is \a v |
180 | //! if \a v is nonnegative. Otherwise just make sure \a u has a predecessor. |
181 | static void assertPred(const Instance<T> &S, const NodeArray<edge> &pred, std::initializer_list<std::pair<int, int>> nodePredPairs) { |
182 | for (auto p : nodePredPairs) { |
183 | AssertThat(pred[S.v[p.first]], !IsNull()); |
184 | if (p.second >= 0) { |
185 | AssertThat(pred[S.v[p.first]]->opposite(S.v[p.first]), Equals(S.v[p.second])); |
186 | } |
187 | } |
188 | } |
189 | |
190 | //! Assert for predefined instance 2, that the third terminal is unreachable... |
191 | //! If \p byDistance is true, it expects the terminal to be unreachable by |
192 | //! distance, too, that is, the distance is \c std::numeric_limits<T>::max(). |
193 | static void assertThirdUnreachable(const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred, bool byDistance) { |
194 | assertHasNoPred(S, pred, {1, 2}); |
195 | assertPred(S, pred, {{0, -1}}); |
196 | assertDistanceTo(S, distance, {{0, 2}, {2, 0}}); |
197 | if (byDistance) { |
198 | AssertThat(distance[S.v[1]], Equals(std::numeric_limits<T>::max())); |
199 | } else { |
200 | AssertThat(distance[S.v[1]], IsGreaterThan(3)); |
201 | AssertThat(distance[S.v[1]], IsLessThan(5)); |
202 | } |
203 | } |
204 | |
205 | static void itMimicsOrdinaryShortestPath(const std::string spName, |
206 | std::function<void(const Instance<T> &S, Arguments<T> &arg)> spAlg) { |
207 | testIt("mimics ordinary " + spName + " when terminals are not in between" , |
208 | 1, spAlg, {{0, [&](const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred) { |
209 | assertHasNoPred(S, pred, {0}); |
210 | assertDistanceTo(S, distance, {{0, 0}, {1, 1}, {2, 2}, {3, 6}}); |
211 | assertPred(S, pred, {{1, 0}, {2, 1}, {3, 2}}); |
212 | }}, {3, [&](const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred) { |
213 | assertHasNoPred(S, pred, {3}); |
214 | assertDistanceTo(S, distance, {{3, 0}, {1, 5}, {2, 4}, {0, 6}}); |
215 | assertPred(S, pred, {{0, 1}, {1, 2}, {2, 3}}); |
216 | }}}); |
217 | } |
218 | |
219 | //! The test for the algorithm variants preferring paths over terminals |
220 | static void callExpectPreferTerminals(const std::string spName, |
221 | std::function<void(const Instance<T> &S, Arguments<T> &arg)> spAlg) { |
222 | describe(spName + " preferring terminals heuristic" , [&] { |
223 | itMimicsOrdinaryShortestPath(spName, spAlg); |
224 | |
225 | testIt("marks the third terminal on a path of three terminals as unreachable (by predecessor only)" , |
226 | 2, spAlg, {{2, std::bind(assertThirdUnreachable, _1, _2, _3, false)}}); |
227 | |
228 | testIt("prefers terminals in shortest paths" , |
229 | 4, spAlg, {{0, [&](const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred) { |
230 | assertHasNoPred(S, pred, {0, 2}); |
231 | assertPred(S, pred, {{1, -1}, {3, 0}, {4, 3}, {5, 4}}); |
232 | AssertThat(pred[S.v[1]]->opposite(S.v[1]), Equals(S.v[0]) || Equals(S.v[3])); |
233 | assertDistanceTo(S, distance, {{0, 0}, {3, 1}, {4, 2}, {5, 3}, {1, 2}, {2, 4}}); |
234 | }}, {2, [&](const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred) { |
235 | assertHasNoPred(S, pred, {2, 0, 3}); |
236 | assertPred(S, pred, {{1, 2}, {4, 5}, {5, 2}}); |
237 | assertDistanceTo(S, distance, {{2, 0}, {3, 3}, {4, 2}, {5, 1}, {0, 4}, {1, 2}}); |
238 | }}}); |
239 | }); |
240 | } |
241 | |
242 | //! The test for the algorithm variants avoiding paths over terminals |
243 | static void callExpectForbidTerminals(const std::string spName, |
244 | std::function<void(const Instance<T> &S, Arguments<T> &arg)> spAlg) { |
245 | describe(spName +" avoiding terminals heuristic" , [&] { |
246 | itMimicsOrdinaryShortestPath(spName, spAlg); |
247 | |
248 | testIt("marks the third terminal on a path of three terminals as unreachable (by predecessor and distance)" , |
249 | 2, spAlg, {{2, std::bind(assertThirdUnreachable, _1, _2, _3, true)}}); |
250 | |
251 | testIt("uses a detour as shortest path over nonterminals" , |
252 | 3, spAlg, {{0, [&](const Instance<T> &S, const NodeArray<T> &distance, const NodeArray<edge> &pred) { |
253 | assertHasNoPred(S, pred, {0}); |
254 | assertPred(S, pred, {{1, -1}, {2, 3}, {3, 0}}); |
255 | AssertThat(pred[S.v[1]]->opposite(S.v[1]), Equals(S.v[0]) || Equals(S.v[3])); |
256 | assertDistanceTo(S, distance, {{0, 0}, {3, 1}, {1, 2}, {2, 6}}); |
257 | }}}); |
258 | }); |
259 | } |
260 | }; |
261 | |
262 | template<typename T> |
263 | static void ssspDetour(const Instance<T> &S, Arguments<T> &arg) { |
264 | MinSteinerTreeModule<T>::allTerminalShortestPathsDetour(S.graph, S.terminals, S.isTerminal, arg.distance, arg.pred); |
265 | } |
266 | |
267 | template<typename T> |
268 | static void ssspStrict(const Instance<T> &S, Arguments<T> &arg) { |
269 | MinSteinerTreeModule<T>::allTerminalShortestPathsStrict(S.graph, S.terminals, S.isTerminal, arg.distance, arg.pred); |
270 | } |
271 | |
272 | template<typename T> |
273 | static void apspDetour(const Instance<T> &S, Arguments<T> &arg) { |
274 | ArrayBuffer<node> nonterminals; |
275 | MinSteinerTreeModule<T>::getNonterminals(nonterminals, S.graph, S.isTerminal); |
276 | MinSteinerTreeModule<T>::allPairShortestPathsDetour(S.graph, nonterminals, arg.distance, arg.pred); |
277 | } |
278 | |
279 | template<typename T> |
280 | static void apspStrict(const Instance<T> &S, Arguments<T> &arg) { |
281 | MinSteinerTreeModule<T>::allPairShortestPathsStrict(S.graph, S.isTerminal, arg.distance, arg.pred); |
282 | } |
283 | |
284 | //! Tests for shortest path algorithms (SSSP and APSP variants) |
285 | template<typename T> |
286 | static void testShortestPathAlgorithms() { |
287 | ModifiedShortestPathAlgorithmsTest<T>::callExpectForbidTerminals("SSSP" , ssspDetour<T>); |
288 | ModifiedShortestPathAlgorithmsTest<T>::callExpectPreferTerminals("SSSP" , ssspStrict<T>); |
289 | ModifiedShortestPathAlgorithmsTest<T>::callExpectForbidTerminals("APSP" , apspDetour<T>); |
290 | ModifiedShortestPathAlgorithmsTest<T>::callExpectPreferTerminals("APSP" , apspStrict<T>); |
291 | } |
292 | |
293 | //! Test MinSteinerTreeModule<T>::isSteinerTree() |
294 | template<typename T> |
295 | static void testIsSteinerTree() { |
296 | std::unique_ptr<Instance<T>> S; |
297 | std::unique_ptr<EdgeWeightedGraphCopy<T>> tree; |
298 | |
299 | before_each([&] { |
300 | S.reset(new Instance<T>({0, 2}, {{0, 1, 2}, {1, 2, 3}, {2, 0, 7}})); |
301 | edge eCycle = S->graph.lastEdge(); |
302 | OGDF_ASSERT(eCycle->source() == S->v[2]); |
303 | OGDF_ASSERT(eCycle->target() == S->v[0]); |
304 | |
305 | tree.reset(new EdgeWeightedGraphCopy<T>(S->graph)); |
306 | tree->delEdge(tree->copy(eCycle)); |
307 | }); |
308 | |
309 | it("recognizes a valid Steiner tree" , [&] { |
310 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(S->graph, S->terminals, S->isTerminal, *tree), IsTrue()); |
311 | }); |
312 | |
313 | it("recognizes a disconnected Steiner tree" , [&] { |
314 | tree->delEdge(tree->chooseEdge()); |
315 | |
316 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(S->graph, S->terminals, S->isTerminal, *tree), IsFalse()); |
317 | }); |
318 | |
319 | it("recognizes a Steiner tree with extra nodes" , [&] { |
320 | node v = S->graph.newNode(); |
321 | S->isTerminal[v] = true; |
322 | S->terminals.pushFront(v); |
323 | |
324 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(S->graph, S->terminals, S->isTerminal, *tree), IsFalse()); |
325 | }); |
326 | |
327 | it("recognizes a Steiner tree with redundant Steiner node" , [&] { |
328 | node u = S->terminals.front(); |
329 | node v = S->graph.newNode(); |
330 | edge e = S->graph.newEdge(u, v, 1); |
331 | tree->newNode(v); |
332 | tree->newEdge(e, 1); |
333 | |
334 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(S->graph, S->terminals, S->isTerminal, *tree), IsFalse()); |
335 | }); |
336 | } |
337 | |
338 | //! Test that the module handles calls on trivial instances |
339 | template<typename T> |
340 | static void testCallTrivial() { |
341 | class MinSteinerTreeDummy : public MinSteinerTreeModule<T> { |
342 | protected: |
343 | T computeSteinerTree(const EdgeWeightedGraph<T> &G, const List<node> &terminals, |
344 | const NodeArray<bool> &isTerminal, |
345 | EdgeWeightedGraphCopy<T> *&finalSteinerTree) override { |
346 | throw std::runtime_error("Not implemented." ); |
347 | } |
348 | }; |
349 | |
350 | MinSteinerTreeDummy dummy; |
351 | Instance<T> S({}, |
352 | {{1, 2, 26}, {2, 3, 16}, {3, 1, 10}, |
353 | {0, 1, 15}, {0, 2, 14}, {0, 3, 1}}); |
354 | EdgeWeightedGraphCopy<T> *solution; |
355 | |
356 | before_each([&] { |
357 | solution = nullptr; |
358 | }); |
359 | |
360 | after_each([&] { |
361 | delete solution; |
362 | }); |
363 | |
364 | it("solves an instance without terminals" , [&] { |
365 | T cost = dummy.call(S.graph, S.terminals, S.isTerminal, solution); |
366 | AssertThat(cost, Equals(0)); |
367 | AssertThat(solution, !IsNull()); |
368 | AssertThat(solution->empty(), IsTrue()); |
369 | }); |
370 | |
371 | it("solves an instance with exactly one terminal" , [&] { |
372 | S.setTerminals({3}); |
373 | T cost = dummy.call(S.graph, S.terminals, S.isTerminal, solution); |
374 | AssertThat(cost, Equals(0)); |
375 | AssertThat(solution, !IsNull()); |
376 | AssertThat(solution->numberOfNodes(), Equals(1)); |
377 | AssertThat(solution->numberOfEdges(), Equals(0)); |
378 | AssertThat(solution->original(solution->firstNode()), Equals(S.v[3])); |
379 | }); |
380 | |
381 | it("solves an instance with exactly two terminals" , [&] { |
382 | S.setTerminals({2, 1}); |
383 | T cost = dummy.call(S.graph, S.terminals, S.isTerminal, solution); |
384 | AssertThat(cost, Equals(25)); |
385 | AssertThat(solution, !IsNull()); |
386 | AssertThat(solution->numberOfNodes(), Equals(4)); |
387 | AssertThat(solution->numberOfEdges(), Equals(3)); |
388 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(S.graph, S.terminals, S.isTerminal, *solution), IsTrue()); |
389 | }); |
390 | } |
391 | |
392 | template<typename T> |
393 | static void describeMinSteinerTreeModule(const std::string &&type) { |
394 | describe("MinSteinerTreeModule<" + type + ">" , [&] { |
395 | describe("Modified shortest path algorithms" , [] { |
396 | testShortestPathAlgorithms<T>(); |
397 | }); |
398 | describe("isSteinerTree" , [] { |
399 | testIsSteinerTree<T>(); |
400 | }); |
401 | describe("call on trivial cases" , [] { |
402 | testCallTrivial<T>(); |
403 | }); |
404 | }); |
405 | } |
406 | |
407 | template<typename T> |
408 | static void assertTerminals(const Instance<T> &S, std::initializer_list<node> terminals) { |
409 | for (node t : terminals) { |
410 | AssertThat(S.isTerminal[t], IsTrue()); |
411 | } |
412 | } |
413 | |
414 | template<typename T> |
415 | static void testFull2ComponentGenerator(const steiner_tree::Full2ComponentGenerator<T> &fcg) { |
416 | Instance<T> S({0, 1, 2}, |
417 | {{0, 3, 1}, {3, 2, 1}, {2, 4, 2}, {4, 1, 1}, |
418 | {3, 5, 1}, {5, 4, 2}}); |
419 | |
420 | it("generates full components with terminal-avoiding APSP" , [&] { |
421 | Arguments<T> arg; |
422 | apspDetour(S, arg); |
423 | |
424 | int number = 0; |
425 | fcg.call(S.graph, S.terminals, arg.distance, arg.pred, |
426 | [&](node u, node v, T minCost) { |
427 | ++number; |
428 | assertTerminals(S, {u, v}); |
429 | std::set<std::set<node>> allowed = {{S.v[0], S.v[1]}, {S.v[0], S.v[2]}, {S.v[1], S.v[2]}}; |
430 | std::set<node> actual = {u, v}; |
431 | AssertThat(allowed, Contains(actual)); |
432 | if (std::set<node>{u, v} == std::set<node>{S.v[0], S.v[1]}) { |
433 | AssertThat(et.equal(minCost, T(5)), IsTrue()); |
434 | } else |
435 | if (std::set<node>{u, v} == std::set<node>{S.v[0], S.v[2]}) { |
436 | AssertThat(et.equal(minCost, T(2)), IsTrue()); |
437 | } else |
438 | if (std::set<node>{u, v} == std::set<node>{S.v[1], S.v[2]}) { |
439 | AssertThat(et.equal(minCost, T(3)), IsTrue()); |
440 | } |
441 | }); |
442 | AssertThat(number, Equals(3)); |
443 | }); |
444 | |
445 | it("generates full components with terminal-preferring APSP" , [&] { |
446 | Arguments<T> arg; |
447 | apspStrict(S, arg); |
448 | |
449 | int number = 0; |
450 | fcg.call(S.graph, S.terminals, arg.distance, arg.pred, |
451 | [&](node u, node v, T minCost) { |
452 | ++number; |
453 | assertTerminals(S, {u, v}); |
454 | std::set<std::set<node>> allowed = {{S.v[0], S.v[2]}, {S.v[1], S.v[2]}}; |
455 | std::set<node> actual = {u, v}; |
456 | AssertThat(allowed, Contains(actual)); |
457 | if (std::set<node>{u, v} == std::set<node>{S.v[0], S.v[2]}) { |
458 | AssertThat(et.equal(minCost, T(2)), IsTrue()); |
459 | } else |
460 | if (std::set<node>{u, v} == std::set<node>{S.v[1], S.v[2]}) { |
461 | AssertThat(et.equal(minCost, T(3)), IsTrue()); |
462 | } |
463 | }); |
464 | AssertThat(number, Equals(2)); |
465 | }); |
466 | } |
467 | |
468 | template<typename T> |
469 | static void testFull3ComponentGeneratorModule(std::string name, const steiner_tree::Full3ComponentGeneratorModule<T> &fcg) { |
470 | describe(name, [&] { |
471 | std::unique_ptr<Instance<T>> S; // a pointer because we may change the instance in the "it"s |
472 | |
473 | before_each([&] { |
474 | S.reset(new Instance<T>({0, 1, 2, 3, 4}, |
475 | {{0, 5, 1}, {1, 5, 1}, {3, 5, 1}, |
476 | {5, 6, 1}, {2, 6, 1}, |
477 | {2, 7, 4}, {4, 7, 3}, |
478 | {0, 1, 2}, {2, 1, 3}})); |
479 | }); |
480 | |
481 | it("generates full components with terminal-avoiding APSP" , [&] { |
482 | Arguments<T> arg; |
483 | apspDetour(*S, arg); |
484 | |
485 | int number = 0; |
486 | fcg.call(S->graph, S->terminals, S->isTerminal, arg.distance, arg.pred, |
487 | [&](node u, node v, node w, node center, T minCost) { |
488 | ++number; |
489 | assertTerminals(*S, {u, v, w}); |
490 | AssertThat(S->isTerminal[center], IsFalse()); |
491 | AssertThat(u, !Equals<node>(S->v[4])); |
492 | AssertThat(v, !Equals<node>(S->v[4])); |
493 | AssertThat(w, !Equals<node>(S->v[4])); |
494 | AssertThat(center, Equals<node>(S->v[5])); |
495 | }); |
496 | AssertThat(number, IsGreaterThan(2) || IsLessThan(5)); |
497 | }); |
498 | |
499 | it("generates full components with terminal-preferring APSP" , [&] { |
500 | Arguments<T> arg; |
501 | apspStrict(*S, arg); |
502 | |
503 | int number = 0; |
504 | fcg.call(S->graph, S->terminals, S->isTerminal, arg.distance, arg.pred, |
505 | [&](node u, node v, node w, node center, T minCost) { |
506 | ++number; |
507 | assertTerminals(*S, {u, v, w}); |
508 | AssertThat(S->isTerminal[center], IsFalse()); |
509 | AssertThat(u, !Equals<node>(S->v[4])); |
510 | AssertThat(v, !Equals<node>(S->v[4])); |
511 | AssertThat(w, !Equals<node>(S->v[4])); |
512 | AssertThat(center, Equals<node>(S->v[5])); |
513 | }); |
514 | AssertThat(number, IsGreaterThan(2) || IsLessThan(5)); |
515 | }); |
516 | |
517 | it("omits generating 3-components that are dominated by 2-components" , [&] { |
518 | S->graph.newEdge(S->v[2], S->v[3], 1); |
519 | S->graph.newEdge(S->v[3], S->v[1], 1); |
520 | S->graph.newEdge(S->v[1], S->v[2], 1); |
521 | |
522 | Arguments<T> arg; |
523 | apspStrict(*S, arg); |
524 | |
525 | int number = 0; |
526 | fcg.call(S->graph, S->terminals, S->isTerminal, arg.distance, arg.pred, |
527 | [&](node u, node v, node w, node center, T minCost) { |
528 | ++number; |
529 | }); |
530 | AssertThat(number, Equals(0)); |
531 | }); |
532 | }); |
533 | } |
534 | |
535 | template<typename T> |
536 | static void testFullComponentGeneratorDreyfusWagner() { |
537 | std::unique_ptr<Instance<T>> S; |
538 | using FCG = steiner_tree::FullComponentGeneratorDreyfusWagner<T>; |
539 | |
540 | before_each([&] { |
541 | S.reset(new Instance<T>({0, 1, 2, 3, 4}, |
542 | {{0, 5, 1}, {1, 5, 1}, {3, 5, 1}, |
543 | {5, 6, 1}, {2, 6, 1}, |
544 | {2, 7, 4}, {4, 7, 3}, |
545 | {0, 1, 2}, {2, 1, 3}})); |
546 | }); |
547 | |
548 | auto testComponents = [&](const Arguments<T>& arg, const FCG& fcg, int k) { |
549 | int nTotal = 0; |
550 | int nValid = 0; |
551 | |
552 | SubsetEnumerator<node> terminalSubset(S->terminals); |
553 | for (terminalSubset.begin(k); terminalSubset.valid(); terminalSubset.next()) { |
554 | EdgeWeightedGraphCopy<T> component; |
555 | List<node> terminals; |
556 | terminalSubset.list(terminals); |
557 | fcg.getSteinerTreeFor(terminals, component); |
558 | if (FCG::isValidComponent(component, arg.pred, S->isTerminal)) { |
559 | for (node t : terminals) { |
560 | AssertThat(component.copy(t)->degree(), Equals(1)); |
561 | }; |
562 | ++nValid; |
563 | }; |
564 | ++nTotal; |
565 | }; |
566 | AssertThat(nTotal, Equals(Math::binomial(S->terminals.size(), k))); |
567 | return nValid; |
568 | }; |
569 | |
570 | it("generates full components with terminal-avoiding APSP" , [&] { |
571 | Arguments<T> arg; |
572 | apspDetour(*S, arg); |
573 | |
574 | FCG fcg(S->graph, S->terminals, arg.distance); |
575 | fcg.call(5); |
576 | |
577 | AssertThat(testComponents(arg, fcg, 2), Equals(7)); |
578 | AssertThat(testComponents(arg, fcg, 3), Equals(4)); |
579 | AssertThat(testComponents(arg, fcg, 4), Equals(1)); |
580 | AssertThat(testComponents(arg, fcg, 5), Equals(0)); |
581 | }); |
582 | |
583 | it("generates full components with terminal-preferring APSP" , [&] { |
584 | Arguments<T> arg; |
585 | apspStrict(*S, arg); |
586 | |
587 | FCG fcg(S->graph, S->terminals, arg.distance); |
588 | fcg.call(5); |
589 | |
590 | AssertThat(testComponents(arg, fcg, 2), Equals(7)); |
591 | AssertThat(testComponents(arg, fcg, 3), Equals(4)); |
592 | AssertThat(testComponents(arg, fcg, 4), Equals(1)); |
593 | AssertThat(testComponents(arg, fcg, 5), Equals(0)); |
594 | }); |
595 | |
596 | it("omits generating 3-components that are dominated by 2-components" , [&] { |
597 | S->graph.newEdge(S->v[2], S->v[3], 1); |
598 | S->graph.newEdge(S->v[3], S->v[1], 1); |
599 | S->graph.newEdge(S->v[1], S->v[2], 1); |
600 | |
601 | Arguments<T> arg; |
602 | apspStrict(*S, arg); |
603 | |
604 | FCG fcg(S->graph, S->terminals, arg.distance); |
605 | fcg.call(3); |
606 | |
607 | AssertThat(testComponents(arg, fcg, 3), Equals(0)); |
608 | }); |
609 | } |
610 | |
611 | template<typename T> |
612 | static void describeFullComponentGenerators(const std::string &&type) { |
613 | describe("Full2ComponentGenerator<" + type + ">" , [&] { |
614 | steiner_tree::Full2ComponentGenerator<T> fcg; |
615 | testFull2ComponentGenerator(fcg); |
616 | }); |
617 | describe("Full3ComponentGeneratorModule<" + type + ">" , [&] { |
618 | steiner_tree::Full3ComponentGeneratorVoronoi<T> fcgVoronoi; |
619 | testFull3ComponentGeneratorModule("Voronoi" , fcgVoronoi); |
620 | |
621 | steiner_tree::Full3ComponentGeneratorEnumeration<T> fcgEnumeration; |
622 | testFull3ComponentGeneratorModule("Enumeration" , fcgEnumeration); |
623 | }); |
624 | describe("FullComponentGeneratorDreyfusWagner<" + type + ">" , [&] { |
625 | testFullComponentGeneratorDreyfusWagner<T>(); |
626 | }); |
627 | } |
628 | |
629 | template<typename T> |
630 | static void describeFullComponentStore(const std::string&& type) { |
631 | describe("FullComponentStore<" + type + ">" , [&] { |
632 | Instance<T> S({0, 1, 2, 3}, { |
633 | {0, 4, 1}, {4, 8, 1}, |
634 | {1, 5, 1}, {5, 8, 1}, |
635 | {2, 6, 1}, {6, 8, 1}, |
636 | {3, 7, 1}, {7, 8, 1}, |
637 | }); |
638 | |
639 | std::unique_ptr<steiner_tree::FullComponentStore<T>> fcs; |
640 | before_each([&] { |
641 | fcs.reset(new steiner_tree::FullComponentStore<T>(S.graph, S.terminals, S.isTerminal)); |
642 | }); |
643 | |
644 | it("is empty when nothing is inserted" , [&] { |
645 | AssertThat(fcs->isEmpty(), IsTrue()); |
646 | }); |
647 | |
648 | describe("containing component with degree-2 nodes" , [&] { |
649 | EdgeWeightedGraphCopy<T> copy(S.graph); |
650 | |
651 | before_each([&] { |
652 | fcs->insert(copy); |
653 | }); |
654 | |
655 | it("inserts the component" , [&] { |
656 | AssertThat(fcs->isEmpty(), IsFalse()); |
657 | AssertThat(fcs->size(), Equals(1)); |
658 | AssertThat(fcs->terminals(0).size(), Equals(4)); |
659 | AssertThat(fcs->terminals(0)[0]->index(), Equals(0)); |
660 | AssertThat(fcs->terminals(0)[1]->index(), Equals(1)); |
661 | AssertThat(fcs->terminals(0)[2]->index(), Equals(2)); |
662 | AssertThat(fcs->terminals(0)[3]->index(), Equals(3)); |
663 | }); |
664 | |
665 | it("iterates over all nodes without predecessor matrix" , [&] { |
666 | NodeArray<int> marked(S.graph, 0); |
667 | |
668 | fcs->foreachNode(0, [&](node v) { |
669 | ++marked[v]; |
670 | }); |
671 | |
672 | for (int count : marked) { |
673 | AssertThat(count, Equals(1)); |
674 | } |
675 | }); |
676 | |
677 | it("iterates over all nodes with predecessor matrix" , [&] { |
678 | Arguments<T> arg; |
679 | MinSteinerTreeModule<T>::allPairShortestPathsStrict(S.graph, S.isTerminal, arg.distance, arg.pred); |
680 | NodeArray<int> marked(S.graph, 0); |
681 | |
682 | fcs->foreachNode(0, arg.pred, [&](node v) { |
683 | ++marked[v]; |
684 | }); |
685 | |
686 | for (int count : marked) { |
687 | AssertThat(count, Equals(1)); |
688 | } |
689 | }); |
690 | }); |
691 | |
692 | describe("containing component without degree-2 nodes" , [&] { |
693 | EdgeWeightedGraphCopy<T> component(S.graph); |
694 | for (int i : {4, 5, 6, 7}) { |
695 | component.delNode(component.copy(S.v[i])); |
696 | } |
697 | for (int i : {0, 1, 2, 3}) { |
698 | component.newEdge(component.copy(S.v[i]), component.copy(S.v[8]), 2); |
699 | } |
700 | |
701 | before_each([&] { |
702 | fcs->insert(component); |
703 | }); |
704 | |
705 | it("inserts the component" , [&] { |
706 | AssertThat(fcs->isEmpty(), IsFalse()); |
707 | AssertThat(fcs->size(), Equals(1)); |
708 | AssertThat(fcs->terminals(0).size(), Equals(4)); |
709 | AssertThat(fcs->terminals(0)[0]->index(), Equals(0)); |
710 | AssertThat(fcs->terminals(0)[1]->index(), Equals(1)); |
711 | AssertThat(fcs->terminals(0)[2]->index(), Equals(2)); |
712 | AssertThat(fcs->terminals(0)[3]->index(), Equals(3)); |
713 | }); |
714 | |
715 | it("iterates over all critical nodes only" , [&] { |
716 | NodeArray<int> marked(S.graph, 0); |
717 | |
718 | fcs->foreachNode(0, [&](node v) { |
719 | ++marked[v]; |
720 | }); |
721 | |
722 | AssertThat(marked[S.v[0]], Equals(1)); |
723 | AssertThat(marked[S.v[1]], Equals(1)); |
724 | AssertThat(marked[S.v[2]], Equals(1)); |
725 | AssertThat(marked[S.v[3]], Equals(1)); |
726 | AssertThat(marked[S.v[4]], Equals(0)); |
727 | AssertThat(marked[S.v[5]], Equals(0)); |
728 | AssertThat(marked[S.v[6]], Equals(0)); |
729 | AssertThat(marked[S.v[7]], Equals(0)); |
730 | AssertThat(marked[S.v[8]], Equals(1)); |
731 | }); |
732 | |
733 | it("iterates over all nodes using predecessor matrix" , [&] { |
734 | Arguments<T> arg; |
735 | MinSteinerTreeModule<T>::allPairShortestPathsStrict(S.graph, S.isTerminal, arg.distance, arg.pred); |
736 | NodeArray<int> marked(S.graph, 0); |
737 | |
738 | fcs->foreachNode(0, arg.pred, [&](node v) { |
739 | ++marked[v]; |
740 | }); |
741 | |
742 | for (int count : marked) { |
743 | AssertThat(count, Equals(1)); |
744 | } |
745 | }); |
746 | }); |
747 | }); |
748 | } |
749 | |
750 | go_bandit([]{ |
751 | describe("Steiner tree approximation helpers" , [] { |
752 | describeMinSteinerTreeModule<int>("int" ); |
753 | describeMinSteinerTreeModule<double>("double" ); |
754 | describeFullComponentGenerators<int>("int" ); |
755 | describeFullComponentGenerators<double>("double" ); |
756 | describeFullComponentStore<int>("int" ); |
757 | describeFullComponentStore<double>("double" ); |
758 | }); |
759 | }); |
760 | |