From caf739597e2d90939398c6537b059bdf5f567849 Mon Sep 17 00:00:00 2001 From: awenjb <126257927+awenjb@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:34:58 +0100 Subject: [PATCH] Add acceptance function --- CMakeLists.txt | 1 + src/lns/acceptance/acceptance_function.h | 29 ++++++ src/lns/acceptance/threshold_acceptance.cpp | 22 ++++ src/lns/acceptance/threshold_acceptance.h | 21 ++++ src/lns/lns.cpp | 59 ++++++++++- src/lns/solution/solution.cpp | 89 +++++++++++++--- src/lns/solution/solution.h | 19 +++- src/mains/main.cpp | 109 ++++++++------------ 8 files changed, 263 insertions(+), 86 deletions(-) create mode 100644 src/lns/acceptance/acceptance_function.h create mode 100644 src/lns/acceptance/threshold_acceptance.cpp create mode 100644 src/lns/acceptance/threshold_acceptance.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f9c3518..1501935 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(pdptw src/mains/main.cpp src/input/pdptw_data.cpp src/input/time_window.cpp src/input/json_parser.cpp + src/lns/acceptance/threshold_acceptance.cpp src/lns/solution/route.cpp src/lns/solution/solution.cpp src/lns/constraints/constraint.cpp diff --git a/src/lns/acceptance/acceptance_function.h b/src/lns/acceptance/acceptance_function.h new file mode 100644 index 0000000..e7c2dae --- /dev/null +++ b/src/lns/acceptance/acceptance_function.h @@ -0,0 +1,29 @@ +#pragma once + +class Solution; +/** + * see AcceptanceFunction::operator() + */ +enum class AcceptationStatus +{ + ACCEPT, + REFUSE +}; + +/** + * Function object that accept or not a solution as the new solution for the next LNS iteration. + */ +class AcceptanceFunction +{ +public: + virtual ~AcceptanceFunction() = default; + /** + * Indicate which solution must be used for the next iteration. + * @param candidateSolution the solution that can be accepted + * @param actualSolution the solution used to create the candidate solution + * @param bestSolution the best known solution known to this point. + * @return ACCEPT to use \p candidateSolution for next iteration, REFUSE to continue to use \p actualSolution. + */ + virtual AcceptationStatus operator()(Solution const &candidateSolution, Solution const &actualSolution, + Solution const &bestSolution) const = 0; +}; \ No newline at end of file diff --git a/src/lns/acceptance/threshold_acceptance.cpp b/src/lns/acceptance/threshold_acceptance.cpp new file mode 100644 index 0000000..5f53214 --- /dev/null +++ b/src/lns/acceptance/threshold_acceptance.cpp @@ -0,0 +1,22 @@ +#include "threshold_acceptance.h" + +#include "lns/solution/solution.h" + +AcceptationStatus ThresholdAcceptance::operator()(Solution const &candidateSolution, Solution const &actualSolution, + Solution const &bestSolution) const +{ + // the threshold does not take into account the penalisation cost. + if (candidateSolution.getCost() < bestSolution.getRawCost() * thresholdValue + bestSolution.computePenalisation()) + { + // accept because of threshold on best solution + return AcceptationStatus::ACCEPT; + } + if (candidateSolution.getCost() < actualSolution.getCost()) + { + // accept because better than the actual solution + return AcceptationStatus::ACCEPT; + } + return AcceptationStatus::REFUSE; +} + +ThresholdAcceptance::ThresholdAcceptance(double threshold) : thresholdValue(1 + threshold) {} \ No newline at end of file diff --git a/src/lns/acceptance/threshold_acceptance.h b/src/lns/acceptance/threshold_acceptance.h new file mode 100644 index 0000000..51fc0c7 --- /dev/null +++ b/src/lns/acceptance/threshold_acceptance.h @@ -0,0 +1,21 @@ +#pragma once + +#include "acceptance_function.h" + +/** + * Simple implementation of AcceptanceFunction. + * If the candidate is not better than the best solution, we accept it if the relative difference is under a threshold. + * ACCEPT If better than the actual solution. + */ +class ThresholdAcceptance : public AcceptanceFunction +{ + double thresholdValue = 1.05; + +public: + /** + * @param threshold 0.05 means that a solution with a ratio with a solution worse by less than 5% will be accepted + */ + explicit ThresholdAcceptance(double threshold = 0.05); + AcceptationStatus operator()(Solution const &candidateSolution, Solution const &actualSolution, + Solution const &bestSolution) const override; +}; \ No newline at end of file diff --git a/src/lns/lns.cpp b/src/lns/lns.cpp index 661bbf3..aee47c9 100644 --- a/src/lns/lns.cpp +++ b/src/lns/lns.cpp @@ -1,11 +1,62 @@ #include "lns.h" -output::LnsOutput runLns(Solution const &initialSolution, OperatorSelector &opSelector, AcceptanceFunction const &acceptFunctor) +#include "lns/acceptance/acceptance_function.h" +#include "lns/operators/selector/operator_selector.h" +#include "output/solution_checker.h" + +namespace +{ + bool isBetterSolution(Solution const &candidateSolution, Solution const &bestKnownSol) + { + return candidateSolution.getCost() < bestKnownSol.getCost(); + } + +}// namespace + +output::LnsOutput lns::runLns(Solution const &initialSolution, OperatorSelector &opSelector, + AcceptanceFunction const &acceptFunctor) { + /** + * The solution is the base solution used to create the neighbor solution. + * It is constant unless we accept the candidate solution. + */ + Solution actualSolution = initialSolution; + Solution bestSolution = initialSolution; + + int it = 10; + while (it > 0) + { + /** + * The solution on which we apply the operators. + * It is discarded at the end of each loop if it is not accepted by the Acceptance Function. + */ + Solution candidateSolution = actualSolution; + + + // Chose operator pair + auto destructReconstructPair = opSelector.getOperatorPair(); + // Apply operators + destructReconstructPair.destructor().destroySolution(candidateSolution); + destructReconstructPair.reconstructor().reconstructSolution(candidateSolution, 0.01); - // TO DO + // Update best solution + if (isBetterSolution(candidateSolution, bestSolution)) + { + checker::checkAll(candidateSolution, candidateSolution.getData(), false); + bestSolution = candidateSolution; + opSelector.betterSolutionFound(); + } - //auto result = output::LnsOutput(std::move(runtime.bestSolution), runtime.numberOfIteration, getTimeSinceInSec(runtime.start)); - //return result; + // Check if we use the candidate solution as the new actual solution + // operator can force to take the new solution + if (destructReconstructPair.forceTakeSolution() || + acceptFunctor(candidateSolution, actualSolution, bestSolution) == AcceptationStatus::ACCEPT) + { + actualSolution = std::move(candidateSolution); + } + --it; + } + auto result = output::LnsOutput(std::move(bestSolution), it, it); + return result; } \ No newline at end of file diff --git a/src/lns/solution/solution.cpp b/src/lns/solution/solution.cpp index dbbb005..e563f6e 100644 --- a/src/lns/solution/solution.cpp +++ b/src/lns/solution/solution.cpp @@ -8,6 +8,7 @@ #include "lns/solution/route.h" #include "output/solution_checker.h" +#include <bits/ranges_algo.h> #include <bits/ranges_util.h> #include <utility> @@ -35,10 +36,10 @@ void Solution::initConstraints() void Solution::computeAndStoreSolutionCost() { - routeCost = computeSolutionCost(); + rawCost = computeSolutionCost(); // add penalty for solution in the pairBank ? - totalCost = routeCost + computePenalization(); + totalCost = rawCost + computePenalisation(); } double Solution::computeSolutionCost() const @@ -51,7 +52,7 @@ double Solution::computeSolutionCost() const return cost; } -double Solution::computePenalization() const +double Solution::computePenalisation() const { return getBank().size() * EXCLUSION_PENALTY; } @@ -64,9 +65,9 @@ void Solution::init() computeAndStoreSolutionCost(); } -Solution::Solution(PDPTWData const &data, Solution::PairBank pairbank, std::vector<Route> routes, double routeCost, +Solution::Solution(PDPTWData const &data, Solution::PairBank pairbank, std::vector<Route> routes, double rawCost, double totalCost) - : data(data), pairBank(std::move(pairbank)), routes(std::move(routes)), routeCost(routeCost), totalCost(totalCost) + : data(data), pairBank(std::move(pairbank)), routes(std::move(routes)), rawCost(rawCost), totalCost(totalCost) {} Solution::Solution(PDPTWData const &data) : data(data) @@ -80,6 +81,64 @@ Solution Solution::emptySolution(PDPTWData const &data) return s; } +Solution::~Solution() noexcept = default; + +Solution::Solution(Solution const &rhs) : Solution(rhs.getData()) +{ + *this = rhs; +} + +Solution &Solution::operator=(Solution const &rhs) +{ + if (&rhs == this) + { + return *this; + } + + data = rhs.data; + rawCost = rhs.rawCost; + totalCost = rhs.totalCost; + pairBank = rhs.pairBank; + + routes.clear(); + routes = rhs.routes; + + constraints.clear(); + std::ranges::transform(rhs.constraints, std::back_inserter(constraints), [this](auto const &constraintPtr) { + return constraintPtr->clone(*this); + }); + + return *this; +} + +Solution::Solution(Solution &&sol) noexcept : data(sol.data) +{ + *this = std::move(sol); +} + +Solution &Solution::operator=(Solution &&sol) noexcept +{ + if (&sol == this) + { + return *this; + } + + data = sol.data; + rawCost = sol.rawCost; + totalCost = sol.totalCost; + + pairBank = std::move(sol.pairBank); + routes = std::move(sol.routes); + constraints = std::move(sol.constraints); + + for (auto &constraint: constraints) + { + constraint->setSolution(*this); + } + + return *this; +} + Solution::PairBank const &Solution::getBank() const { return pairBank; @@ -127,7 +186,12 @@ Route const &Solution::getRoute(int routeIndex) const double Solution::getCost() const { - return totalCost; + return rawCost + computePenalisation(); +} + +double Solution::getRawCost() const +{ + return rawCost; } PDPTWData const &Solution::getData() const @@ -145,7 +209,7 @@ int Solution::requestsFulFilledCount() const return count; } -bool Solution::checkModification(AtomicRecreation const &modification) const +bool Solution::checkModification(AtomicRecreation const &modification) const { std::cout << "--- Check Modification Validity : "; ModificationCheckVariant const &checkVariant = modification.asCheckVariant(); @@ -159,10 +223,9 @@ bool Solution::checkModification(AtomicRecreation const &modification) const } } std::cout << "\n"; - return true; + return true; } - void Solution::beforeApplyModification(AtomicModification &modification) { // pre modification check @@ -201,8 +264,8 @@ void Solution::applyDestructSolution(AtomicDestruction &modification) modification.modifySolution(*this); // updating request bank std::vector<int> const &deletedPair = modification.getDeletedPairs(); - - //pairBank.reserve(pairBank.size() + deletedPair.size()); + + //pairBank.reserve(pairBank.size() + deletedPair.size()); pairBank.insert(pairBank.end(), deletedPair.begin(), deletedPair.end()); afterApplyModification(modification); @@ -211,7 +274,7 @@ void Solution::applyDestructSolution(AtomicDestruction &modification) void Solution::check() const { - checker::checkSolutionCoherence(*this, getData()); + checker::checkSolutionCoherence(*this, getData()); } void Solution::print() const @@ -232,7 +295,7 @@ void Solution::print() const } std::cout << "\nConstraints : \n"; - for (const std::unique_ptr<Constraint> &constraint: constraints) + for (std::unique_ptr<Constraint> const &constraint: constraints) { constraint->print(); } diff --git a/src/lns/solution/solution.h b/src/lns/solution/solution.h index fca2ddc..776a0ad 100644 --- a/src/lns/solution/solution.h +++ b/src/lns/solution/solution.h @@ -34,16 +34,29 @@ private: * Vector of routes representing the solution */ std::vector<Route> routes; - double routeCost; + double rawCost; double totalCost; std::vector<std::unique_ptr<Constraint>> constraints; public: + //========CONSTRUCTORS, COPY, MOVE, DESTRUCTORS=========== /** * Expected way to construct a solution. * Generate an empty solution with all pairs in the pairBank and one empty route. */ static Solution emptySolution(PDPTWData const &data); + /** + * In depth copy of the solution + */ + Solution(Solution const &); + /** + * In depth copy of the solution + */ + Solution &operator=(Solution const &); + Solution(Solution &&) noexcept; + Solution &operator=(Solution &&) noexcept; + ~Solution() noexcept; + explicit Solution(PDPTWData const &data); @@ -61,6 +74,7 @@ public: std::vector<Route> const &getRoutes() const; Route const &getRoute(int routeIndex) const; PDPTWData const &getData() const; + double getRawCost() const; double getCost() const; /** @@ -104,6 +118,7 @@ public: */ void applyDestructSolution(AtomicDestruction &modification); + double computePenalisation() const; // For route modification std::vector<Route> &getRoutes(); @@ -131,6 +146,4 @@ private: * Compute the cost of the solution (routes cost) */ double computeSolutionCost() const; - - double computePenalization() const; }; \ No newline at end of file diff --git a/src/mains/main.cpp b/src/mains/main.cpp index db4f2f9..fc86864 100644 --- a/src/mains/main.cpp +++ b/src/mains/main.cpp @@ -1,37 +1,36 @@ -#include <nlohmann/json.hpp> -#include <vector> -#include <iostream> -#include <string> -#include <filesystem> -#include <fstream> -#include <spdlog/spdlog.h> - +#include "input/data.h" +#include "input/json_parser.h" #include "input/location.h" #include "input/pdptw_data.h" #include "input/time_window.h" -#include "input/json_parser.h" -#include "input/data.h" +#include "lns/acceptance/threshold_acceptance.h" #include "lns/constraints/capacity/capacity_constraint.h" #include "lns/constraints/time_window/time_window_constraint.h" -#include "lns/operators/destruction/random_destroy.h" -#include "lns/operators/reconstruction/list_heuristic_cost_oriented.h" -#include "lns/operators/selector/operator_selector.h" -#include "lns/operators/selector/small_large_selector.h" -#include "lns/solution/solution.h" +#include "lns/lns.h" #include "lns/modification/pair/insert_pair.h" -#include "lns/modification/route/insert_route.h" #include "lns/modification/pair/remove_pair.h" +#include "lns/modification/route/insert_route.h" #include "lns/modification/route/remove_route.h" - +#include "lns/operators/destruction/random_destroy.h" +#include "lns/operators/reconstruction/enumerate.h" +#include "lns/operators/reconstruction/list_heuristic_cost_oriented.h" #include "lns/operators/reconstruction/list_heuristic_insertion.h" +#include "lns/operators/selector/operator_selector.h" +#include "lns/operators/selector/small_large_selector.h" #include "lns/operators/sorting_strategy.h" - -#include "lns/operators/reconstruction/enumerate.h" - +#include "lns/solution/solution.h" #include "mains/main_interface.h" #include "output/solution_exporter.h" +#include <filesystem> +#include <fstream> +#include <iostream> +#include <nlohmann/json.hpp> +#include <spdlog/spdlog.h> +#include <string> +#include <vector> + using json = nlohmann::json; void simpleLNS(PDPTWData const &data, Solution &startingSolution) @@ -43,7 +42,7 @@ void simpleLNS(PDPTWData const &data, Solution &startingSolution) int manyPairs = requests * 40 / 100; // threshold function to do - + ThresholdAcceptance acceptor(0.05); // lns operators SimpleOperatorSelector smallSelector; @@ -54,67 +53,45 @@ void simpleLNS(PDPTWData const &data, Solution &startingSolution) addAllReconstructor(largeSelector); largeSelector.addDestructor(RandomDestroy(pairs)); - SimpleOperatorSelector veryLargeSelector; - addAllReconstructor(veryLargeSelector); - veryLargeSelector.addDestructor(RandomDestroy(pairs)); + // SimpleOperatorSelector veryLargeSelector; + // addAllReconstructor(veryLargeSelector); + // veryLargeSelector.addDestructor(RandomDestroy(pairs)); - SimpleOperatorSelector hugeSelector; - addAllReconstructor(hugeSelector); - hugeSelector.addDestructor(RandomDestroy(pairs)); + // SimpleOperatorSelector hugeSelector; + // addAllReconstructor(hugeSelector); + // hugeSelector.addDestructor(RandomDestroy(pairs)); - SimpleOperatorSelector lastSelector; - addAllReconstructor(lastSelector); - lastSelector.addDestructor(RandomDestroy(manyPairs)); + // SimpleOperatorSelector lastSelector; + // addAllReconstructor(lastSelector); + // lastSelector.addDestructor(RandomDestroy(manyPairs)); std::vector<SmallLargeOperatorSelector::StepSelector> selectors; - selectors.emplace_back(500, std::move(smallSelector)); - selectors.emplace_back(1000, std::move(largeSelector)); - selectors.emplace_back(1000, std::move(veryLargeSelector)); - selectors.emplace_back(3000, std::move(hugeSelector)); - selectors.emplace_back(1, std::move(lastSelector)); + selectors.emplace_back(2, std::move(smallSelector)); + selectors.emplace_back(2, std::move(largeSelector)); + // selectors.emplace_back(2, std::move(veryLargeSelector)); + // selectors.emplace_back(2, std::move(hugeSelector)); + // selectors.emplace_back(2, std::move(lastSelector)); SmallLargeOperatorSelector smallLargeSelector(std::move(selectors)); // run lns - //lns::runLns(startingSolution, smallLargeSelector, acceptor); + lns::runLns(startingSolution, smallLargeSelector, acceptor); } -int main(int argc, char const *argv[]) +int main(int argc, char **argv) { - //std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/n100/bar-n100-1.json"; - std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/Nantes_1.json"; - //std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/n5000/bar-n5000-1.json"; - - + //return mainInterface(argc, argv, &simpleLNS); ///////////////////////////////////////////////////////////////////////:: - // std::cout << filepath << "\n"; - - // data.print(); - - // std::cout << " \n"; - // /* - // * test - // */ - // Solution solution = Solution::emptySolution(data); - - // std::cout << "--- Empty Solution --- \n"; - // solution.print(); - - // double blinkRate = 0; - // SortingStrategyType strategy = SortingStrategyType::SHUFFLE; - // EnumerationType enumeration = EnumerationType::ALL_INSERT_PAIR; - - // std::cout << "\n --- Operator - SHUFFLE - ALL_INSERTPAIR -> reconstruction (NO COST UPDATE)\n"; - - // ListHeuristicCostOriented heuristic; - // heuristic.reconstructSolution(solution, blinkRate, strategy, enumeration); + //std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/n100/bar-n100-1.json"; + std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/Nantes_1.json"; + //std::string filepath = "/home/a24jacqb/Documents/Code/pdptw-main/data_in/n5000/bar-n5000-1.json"; - // RandomDestroy randomdestroy = RandomDestroy(4); - // randomdestroy.destroySolution(solution); + PDPTWData data = parsing::parseJson(filepath); + Solution startingSolution = Solution::emptySolution(data); + simpleLNS(data, startingSolution); - //output::exportToJson(solution); return 0; } -- GitLab