Skip to content

Commit 1666cf4

Browse files
lperronMizux
authored andcommitted
routing improvements
1 parent 97b64b4 commit 1666cf4

File tree

3 files changed

+158
-3
lines changed

3 files changed

+158
-3
lines changed

ortools/constraint_solver/routing_ils.cc

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <cstddef>
1919
#include <cstdint>
2020
#include <functional>
21+
#include <limits>
2122
#include <memory>
2223
#include <optional>
2324
#include <random>
@@ -30,7 +31,6 @@
3031
#include "absl/time/time.h"
3132
#include "absl/types/span.h"
3233
#include "google/protobuf/repeated_ptr_field.h"
33-
#include "ortools/base/protoutil.h"
3434
#include "ortools/constraint_solver/constraint_solver.h"
3535
#include "ortools/constraint_solver/routing.h"
3636
#include "ortools/constraint_solver/routing_ils.pb.h"
@@ -468,6 +468,121 @@ class AllNodesPerformedAcceptanceCriterion
468468
const RoutingModel& model_;
469469
};
470470

471+
// Returns the number of performed non-start/end nodes in the given assignment.
472+
int CountPerformedNodes(const RoutingModel& model,
473+
const Assignment& assignment) {
474+
int count = 0;
475+
for (int v = 0; v < model.vehicles(); ++v) {
476+
int64_t current_node_index = model.Start(v);
477+
while (true) {
478+
current_node_index = assignment.Value(model.NextVar(current_node_index));
479+
if (model.IsEnd(current_node_index)) {
480+
break;
481+
}
482+
count++;
483+
}
484+
}
485+
return count;
486+
}
487+
488+
// Acceptance criterion in which a candidate assignment is accepted when it
489+
// performs at least one more node than the reference assignment.
490+
class MoreNodesPerformedAcceptanceCriterion
491+
: public NeighborAcceptanceCriterion {
492+
public:
493+
explicit MoreNodesPerformedAcceptanceCriterion(const RoutingModel& model)
494+
: model_(model) {}
495+
496+
bool Accept([[maybe_unused]] const SearchState& search_state,
497+
const Assignment* candidate,
498+
const Assignment* reference) override {
499+
return CountPerformedNodes(model_, *candidate) >
500+
CountPerformedNodes(model_, *reference);
501+
}
502+
503+
private:
504+
const RoutingModel& model_;
505+
};
506+
507+
class AbsencesBasedAcceptanceCriterion : public NeighborAcceptanceCriterion {
508+
public:
509+
explicit AbsencesBasedAcceptanceCriterion(
510+
const RoutingModel& model, bool remove_route_with_lowest_absences)
511+
: model_(model),
512+
remove_route_with_lowest_absences_(remove_route_with_lowest_absences),
513+
absences_(model.Size(), 0) {}
514+
515+
bool Accept([[maybe_unused]] const SearchState& search_state,
516+
const Assignment* candidate,
517+
const Assignment* reference) override {
518+
int sum_candidate_absences = 0;
519+
int sum_reference_absences = 0;
520+
for (int node = 0; node < model_.Size(); ++node) {
521+
if (model_.IsStart(node) || model_.IsEnd(node)) continue;
522+
if (candidate->Value(model_.NextVar(node)) == node) {
523+
sum_candidate_absences += absences_[node];
524+
}
525+
if (reference->Value(model_.NextVar(node)) == node) {
526+
sum_reference_absences += absences_[node];
527+
}
528+
}
529+
return sum_candidate_absences < sum_reference_absences;
530+
}
531+
532+
void OnIterationEnd(const Assignment* reference) override {
533+
for (int node = 0; node < model_.Size(); ++node) {
534+
if (model_.IsStart(node) || model_.IsEnd(node)) continue;
535+
if (reference->Value(model_.NextVar(node)) == node) {
536+
++absences_[node];
537+
}
538+
}
539+
}
540+
541+
void OnBestSolutionFound(Assignment* reference) override {
542+
if (!remove_route_with_lowest_absences_) return;
543+
544+
int candidate_route = -1;
545+
int min_sum_absences = std::numeric_limits<int>::max();
546+
547+
for (int route = 0; route < model_.vehicles(); ++route) {
548+
if (model_.Next(*reference, model_.Start(route)) == model_.End(route))
549+
continue;
550+
int sum_absences = 0;
551+
for (int64_t node = reference->Value(model_.NextVar(model_.Start(route)));
552+
node != model_.End(route);
553+
node = reference->Value(model_.NextVar(node))) {
554+
sum_absences += absences_[node];
555+
}
556+
557+
if (sum_absences < min_sum_absences) {
558+
candidate_route = route;
559+
min_sum_absences = sum_absences;
560+
}
561+
}
562+
563+
// Remove the route with the lowest sum of absences.
564+
if (candidate_route != -1) {
565+
// Set next pointers for inner nodes.
566+
int64_t node =
567+
reference->Value(model_.NextVar(model_.Start(candidate_route)));
568+
while (node != model_.End(candidate_route)) {
569+
const int64_t next_node = reference->Value(model_.NextVar(node));
570+
reference->SetValue(model_.NextVar(node), node);
571+
reference->SetValue(model_.VehicleVar(node), -1);
572+
node = next_node;
573+
}
574+
// Set next pointer for start node.
575+
reference->SetValue(model_.NextVar(model_.Start(candidate_route)),
576+
model_.End(candidate_route));
577+
}
578+
}
579+
580+
private:
581+
const RoutingModel& model_;
582+
bool remove_route_with_lowest_absences_;
583+
std::vector<int> absences_;
584+
};
585+
471586
// Returns whether the given assignment has at least one performed node.
472587
bool HasPerformedNodes(const RoutingModel& model,
473588
const Assignment& assignment) {
@@ -1166,6 +1281,12 @@ std::unique_ptr<NeighborAcceptanceCriterion> MakeNeighborAcceptanceCriterion(
11661281
rnd);
11671282
case AcceptanceStrategy::kAllNodesPerformed:
11681283
return std::make_unique<AllNodesPerformedAcceptanceCriterion>(model);
1284+
case AcceptanceStrategy::kMoreNodesPerformed:
1285+
return std::make_unique<MoreNodesPerformedAcceptanceCriterion>(model);
1286+
case AcceptanceStrategy::kAbsencesBased:
1287+
return std::make_unique<AbsencesBasedAcceptanceCriterion>(
1288+
model, acceptance_strategy.absences_based()
1289+
.remove_route_with_lowest_absences());
11691290
default:
11701291
LOG(DFATAL) << "Unsupported acceptance strategy.";
11711292
return nullptr;

ortools/constraint_solver/routing_ils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ class NeighborAcceptanceCriterion {
265265
virtual bool Accept(const SearchState& search_state,
266266
const Assignment* candidate,
267267
const Assignment* reference) = 0;
268+
269+
// Called at the end of an ILS iteration.
270+
virtual void OnIterationEnd([[maybe_unused]] const Assignment* reference) {}
271+
272+
// Called when a new best solution found is found.
273+
virtual void OnBestSolutionFound([[maybe_unused]] Assignment* reference) {}
268274
};
269275

270276
// Returns a neighbor acceptance criterion based on the given parameters.

ortools/constraint_solver/routing_ils.proto

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ message RandomWalkRuinStrategy {
5454
// Ruin strategy based on the "Slack Induction by String Removals for Vehicle
5555
// Routing Problems" by Jan Christiaens and Greet Vanden Berghe, Transportation
5656
// Science 2020.
57-
// Link to paper:
58-
// 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
5957
//
6058
// Note that, in this implementation, the notion of "string" is replaced by
6159
// "sequence".
@@ -296,12 +294,42 @@ message SimulatedAnnealingAcceptanceStrategy {
296294
// performed.
297295
message AllNodesPerformedAcceptanceStrategy {}
298296

297+
// Acceptance strategy in which a solution is accepted only if it performs at
298+
// least one more node than the reference solution.
299+
message MoreNodesPerformedAcceptanceStrategy {}
300+
301+
// Acceptance strategy in which a solution is accepted only if it has less
302+
// absences than the reference solution (see Slack Induction by String Removals
303+
// for Vehicle Routing Problems" Christiaens and Vanden Berghe, Transportation
304+
// Science 2020).
305+
//
306+
// In particular, for each node n, the number of solutions where n was not
307+
// performed by any route is tracked by a counter absences[n]. A candidate is
308+
// accepted if
309+
// sum(absences[n]) < sum(absences[m])
310+
// with
311+
// n in unperformed(candidate)
312+
// m in unperformed(reference)
313+
//
314+
// The counter absences is increased after every ILS iteration for the
315+
// unperformed nodes in the reference solution. In addition, when
316+
// remove_route_with_lowest_absences is true and a new best found solution is
317+
// found, the route with the lowest sum of absences is removed from the
318+
// reference solution.
319+
message AbsencesBasedAcceptanceStrategy {
320+
// If true, when a new best solution is found, the route with the lowest sum
321+
// of absences is removed from the reference solution.
322+
optional bool remove_route_with_lowest_absences = 1;
323+
}
324+
299325
// Determines when a candidate solution replaces another one.
300326
message AcceptanceStrategy {
301327
oneof strategy {
302328
GreedyDescentAcceptanceStrategy greedy_descent = 1;
303329
SimulatedAnnealingAcceptanceStrategy simulated_annealing = 2;
304330
AllNodesPerformedAcceptanceStrategy all_nodes_performed = 3;
331+
MoreNodesPerformedAcceptanceStrategy more_nodes_performed = 4;
332+
AbsencesBasedAcceptanceStrategy absences_based = 5;
305333
}
306334
}
307335

0 commit comments

Comments
 (0)