routing: backport change from main

This commit is contained in:
Corentin Le Molgat
2025-09-17 16:27:47 +02:00
parent c0ad2c2914
commit 90aad4ab4b
25 changed files with 531 additions and 177 deletions

View File

@@ -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"],

View File

@@ -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) {

View File

@@ -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_;

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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:

View File

@@ -151,6 +151,7 @@ message RuinStrategy {
message RecreateParameters {
oneof parameters {
LocalCheapestInsertionParameters local_cheapest_insertion = 1;
SavingsParameters savings = 2;
}
}

View 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

View 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_

View File

@@ -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

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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",

View File

@@ -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})

View File

@@ -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.

View File

@@ -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"

View File

@@ -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);

View File

@@ -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

View File

@@ -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"

View File

@@ -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: {

View File

@@ -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",

View File

@@ -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], &section_)) {
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) {

View File

@@ -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_; }

View File

@@ -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++;
}

View File

@@ -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"