| 1 | /** \file |
| 2 | * \brief Basic bandit test suite used for Steiner Tree problem reductions. |
| 3 | * |
| 4 | * \author Mihai Popa, 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 <string> |
| 33 | #include <sstream> |
| 34 | |
| 35 | #include <ogdf/graphalg/SteinerTreePreprocessing.h> |
| 36 | #include <ogdf/basic/graph_generators.h> |
| 37 | #include <ogdf/graphalg/MinSteinerTreeDirectedCut.h> |
| 38 | |
| 39 | #include <testing.h> |
| 40 | |
| 41 | using namespace std; |
| 42 | |
| 43 | template<typename T> |
| 44 | static void |
| 45 | putRandomTerminals(const EdgeWeightedGraph<T> &wg, List<node> &terminals, NodeArray<bool> &isTerminal, int numberOfTerminals) |
| 46 | { |
| 47 | isTerminal.init(wg, false); |
| 48 | |
| 49 | Array<node> nodes(wg.numberOfNodes()); |
| 50 | wg.allNodes(nodes); |
| 51 | nodes.permute(); |
| 52 | |
| 53 | Math::updateMin(numberOfTerminals, wg.numberOfNodes() - 1); |
| 54 | for (int i = 0; i < numberOfTerminals; ++i) { |
| 55 | const node v = nodes[i]; |
| 56 | terminals.pushBack(v); |
| 57 | isTerminal[v] = true; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | template<typename T> |
| 62 | static void |
| 63 | putRandomCosts(EdgeWeightedGraph<T> &wg, T x, T y, typename std::enable_if<std::is_integral<T>::value >::type* = 0) |
| 64 | { |
| 65 | for (edge e : wg.edges) { |
| 66 | wg.setWeight(e, randomNumber(x, y)); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | template<typename T> |
| 71 | static void |
| 72 | putRandomCosts(EdgeWeightedGraph<T> &wg, T x, T y, typename std::enable_if<std::is_floating_point<T>::value >::type* = 0) |
| 73 | { |
| 74 | for (edge e : wg.edges) { |
| 75 | wg.setWeight(e, randomDouble(x, y)); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | template<typename T> |
| 80 | static void |
| 81 | randomEdgeWeightedGraph(int numberOfnodes, int numberOfedges, int numberOfTerminals, T maxEdgeCost, EdgeWeightedGraph<T> &wg, List<node> &terminals, NodeArray<bool> &isTerminal) |
| 82 | { |
| 83 | randomGraph(wg, numberOfnodes, numberOfedges); |
| 84 | makeConnected(wg); |
| 85 | putRandomTerminals<T>(wg, terminals, isTerminal, numberOfTerminals); |
| 86 | putRandomCosts<T>(wg, 1, maxEdgeCost); |
| 87 | } |
| 88 | |
| 89 | template<typename T> |
| 90 | static T |
| 91 | getCostOfSolution(const EdgeWeightedGraphCopy<T> &wg) |
| 92 | { |
| 93 | T cost = 0; |
| 94 | for (edge e : wg.edges) { |
| 95 | cost += wg.weight(e); |
| 96 | } |
| 97 | |
| 98 | return cost; |
| 99 | } |
| 100 | |
| 101 | template<typename T, typename Fun> |
| 102 | static void |
| 103 | testReduction(Fun reductionFun) |
| 104 | { |
| 105 | int numberOfNodes = randomNumber(50, 120); |
| 106 | int numberOfEdges = randomNumber(numberOfNodes-1, 3*numberOfNodes); |
| 107 | int numberOfTerminals = randomNumber(1, numberOfNodes); |
| 108 | T maxEdgeCost = randomNumber(3, 1000000); |
| 109 | MinSteinerTreeDirectedCut<T> mst; |
| 110 | |
| 111 | EdgeWeightedGraph<T> wg; |
| 112 | List<node> terminals; |
| 113 | NodeArray<bool> isTerminal; |
| 114 | randomEdgeWeightedGraph<T>(numberOfNodes, numberOfEdges, numberOfTerminals, maxEdgeCost, wg, terminals, isTerminal); |
| 115 | |
| 116 | EdgeWeightedGraphCopy<T> *treeBefore = nullptr; |
| 117 | T costBefore = mst.call(wg, terminals, isTerminal, treeBefore); |
| 118 | |
| 119 | SteinerTreePreprocessing<T> stprep(wg, terminals, isTerminal); |
| 120 | reductionFun(stprep); |
| 121 | |
| 122 | EdgeWeightedGraphCopy<T> *treeAfter = nullptr; |
| 123 | T costAfter = stprep.solve(mst, treeAfter); |
| 124 | |
| 125 | EpsilonTest et(1e-6); |
| 126 | AssertThat(et.equal(costAfter, costBefore), IsTrue()); |
| 127 | |
| 128 | AssertThat(MinSteinerTreeModule<T>::isSteinerTree(wg, terminals, isTerminal, *treeAfter), IsTrue()); |
| 129 | AssertThat(et.equal(getCostOfSolution<T>(*treeAfter), costBefore), IsTrue()); |
| 130 | |
| 131 | delete treeBefore; |
| 132 | delete treeAfter; |
| 133 | } |
| 134 | |
| 135 | template<typename T> |
| 136 | static void |
| 137 | testBasicReductions(int numberOfTests) |
| 138 | { |
| 139 | for (int i = 1; i <= numberOfTests; ++i) { |
| 140 | it("does not change solution cost and finds a solution in the original graph" , [&]() { |
| 141 | testReduction<T>([](SteinerTreePreprocessing<T> &stp) { |
| 142 | stp.deleteLeaves(); |
| 143 | stp.degree2Test(); |
| 144 | stp.makeSimple(); |
| 145 | stp.leastCostTest(); |
| 146 | }); |
| 147 | }); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | template<typename T> |
| 152 | static void |
| 153 | testPrecomposedReductions(int numberOfTests) |
| 154 | { |
| 155 | std::pair<const string, std::function<void(SteinerTreePreprocessing<T> &)>> reductions[] = { |
| 156 | { "trivial reductions" , [](SteinerTreePreprocessing<T> &stprep) { |
| 157 | stprep.reduceTrivial(); |
| 158 | }}, |
| 159 | { "fast reductions" , [](SteinerTreePreprocessing<T> &stprep) { |
| 160 | stprep.reduceFast(); |
| 161 | }}, |
| 162 | }; |
| 163 | for (const auto &reduction : reductions) { |
| 164 | describe(reduction.first, [numberOfTests, &reduction]() { |
| 165 | for (int i = 1; i <= numberOfTests; ++i) { |
| 166 | it("does not change solution cost and finds a solution in the original graph" , [&]() { |
| 167 | testReduction<T>(reduction.second); |
| 168 | }); |
| 169 | } |
| 170 | }); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | template<typename T> |
| 175 | static void |
| 176 | testWildMixesOfReductions(string typeName, int numberOfTests) |
| 177 | { |
| 178 | std::pair<string, std::function<void(SteinerTreePreprocessing<T> &)>> reductions[] = { |
| 179 | { "nearest-vertex" , [](SteinerTreePreprocessing<T> &stprep) { |
| 180 | stprep.makeSimple(); |
| 181 | stprep.deleteComponentsWithoutTerminals(); |
| 182 | stprep.nearestVertexTest(); |
| 183 | }}, |
| 184 | { "shortest-link" , [](SteinerTreePreprocessing<T> &stprep) { |
| 185 | stprep.deleteComponentsWithoutTerminals(); |
| 186 | stprep.shortLinksTest(); |
| 187 | }}, |
| 188 | { "PTm" , [](SteinerTreePreprocessing<T> &stprep) { |
| 189 | stprep.deleteComponentsWithoutTerminals(); |
| 190 | stprep.PTmTest(); |
| 191 | }}, |
| 192 | { "terminal-distance" , [](SteinerTreePreprocessing<T> &stprep) { |
| 193 | stprep.deleteComponentsWithoutTerminals(); |
| 194 | stprep.terminalDistanceTest(); |
| 195 | }}, |
| 196 | { "long-edge" , [](SteinerTreePreprocessing<T> &stprep) { |
| 197 | stprep.longEdgesTest(); |
| 198 | }}, |
| 199 | { "NTDk" , [](SteinerTreePreprocessing<T> &stprep) { |
| 200 | stprep.makeSimple(); |
| 201 | stprep.deleteComponentsWithoutTerminals(); |
| 202 | stprep.NTDkTest(); |
| 203 | }}, |
| 204 | { "lower-bound-node" , [](SteinerTreePreprocessing<T> &stprep) { |
| 205 | stprep.deleteComponentsWithoutTerminals(); |
| 206 | stprep.lowerBoundBasedNodeTest(); |
| 207 | }}, |
| 208 | { "lower-bound-edge" , [](SteinerTreePreprocessing<T> &stprep) { |
| 209 | stprep.deleteComponentsWithoutTerminals(); |
| 210 | stprep.lowerBoundBasedEdgeTest(); |
| 211 | }}, |
| 212 | { "reachability" , [](SteinerTreePreprocessing<T> &stprep) { |
| 213 | stprep.makeSimple(); |
| 214 | stprep.deleteComponentsWithoutTerminals(); |
| 215 | stprep.reachabilityTest(); |
| 216 | }}, |
| 217 | { "cut-reachability" , [](SteinerTreePreprocessing<T> &stprep) { |
| 218 | stprep.deleteLeaves(); |
| 219 | stprep.deleteComponentsWithoutTerminals(); |
| 220 | stprep.cutReachabilityTest(); |
| 221 | }}, |
| 222 | }; |
| 223 | auto size = sizeof reductions / sizeof reductions[0]; |
| 224 | for (auto reductionBitmask = 1; reductionBitmask < (1 << size); ++reductionBitmask) { |
| 225 | string desc = "appliance of reductions" ; |
| 226 | ArrayBuffer<int> usedReductions; |
| 227 | for (auto cur = reductionBitmask, i = 0; cur; ++i, cur >>= 1) { |
| 228 | if (cur & 1) { |
| 229 | usedReductions.push(i); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | for (auto i : usedReductions) { |
| 234 | desc += " " + reductions[i].first; |
| 235 | } |
| 236 | desc += " (" + typeName + ")" ; |
| 237 | |
| 238 | describe(desc, [numberOfTests, &usedReductions, &reductions]() { |
| 239 | Array<int> order(usedReductions.size()); |
| 240 | for (int j = 0; j < order.size(); ++j) { |
| 241 | order[j] = j; |
| 242 | } |
| 243 | for (int i = 1; i <= numberOfTests; ++i) { |
| 244 | std::stringstream ss; |
| 245 | ss << "does not change solution cost and finds a solution in the original graph (order " |
| 246 | << order << ")" ; |
| 247 | it(ss.str(), [&]() { |
| 248 | testReduction<T>([&usedReductions, &reductions](SteinerTreePreprocessing<T> &stp) { |
| 249 | for (auto redIndex : usedReductions) { |
| 250 | reductions[redIndex].second(stp); |
| 251 | } |
| 252 | }); |
| 253 | }); |
| 254 | order.permute(); |
| 255 | } |
| 256 | }); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | template<typename T> |
| 261 | static void |
| 262 | registerSuite(const string &typeName) |
| 263 | { |
| 264 | describe("basic reductions (" + typeName + ")" , []() { |
| 265 | testBasicReductions<T>(15); |
| 266 | }); |
| 267 | describe_skip("mix of all subsets of reductions (" + typeName + ")" , [&typeName]() { |
| 268 | testWildMixesOfReductions<T>(typeName, 3); |
| 269 | }); |
| 270 | describe("precomposed reductions (" + typeName + ")" , []() { |
| 271 | testPrecomposedReductions<T>(15); |
| 272 | }); |
| 273 | } |
| 274 | |
| 275 | go_bandit([]() { |
| 276 | describe("SteinerTreePreprocessing" , []() { |
| 277 | registerSuite<int>("int" ); |
| 278 | registerSuite<double>("double" ); |
| 279 | }); |
| 280 | }); |
| 281 | |