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