routing: backport change from main
This commit is contained in:
@@ -314,6 +314,7 @@ cc_library(
|
||||
":routing_enums_cc_proto",
|
||||
":routing_heuristic_parameters_cc_proto",
|
||||
":routing_ils_cc_proto",
|
||||
":routing_ils_parameters_utils",
|
||||
":routing_parameters_cc_proto",
|
||||
":routing_parameters_utils",
|
||||
":solver_parameters_cc_proto",
|
||||
@@ -326,6 +327,7 @@ cc_library(
|
||||
"//ortools/util:optional_boolean_cc_proto",
|
||||
"//ortools/util:testing_utils",
|
||||
"@abseil-cpp//absl/container:flat_hash_map",
|
||||
"@abseil-cpp//absl/log",
|
||||
"@abseil-cpp//absl/strings",
|
||||
"@abseil-cpp//absl/strings:str_format",
|
||||
"@abseil-cpp//absl/time",
|
||||
@@ -345,6 +347,17 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "routing_ils_parameters_utils",
|
||||
srcs = ["routing_ils_parameters_utils.cc"],
|
||||
hdrs = ["routing_ils_parameters_utils.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":routing_enums_cc_proto",
|
||||
":routing_ils_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "routing_types",
|
||||
hdrs = ["routing_types.h"],
|
||||
|
||||
@@ -2981,6 +2981,15 @@ void RoutingModel::CloseModelWithParameters(
|
||||
solver_->AddConstraint(solver_->MakePathPrecedenceConstraint(
|
||||
nexts_, pickup_delivery_precedences, lifo_vehicles, fifo_vehicles));
|
||||
|
||||
// Add ordered activity group constraints.
|
||||
for (const auto& nodes : ordered_activity_groups_) {
|
||||
if (nodes.size() <= 1) continue;
|
||||
for (int i = 1; i < nodes.size(); ++i) {
|
||||
solver_->AddConstraint(solver_->MakeLessOrEqual(ActiveVar(nodes[i]),
|
||||
ActiveVar(nodes[i - 1])));
|
||||
}
|
||||
}
|
||||
|
||||
// Detect constraints
|
||||
enable_deep_serialization_ = false;
|
||||
std::unique_ptr<RoutingModelInspector> inspector(
|
||||
@@ -5354,6 +5363,10 @@ RoutingModel::CreateLocalSearchFilters(
|
||||
filter_events.push_back(
|
||||
{MakeActiveNodeGroupFilter(*this), kAccept, priority});
|
||||
}
|
||||
if (!GetOrderedActivityGroups().empty()) {
|
||||
filter_events.push_back(
|
||||
{MakeOrderedActivityGroupFilter(*this), kAccept, priority});
|
||||
}
|
||||
|
||||
if (!disjunctions_.empty()) {
|
||||
if (options.filter_objective || HasMandatoryDisjunctions() ||
|
||||
@@ -6858,7 +6871,7 @@ void RoutingDimension::InitializeTransits(
|
||||
InitializeTransitVariables(slack_max);
|
||||
}
|
||||
|
||||
// TODO(user): Apply -pointer-following.
|
||||
// TODO(user): Apply http://go/minimize-pointer-following.
|
||||
void FillPathEvaluation(absl::Span<const int64_t> path,
|
||||
const RoutingModel::TransitCallback2& evaluator,
|
||||
std::vector<int64_t>* values) {
|
||||
|
||||
@@ -1923,6 +1923,14 @@ class OR_DLL RoutingModel {
|
||||
DCHECK(closed_);
|
||||
return same_vehicle_groups_[same_vehicle_group_[node]];
|
||||
}
|
||||
void AddSameActivityGroup(const std::vector<int>& nodes) {
|
||||
DCHECK(!closed_);
|
||||
if (nodes.size() <= 1) return;
|
||||
for (auto it = nodes.begin() + 1; it != nodes.end(); ++it) {
|
||||
solver_->AddConstraint(
|
||||
solver_->MakeEquality(ActiveVar(*it), ActiveVar(*(it - 1))));
|
||||
}
|
||||
}
|
||||
/// Returns variable indices of nodes constrained to have the same activity.
|
||||
const std::vector<int>& GetSameActivityIndicesOfIndex(int node) const {
|
||||
DCHECK(closed_);
|
||||
@@ -1933,6 +1941,11 @@ class OR_DLL RoutingModel {
|
||||
DCHECK(closed_);
|
||||
return same_active_var_group_[node];
|
||||
}
|
||||
/// Returns same activity groups of all nodes.
|
||||
const std::vector<int>& GetSameActivityGroups() const {
|
||||
DCHECK(closed_);
|
||||
return same_active_var_group_;
|
||||
}
|
||||
/// Returns the number of same activity groups.
|
||||
int GetSameActivityGroupsCount() const {
|
||||
DCHECK(closed_);
|
||||
@@ -1943,7 +1956,19 @@ class OR_DLL RoutingModel {
|
||||
DCHECK(closed_);
|
||||
return same_active_var_groups_[group];
|
||||
}
|
||||
|
||||
/// Adds an ordered activity group. This enforces that if nodes[i] is active,
|
||||
/// then nodes[i-1] must be active.
|
||||
void AddOrderedActivityGroup(std::vector<int> nodes) {
|
||||
DCHECK(!closed_);
|
||||
if (nodes.size() <= 1) return;
|
||||
ordered_activity_groups_.push_back(std::move(nodes));
|
||||
}
|
||||
#ifndef SWIG
|
||||
/// Returns all ordered activity groups.
|
||||
const std::vector<std::vector<int>>& GetOrderedActivityGroups() const {
|
||||
return ordered_activity_groups_;
|
||||
}
|
||||
#endif // SWIG
|
||||
const VehicleTypeContainer& GetVehicleTypeContainer() const {
|
||||
DCHECK(closed_);
|
||||
return vehicle_type_container_;
|
||||
@@ -2620,6 +2645,8 @@ class OR_DLL RoutingModel {
|
||||
std::vector<int> same_active_var_group_;
|
||||
// Same active var groups.
|
||||
std::vector<std::vector<int>> same_active_var_groups_;
|
||||
// Ordered activity groups.
|
||||
std::vector<std::vector<int>> ordered_activity_groups_;
|
||||
// Node visit types
|
||||
// Variable index to visit type index.
|
||||
std::vector<int> index_to_visit_type_;
|
||||
|
||||
@@ -227,12 +227,151 @@ IntVarLocalSearchFilter* MakeMaxActiveVehiclesFilter(
|
||||
|
||||
namespace {
|
||||
|
||||
class SameActivityGroupManager {
|
||||
public:
|
||||
explicit SameActivityGroupManager(const RoutingModel& routing_model)
|
||||
: routing_model_(routing_model) {}
|
||||
int NumberOfGroups() const {
|
||||
return routing_model_.GetSameActivityGroupsCount();
|
||||
}
|
||||
absl::Span<const int> GetGroupsFromNode(int node) const {
|
||||
return absl::MakeConstSpan(routing_model_.GetSameActivityGroups())
|
||||
.subspan(node, 1);
|
||||
}
|
||||
const std::vector<int>& GetGroupNodes(int group) const {
|
||||
return routing_model_.GetSameActivityIndicesOfGroup(group);
|
||||
}
|
||||
void Revert() {}
|
||||
bool CheckGroup(int group, int active, int unknown,
|
||||
const CommittableArray<bool>& /*node_is_active*/,
|
||||
const CommittableArray<bool>& /*node_is_unknown*/) const {
|
||||
const int group_size = GetGroupNodes(group).size();
|
||||
// The group constraint is respected iff either 0 or group size is inside
|
||||
// interval [num_active, num_active + num_unknown],
|
||||
if (active == 0) return true;
|
||||
if (active <= group_size && group_size <= active + unknown) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const RoutingModel& routing_model_;
|
||||
};
|
||||
|
||||
class OrderedActivityGroupManager {
|
||||
public:
|
||||
explicit OrderedActivityGroupManager(const RoutingModel& routing_model)
|
||||
: groups_(routing_model.GetOrderedActivityGroups()),
|
||||
group_bounds_(routing_model.GetOrderedActivityGroups().size(), {0, 0}) {
|
||||
node_groups_.resize(routing_model.Size());
|
||||
for (int group = 0; group < groups_.size(); ++group) {
|
||||
for (int node : groups_[group]) {
|
||||
node_groups_[node].push_back(group);
|
||||
}
|
||||
group_bounds_.Set(group, std::make_pair(0, groups_[group].size() - 1));
|
||||
}
|
||||
group_bounds_.Commit();
|
||||
}
|
||||
int NumberOfGroups() const { return groups_.size(); }
|
||||
absl::Span<const int> GetGroupsFromNode(int node) const {
|
||||
return node_groups_[node];
|
||||
}
|
||||
const std::vector<int>& GetGroupNodes(int group) const {
|
||||
return groups_[group];
|
||||
}
|
||||
void Revert() {
|
||||
group_bounds_.Revert();
|
||||
touched_nodes_.clear();
|
||||
}
|
||||
bool CheckGroup(int group, int active, int unknown,
|
||||
CommittableArray<bool>& node_is_active,
|
||||
CommittableArray<bool>& node_is_unknown) {
|
||||
if (active == 0) return true;
|
||||
auto& [min_rank, max_rank] = group_bounds_.GetMutable(group);
|
||||
for (int rank = min_rank; rank <= max_rank; ++rank) {
|
||||
const int node = groups_[group][rank];
|
||||
if (node_is_unknown.Get(node)) continue;
|
||||
if (!node_is_active.Get(node)) {
|
||||
touched_nodes_.push_back(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int rank = max_rank; rank >= min_rank; --rank) {
|
||||
const int node = groups_[group][rank];
|
||||
if (node_is_unknown.Get(node)) continue;
|
||||
if (node_is_active.Get(node)) {
|
||||
touched_nodes_.push_back(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!touched_nodes_.empty()) {
|
||||
const int node = touched_nodes_.back();
|
||||
touched_nodes_.pop_back();
|
||||
if (!Propagate(node, node_is_active, node_is_unknown)) {
|
||||
return false;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
for (int n : touched_nodes_) DCHECK_NE(n, node);
|
||||
#endif // NDEBUG
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool Propagate(int node, CommittableArray<bool>& node_is_active,
|
||||
CommittableArray<bool>& node_is_unknown) {
|
||||
for (int group_index : node_groups_[node]) {
|
||||
const std::vector<int>& group = groups_[group_index];
|
||||
auto& [min_rank, max_rank] = group_bounds_.GetMutable(group_index);
|
||||
if (max_rank < min_rank) continue;
|
||||
if (node_is_active.Get(node)) {
|
||||
// Make all active between min_rank and node.
|
||||
int rank = min_rank;
|
||||
while (group[rank] != node) {
|
||||
const int current_node = group[rank];
|
||||
if (node_is_unknown.Get(current_node)) {
|
||||
node_is_active.Set(current_node, true);
|
||||
node_is_unknown.Set(current_node, false);
|
||||
touched_nodes_.push_back(current_node);
|
||||
} else if (!node_is_active.Get(current_node)) {
|
||||
return false;
|
||||
}
|
||||
rank++;
|
||||
}
|
||||
min_rank = rank + 1;
|
||||
} else {
|
||||
// Make all inactive between node and max_rank.
|
||||
int rank = max_rank;
|
||||
while (group[rank] != node) {
|
||||
const int current_node = group[rank];
|
||||
if (node_is_unknown.Get(current_node)) {
|
||||
node_is_active.Set(current_node, false);
|
||||
node_is_unknown.Set(current_node, false);
|
||||
touched_nodes_.push_back(current_node);
|
||||
} else if (node_is_active.Get(current_node)) {
|
||||
return false;
|
||||
}
|
||||
rank--;
|
||||
}
|
||||
max_rank = rank - 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<std::vector<int>>& groups_;
|
||||
std::vector<std::vector<int>> node_groups_;
|
||||
CommittableArray<std::pair<int, int>> group_bounds_;
|
||||
std::vector<int> touched_nodes_;
|
||||
};
|
||||
|
||||
template <typename GroupAccessor>
|
||||
class ActiveNodeGroupFilter : public IntVarLocalSearchFilter {
|
||||
public:
|
||||
explicit ActiveNodeGroupFilter(const RoutingModel& routing_model)
|
||||
: IntVarLocalSearchFilter(routing_model.Nexts()),
|
||||
routing_model_(routing_model),
|
||||
active_count_per_group_(routing_model.GetSameActivityGroupsCount(),
|
||||
group_accessor_(routing_model),
|
||||
active_count_per_group_(group_accessor_.NumberOfGroups(),
|
||||
{.active = 0, .unknown = 0}),
|
||||
node_is_active_(routing_model.Nexts().size(), false),
|
||||
node_is_unknown_(routing_model.Nexts().size(), false) {}
|
||||
@@ -240,35 +379,43 @@ class ActiveNodeGroupFilter : public IntVarLocalSearchFilter {
|
||||
bool Accept(const Assignment* delta, const Assignment* /*deltadelta*/,
|
||||
int64_t /*objective_min*/, int64_t /*objective_max*/) override {
|
||||
active_count_per_group_.Revert();
|
||||
node_is_active_.Revert();
|
||||
node_is_unknown_.Revert();
|
||||
group_accessor_.Revert();
|
||||
const Assignment::IntContainer& container = delta->IntVarContainer();
|
||||
// Updating group counters.
|
||||
for (const IntVarElement& new_element : container.elements()) {
|
||||
IntVar* const var = new_element.Var();
|
||||
int64_t index = -1;
|
||||
if (!FindIndex(var, &index)) continue;
|
||||
const int group = routing_model_.GetSameActivityGroupOfIndex(index);
|
||||
ActivityCounts counts = active_count_per_group_.Get(group);
|
||||
// Change contribution to counts: remove old state, add new state.
|
||||
if (node_is_unknown_[index]) --counts.unknown;
|
||||
if (node_is_active_[index]) --counts.active;
|
||||
if (new_element.Min() != new_element.Max()) {
|
||||
++counts.unknown;
|
||||
} else if (new_element.Min() != index) {
|
||||
++counts.active;
|
||||
for (const int group : group_accessor_.GetGroupsFromNode(index)) {
|
||||
ActivityCounts counts = active_count_per_group_.Get(group);
|
||||
// Change contribution to counts: remove old state, add new state.
|
||||
if (node_is_unknown_.Get(index)) --counts.unknown;
|
||||
if (node_is_active_.Get(index)) --counts.active;
|
||||
if (new_element.Min() != new_element.Max()) {
|
||||
++counts.unknown;
|
||||
} else if (new_element.Min() != index) {
|
||||
++counts.active;
|
||||
}
|
||||
active_count_per_group_.Set(group, counts);
|
||||
}
|
||||
active_count_per_group_.Set(group, counts);
|
||||
}
|
||||
// Updating node states.
|
||||
for (const IntVarElement& new_element : container.elements()) {
|
||||
IntVar* const var = new_element.Var();
|
||||
int64_t index = -1;
|
||||
if (!FindIndex(var, &index)) continue;
|
||||
node_is_unknown_.Set(index, new_element.Min() != new_element.Max());
|
||||
node_is_active_.Set(index, new_element.Min() == new_element.Max() &&
|
||||
new_element.Min() != index);
|
||||
}
|
||||
for (const int group : active_count_per_group_.ChangedIndices()) {
|
||||
const ActivityCounts counts = active_count_per_group_.Get(group);
|
||||
const int group_size =
|
||||
routing_model_.GetSameActivityIndicesOfGroup(group).size();
|
||||
// The group constraint is respected iff either 0 or group size is inside
|
||||
// interval [num_active, num_active + num_unknown],
|
||||
if (counts.active == 0) continue;
|
||||
if (counts.active <= group_size &&
|
||||
group_size <= counts.active + counts.unknown) {
|
||||
continue;
|
||||
if (!group_accessor_.CheckGroup(group, counts.active, counts.unknown,
|
||||
node_is_active_, node_is_unknown_)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -276,27 +423,29 @@ class ActiveNodeGroupFilter : public IntVarLocalSearchFilter {
|
||||
|
||||
private:
|
||||
void OnSynchronize(const Assignment* /*delta*/) override {
|
||||
const int num_groups = routing_model_.GetSameActivityGroupsCount();
|
||||
const int num_groups = group_accessor_.NumberOfGroups();
|
||||
for (int group = 0; group < num_groups; ++group) {
|
||||
ActivityCounts counts = {.active = 0, .unknown = 0};
|
||||
for (int node : routing_model_.GetSameActivityIndicesOfGroup(group)) {
|
||||
for (int node : group_accessor_.GetGroupNodes(group)) {
|
||||
if (IsVarSynced(node)) {
|
||||
const bool is_active = (Value(node) != node);
|
||||
node_is_active_[node] = is_active;
|
||||
node_is_unknown_[node] = false;
|
||||
node_is_active_.Set(node, is_active);
|
||||
node_is_unknown_.Set(node, false);
|
||||
counts.active += is_active ? 1 : 0;
|
||||
} else {
|
||||
++counts.unknown;
|
||||
node_is_unknown_[node] = true;
|
||||
node_is_active_[node] = false;
|
||||
node_is_unknown_.Set(node, true);
|
||||
node_is_active_.Set(node, false);
|
||||
}
|
||||
}
|
||||
active_count_per_group_.Set(group, counts);
|
||||
}
|
||||
active_count_per_group_.Commit();
|
||||
node_is_active_.Commit();
|
||||
node_is_unknown_.Commit();
|
||||
}
|
||||
|
||||
const RoutingModel& routing_model_;
|
||||
GroupAccessor group_accessor_;
|
||||
struct ActivityCounts {
|
||||
int active;
|
||||
int unknown;
|
||||
@@ -304,10 +453,10 @@ class ActiveNodeGroupFilter : public IntVarLocalSearchFilter {
|
||||
CommittableArray<ActivityCounts> active_count_per_group_;
|
||||
// node_is_active_[node] is true iff node was synced and active at last
|
||||
// Synchronize().
|
||||
std::vector<bool> node_is_active_;
|
||||
CommittableArray<bool> node_is_active_;
|
||||
// node_is_unknown_[node] is true iff node was not synced at last
|
||||
// Synchronize().
|
||||
std::vector<bool> node_is_unknown_;
|
||||
CommittableArray<bool> node_is_unknown_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -315,7 +464,13 @@ class ActiveNodeGroupFilter : public IntVarLocalSearchFilter {
|
||||
IntVarLocalSearchFilter* MakeActiveNodeGroupFilter(
|
||||
const RoutingModel& routing_model) {
|
||||
return routing_model.solver()->RevAlloc(
|
||||
new ActiveNodeGroupFilter(routing_model));
|
||||
new ActiveNodeGroupFilter<SameActivityGroupManager>(routing_model));
|
||||
}
|
||||
|
||||
IntVarLocalSearchFilter* MakeOrderedActivityGroupFilter(
|
||||
const RoutingModel& routing_model) {
|
||||
return routing_model.solver()->RevAlloc(
|
||||
new ActiveNodeGroupFilter<OrderedActivityGroupManager>(routing_model));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -81,6 +81,11 @@ IntVarLocalSearchFilter* MakeMaxActiveVehiclesFilter(
|
||||
IntVarLocalSearchFilter* MakeActiveNodeGroupFilter(
|
||||
const RoutingModel& routing_model);
|
||||
|
||||
/// Returns a filter ensuring that for each ordered activity group,
|
||||
/// if nodes[i] is active then nodes[i-1] is active.
|
||||
IntVarLocalSearchFilter* MakeOrderedActivityGroupFilter(
|
||||
const RoutingModel& routing_model);
|
||||
|
||||
/// Returns a filter ensuring that node disjunction constraints are enforced.
|
||||
IntVarLocalSearchFilter* MakeNodeDisjunctionFilter(
|
||||
const RoutingModel& routing_model, bool filter_cost);
|
||||
|
||||
@@ -66,7 +66,8 @@ MakeGlobalCheapestInsertionParameters(
|
||||
|
||||
// Returns local cheapest insertion parameters based on the given recreate
|
||||
// strategy if available. Returns default parameters otherwise.
|
||||
LocalCheapestInsertionParameters GetLocalCheapestInsertionParameters(
|
||||
LocalCheapestInsertionParameters
|
||||
GetLocalCheapestInsertionParametersForRecreateStrategy(
|
||||
const RecreateStrategy& recreate_strategy,
|
||||
const LocalCheapestInsertionParameters& default_parameters) {
|
||||
return recreate_strategy.has_parameters() &&
|
||||
@@ -75,6 +76,15 @@ LocalCheapestInsertionParameters GetLocalCheapestInsertionParameters(
|
||||
: default_parameters;
|
||||
}
|
||||
|
||||
SavingsParameters GetSavingsParametersForRecreateStrategy(
|
||||
const RecreateStrategy& recreate_strategy,
|
||||
const SavingsParameters& default_parameters) {
|
||||
return recreate_strategy.has_parameters() &&
|
||||
recreate_strategy.parameters().has_savings()
|
||||
? recreate_strategy.parameters().savings()
|
||||
: default_parameters;
|
||||
}
|
||||
|
||||
// Returns a ruin procedure based on the given ruin strategy.
|
||||
std::unique_ptr<RuinProcedure> MakeRuinProcedure(
|
||||
RoutingModel* model, std::mt19937* rnd, RuinStrategy ruin,
|
||||
@@ -229,7 +239,7 @@ std::unique_ptr<RoutingFilteredHeuristic> MakeRecreateProcedure(
|
||||
return std::make_unique<LocalCheapestInsertionFilteredHeuristic>(
|
||||
model, std::move(stop_search),
|
||||
absl::bind_front(&RoutingModel::GetArcCostForVehicle, model),
|
||||
GetLocalCheapestInsertionParameters(
|
||||
GetLocalCheapestInsertionParametersForRecreateStrategy(
|
||||
recreate_strategy,
|
||||
parameters.local_cheapest_insertion_parameters()),
|
||||
filter_manager, model->GetBinCapacities());
|
||||
@@ -238,7 +248,7 @@ std::unique_ptr<RoutingFilteredHeuristic> MakeRecreateProcedure(
|
||||
return std::make_unique<LocalCheapestInsertionFilteredHeuristic>(
|
||||
model, std::move(stop_search),
|
||||
/*evaluator=*/nullptr,
|
||||
GetLocalCheapestInsertionParameters(
|
||||
GetLocalCheapestInsertionParametersForRecreateStrategy(
|
||||
recreate_strategy,
|
||||
parameters.local_cheapest_cost_insertion_parameters()),
|
||||
filter_manager, model->GetBinCapacities());
|
||||
@@ -266,15 +276,17 @@ std::unique_ptr<RoutingFilteredHeuristic> MakeRecreateProcedure(
|
||||
filter_manager, gci_parameters);
|
||||
}
|
||||
case FirstSolutionStrategy::SAVINGS: {
|
||||
// TODO(user): support ILS-specific savings parameters.
|
||||
return std::make_unique<SequentialSavingsFilteredHeuristic>(
|
||||
model, std::move(stop_search), parameters.savings_parameters(),
|
||||
model, std::move(stop_search),
|
||||
GetSavingsParametersForRecreateStrategy(
|
||||
recreate_strategy, parameters.savings_parameters()),
|
||||
filter_manager);
|
||||
}
|
||||
case FirstSolutionStrategy::PARALLEL_SAVINGS: {
|
||||
// TODO(user): support ILS-specific savings parameters.
|
||||
return std::make_unique<ParallelSavingsFilteredHeuristic>(
|
||||
model, std::move(stop_search), parameters.savings_parameters(),
|
||||
model, std::move(stop_search),
|
||||
GetSavingsParametersForRecreateStrategy(
|
||||
recreate_strategy, parameters.savings_parameters()),
|
||||
filter_manager);
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -151,6 +151,7 @@ message RuinStrategy {
|
||||
message RecreateParameters {
|
||||
oneof parameters {
|
||||
LocalCheapestInsertionParameters local_cheapest_insertion = 1;
|
||||
SavingsParameters savings = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
58
ortools/constraint_solver/routing_ils_parameters_utils.cc
Normal file
58
ortools/constraint_solver/routing_ils_parameters_utils.cc
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2010-2025 Google LLC
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/constraint_solver/routing_ils_parameters_utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/constraint_solver/routing_enums.pb.h"
|
||||
#include "ortools/constraint_solver/routing_ils.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
RecreateParameters::ParametersCase GetParameterCaseForRecreateHeuristic(
|
||||
FirstSolutionStrategy::Value recreate_heuristic) {
|
||||
switch (recreate_heuristic) {
|
||||
case FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION:
|
||||
return RecreateParameters::kLocalCheapestInsertion;
|
||||
case FirstSolutionStrategy::LOCAL_CHEAPEST_COST_INSERTION:
|
||||
return RecreateParameters::kLocalCheapestInsertion;
|
||||
case FirstSolutionStrategy::SAVINGS:
|
||||
return RecreateParameters::kSavings;
|
||||
case FirstSolutionStrategy::PARALLEL_SAVINGS:
|
||||
return RecreateParameters::kSavings;
|
||||
default:
|
||||
return RecreateParameters::PARAMETERS_NOT_SET;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RecreateParameters::ParametersCase>
|
||||
GetSupportedRecreateParametersCases() {
|
||||
return {RecreateParameters::kLocalCheapestInsertion,
|
||||
RecreateParameters::kSavings};
|
||||
}
|
||||
|
||||
std::string GetRecreateParametersName(
|
||||
RecreateParameters::ParametersCase parameters_case) {
|
||||
switch (parameters_case) {
|
||||
case RecreateParameters::kLocalCheapestInsertion:
|
||||
return "local_cheapest_insertion";
|
||||
case RecreateParameters::kSavings:
|
||||
return "savings";
|
||||
case RecreateParameters::PARAMETERS_NOT_SET:
|
||||
return "PARAMETERS_NOT_SET";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
40
ortools/constraint_solver/routing_ils_parameters_utils.h
Normal file
40
ortools/constraint_solver/routing_ils_parameters_utils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2010-2025 Google LLC
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_ILS_PARAMETERS_UTILS_H_
|
||||
#define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_ILS_PARAMETERS_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/constraint_solver/routing_enums.pb.h"
|
||||
#include "ortools/constraint_solver/routing_ils.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// Returns the appropriate parameters case for the given recreate heuristic.
|
||||
// Returns PARAMETERS_NOT_SET if the heuristic is not supported.
|
||||
RecreateParameters::ParametersCase GetParameterCaseForRecreateHeuristic(
|
||||
FirstSolutionStrategy::Value recreate_heuristic);
|
||||
|
||||
// Returns the list of supported recreate parameters cases.
|
||||
std::vector<RecreateParameters::ParametersCase>
|
||||
GetSupportedRecreateParametersCases();
|
||||
|
||||
// Returns the name of the given recreate parameter.
|
||||
std::string GetRecreateParametersName(
|
||||
RecreateParameters::ParametersCase parameters_case);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_ILS_PARAMETERS_UTILS_H_
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "ortools/base/base_export.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
#include "ortools/constraint_solver/routing_types.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
/// Manager for any NodeIndex <-> variable index conversion. The routing solver
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "ortools/constraint_solver/routing_enums.pb.h"
|
||||
#include "ortools/constraint_solver/routing_heuristic_parameters.pb.h"
|
||||
#include "ortools/constraint_solver/routing_ils.pb.h"
|
||||
#include "ortools/constraint_solver/routing_ils_parameters_utils.h"
|
||||
#include "ortools/constraint_solver/routing_parameters.pb.h"
|
||||
#include "ortools/constraint_solver/solver_parameters.pb.h"
|
||||
#include "ortools/port/proto_utils.h"
|
||||
@@ -304,6 +305,32 @@ void FindErrorsInLocalCheapestInsertionParameters(
|
||||
}
|
||||
}
|
||||
|
||||
// Searches for errors in SavingsParameters and appends them to the given
|
||||
// `errors` vector.
|
||||
void FindErrorsInSavingsParameters(const absl::string_view prefix,
|
||||
const SavingsParameters& savings_parameters,
|
||||
std::vector<std::string>& errors) {
|
||||
using absl::StrCat;
|
||||
|
||||
if (const double ratio = savings_parameters.neighbors_ratio();
|
||||
std::isnan(ratio) || ratio <= 0 || ratio > 1) {
|
||||
errors.emplace_back(StrCat(
|
||||
prefix, " - Invalid savings_parameters.neighbors_ratio: ", ratio));
|
||||
}
|
||||
if (const double max_memory = savings_parameters.max_memory_usage_bytes();
|
||||
std::isnan(max_memory) || max_memory <= 0 || max_memory > 1e10) {
|
||||
errors.emplace_back(StrCat(
|
||||
prefix,
|
||||
" - Invalid savings_parameters.max_memory_usage_bytes: ", max_memory));
|
||||
}
|
||||
if (const double coefficient = savings_parameters.arc_coefficient();
|
||||
std::isnan(coefficient) || coefficient <= 0 || std::isinf(coefficient)) {
|
||||
errors.emplace_back(
|
||||
StrCat(prefix,
|
||||
" - Invalid savings_parameters.arc_coefficient: ", coefficient));
|
||||
}
|
||||
}
|
||||
|
||||
void FindErrorsInRecreateParameters(
|
||||
const FirstSolutionStrategy::Value heuristic,
|
||||
const RecreateParameters& parameters, std::vector<std::string>& errors) {
|
||||
@@ -317,21 +344,16 @@ void FindErrorsInRecreateParameters(
|
||||
prefix, parameters.local_cheapest_insertion(), errors);
|
||||
break;
|
||||
}
|
||||
case RecreateParameters::kSavings:
|
||||
FindErrorsInSavingsParameters("Savings (recreate heuristic)",
|
||||
parameters.savings(), errors);
|
||||
break;
|
||||
default:
|
||||
LOG(DFATAL) << "Unsupported unset recreate parameters.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetRecreateParametersName(const RecreateParameters& parameters) {
|
||||
switch (parameters.parameters_case()) {
|
||||
case RecreateParameters::kLocalCheapestInsertion:
|
||||
return "local_cheapest_insertion";
|
||||
case RecreateParameters::PARAMETERS_NOT_SET:
|
||||
return "PARAMETERS_NOT_SET";
|
||||
}
|
||||
}
|
||||
|
||||
// Searches for errors in ILS parameters and appends them to the given `errors`
|
||||
// vector.
|
||||
void FindErrorsInIteratedLocalSearchParameters(
|
||||
@@ -462,32 +484,20 @@ void FindErrorsInIteratedLocalSearchParameters(
|
||||
rr.recreate_strategy().parameters();
|
||||
if (recreate_params.parameters_case() ==
|
||||
RecreateParameters::PARAMETERS_NOT_SET) {
|
||||
errors.emplace_back(
|
||||
StrCat("Invalid value for "
|
||||
"iterated_local_search_parameters.ruin_recreate_parameters."
|
||||
"recreate_strategy.parameters: ",
|
||||
GetRecreateParametersName(recreate_params)));
|
||||
errors.emplace_back(StrCat(
|
||||
"Invalid value for "
|
||||
"iterated_local_search_parameters.ruin_recreate_parameters."
|
||||
"recreate_strategy.parameters: ",
|
||||
GetRecreateParametersName(recreate_params.parameters_case())));
|
||||
} else {
|
||||
const absl::flat_hash_map<FirstSolutionStrategy::Value,
|
||||
RecreateParameters::ParametersCase>
|
||||
strategy_to_parameters_case_map = {
|
||||
{FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION,
|
||||
RecreateParameters::kLocalCheapestInsertion},
|
||||
{FirstSolutionStrategy::LOCAL_CHEAPEST_COST_INSERTION,
|
||||
RecreateParameters::kLocalCheapestInsertion}};
|
||||
|
||||
const RecreateParameters& recreate_params =
|
||||
rr.recreate_strategy().parameters();
|
||||
|
||||
if (const auto params =
|
||||
strategy_to_parameters_case_map.find(recreate_heuristic);
|
||||
params == strategy_to_parameters_case_map.end() ||
|
||||
recreate_params.parameters_case() != params->second) {
|
||||
errors.emplace_back(
|
||||
StrCat("recreate_strategy.heuristic is set to ",
|
||||
FirstSolutionStrategy::Value_Name(recreate_heuristic),
|
||||
" but recreate_strategy.parameters define ",
|
||||
GetRecreateParametersName(recreate_params)));
|
||||
if (const RecreateParameters::ParametersCase params =
|
||||
GetParameterCaseForRecreateHeuristic(recreate_heuristic);
|
||||
recreate_params.parameters_case() != params) {
|
||||
errors.emplace_back(StrCat(
|
||||
"recreate_strategy.heuristic is set to ",
|
||||
FirstSolutionStrategy::Value_Name(recreate_heuristic),
|
||||
" but recreate_strategy.parameters define ",
|
||||
GetRecreateParametersName(recreate_params.parameters_case())));
|
||||
} else {
|
||||
FindErrorsInRecreateParameters(recreate_heuristic, recreate_params,
|
||||
errors);
|
||||
@@ -598,24 +608,8 @@ std::vector<std::string> FindErrorsInRoutingSearchParameters(
|
||||
}
|
||||
}
|
||||
#endif // !__ANDROID__ && !__wasm__
|
||||
if (const double ratio =
|
||||
search_parameters.savings_parameters().neighbors_ratio();
|
||||
std::isnan(ratio) || ratio <= 0 || ratio > 1) {
|
||||
errors.emplace_back(
|
||||
StrCat("Invalid savings_parameters.neighbors_ratio: ", ratio));
|
||||
}
|
||||
if (const double max_memory =
|
||||
search_parameters.savings_parameters().max_memory_usage_bytes();
|
||||
std::isnan(max_memory) || max_memory <= 0 || max_memory > 1e10) {
|
||||
errors.emplace_back(StrCat(
|
||||
"Invalid savings_parameters.max_memory_usage_bytes: ", max_memory));
|
||||
}
|
||||
if (const double coefficient =
|
||||
search_parameters.savings_parameters().arc_coefficient();
|
||||
std::isnan(coefficient) || coefficient <= 0 || std::isinf(coefficient)) {
|
||||
errors.emplace_back(
|
||||
StrCat("Invalid savings_parameters.arc_coefficient: ", coefficient));
|
||||
}
|
||||
FindErrorsInSavingsParameters("Savings (first solution heuristic)",
|
||||
search_parameters.savings_parameters(), errors);
|
||||
if (const double ratio =
|
||||
search_parameters.cheapest_insertion_farthest_seeds_ratio();
|
||||
std::isnan(ratio) || ratio < 0 || ratio > 1) {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#ifndef OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_SEARCH_H_
|
||||
#define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_SEARCH_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
Reference in New Issue
Block a user