routing improvements
This commit is contained in:
committed by
Mizux Seiha
parent
97b64b49f2
commit
1666cf41ab
@@ -18,6 +18,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
@@ -30,7 +31,6 @@
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "google/protobuf/repeated_ptr_field.h"
|
||||
#include "ortools/base/protoutil.h"
|
||||
#include "ortools/constraint_solver/constraint_solver.h"
|
||||
#include "ortools/constraint_solver/routing.h"
|
||||
#include "ortools/constraint_solver/routing_ils.pb.h"
|
||||
@@ -468,6 +468,121 @@ class AllNodesPerformedAcceptanceCriterion
|
||||
const RoutingModel& model_;
|
||||
};
|
||||
|
||||
// Returns the number of performed non-start/end nodes in the given assignment.
|
||||
int CountPerformedNodes(const RoutingModel& model,
|
||||
const Assignment& assignment) {
|
||||
int count = 0;
|
||||
for (int v = 0; v < model.vehicles(); ++v) {
|
||||
int64_t current_node_index = model.Start(v);
|
||||
while (true) {
|
||||
current_node_index = assignment.Value(model.NextVar(current_node_index));
|
||||
if (model.IsEnd(current_node_index)) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Acceptance criterion in which a candidate assignment is accepted when it
|
||||
// performs at least one more node than the reference assignment.
|
||||
class MoreNodesPerformedAcceptanceCriterion
|
||||
: public NeighborAcceptanceCriterion {
|
||||
public:
|
||||
explicit MoreNodesPerformedAcceptanceCriterion(const RoutingModel& model)
|
||||
: model_(model) {}
|
||||
|
||||
bool Accept([[maybe_unused]] const SearchState& search_state,
|
||||
const Assignment* candidate,
|
||||
const Assignment* reference) override {
|
||||
return CountPerformedNodes(model_, *candidate) >
|
||||
CountPerformedNodes(model_, *reference);
|
||||
}
|
||||
|
||||
private:
|
||||
const RoutingModel& model_;
|
||||
};
|
||||
|
||||
class AbsencesBasedAcceptanceCriterion : public NeighborAcceptanceCriterion {
|
||||
public:
|
||||
explicit AbsencesBasedAcceptanceCriterion(
|
||||
const RoutingModel& model, bool remove_route_with_lowest_absences)
|
||||
: model_(model),
|
||||
remove_route_with_lowest_absences_(remove_route_with_lowest_absences),
|
||||
absences_(model.Size(), 0) {}
|
||||
|
||||
bool Accept([[maybe_unused]] const SearchState& search_state,
|
||||
const Assignment* candidate,
|
||||
const Assignment* reference) override {
|
||||
int sum_candidate_absences = 0;
|
||||
int sum_reference_absences = 0;
|
||||
for (int node = 0; node < model_.Size(); ++node) {
|
||||
if (model_.IsStart(node) || model_.IsEnd(node)) continue;
|
||||
if (candidate->Value(model_.NextVar(node)) == node) {
|
||||
sum_candidate_absences += absences_[node];
|
||||
}
|
||||
if (reference->Value(model_.NextVar(node)) == node) {
|
||||
sum_reference_absences += absences_[node];
|
||||
}
|
||||
}
|
||||
return sum_candidate_absences < sum_reference_absences;
|
||||
}
|
||||
|
||||
void OnIterationEnd(const Assignment* reference) override {
|
||||
for (int node = 0; node < model_.Size(); ++node) {
|
||||
if (model_.IsStart(node) || model_.IsEnd(node)) continue;
|
||||
if (reference->Value(model_.NextVar(node)) == node) {
|
||||
++absences_[node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnBestSolutionFound(Assignment* reference) override {
|
||||
if (!remove_route_with_lowest_absences_) return;
|
||||
|
||||
int candidate_route = -1;
|
||||
int min_sum_absences = std::numeric_limits<int>::max();
|
||||
|
||||
for (int route = 0; route < model_.vehicles(); ++route) {
|
||||
if (model_.Next(*reference, model_.Start(route)) == model_.End(route))
|
||||
continue;
|
||||
int sum_absences = 0;
|
||||
for (int64_t node = reference->Value(model_.NextVar(model_.Start(route)));
|
||||
node != model_.End(route);
|
||||
node = reference->Value(model_.NextVar(node))) {
|
||||
sum_absences += absences_[node];
|
||||
}
|
||||
|
||||
if (sum_absences < min_sum_absences) {
|
||||
candidate_route = route;
|
||||
min_sum_absences = sum_absences;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the route with the lowest sum of absences.
|
||||
if (candidate_route != -1) {
|
||||
// Set next pointers for inner nodes.
|
||||
int64_t node =
|
||||
reference->Value(model_.NextVar(model_.Start(candidate_route)));
|
||||
while (node != model_.End(candidate_route)) {
|
||||
const int64_t next_node = reference->Value(model_.NextVar(node));
|
||||
reference->SetValue(model_.NextVar(node), node);
|
||||
reference->SetValue(model_.VehicleVar(node), -1);
|
||||
node = next_node;
|
||||
}
|
||||
// Set next pointer for start node.
|
||||
reference->SetValue(model_.NextVar(model_.Start(candidate_route)),
|
||||
model_.End(candidate_route));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const RoutingModel& model_;
|
||||
bool remove_route_with_lowest_absences_;
|
||||
std::vector<int> absences_;
|
||||
};
|
||||
|
||||
// Returns whether the given assignment has at least one performed node.
|
||||
bool HasPerformedNodes(const RoutingModel& model,
|
||||
const Assignment& assignment) {
|
||||
@@ -1166,6 +1281,12 @@ std::unique_ptr<NeighborAcceptanceCriterion> MakeNeighborAcceptanceCriterion(
|
||||
rnd);
|
||||
case AcceptanceStrategy::kAllNodesPerformed:
|
||||
return std::make_unique<AllNodesPerformedAcceptanceCriterion>(model);
|
||||
case AcceptanceStrategy::kMoreNodesPerformed:
|
||||
return std::make_unique<MoreNodesPerformedAcceptanceCriterion>(model);
|
||||
case AcceptanceStrategy::kAbsencesBased:
|
||||
return std::make_unique<AbsencesBasedAcceptanceCriterion>(
|
||||
model, acceptance_strategy.absences_based()
|
||||
.remove_route_with_lowest_absences());
|
||||
default:
|
||||
LOG(DFATAL) << "Unsupported acceptance strategy.";
|
||||
return nullptr;
|
||||
|
||||
@@ -265,6 +265,12 @@ class NeighborAcceptanceCriterion {
|
||||
virtual bool Accept(const SearchState& search_state,
|
||||
const Assignment* candidate,
|
||||
const Assignment* reference) = 0;
|
||||
|
||||
// Called at the end of an ILS iteration.
|
||||
virtual void OnIterationEnd([[maybe_unused]] const Assignment* reference) {}
|
||||
|
||||
// Called when a new best solution found is found.
|
||||
virtual void OnBestSolutionFound([[maybe_unused]] Assignment* reference) {}
|
||||
};
|
||||
|
||||
// Returns a neighbor acceptance criterion based on the given parameters.
|
||||
|
||||
@@ -54,8 +54,6 @@ message RandomWalkRuinStrategy {
|
||||
// Ruin strategy based on the "Slack Induction by String Removals for Vehicle
|
||||
// Routing Problems" by Jan Christiaens and Greet Vanden Berghe, Transportation
|
||||
// Science 2020.
|
||||
// Link to paper:
|
||||
// https://kuleuven.limo.libis.be/discovery/fulldisplay?docid=lirias1988666&context=SearchWebhook&vid=32KUL_KUL:Lirias&lang=en&search_scope=lirias_profile&adaptor=SearchWebhook&tab=LIRIAS&query=any,contains,LIRIAS1988666&offset=0
|
||||
//
|
||||
// Note that, in this implementation, the notion of "string" is replaced by
|
||||
// "sequence".
|
||||
@@ -296,12 +294,42 @@ message SimulatedAnnealingAcceptanceStrategy {
|
||||
// performed.
|
||||
message AllNodesPerformedAcceptanceStrategy {}
|
||||
|
||||
// Acceptance strategy in which a solution is accepted only if it performs at
|
||||
// least one more node than the reference solution.
|
||||
message MoreNodesPerformedAcceptanceStrategy {}
|
||||
|
||||
// Acceptance strategy in which a solution is accepted only if it has less
|
||||
// absences than the reference solution (see Slack Induction by String Removals
|
||||
// for Vehicle Routing Problems" Christiaens and Vanden Berghe, Transportation
|
||||
// Science 2020).
|
||||
//
|
||||
// In particular, for each node n, the number of solutions where n was not
|
||||
// performed by any route is tracked by a counter absences[n]. A candidate is
|
||||
// accepted if
|
||||
// sum(absences[n]) < sum(absences[m])
|
||||
// with
|
||||
// n in unperformed(candidate)
|
||||
// m in unperformed(reference)
|
||||
//
|
||||
// The counter absences is increased after every ILS iteration for the
|
||||
// unperformed nodes in the reference solution. In addition, when
|
||||
// remove_route_with_lowest_absences is true and a new best found solution is
|
||||
// found, the route with the lowest sum of absences is removed from the
|
||||
// reference solution.
|
||||
message AbsencesBasedAcceptanceStrategy {
|
||||
// If true, when a new best solution is found, the route with the lowest sum
|
||||
// of absences is removed from the reference solution.
|
||||
optional bool remove_route_with_lowest_absences = 1;
|
||||
}
|
||||
|
||||
// Determines when a candidate solution replaces another one.
|
||||
message AcceptanceStrategy {
|
||||
oneof strategy {
|
||||
GreedyDescentAcceptanceStrategy greedy_descent = 1;
|
||||
SimulatedAnnealingAcceptanceStrategy simulated_annealing = 2;
|
||||
AllNodesPerformedAcceptanceStrategy all_nodes_performed = 3;
|
||||
MoreNodesPerformedAcceptanceStrategy more_nodes_performed = 4;
|
||||
AbsencesBasedAcceptanceStrategy absences_based = 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user