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>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@protobuf//bazel:proto_library.bzl", "proto_library")
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_cc//cc:cc_test.bzl", "cc_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -262,6 +263,7 @@ cc_test(
|
||||
":tsplib_parser",
|
||||
"//ortools/base",
|
||||
"//ortools/base:file",
|
||||
"//ortools/base:gmock",
|
||||
"//ortools/base:memfile",
|
||||
"@abseil-cpp//absl/container:btree",
|
||||
"@abseil-cpp//absl/strings",
|
||||
|
||||
@@ -26,11 +26,13 @@ target_include_directories(${NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>)
|
||||
target_link_libraries(${NAME} PRIVATE
|
||||
ZLIB::ZLIB
|
||||
absl::memory
|
||||
absl::strings
|
||||
absl::status
|
||||
absl::str_format
|
||||
protobuf::libprotobuf
|
||||
${RE2_DEPS}
|
||||
${PROJECT_NAMESPACE}::ortools_proto)
|
||||
${PROJECT_NAMESPACE}::ortools_proto
|
||||
)
|
||||
#add_library(${PROJECT_NAMESPACE}::routing_parsers ALIAS ${NAME})
|
||||
|
||||
@@ -6,6 +6,7 @@ and utilities directly related to these file formats.
|
||||
`solution_serializer.h` contains a generic serializer for routing solutions for
|
||||
many formats.
|
||||
|
||||
<!-- mdformat off(go/g3mark doesn't support multiline tables, b/267197123) -->
|
||||
| Problem type | File format | Corresponding parser | Data storage | Data sets |
|
||||
| ------------ | ----------- | -------------------- | ------------ | --------- |
|
||||
| TSP | TSPLIB | `tsplib_parser.h` | | [TSPLIB95][tsplib95] |
|
||||
@@ -17,6 +18,7 @@ many formats.
|
||||
| NEARP | NEARPLIB | `nearplib_parser.h` | | [NEARPLIB][nearplib] |
|
||||
| PDPTW | LiLim | `lilim_parser.h` | | [LiLim][lilim] |
|
||||
| MCND | Dow | `dow_parser.h` | `capacity_planning.proto` | [Canad][canad] |
|
||||
<!-- mdformat on -->
|
||||
|
||||
In the future, this folder will contain the whole routing solver.
|
||||
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
#include "ortools/routing/parsers/carp_parser.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
@@ -168,7 +168,7 @@ void DisplayPlan(const RoutingIndexManager& manager,
|
||||
bool use_same_vehicle_costs, int64_t max_nodes_per_group,
|
||||
int64_t same_vehicle_cost,
|
||||
absl::Span<const std::string> dimension_names) {
|
||||
std::vector<const operations_research::RoutingDimension*> dimensions;
|
||||
std::vector<const RoutingDimension*> dimensions;
|
||||
for (const std::string& dimension_name : dimension_names) {
|
||||
dimensions.push_back(&routing.GetDimensionOrDie(dimension_name));
|
||||
}
|
||||
@@ -239,7 +239,7 @@ void DisplayPlan(const RoutingIndexManager& manager,
|
||||
while (true) {
|
||||
absl::StrAppendFormat(&plan_output, "%d ",
|
||||
manager.IndexToNode(order).value());
|
||||
for (const operations_research::RoutingDimension* dimension : dimensions) {
|
||||
for (const RoutingDimension* dimension : dimensions) {
|
||||
str_append_variable(dimension->CumulVar(order), dimension->name());
|
||||
operations_research::IntVar* const slack_var =
|
||||
routing.IsEnd(order) ? nullptr : dimension->SlackVar(order);
|
||||
|
||||
@@ -95,43 +95,42 @@ class RandomDemand {
|
||||
// Service time (proportional to demand) + transition time callback.
|
||||
class ServiceTimePlusTransition {
|
||||
public:
|
||||
ServiceTimePlusTransition(
|
||||
int64_t time_per_demand_unit,
|
||||
operations_research::RoutingNodeEvaluator2 demand,
|
||||
operations_research::RoutingNodeEvaluator2 transition_time);
|
||||
ServiceTimePlusTransition(int64_t time_per_demand_unit,
|
||||
RoutingNodeEvaluator2 demand,
|
||||
RoutingNodeEvaluator2 transition_time);
|
||||
int64_t Compute(RoutingIndexManager::NodeIndex from,
|
||||
RoutingIndexManager::NodeIndex to) const;
|
||||
|
||||
private:
|
||||
const int64_t time_per_demand_unit_;
|
||||
operations_research::RoutingNodeEvaluator2 demand_;
|
||||
operations_research::RoutingNodeEvaluator2 transition_time_;
|
||||
RoutingNodeEvaluator2 demand_;
|
||||
RoutingNodeEvaluator2 transition_time_;
|
||||
};
|
||||
|
||||
// Stop service time + transition time callback.
|
||||
class StopServiceTimePlusTransition {
|
||||
public:
|
||||
StopServiceTimePlusTransition(
|
||||
int64_t stop_time, const LocationContainer& location_container,
|
||||
operations_research::RoutingNodeEvaluator2 transition_time);
|
||||
StopServiceTimePlusTransition(int64_t stop_time,
|
||||
const LocationContainer& location_container,
|
||||
RoutingNodeEvaluator2 transition_time);
|
||||
int64_t Compute(RoutingIndexManager::NodeIndex from,
|
||||
RoutingIndexManager::NodeIndex to) const;
|
||||
|
||||
private:
|
||||
const int64_t stop_time_;
|
||||
const LocationContainer& location_container_;
|
||||
operations_research::RoutingNodeEvaluator2 demand_;
|
||||
operations_research::RoutingNodeEvaluator2 transition_time_;
|
||||
RoutingNodeEvaluator2 demand_;
|
||||
RoutingNodeEvaluator2 transition_time_;
|
||||
};
|
||||
|
||||
// Route plan displayer.
|
||||
// TODO(user): Move the display code to the routing library.
|
||||
void DisplayPlan(
|
||||
const operations_research::RoutingIndexManager& manager,
|
||||
const operations_research::RoutingModel& routing,
|
||||
const operations_research::Assignment& plan, bool use_same_vehicle_costs,
|
||||
int64_t max_nodes_per_group, int64_t same_vehicle_cost,
|
||||
absl::Span<const std::string> dimension_names);
|
||||
void DisplayPlan(const RoutingIndexManager& manager,
|
||||
const RoutingModel& routing,
|
||||
const operations_research::Assignment& plan,
|
||||
bool use_same_vehicle_costs, int64_t max_nodes_per_group,
|
||||
int64_t same_vehicle_cost,
|
||||
absl::Span<const std::string> dimension_names);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
#include "ortools/routing/parsers/dow_parser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
|
||||
@@ -13,33 +13,19 @@
|
||||
|
||||
#include "ortools/routing/parsers/pdtsp_parser.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/gzipfile.h"
|
||||
#include "ortools/base/mathutil.h"
|
||||
#include "ortools/base/numbers.h"
|
||||
#include "ortools/base/path.h"
|
||||
#include "ortools/base/strtoint.h"
|
||||
#include "ortools/util/filelineiter.h"
|
||||
|
||||
namespace operations_research::routing {
|
||||
namespace {
|
||||
|
||||
using absl::ByAnyChar;
|
||||
|
||||
File* OpenReadOnly(absl::string_view file_name) {
|
||||
File* file = nullptr;
|
||||
if (file::Open(file_name, "r", &file, file::Defaults()).ok() &&
|
||||
file::Extension(file_name) == "gz") {
|
||||
file = GZipFileReader(file_name, file, TAKE_OWNERSHIP);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PdTspParser::PdTspParser() : section_(SIZE_SECTION) {}
|
||||
|
||||
@@ -63,7 +49,7 @@ std::function<int64_t(int, int)> PdTspParser::Distances() const {
|
||||
|
||||
void PdTspParser::ProcessNewLine(const std::string& line) {
|
||||
const std::vector<std::string> words =
|
||||
absl::StrSplit(line, ByAnyChar(" :\t"), absl::SkipEmpty());
|
||||
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
|
||||
if (!words.empty()) {
|
||||
switch (section_) {
|
||||
case SIZE_SECTION: {
|
||||
|
||||
@@ -17,14 +17,13 @@
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/commandlineflags.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/path.h"
|
||||
|
||||
#define ROOT_DIR "_main/"
|
||||
|
||||
ABSL_FLAG(std::string, solomon_test_archive,
|
||||
"ortools/bench/solomon/"
|
||||
"ortools/routing/benchmarks/solomon/"
|
||||
"testdata/solomon.zip",
|
||||
"Solomon: testing archive");
|
||||
ABSL_FLAG(std::string, solomon_test_instance, "google2.txt",
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -23,16 +25,25 @@
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/file.h"
|
||||
#include "ortools/base/map_util.h"
|
||||
#include "ortools/base/numbers.h"
|
||||
#include "ortools/base/options.h"
|
||||
#include "ortools/base/path.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/base/strtoint.h"
|
||||
#include "ortools/base/zipfile.h"
|
||||
#include "ortools/routing/parsers/simple_graph.h"
|
||||
#include "ortools/util/filelineiter.h"
|
||||
#include "re2/re2.h"
|
||||
|
||||
@@ -158,8 +169,8 @@ std::shared_ptr<zipfile::ZipArchive> OpenZipArchiveIfItExists(
|
||||
|
||||
TspLibParser::TspLibParser()
|
||||
: size_(0),
|
||||
capacity_(kint64max),
|
||||
max_distance_(kint64max),
|
||||
capacity_(std::numeric_limits<int64_t>::max()),
|
||||
max_distance_(std::numeric_limits<int64_t>::max()),
|
||||
distance_function_(nullptr),
|
||||
explicit_costs_(),
|
||||
depot_(0),
|
||||
@@ -171,28 +182,45 @@ TspLibParser::TspLibParser()
|
||||
edge_column_(0),
|
||||
to_read_(0) {}
|
||||
|
||||
bool TspLibParser::LoadFile(absl::string_view file_name) {
|
||||
namespace {
|
||||
absl::StatusOr<File*> OpenFile(absl::string_view file_name) {
|
||||
File* file = nullptr;
|
||||
RETURN_IF_ERROR(file::Open(file_name, "r", &file, file::Defaults()));
|
||||
return file;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
absl::Status TspLibParser::LoadFile(absl::string_view file_name) {
|
||||
std::shared_ptr<zipfile::ZipArchive> zip_archive(
|
||||
OpenZipArchiveIfItExists(file_name));
|
||||
valid_section_found_ = false;
|
||||
ASSIGN_OR_RETURN(File* const file, OpenFile(file_name));
|
||||
for (const std::string& line :
|
||||
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
FileLines(file_name, file, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
ProcessNewLine(line);
|
||||
}
|
||||
FinalizeEdgeWeights();
|
||||
return true;
|
||||
if (!valid_section_found_) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "Could not find any valid sections in " << file_name;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
int TspLibParser::SizeFromFile(absl::string_view file_name) const {
|
||||
absl::StatusOr<int> TspLibParser::SizeFromFile(
|
||||
absl::string_view file_name) const {
|
||||
std::shared_ptr<zipfile::ZipArchive> zip_archive(
|
||||
OpenZipArchiveIfItExists(file_name));
|
||||
ASSIGN_OR_RETURN(File* const file, OpenFile(file_name));
|
||||
int size = 0;
|
||||
for (const std::string& line :
|
||||
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
FileLines(file_name, file, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
if (RE2::PartialMatch(line, "DIMENSION\\s*:\\s*(\\d+)", &size)) {
|
||||
break;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "Could not determine problem size from " << file_name;
|
||||
}
|
||||
|
||||
void TspLibParser::ParseExplicitFullMatrix(
|
||||
@@ -398,12 +426,12 @@ void TspLibParser::FinalizeEdgeWeights() {
|
||||
}
|
||||
}
|
||||
|
||||
void TspLibParser::ParseSections(absl::Span<const std::string> words) {
|
||||
bool TspLibParser::ParseSections(absl::Span<const std::string> words) {
|
||||
const int words_size = words.size();
|
||||
CHECK_GT(words_size, 0);
|
||||
if (!gtl::FindCopy(*kSections, words[0], §ion_)) {
|
||||
LOG(WARNING) << "Unknown section: " << words[0];
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const std::string& last_word = words[words_size - 1];
|
||||
switch (section_) {
|
||||
@@ -483,7 +511,7 @@ void TspLibParser::ParseSections(absl::Span<const std::string> words) {
|
||||
break;
|
||||
}
|
||||
case FIXED_EDGES_SECTION: {
|
||||
to_read_ = kint64max;
|
||||
to_read_ = std::numeric_limits<int64_t>::max();
|
||||
break;
|
||||
}
|
||||
case NODE_COORD_TYPE: {
|
||||
@@ -501,7 +529,7 @@ void TspLibParser::ParseSections(absl::Span<const std::string> words) {
|
||||
break;
|
||||
}
|
||||
case DEPOT_SECTION: {
|
||||
to_read_ = kint64max;
|
||||
to_read_ = std::numeric_limits<int64_t>::max();
|
||||
break;
|
||||
}
|
||||
case DEMAND_SECTION: {
|
||||
@@ -516,6 +544,7 @@ void TspLibParser::ParseSections(absl::Span<const std::string> words) {
|
||||
LOG(WARNING) << "Unknown section: " << words[0];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TspLibParser::ProcessNewLine(const std::string& line) {
|
||||
@@ -653,7 +682,9 @@ void TspLibParser::ProcessNewLine(const std::string& line) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ParseSections(words);
|
||||
// TODO(user): Check that proper sections were read (necessary and
|
||||
// non-overlapping ones).
|
||||
valid_section_found_ |= ParseSections(words);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,19 +776,18 @@ const absl::flat_hash_map<std::string, TspLibParser::EdgeWeightFormats>* const
|
||||
|
||||
TspLibTourParser::TspLibTourParser() : section_(UNDEFINED_SECTION), size_(0) {}
|
||||
|
||||
// TODO(user): Return false when issues were encountered while parsing the
|
||||
// file.
|
||||
bool TspLibTourParser::LoadFile(absl::string_view file_name) {
|
||||
absl::Status TspLibTourParser::LoadFile(absl::string_view file_name) {
|
||||
section_ = UNDEFINED_SECTION;
|
||||
comments_.clear();
|
||||
tour_.clear();
|
||||
std::shared_ptr<zipfile::ZipArchive> zip_archive(
|
||||
OpenZipArchiveIfItExists(file_name));
|
||||
ASSIGN_OR_RETURN(File* const file, OpenFile(file_name));
|
||||
for (const std::string& line :
|
||||
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
FileLines(file_name, file, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
ProcessNewLine(line);
|
||||
}
|
||||
return true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void TspLibTourParser::ProcessNewLine(const std::string& line) {
|
||||
@@ -816,18 +846,17 @@ const absl::flat_hash_map<std::string, TspLibTourParser::Sections>* const
|
||||
|
||||
CVRPToursParser::CVRPToursParser() : cost_(0) {}
|
||||
|
||||
// TODO(user): Return false when issues were encountered while parsing the
|
||||
// file.
|
||||
bool CVRPToursParser::LoadFile(absl::string_view file_name) {
|
||||
absl::Status CVRPToursParser::LoadFile(absl::string_view file_name) {
|
||||
tours_.clear();
|
||||
cost_ = 0;
|
||||
std::shared_ptr<zipfile::ZipArchive> zip_archive(
|
||||
OpenZipArchiveIfItExists(file_name));
|
||||
ASSIGN_OR_RETURN(File* const file, OpenFile(file_name));
|
||||
for (const std::string& line :
|
||||
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
FileLines(file_name, file, FileLineIterator::REMOVE_INLINE_CR)) {
|
||||
ProcessNewLine(line);
|
||||
}
|
||||
return true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void CVRPToursParser::ProcessNewLine(const std::string& line) {
|
||||
|
||||
@@ -24,16 +24,17 @@
|
||||
#ifndef OR_TOOLS_ROUTING_PARSERS_TSPLIB_PARSER_H_
|
||||
#define OR_TOOLS_ROUTING_PARSERS_TSPLIB_PARSER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/types.h"
|
||||
#include "ortools/routing/parsers/simple_graph.h"
|
||||
|
||||
namespace operations_research::routing {
|
||||
@@ -45,9 +46,9 @@ class TspLibParser final {
|
||||
|
||||
TspLibParser();
|
||||
// Loads and parses a routing problem from a given file.
|
||||
bool LoadFile(absl::string_view file_name);
|
||||
absl::Status LoadFile(absl::string_view file_name);
|
||||
// Returns the number of nodes in the routing problem stored in a given file.
|
||||
int SizeFromFile(absl::string_view file_name) const;
|
||||
absl::StatusOr<int> SizeFromFile(absl::string_view file_name) const;
|
||||
// Returns a function returning edge weights between nodes.
|
||||
EdgeWeights GetEdgeWeights() const { return distance_function_; }
|
||||
// Returns the index of the depot.
|
||||
@@ -150,7 +151,7 @@ class TspLibParser final {
|
||||
void ParseNodeCoord(absl::Span<const std::string> words);
|
||||
void SetUpEdgeWeightSection();
|
||||
void FinalizeEdgeWeights();
|
||||
void ParseSections(absl::Span<const std::string> words);
|
||||
bool ParseSections(absl::Span<const std::string> words);
|
||||
void ProcessNewLine(const std::string& line);
|
||||
void SetExplicitCost(int from, int to, int64_t cost) {
|
||||
if (explicit_costs_.size() != size_ * size_) {
|
||||
@@ -190,6 +191,7 @@ class TspLibParser final {
|
||||
std::string name_;
|
||||
std::string comments_;
|
||||
int64_t to_read_;
|
||||
bool valid_section_found_ = false;
|
||||
};
|
||||
|
||||
// Class parsing tour (solution) data in TSLIB95 format.
|
||||
@@ -198,7 +200,7 @@ class TspLibTourParser final {
|
||||
public:
|
||||
TspLibTourParser();
|
||||
// Loads and parses a given tour file.
|
||||
bool LoadFile(absl::string_view file_name);
|
||||
absl::Status LoadFile(absl::string_view file_name);
|
||||
// Returns a vector corresponding to the sequence of nodes of the tour.
|
||||
const std::vector<int>& tour() const { return tour_; }
|
||||
// Returns the size of the tour.
|
||||
@@ -237,7 +239,7 @@ class CVRPToursParser final {
|
||||
public:
|
||||
CVRPToursParser();
|
||||
// Loads and parses a given tours file.
|
||||
bool LoadFile(absl::string_view file_name);
|
||||
absl::Status LoadFile(absl::string_view file_name);
|
||||
// Returns a vector corresponding to the sequence of nodes of tours.
|
||||
const std::vector<std::vector<int>>& tours() const { return tours_; }
|
||||
int64_t cost() const { return cost_; }
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/container/btree_set.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/filesystem.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/memfile.h"
|
||||
#include "ortools/base/options.h"
|
||||
#include "ortools/base/path.h"
|
||||
@@ -35,6 +37,9 @@
|
||||
namespace operations_research::routing {
|
||||
namespace {
|
||||
|
||||
using ::testing::Gt;
|
||||
using ::testing::status::IsOkAndHolds;
|
||||
|
||||
TEST(TspLibParserTest, GeneratedDataSets) {
|
||||
static const char kName[] = "GoogleTest";
|
||||
static const char* const kTypes[] = {"TSP", "CVRP"};
|
||||
@@ -187,8 +192,9 @@ TEST(TspLibParserTest, GeneratedDataSets) {
|
||||
const std::string kMMFileName{std::tmpnam(nullptr)};
|
||||
RegisteredMemFile registered(kMMFileName, data);
|
||||
TspLibParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(kMMFileName));
|
||||
EXPECT_EQ(kDimension, parser.SizeFromFile(kMMFileName));
|
||||
EXPECT_OK(parser.LoadFile(kMMFileName));
|
||||
EXPECT_THAT(parser.SizeFromFile(kMMFileName),
|
||||
IsOkAndHolds(kDimension));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,8 +216,8 @@ TEST(TspLibParserTest, ParseHCPEdgeList) {
|
||||
const std::string kMMFileName{std::tmpnam(nullptr)};
|
||||
RegisteredMemFile registered(kMMFileName, kData);
|
||||
TspLibParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(kMMFileName));
|
||||
EXPECT_EQ(3, parser.SizeFromFile(kMMFileName));
|
||||
EXPECT_OK(parser.LoadFile(kMMFileName));
|
||||
EXPECT_THAT(parser.SizeFromFile(kMMFileName), IsOkAndHolds(3));
|
||||
EXPECT_EQ(2, parser.edges()[0].size());
|
||||
EXPECT_EQ(1, parser.edges()[0][0]);
|
||||
EXPECT_EQ(2, parser.edges()[0][1]);
|
||||
@@ -232,8 +238,8 @@ TEST(TspLibParserTest, ParseHCPAdjList) {
|
||||
const std::string kMMFileName{std::tmpnam(nullptr)};
|
||||
RegisteredMemFile registered(kMMFileName, kData);
|
||||
TspLibParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(kMMFileName));
|
||||
EXPECT_EQ(3, parser.SizeFromFile(kMMFileName));
|
||||
EXPECT_OK(parser.LoadFile(kMMFileName));
|
||||
EXPECT_THAT(parser.SizeFromFile(kMMFileName), IsOkAndHolds(3));
|
||||
EXPECT_EQ(1, parser.edges()[0].size());
|
||||
EXPECT_EQ(2, parser.edges()[0][0]);
|
||||
EXPECT_EQ(1, parser.edges()[1].size());
|
||||
@@ -247,7 +253,7 @@ TEST(TspLibParserTest, ParseKytojoki33Depot) {
|
||||
::testing::SrcDir(), ROOT_DIR "ortools/routing/parsers/testdata/",
|
||||
"tsplib_Kytojoki_33.vrp");
|
||||
TspLibParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(file_name));
|
||||
EXPECT_OK(parser.LoadFile(file_name));
|
||||
// The depot is a new node, given by its coordinates, instead of an existing
|
||||
// node in the graph.
|
||||
EXPECT_EQ(2400, parser.depot());
|
||||
@@ -256,6 +262,18 @@ TEST(TspLibParserTest, ParseKytojoki33Depot) {
|
||||
EXPECT_EQ(0.0, parser.coordinates()[parser.depot()].y);
|
||||
}
|
||||
|
||||
// Make sure we properly fail when reading an invalid file. To test this,
|
||||
// reading from a raw tar file instead of the included subfiles.
|
||||
TEST(TspLibParserTest, ReadFailOnInvalidFile) {
|
||||
std::string file_name =
|
||||
file::JoinPath(::testing::SrcDir(), ROOT_DIR
|
||||
"operations_research_data/operations_research_data/"
|
||||
"TSPLIB95/ALL_tsp.tar.gz");
|
||||
TspLibParser parser;
|
||||
EXPECT_THAT(parser.LoadFile(file_name),
|
||||
testing::status::StatusIs(absl::StatusCode::kInvalidArgument));
|
||||
}
|
||||
|
||||
TEST(TspLibTourParserTest, LoadAllDataSets) {
|
||||
static const char kArchive[] =
|
||||
ROOT_DIR "operations_research_data/TSPLIB95/ALL_tsp.tar.gz";
|
||||
@@ -300,7 +318,7 @@ TEST(TspLibTourParserTest, LoadAllDataSets) {
|
||||
.ok()) {
|
||||
for (const std::string& match : matches) {
|
||||
TspLibTourParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(match));
|
||||
EXPECT_OK(parser.LoadFile(match));
|
||||
EXPECT_EQ(kExpectedComments[file_index], parser.comments());
|
||||
file_index++;
|
||||
}
|
||||
@@ -335,7 +353,7 @@ TEST(CVRPToursParserTest, LoadAllDataSets) {
|
||||
.ok()) {
|
||||
for (const std::string& match : matches) {
|
||||
CVRPToursParser parser;
|
||||
EXPECT_TRUE(parser.LoadFile(match));
|
||||
EXPECT_OK(parser.LoadFile(match));
|
||||
EXPECT_EQ(kExpectedCosts[file_index], parser.cost());
|
||||
file_index++;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ortools/base/mathutil.h"
|
||||
#include "ortools/base/numbers.h"
|
||||
#include "ortools/base/path.h"
|
||||
#include "ortools/base/strtoint.h"
|
||||
#include "ortools/base/zipfile.h"
|
||||
#include "ortools/util/filelineiter.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user