diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d74633..c6ffa59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(${PROJECT_NAME} src/analysis/real_data.cpp src/cvrp/cvrp.cpp src/cvrp/stage_1.cpp + src/cvrp/stage_2.cpp src/cvrp/visualization.cpp src/data_structures/quadtree.cpp src/data_structures/kd_tree.cpp diff --git a/src/cvrp/stage_2.cpp b/src/cvrp/stage_2.cpp new file mode 100644 index 0000000..d77be6d --- /dev/null +++ b/src/cvrp/stage_2.cpp @@ -0,0 +1,94 @@ + +#include + +#include "stage_2.hpp" +#include "../algorithms/ant_colony.hpp" +#include "../algorithms/greedy.hpp" +#include "../algorithms/simulated_annealing.hpp" +#include "../algorithms/tabu_search.hpp" + +using namespace std; + +template +void readOption(T& option, const char* prompt, function conversionFunc) { + cout << prompt << flush; + string str; + getline(cin, str); + + if (!str.empty()) { + option = conversionFunc(str); + } +} + +inline u64 convertUnsignedInt(const string& str) { + return stoull(str); +} + +inline double convertDouble(const string& str) { + return stod(str); +} + +bool convertBool(const string& str) { + string lower(str); + transform(str.begin(), str.end(), lower.begin(), ::tolower); + return lower == "yes"; +} + +CvrpSolution applyAntColonyOptimization(const CvrpInstance& instance, bool config) { + AntColonyConfig acoConfig; + + readOption(acoConfig.alpha, "Alpha: ", convertDouble); + readOption(acoConfig.beta, "Beta: ", convertDouble); + readOption(acoConfig.numAnts, "Num ants: ", convertUnsignedInt); + readOption(acoConfig.eliteAnts, "Elite ants: ", convertUnsignedInt); + readOption(acoConfig.maxIterations, "Max. iterations: ", convertUnsignedInt); + readOption(acoConfig.useSwapHeuristic, "Use swap heuristic (yes / no): ", convertBool); + + return antColonyOptimization(instance, acoConfig); +} + +CvrpSolution applyClarkeWrightSavings(const CvrpInstance& instance, bool config) { + return clarkeWrightSavings(instance); +} + +CvrpSolution applyGranularTabuSearch(const CvrpInstance& instance, bool config) { + size_t maxIterations = 1000; + double beta = 1.5; + + readOption(maxIterations, "Max. iterations: ", convertUnsignedInt); + readOption(beta, "Beta: ", convertDouble); + + return granularTabuSearch(instance, maxIterations, beta); +} + +CvrpSolution applyGreedyAlgorithm(const CvrpInstance& instance, bool config) { + return greedyAlgorithm(instance); +} + +CvrpSolution applySimulatedAnnealing(const CvrpInstance& instance, bool config) { + InitialSolution initialSolution; + + readOption( + initialSolution, + "Initial solution (0 - trivial, 1 - greedy, 2 - Clarke-Wright): ", + [](const string& str) { return (InitialSolution) stoi(str); } + ); + + return simulatedAnnealing(instance, initialSolution); +} + +CvrpSolution applyCvrpAlgorithm(string algorithm, const CvrpInstance& instance, bool config) { + static const unordered_map> algorithms = { + {"aco", applyAntColonyOptimization}, + {"cws", applyClarkeWrightSavings}, + {"gts", applyGranularTabuSearch}, + {"greedy", applyGreedyAlgorithm}, + {"sa", applySimulatedAnnealing} + }; + + if (algorithms.count(algorithm.c_str())) { + return algorithms.at(algorithm.c_str())(instance, config); + } + + return { {} , 0 }; +} diff --git a/src/cvrp/stage_2.hpp b/src/cvrp/stage_2.hpp new file mode 100644 index 0000000..55905af --- /dev/null +++ b/src/cvrp/stage_2.hpp @@ -0,0 +1,10 @@ + +#ifndef CVRP_STAGE_2_H +#define CVRP_STAGE_2_H + +#include +#include "cvrp.hpp" + +CvrpSolution applyCvrpAlgorithm(std::string algorithm, const CvrpInstance& instance, bool config); + +#endif // CVRP_STAGE_2_H diff --git a/src/cvrp/visualization.cpp b/src/cvrp/visualization.cpp index c386f4e..9f35e35 100644 --- a/src/cvrp/visualization.cpp +++ b/src/cvrp/visualization.cpp @@ -12,8 +12,9 @@ sf::Vector2f vecFromCoordinates(const Coordinates& coords, float scale = 1.0) { return sf::Vector2f(coords.getLongitude() * scale, coords.getLatitude() * scale); } -GraphVisualizationResult generateGraphVisualization(const OsmXmlData& data, float scale) { - GraphVisualizationResult result; +GraphVisualizationResult* generateGraphVisualization(const OsmXmlData& data, float scale) { + GraphVisualizationResult* resultPtr = new GraphVisualizationResult(); + GraphVisualizationResult& result = *resultPtr; // TODO: make this a parameter? result.gv->setScale(5); @@ -49,7 +50,7 @@ GraphVisualizationResult generateGraphVisualization(const OsmXmlData& data, floa ); setGraphCenter(*(result.gv), center, scale); - return result; + return resultPtr; } void setGraphCenter(GraphViewer& gv, const Coordinates& coords, float scale) { @@ -110,7 +111,7 @@ void highlightPath(GraphVisualizationResult& result, const list& path, cons static const u32 MAX_RGB = 510; void showSolution(GraphVisualizationResult& result, const MapMatchingResult& mmResult, const Graph& graph, const CvrpSolution& solution) { - auto matchedNode = [mmResult](u64 idx) { + auto matchedNode = [&mmResult](u64 idx) { return idx == 0 ? mmResult.originNode : mmResult.deliveryNodes[idx - 1]; }; @@ -118,6 +119,12 @@ void showSolution(GraphVisualizationResult& result, const MapMatchingResult& mmR chrono::system_clock::now().time_since_epoch().count() ); + auto& origin = result.gv->getNode(mmResult.originNode); + origin.setColor(sf::Color::Green); + origin.setSize(35); + origin.setLabel("O"); + origin.enable(); + for (const auto& route : solution.routes) { array rgb = {0, 0, 0}; rgb[0] = min(MAX_RGB, (u32) rand() % 256); @@ -131,6 +138,14 @@ void showSolution(GraphVisualizationResult& result, const MapMatchingResult& mmR u64 from = matchedNode(route[i]), to = matchedNode(route[i + 1]); list path = aStarSearch(graph, from, to).first; highlightPath(result, path, color); + + if (route[i + 1] != 0) { + auto& node = result.gv->getNode(to); + node.setColor(color); + node.setSize(35); + node.setLabel(to_string(route[i + 1])); + node.enable(); + } } } } diff --git a/src/cvrp/visualization.hpp b/src/cvrp/visualization.hpp index b0d4725..54fbd4c 100644 --- a/src/cvrp/visualization.hpp +++ b/src/cvrp/visualization.hpp @@ -26,7 +26,7 @@ struct GraphVisualizationResult { } }; -GraphVisualizationResult generateGraphVisualization(const OsmXmlData& data, float scale = 200000.0); +GraphVisualizationResult* generateGraphVisualization(const OsmXmlData& data, float scale = 200000.0); void setGraphCenter(GraphViewer& gv, const Coordinates& coords, float scale = 200000.0); void showMapMatchingResults(GraphViewer& gv, const CvrpInstance& instance, const MapMatchingResult& result, float scale = 200000.0); diff --git a/src/main.cpp b/src/main.cpp index ca261bc..f7063cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,11 +9,16 @@ #include "analysis/real_data.hpp" #include "cvrp/cvrp.hpp" #include "cvrp/stage_1.hpp" +#include "cvrp/stage_2.hpp" #include "cvrp/visualization.hpp" #include "utils.hpp" using namespace std; +static const unordered_set algorithms = { + "greedy", "cws", "sa", "gts", "aco" +}; + int main(int argc, char** argv) { cxxopts::Options opts("cvrp", "Solver for large CVRP instances from the LoggiBUD dataset"); @@ -25,11 +30,14 @@ int main(int argc, char** argv) { ("dm", "[OPT] Path to distance matrix", cxxopts::value()) ("vmm", "[OPT] Visualize map matching") ("vsp", "[OPT] Visualize shortest paths (for depot point)") + ("vs", "[OPT] Visualize the CVRP solution obtained by the solver") ("t,threads", "[OPT] Number of threads to use in shortest path calculation", cxxopts::value()->default_value("1")) ("h,help", "[OPT] Print usage") ("l,logs", "[OPT] Enable additional execution logs") ("quadtree", "[OPT] Use quadtrees instead of k-d trees for map matching") ("bin-heap", "[OPT] Use binary heaps instead of Fibonacci heaps for Dijkstra's algorithm") + ("a,algorithm", "[OPT] Algorithm used to solve the CVRP. Possibilities are: 'greedy', 'cws', 'sa', 'gts' and 'aco'. Defaults to 'cws'", cxxopts::value()) + ("c,config", "[OPT] Use custom configuration for chosen CVRP algorithm") ; auto result = opts.parse(argc, argv); @@ -39,11 +47,24 @@ int main(int argc, char** argv) { exit(0); } + string cvrpAlgorithm = "cws"; + if (result.count("algorithm")) { + cvrpAlgorithm = result["algorithm"].as(); + if (!algorithms.count(cvrpAlgorithm.c_str())) { + cerr << "Error: `algorithm` must be a valid CVRP algorithm (given: '" + << cvrpAlgorithm << "')." << endl; + exit(1); + } + } + bool logs = result["logs"].as(); u32 threads = result["threads"].as(); - bool mmVis = result["vmm"].as(), spVis = result["vsp"].as(); - + bool mmVis = result["vmm"].as(), spVis = result["vsp"].as(), + solVis = result["vs"].as(); + + bool config = result["config"].as(); + MapMatchingDataStructure mmDataStructure = result["quadtree"].as() ? QUADTREE : KD_TREE; ShortestPathDataStructure spDataStructure = result["bin-heap"].as() ? BINARY_HEAP : FIBONACCI_HEAP; @@ -67,21 +88,27 @@ int main(int argc, char** argv) { ifstream ifs(cvrpPath); CvrpInstance instance(ifs); - cout << "Generating graph visualization..." << endl; - GraphVisualizationResult gvr = generateGraphVisualization(data); - GraphViewer& gv = gvr.gvRef(); + GraphVisualizationResult* gvr; + GraphViewer* gv = nullptr; + + if (spVis || mmVis || solVis) { + cout << "Generating graph visualization..." << endl; + gvr = generateGraphVisualization(data); + gv = gvr->gv; + } cout << "Matching coordinates to OSM network nodes..." << endl; MapMatchingResult mmResult = matchLocations(data, instance, mmDataStructure, logs); - showMapMatchingResults(gv, instance, mmResult); - setGraphCenter(gv, instance.getOrigin()); - gv.setZipEdges(true); - gv.setEnabledEdgesText(false); if (mmVis) { - gv.createWindow(1280, 720); - gv.join(); - gv.closeWindow(); + showMapMatchingResults(*gv, instance, mmResult); + setGraphCenter(*gv, instance.getOrigin()); + gv->setZipEdges(true); + gv->setEnabledEdgesText(false); + + gv->createWindow(1280, 720); + gv->join(); + gv->closeWindow(); } string dmPath = ""; @@ -107,19 +134,34 @@ int main(int argc, char** argv) { mmResult.originNode, mmResult.deliveryNodes, spDataStructure); for (const auto& res : spResult) { - highlightPath(gvr, res.path); + highlightPath(*gvr, res.path); } - gv.setZipEdges(true); + gv->setZipEdges(true); - gv.createWindow(1280, 720); - gv.join(); - gv.closeWindow(); + gv->createWindow(1280, 720); + gv->join(); + gv->closeWindow(); } if (!dmPath.empty()) { instance.writeDistanceMatrixToFile(dmPath.c_str()); } } + + CvrpSolution solution = applyCvrpAlgorithm(cvrpAlgorithm, instance, config); + + cout << "Final solution has length " << solution.length / 1000.0 << " and uses " + << solution.routes.size() << " vehicles." << endl; + + if (solVis) { + showSolution(*gvr, mmResult, data.graph, solution); + gv->setZipEdges(true); + gv->createWindow(1280, 720); + gv->join(); + gv->closeWindow(); + } + + delete gvr; } else { cerr << "Error: `cvrp` and `osm` options are required." << endl;