google3: export routing/

This commit is contained in:
Corentin Le Molgat
2022-10-07 18:28:18 +02:00
parent a0c86afec1
commit e961176d20
43 changed files with 8586 additions and 0 deletions

293
ortools/routing/BUILD.bazel Normal file
View File

@@ -0,0 +1,293 @@
# Copyright 2010-2022 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.
package(default_visibility = ["//visibility:public"])
cc_library(
name = "simple_graph",
srcs = ["simple_graph.cc"],
hdrs = ["simple_graph.h"],
deps = [
"@com_google_absl//absl/hash",
],
)
cc_test(
name = "simple_graph_test",
size = "small",
srcs = ["simple_graph_test.cc"],
deps = [
":simple_graph",
"@com_google_absl//absl/hash:hash_testing",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "solomon_parser",
srcs = ["solomon_parser.cc"],
hdrs = ["solomon_parser.h"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:map_util",
"//ortools/base:numbers",
"//ortools/base:path",
"//ortools/base:zipfile",
"//ortools/util:filelineiter",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "solomon_parser_test",
size = "small",
srcs = ["solomon_parser_test.cc"],
data = ["//ortools/routing/testdata:solomon.zip"],
deps = [
":solomon_parser",
"//ortools/base",
"//ortools/base:file",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "carp_parser",
srcs = ["carp_parser.cc"],
hdrs = ["carp_parser.h"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:linked_hash_map",
"//ortools/base:numbers",
"//ortools/util:filelineiter",
],
)
cc_test(
name = "carp_parser_test",
size = "small",
srcs = ["carp_parser_test.cc"],
data = [
"//ortools/routing/testdata:carp_gdb19.dat",
"//ortools/routing/testdata:carp_gdb19_diferente_deposito.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecta_lista_aristas_req.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_arinoreq.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_arireq.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_capacidad.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_coste.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_deposito.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_tipo.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_vehiculos.dat",
"//ortools/routing/testdata:carp_gdb19_incorrecto_vertices.dat",
"//ortools/routing/testdata:carp_gdb19_mixed_arcs.dat",
"//ortools/routing/testdata:carp_gdb19_no_arista_req.dat",
],
deps = [
":carp_parser",
"//ortools/base:mock_log",
"//ortools/base:path",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/hash:hash_testing",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "nearp_parser",
srcs = ["nearp_parser.cc"],
hdrs = ["nearp_parser.h"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:linked_hash_map",
"//ortools/base:numbers",
"//ortools/util:filelineiter",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/hash",
],
)
cc_test(
name = "nearp_parser_test",
size = "small",
srcs = ["nearp_parser_test.cc"],
data = [
"//ortools/routing/testdata:nearp_BHW1.dat",
"//ortools/routing/testdata:nearp_toy.dat",
],
deps = [
":nearp_parser",
"//ortools/base:file",
"//ortools/base:mock_log",
"//ortools/base:path",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/hash:hash_testing",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "pdtsp_parser",
srcs = ["pdtsp_parser.cc"],
hdrs = ["pdtsp_parser.h"],
visibility = ["//visibility:public"],
deps = [
"//ortools/base",
"//ortools/base:file",
"//ortools/base:gzipfile",
"//ortools/base:mathutil",
"//ortools/base:numbers",
"//ortools/base:path",
"//ortools/base:strtoint",
"//ortools/util:filelineiter",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "pdtsp_parser_test",
size = "small",
srcs = ["pdtsp_parser_test.cc"],
data = [
"//ortools/routing/testdata:pdtsp_prob10b.txt",
"//ortools/routing/testdata:pdtsp_prob10b.txt.gz",
],
deps = [
":pdtsp_parser",
"//ortools/base",
"//ortools/base:path",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "tsplib_parser",
srcs = ["tsplib_parser.cc"],
hdrs = ["tsplib_parser.h"],
visibility = ["//visibility:public"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:map_util",
"//ortools/base:mathutil",
"//ortools/base:numbers",
"//ortools/base:path",
"//ortools/base:strtoint",
"//ortools/base:zipfile",
"//ortools/util:filelineiter",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_re2//:re2",
],
)
cc_test(
name = "tsplib_parser_test",
size = "small",
timeout = "moderate",
srcs = ["tsplib_parser_test.cc"],
data = [
#"@com_google_ortools_data//TSPLIB95:ALL_atsp.tar",
#"@com_google_ortools_data//TSPLIB95:ALL_hcp.tar",
#"@com_google_ortools_data//TSPLIB95:ALL_sop.tar",
#"@com_google_ortools_data//TSPLIB95:ALL_tsp.tar.gz",
#"@com_google_ortools_data//TSPLIB95:ALL_tsp.zip",
#"@com_google_ortools_data//TSPLIB95:ALL_vrp.tar",
#"@com_google_ortools_data//TSPLIB95:ALL_vrp.zip",
"//ortools/routing/testdata:tsplib_Kytojoki_33.vrp",
],
deps = [
":tsplib_parser",
"//ortools/base",
"//ortools/base:filesystem",
"//ortools/base:memfile",
"@com_google_absl//absl/container:btree",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "tsptw_parser",
srcs = ["tsptw_parser.cc"],
hdrs = ["tsptw_parser.h"],
visibility = ["//visibility:public"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:mathutil",
"//ortools/base:numbers",
"//ortools/base:path",
"//ortools/base:strtoint",
"//ortools/base:zipfile",
"//ortools/util:filelineiter",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "tsptw_parser_test",
size = "small",
srcs = ["tsptw_parser_test.cc"],
data = [
#"//ortools/util/testdata:n20w20.001.txt",
#"//ortools/util/testdata:n20w20.002.txt",
#"//ortools/util/testdata:rc201.0",
],
deps = [
":tsptw_parser",
"//ortools/base",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "solution_serializer",
srcs = ["solution_serializer.cc"],
hdrs = ["solution_serializer.h"],
deps = [
":simple_graph",
"//ortools/base",
"//ortools/base:file",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "solution_serializer_test",
size = "small",
srcs = ["solution_serializer_test.cc"],
deps = [
":solution_serializer",
"//ortools/base",
"//ortools/base:mutable_memfile",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "cvrptw_lib",
srcs = ["cvrptw_lib.cc"],
hdrs = ["cvrptw_lib.h"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/util:random_engine",
],
)

View File

@@ -0,0 +1,248 @@
// Copyright 2010-2022 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/routing/carp_parser.h"
#include <array>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "ortools/base/numbers.h"
#include "ortools/util/filelineiter.h"
namespace operations_research {
CarpParser::CarpParser() { Initialize(); }
void CarpParser::Initialize() {
name_.clear();
comment_.clear();
number_of_nodes_ = 0;
number_of_edges_with_servicing_ = 0;
number_of_edges_without_servicing_ = 0;
total_servicing_cost_ = 0;
depot_ = 0;
traversing_costs_.clear();
servicing_demands_.clear();
n_vehicles_ = 0;
capacity_ = 0;
section_ = METADATA;
}
bool CarpParser::LoadFile(const std::string& file_name) {
Initialize();
return ParseFile(file_name);
}
bool CarpParser::ParseFile(const std::string& file_name) {
static auto section_headers = std::array<const char*, 12>({
"NOMBRE",
"COMENTARIO",
"VERTICES",
"ARISTAS_REQ",
"ARISTAS_NOREQ",
"VEHICULOS",
"CAPACIDAD",
"TIPO_COSTES_ARISTAS",
"COSTE_TOTAL_REQ",
"LISTA_ARISTAS_REQ",
"LISTA_ARISTAS_NOREQ",
"DEPOSITO",
});
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
if (absl::c_linear_search(section_headers, words[0])) {
// First, check if a new section has been met.
if (words[0] == "LISTA_ARISTAS_REQ") {
traversing_costs_.reserve(NumberOfEdges());
servicing_demands_.reserve(NumberOfEdgesWithServicing());
section_ = ARCS_WITH_SERVICING;
} else if (words[0] == "LISTA_ARISTAS_NOREQ") {
traversing_costs_.reserve(NumberOfEdges());
section_ = ARCS_WITHOUT_SERVICING;
} else {
if (!ParseMetadataLine(words)) {
LOG(ERROR) << "Error when parsing the following metadata line: "
<< line;
return false;
}
}
} else {
// If no new section is detected, process according to the current state.
switch (section_) {
case ARCS_WITH_SERVICING:
if (!ParseEdge(line, true)) {
LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_REQ: " << line;
return false;
}
break;
case ARCS_WITHOUT_SERVICING:
if (!ParseEdge(line, false)) {
LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_NOREQ: "
<< line;
return false;
}
break;
default:
LOG(ERROR) << "Could not parse line outside edge lists: " << line;
return false;
}
}
}
return !servicing_demands_.empty();
}
namespace {
std::optional<int64_t> ParseNodeIndex(std::string_view text);
} // namespace
bool CarpParser::ParseMetadataLine(const std::vector<std::string>& words) {
if (words[0] == "NOMBRE") {
name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
} else if (words[0] == "COMENTARIO") {
comment_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
} else if (words[0] == "VERTICES") {
number_of_nodes_ = strings::ParseLeadingInt64Value(words[1], -1);
if (number_of_nodes_ <= 0) {
LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
return false;
}
} else if (words[0] == "ARISTAS_REQ") {
number_of_edges_with_servicing_ =
strings::ParseLeadingInt64Value(words[1], -1);
if (number_of_edges_with_servicing_ <= 0) {
LOG(ERROR) << "Error when parsing the number of edges with servicing: "
<< words[1];
return false;
}
} else if (words[0] == "ARISTAS_NOREQ") {
number_of_edges_without_servicing_ =
strings::ParseLeadingInt64Value(words[1], -1);
if (number_of_edges_without_servicing_ < 0) {
// It is possible to have a valid instance with zero edges that have no
// servicing (i.e. number_of_edges_without_servicing_ == 0).
LOG(ERROR) << "Error when parsing the number of edges without servicing: "
<< words[1];
return false;
}
} else if (words[0] == "VEHICULOS") {
n_vehicles_ = strings::ParseLeadingInt64Value(words[1], -1);
if (n_vehicles_ <= 0) {
LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
return false;
}
} else if (words[0] == "CAPACIDAD") {
capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
if (capacity_ <= 0) {
LOG(ERROR) << "Error when parsing the capacity: " << words[1];
return false;
}
} else if (words[0] == "TIPO_COSTES_ARISTAS") {
if (words[1] != "EXPLICITOS") {
// Actually, this is the only defined value for this file format.
LOG(ERROR) << "Value of TIPO_COSTES_ARISTAS is unexpected, only "
"EXPLICITOS is supported, but "
<< words[1] << " was found";
return false;
}
} else if (words[0] == "COSTE_TOTAL_REQ") {
total_servicing_cost_ = strings::ParseLeadingInt64Value(words[1], -1);
if (total_servicing_cost_ == -1) {
LOG(ERROR) << "Error when parsing the total servicing cost: " << words[1];
return false;
}
} else if (words[0] == "DEPOSITO") {
// Supposed to be the last value of the file.
const std::optional<int64_t> depot = ParseNodeIndex(words[1]);
if (!depot.has_value()) {
LOG(ERROR) << "Error when parsing the depot: " << words[1];
return false;
}
depot_ = depot.value();
}
return true;
}
bool CarpParser::ParseEdge(std::string_view line, bool with_servicing) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
// Parse the edge.
std::optional<int64_t> opt_head = ParseNodeIndex(words[0]);
if (!opt_head.has_value()) {
LOG(ERROR) << "Error when parsing the head node: " << words[0];
return false;
}
const int64_t head = opt_head.value();
std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
if (!opt_tail.has_value()) {
LOG(ERROR) << "Error when parsing the tail node: " << words[1];
return false;
}
const int64_t tail = opt_tail.value();
if (head == tail) {
LOG(ERROR) << "The head and tail nodes are identical: " << line;
return false;
}
// Parse the cost.
if (words[2] != "coste") {
LOG(ERROR) << "Unexpected keyword: " << words[2];
return false;
}
const int64_t cost = strings::ParseLeadingInt64Value(words[3], -1);
traversing_costs_[{tail, head}] = cost;
// Parse the servicing if needed.
if (with_servicing) {
if (words[4] != "demanda") {
LOG(ERROR) << "Unexpected keyword: " << words[2];
return false;
}
const int64_t servicing = strings::ParseLeadingInt64Value(words[5], -1);
servicing_demands_[{tail, head}] = servicing;
}
// Ensure there are no extraneous elements.
const int64_t next_id = (with_servicing) ? 6 : 4;
if (words.size() > next_id) {
LOG(ERROR) << "Extraneous elements in line, starting with: "
<< words[next_id];
return false;
}
return true;
}
namespace {
std::optional<int64_t> ParseNodeIndex(std::string_view text) {
const int64_t node = strings::ParseLeadingInt64Value(text, -1);
if (node < 0) {
LOG(ERROR) << "Could not parse node index: " << text;
return std::nullopt;
}
return {node - 1};
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,185 @@
// Copyright 2010-2022 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.
// A parser for CARPLIB instances. The base files are available online, as well
// as a description of the (Spanish-based) format:
// https://www.uv.es/belengue/carp.html ("CARPLIB")
// https://www.uv.es/~belengue/carp/READ_ME
//
// The goal is to find routes starting and ending at a depot which visit a
// set of arcs (whereas a VRP visits nodes). The objective is to minimize the
// total cost, which is due to either servicing an edge (i.e. performing the
// required action) or traversing an edge (to get to another point in space).
// Not all arcs/edges in the graph must be serviced.
//
// By this formulation, the total cost of servicing is known in advance.
// All vehicles start at the same node, the depot, having index 1.
// Servicing an edge requires resources, vehicles have a limited capacity. All
// vehicles have the same capacity.
//
// The format of the data is the following:
//
// NOMBRE : <INSTANCE-NAME>
// COMENTARIO : <ARBITRARY-COMMENT>
// VERTICES : <NUMBER-OF-NODES, int>
// ARISTAS_REQ : <NUMBER-OF-EDGES-WITH-NONZERO-SERVICING, int>
// ARISTAS_NOREQ : <NUMBER-OF-EDGES-WITH-ZERO-SERVICING, int>
// VEHICULOS : <NUMBER-OF-VEHICLES, int>
// CAPACIDAD : <CAPACITY-OF-EACH-VEHICLE, int>
// TIPO_COSTES_ARISTAS : EXPLICITOS
// COSTE_TOTAL_REQ : <TOTAL-SERVICING-COST>
// LISTA_ARISTAS_REQ :
// ( <HEAD-NODE-OF-EDGE, int>, <TAIL-NODE-OF-EDGE, int> )
// coste <TRAVERSING-COST, int> demanda <SERVICING, int>
// <repeated, one edge per line>
// LISTA_ARISTAS_NOREQ :
// ( <HEAD-NODE-OF-EDGE, int>, <TAIL-NODE-OF-EDGE, int> )
// coste <TRAVERSING-COST, int>
// <repeated, one edge per line>
// DEPOSITO : 1
//
// While the file format is defined with 1-based indexing, the output of the
// parser is always 0-based. Users of this parser should never see any 1-based
// index; only 0-based index should be used to query values.
#ifndef OR_TOOLS_ROUTING_CARP_PARSER_H_
#define OR_TOOLS_ROUTING_CARP_PARSER_H_
#include <algorithm>
#include <string>
#include <string_view>
#include <vector>
#include "ortools/base/linked_hash_map.h"
#include "ortools/base/logging.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
class CarpParser {
public:
CarpParser();
#ifndef SWIG
CarpParser(const CarpParser&) = delete;
const CarpParser& operator=(const CarpParser&) = delete;
#endif
// Loads instance from a file into this parser object.
bool LoadFile(const std::string& file_name);
// Returns the name of the instance being solved.
const std::string& name() const { return name_; }
// Returns the comment of the instance being solved, typically an upper bound.
const std::string& comment() const { return comment_; }
// Returns the index of the depot.
int64_t depot() const { return depot_; }
// Returns the number of nodes in the current routing problem.
int64_t NumberOfNodes() const { return number_of_nodes_; }
// Returns the number of edges in the current routing problem, with or
// without servicing required.
int64_t NumberOfEdges() const {
return NumberOfEdgesWithServicing() + NumberOfEdgesWithoutServicing();
}
// Returns the number of edges in the current routing problem that require
// servicing.
int64_t NumberOfEdgesWithServicing() const {
return number_of_edges_with_servicing_;
}
// Returns the number of edges in the current routing problem that do not
// require servicing.
int64_t NumberOfEdgesWithoutServicing() const {
return number_of_edges_without_servicing_;
}
// Returns the total servicing cost for all arcs.
int64_t TotalServicingCost() const { return total_servicing_cost_; }
// Returns the servicing of the edges in the current routing problem.
const gtl::linked_hash_map<Edge, int64_t>& servicing_demands() const {
return servicing_demands_;
}
// Returns the traversing costs of the edges in the current routing problem.
const gtl::linked_hash_map<Edge, int64_t>& traversing_costs() const {
return traversing_costs_;
}
// Returns the maximum number of vehicles to use.
int64_t NumberOfVehicles() const { return n_vehicles_; }
// Returns the capacity of the vehicles.
int64_t capacity() const { return capacity_; }
// Returns the traversing cost for an edge. All edges are supposed to have
// a traversing cost.
int64_t GetTraversingCost(Edge edge) const {
CHECK(traversing_costs_.contains(edge))
<< "Unknown edge: " << edge.tail() << " - " << edge.head();
return traversing_costs_.at(edge);
}
int64_t GetTraversingCost(int64_t tail, int64_t head) const {
return GetTraversingCost({tail, head});
}
// Checks whether this edge requires servicing.
int64_t HasServicingNeed(Edge edge) const {
return servicing_demands_.contains(edge);
}
int64_t HasServicingNeed(int64_t tail, int64_t head) const {
return HasServicingNeed({tail, head});
}
// Returns the servicing for an edge. Only a subset of edges have a servicing
// need.
int64_t GetServicing(Edge edge) const {
CHECK(HasServicingNeed(edge))
<< "Unknown edge: " << edge.tail() << " - " << edge.head();
return servicing_demands_.at(edge);
}
int64_t GetServicing(int64_t tail, int64_t head) const {
return GetServicing({tail, head});
}
private:
// Parsing.
enum Section {
METADATA,
ARCS_WITH_SERVICING,
ARCS_WITHOUT_SERVICING,
UNDEFINED_SECTION
};
void Initialize();
bool ParseFile(const std::string& file_name);
bool ParseMetadataLine(const std::vector<std::string>& words);
bool ParseEdge(std::string_view line, bool with_servicing);
// Parsing data.
Section section_;
// Instance data:
// - metadata
std::string name_;
std::string comment_;
int64_t number_of_nodes_;
int64_t number_of_edges_with_servicing_;
int64_t number_of_edges_without_servicing_;
int64_t total_servicing_cost_;
int64_t depot_;
// - graph costs and servicing demands. Keep track of the order of the
// demands: the output format requires to use the servicing-demands IDs,
// which are indices when iterating over this map.
gtl::linked_hash_map<Edge, int64_t> traversing_costs_;
gtl::linked_hash_map<Edge, int64_t> servicing_demands_;
// - vehicles
int64_t n_vehicles_;
int64_t capacity_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_CARP_PARSER_H_

View File

@@ -0,0 +1,270 @@
// Copyright 2010-2022 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/routing/carp_parser.h"
#include <string>
#include "absl/flags/flag.h"
#include "gtest/gtest.h"
#include "ortools/base/mock-log.h"
#include "ortools/base/path.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
namespace operations_research {
namespace {
TEST(CarpParserTest, Constructor) {
CarpParser parser;
EXPECT_EQ(parser.name(), "");
EXPECT_EQ(parser.comment(), "");
EXPECT_EQ(parser.NumberOfNodes(), 0);
EXPECT_EQ(parser.NumberOfEdgesWithServicing(), 0);
EXPECT_EQ(parser.NumberOfEdgesWithoutServicing(), 0);
EXPECT_EQ(parser.NumberOfEdges(), 0);
EXPECT_EQ(parser.NumberOfVehicles(), 0);
EXPECT_EQ(parser.capacity(), 0);
EXPECT_EQ(parser.TotalServicingCost(), 0);
EXPECT_EQ(parser.depot(), 0);
}
TEST(CarpParserTest, LoadEmptyFileName) {
std::string empty_file_name;
CarpParser parser;
EXPECT_FALSE(parser.LoadFile(empty_file_name));
}
TEST(CarpParserTest, LoadNonExistingFile) {
CarpParser parser;
EXPECT_FALSE(parser.LoadFile(""));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectNumberOfNodes) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the number of nodes: -4"));
EXPECT_CALL(
log,
Log(ERROR, testing::_,
"Error when parsing the following metadata line: VERTICES : -4"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_vertices.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectNumberOfArcsWithServicings) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(
log, Log(ERROR, testing::_,
"Error when parsing the number of edges with servicing: -11"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the following metadata line: "
"ARISTAS_REQ : -11"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_arireq.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectNumberOfArcsWithoutServicings) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(
log, Log(ERROR, testing::_,
"Error when parsing the number of edges without servicing: a"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the following metadata line: "
"ARISTAS_NOREQ : a"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_arinoreq.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectNumberOfVehicles) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the number of vehicles: 0"));
EXPECT_CALL(
log,
Log(ERROR, testing::_,
"Error when parsing the following metadata line: VEHICULOS : 0"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_vehiculos.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectCapacity) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log,
Log(ERROR, testing::_, "Error when parsing the capacity: 0"));
EXPECT_CALL(
log,
Log(ERROR, testing::_,
"Error when parsing the following metadata line: CAPACIDAD : 0"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_capacidad.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectTypeOfArcCost) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_,
"Value of TIPO_COSTES_ARISTAS is unexpected, only "
"EXPLICITOS is supported, but IMPLICITOS was found"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the following metadata line: "
"TIPO_COSTES_ARISTAS : IMPLICITOS"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_tipo.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectTotalServicingCost) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the total servicing cost: qwertz"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Error when parsing the following metadata line: "
"COSTE_TOTAL_REQ : qwertz"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_coste.dat")));
}
TEST(CarpParserTest, LoadInvalidFileIncorrectDepot) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_, "Could not parse node index: -1"));
EXPECT_CALL(log, Log(ERROR, testing::_, "Error when parsing the depot: -1"));
EXPECT_CALL(
log,
Log(ERROR, testing::_,
"Error when parsing the following metadata line: DEPOSITO : -1"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecto_deposito.dat")));
}
TEST(CarpParserTest, LoadInvalidFileNoEdgeWithServicing) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log,
Log(ERROR, testing::_,
"Error when parsing the number of edges with servicing: 0"));
EXPECT_CALL(
log,
Log(ERROR, testing::_,
"Error when parsing the following metadata line: ARISTAS_REQ : 0"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/"
"testdata/carp_gdb19_no_arista_req.dat")));
}
TEST(CarpParserTest, LoadInvalidFileServicingForArcsWithoutServicing) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_,
"Extraneous elements in line, starting with: demanda"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Could not parse line in LISTA_ARISTAS_NOREQ: ( 1, 4) "
"coste 3 demanda 3"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(
parser.LoadFile(file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/"
"testdata/carp_gdb19_mixed_arcs.dat")));
}
TEST(CarpParserTest, LoadInvalidFileServicingForArcsInWrongOrder) {
testing::ScopedMockLog log(testing::kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log(ERROR, testing::_, "Unexpected keyword: demanda"));
EXPECT_CALL(log, Log(ERROR, testing::_,
"Could not parse line in LISTA_ARISTAS_REQ: ( 1, 4) "
"demanda 3 coste 3"));
log.StartCapturingLogs();
CarpParser parser;
EXPECT_FALSE(parser.LoadFile(
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_incorrecta_lista_aristas_req.dat")));
}
TEST(CarpParserTest, LoadInstanceFile) {
std::string file_name =
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/carp_gdb19.dat");
CarpParser parser;
EXPECT_TRUE(parser.LoadFile(file_name));
EXPECT_EQ(parser.name(), "gdb19");
EXPECT_EQ(parser.comment(), "10000 (cota superior)");
EXPECT_EQ(parser.NumberOfNodes(), 8);
EXPECT_EQ(parser.NumberOfEdgesWithServicing(), 11);
EXPECT_EQ(parser.NumberOfEdgesWithoutServicing(), 0);
EXPECT_EQ(parser.NumberOfEdges(), 11);
EXPECT_EQ(parser.NumberOfVehicles(), 3);
EXPECT_EQ(parser.capacity(), 27);
EXPECT_EQ(parser.TotalServicingCost(), 45);
EXPECT_EQ(parser.depot(), 0);
EXPECT_EQ(parser.traversing_costs().size(), 11);
EXPECT_EQ(parser.GetTraversingCost(0, 1), 4);
EXPECT_EQ(parser.GetTraversingCost(1, 0), 4);
EXPECT_EQ(parser.servicing_demands().size(), 11);
EXPECT_EQ(parser.GetServicing(0, 1), 8);
EXPECT_EQ(parser.GetServicing(1, 0), 8);
}
TEST(CarpParserTest, LoadInstanceFileWithDifferentDepot) {
std::string file_name = file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/"
"carp_gdb19_diferente_deposito.dat");
CarpParser parser;
EXPECT_TRUE(parser.LoadFile(file_name));
EXPECT_EQ(parser.depot(), 4);
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,246 @@
// Copyright 2010-2022 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/routing/cvrptw_lib.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "absl/container/btree_set.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_format.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/util/random_engine.h"
namespace operations_research {
using NodeIndex = RoutingIndexManager::NodeIndex;
int32_t GetSeed(bool deterministic) {
if (deterministic) {
return 7777777;
} else {
return absl::Uniform<int64_t>(absl::BitGen(), 0,
std::numeric_limits<int64_t>::max());
}
}
LocationContainer::LocationContainer(int64_t speed, bool use_deterministic_seed)
: randomizer_(GetSeed(use_deterministic_seed)), speed_(speed) {
CHECK_LT(0, speed_);
}
void LocationContainer::AddRandomLocation(int64_t x_max, int64_t y_max) {
AddRandomLocation(x_max, y_max, 1);
}
void LocationContainer::AddRandomLocation(int64_t x_max, int64_t y_max,
int duplicates) {
const int64_t x = randomizer_.Uniform(x_max + 1);
const int64_t y = randomizer_.Uniform(y_max + 1);
for (int i = 0; i < duplicates; ++i) {
AddLocation(x, y);
}
}
int64_t LocationContainer::ManhattanDistance(NodeIndex from,
NodeIndex to) const {
return locations_[from].DistanceTo(locations_[to]);
}
int64_t LocationContainer::NegManhattanDistance(NodeIndex from,
NodeIndex to) const {
return -ManhattanDistance(from, to);
}
int64_t LocationContainer::ManhattanTime(NodeIndex from, NodeIndex to) const {
return ManhattanDistance(from, to) / speed_;
}
bool LocationContainer::SameLocation(NodeIndex node1, NodeIndex node2) const {
if (node1 < locations_.size() && node2 < locations_.size()) {
return locations_[node1].IsAtSameLocation(locations_[node2]);
}
return false;
}
int64_t LocationContainer::SameLocationFromIndex(int64_t node1,
int64_t node2) const {
// The direct conversion from constraint model indices to routing model
// nodes is correct because the depot is node 0.
// TODO(user): Fetch proper indices from routing model.
return SameLocation(NodeIndex(node1), NodeIndex(node2));
}
LocationContainer::Location::Location() : x_(0), y_(0) {}
LocationContainer::Location::Location(int64_t x, int64_t y) : x_(x), y_(y) {}
int64_t LocationContainer::Location::DistanceTo(
const Location& location) const {
return Abs(x_ - location.x_) + Abs(y_ - location.y_);
}
bool LocationContainer::Location::IsAtSameLocation(
const Location& location) const {
return x_ == location.x_ && y_ == location.y_;
}
int64_t LocationContainer::Location::Abs(int64_t value) {
return std::max(value, -value);
}
RandomDemand::RandomDemand(int size, NodeIndex depot,
bool use_deterministic_seed)
: size_(size),
depot_(depot),
use_deterministic_seed_(use_deterministic_seed) {
CHECK_LT(0, size_);
}
void RandomDemand::Initialize() {
const int64_t kDemandMax = 5;
const int64_t kDemandMin = 1;
demand_ = std::make_unique<int64_t[]>(size_);
random_engine_t randomizer(GetSeed(use_deterministic_seed_));
for (int order = 0; order < size_; ++order) {
if (order == depot_) {
demand_[order] = 0;
} else {
demand_[order] =
kDemandMin + randomizer.Uniform(kDemandMax - kDemandMin + 1);
}
}
}
int64_t RandomDemand::Demand(NodeIndex from, NodeIndex /*to*/) const {
return demand_[from.value()];
}
ServiceTimePlusTransition::ServiceTimePlusTransition(
int64_t time_per_demand_unit, RoutingNodeEvaluator2 demand,
RoutingNodeEvaluator2 transition_time)
: time_per_demand_unit_(time_per_demand_unit),
demand_(std::move(demand)),
transition_time_(std::move(transition_time)) {}
int64_t ServiceTimePlusTransition::Compute(NodeIndex from, NodeIndex to) const {
return time_per_demand_unit_ * demand_(from, to) + transition_time_(from, to);
}
StopServiceTimePlusTransition::StopServiceTimePlusTransition(
int64_t stop_time, const LocationContainer& location_container,
RoutingNodeEvaluator2 transition_time)
: stop_time_(stop_time),
location_container_(location_container),
transition_time_(std::move(transition_time)) {}
int64_t StopServiceTimePlusTransition::Compute(NodeIndex from,
NodeIndex to) const {
return location_container_.SameLocation(from, to)
? 0
: stop_time_ + transition_time_(from, to);
}
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,
const operations_research::RoutingDimension& capacity_dimension,
const operations_research::RoutingDimension& time_dimension) {
// Display plan cost.
std::string plan_output = absl::StrFormat("Cost %d\n", plan.ObjectiveValue());
// Display dropped orders.
std::string dropped;
for (int64_t order = 0; order < routing.Size(); ++order) {
if (routing.IsStart(order) || routing.IsEnd(order)) continue;
if (plan.Value(routing.NextVar(order)) == order) {
if (dropped.empty()) {
absl::StrAppendFormat(&dropped, " %d",
manager.IndexToNode(order).value());
} else {
absl::StrAppendFormat(&dropped, ", %d",
manager.IndexToNode(order).value());
}
}
}
if (!dropped.empty()) {
plan_output += "Dropped orders:" + dropped + "\n";
}
if (use_same_vehicle_costs) {
int group_size = 0;
int64_t group_same_vehicle_cost = 0;
absl::btree_set<int> visited;
for (int64_t order = 0; order < routing.Size(); ++order) {
if (routing.IsStart(order) || routing.IsEnd(order)) continue;
++group_size;
visited.insert(plan.Value(routing.VehicleVar(order)));
if (group_size == max_nodes_per_group) {
if (visited.size() > 1) {
group_same_vehicle_cost += (visited.size() - 1) * same_vehicle_cost;
}
group_size = 0;
visited.clear();
}
}
if (visited.size() > 1) {
group_same_vehicle_cost += (visited.size() - 1) * same_vehicle_cost;
}
LOG(INFO) << "Same vehicle costs: " << group_same_vehicle_cost;
}
// Display actual output for each vehicle.
for (int route_number = 0; route_number < routing.vehicles();
++route_number) {
int64_t order = routing.Start(route_number);
absl::StrAppendFormat(&plan_output, "Route %d: ", route_number);
if (routing.IsEnd(plan.Value(routing.NextVar(order)))) {
plan_output += "Empty\n";
} else {
while (true) {
operations_research::IntVar* const load_var =
capacity_dimension.CumulVar(order);
operations_research::IntVar* const time_var =
time_dimension.CumulVar(order);
operations_research::IntVar* const slack_var =
routing.IsEnd(order) ? nullptr : time_dimension.SlackVar(order);
if (slack_var != nullptr && plan.Contains(slack_var)) {
absl::StrAppendFormat(
&plan_output, "%d Load(%d) Time(%d, %d) Slack(%d, %d)",
manager.IndexToNode(order).value(), plan.Value(load_var),
plan.Min(time_var), plan.Max(time_var), plan.Min(slack_var),
plan.Max(slack_var));
} else {
absl::StrAppendFormat(&plan_output, "%d Load(%d) Time(%d, %d)",
manager.IndexToNode(order).value(),
plan.Value(load_var), plan.Min(time_var),
plan.Max(time_var));
}
if (routing.IsEnd(order)) break;
plan_output += " -> ";
order = plan.Value(routing.NextVar(order));
}
plan_output += "\n";
}
}
LOG(INFO) << plan_output;
}
} // namespace operations_research

View File

@@ -0,0 +1,136 @@
// Copyright 2010-2022 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.
// This header provides functions to help create random instances of the
// vehicle routing problem; random capacities and random time windows.
#ifndef OR_TOOLS_ROUTING_CVRPTW_LIB_H_
#define OR_TOOLS_ROUTING_CVRPTW_LIB_H_
#include <cstdint>
#include <functional>
#include <memory>
#include "ortools/base/strong_vector.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/util/random_engine.h"
namespace operations_research {
typedef std::function<int64_t(RoutingNodeIndex, RoutingNodeIndex)>
RoutingNodeEvaluator2;
// Random seed generator.
int32_t GetSeed(bool deterministic);
// Location container, contains positions of orders and can be used to obtain
// Manhattan distances/times between locations.
class LocationContainer {
public:
LocationContainer(int64_t speed, bool use_deterministic_seed);
void AddLocation(int64_t x, int64_t y) {
locations_.push_back(Location(x, y));
}
void AddRandomLocation(int64_t x_max, int64_t y_max);
void AddRandomLocation(int64_t x_max, int64_t y_max, int duplicates);
int64_t ManhattanDistance(RoutingIndexManager::NodeIndex from,
RoutingIndexManager::NodeIndex to) const;
int64_t NegManhattanDistance(RoutingIndexManager::NodeIndex from,
RoutingIndexManager::NodeIndex to) const;
int64_t ManhattanTime(RoutingIndexManager::NodeIndex from,
RoutingIndexManager::NodeIndex to) const;
bool SameLocation(RoutingIndexManager::NodeIndex node1,
RoutingIndexManager::NodeIndex node2) const;
int64_t SameLocationFromIndex(int64_t node1, int64_t node2) const;
private:
class Location {
public:
Location();
Location(int64_t x, int64_t y);
int64_t DistanceTo(const Location& location) const;
bool IsAtSameLocation(const Location& location) const;
private:
static int64_t Abs(int64_t value);
int64_t x_;
int64_t y_;
};
random_engine_t randomizer_;
const int64_t speed_;
absl::StrongVector<RoutingIndexManager::NodeIndex, Location> locations_;
};
// Random demand.
class RandomDemand {
public:
RandomDemand(int size, RoutingIndexManager::NodeIndex depot,
bool use_deterministic_seed);
void Initialize();
int64_t Demand(RoutingIndexManager::NodeIndex from,
RoutingIndexManager::NodeIndex to) const;
private:
std::unique_ptr<int64_t[]> demand_;
const int size_;
const RoutingIndexManager::NodeIndex depot_;
const bool use_deterministic_seed_;
};
// 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);
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_;
};
// Stop service time + transition time callback.
class StopServiceTimePlusTransition {
public:
StopServiceTimePlusTransition(
int64_t stop_time, const LocationContainer& location_container,
operations_research::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_;
};
// 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,
const operations_research::RoutingDimension& capacity_dimension,
const operations_research::RoutingDimension& time_dimension);
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_CVRPTW_LIB_H_

View File

@@ -0,0 +1,450 @@
// Copyright 2010-2022 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/routing/nearp_parser.h"
#include <array>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "ortools/base/numbers.h"
#include "ortools/util/filelineiter.h"
namespace operations_research {
NearpParser::NearpParser() { Initialize(); }
void NearpParser::Initialize() {
name_.clear();
comment_.clear();
num_arcs_ = 0;
num_edges_ = 0;
num_nodes_ = 0;
num_arcs_with_servicing_ = 0;
num_edges_with_servicing_ = 0;
num_nodes_with_servicing_ = 0;
depot_ = 0;
arc_traversing_costs_.clear();
edge_traversing_costs_.clear();
arc_servicing_demands_.clear();
edge_servicing_demands_.clear();
node_servicing_demands_.clear();
num_vehicles_ = 0;
capacity_ = 0;
section_ = METADATA;
}
bool NearpParser::LoadFile(const std::string& file_name) {
Initialize();
return ParseFile(file_name);
}
bool NearpParser::ParseFile(const std::string& file_name) {
// Only put the first word as header, as the main check is just done on this
// first word (no ambiguity is possible for well-formed files; a more precise
// check is done for metadata).
static auto section_headers = std::array<const char*, 14>({
"Name",
"Optimal", // "value"
"#Vehicles",
"Capacity",
"Depot", // "Node"
"#Nodes",
"#Edges",
"#Arcs",
"#Required", // "N", "E", or "A"
"ReN.",
"ReE.",
"EDGE",
"ReA.",
"ARC",
});
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
if (words.empty()) continue;
if (absl::c_linear_search(section_headers, words[0])) {
// First, check if a new section has been met.
if (words[0] == "ReN.") {
node_servicing_demands_.reserve(NumberOfNodesWithServicing());
section_ = NODES_WITH_SERVICING;
} else if (words[0] == "ReE.") {
edge_traversing_costs_.reserve(NumberOfEdges());
edge_servicing_demands_.reserve(NumberOfEdgesWithServicing());
section_ = EDGES_WITH_SERVICING;
} else if (words[0] == "EDGE") {
edge_traversing_costs_.reserve(NumberOfEdges());
section_ = EDGES_WITHOUT_SERVICING;
} else if (words[0] == "ReA.") {
arc_traversing_costs_.reserve(NumberOfArcs());
arc_servicing_demands_.reserve(NumberOfArcsWithServicing());
section_ = ARCS_WITH_SERVICING;
} else if (words[0] == "ARC") {
arc_traversing_costs_.reserve(NumberOfArcs());
section_ = ARCS_WITHOUT_SERVICING;
} else {
if (!ParseMetadataLine(words)) {
LOG(ERROR) << "Error when parsing a metadata line: " << line;
return false;
}
}
} else {
// If no new section is detected, process according to the current state.
// Is there still data expected? Don't process the line if every element
// it should contain have been read: there might be some garbage at the
// end (like comments without delimiter).
switch (section_) {
case NODES_WITH_SERVICING:
if (node_servicing_demands_.size() == NumberOfNodesWithServicing())
continue;
break;
case EDGES_WITH_SERVICING:
if (edge_servicing_demands_.size() == NumberOfEdgesWithServicing())
continue;
break;
case EDGES_WITHOUT_SERVICING:
// edge_traversing_costs_ is filled with all edges, whether they need
// servicing or not. The section EDGES_WITHOUT_SERVICING must be after
// EDGES_WITH_SERVICING: once edge_traversing_costs_ has one value
// per edge (coming first from EDGES_WITH_SERVICING, then from
// EDGES_WITHOUT_SERVICING), no more value should come.
if (edge_traversing_costs_.size() == NumberOfEdges()) continue;
break;
case ARCS_WITH_SERVICING:
if (arc_servicing_demands_.size() == NumberOfArcsWithServicing())
continue;
break;
case ARCS_WITHOUT_SERVICING:
// arc_traversing_costs_ is filled with all arcs, do they need
// servicing or not. The section ARCS_WITHOUT_SERVICING must be after
// ARCS_WITH_SERVICING: once edge_traversing_costs_ has one value
// per arc (coming first from ARCS_WITH_SERVICING, then from
// ARCS_WITHOUT_SERVICING), no more value should come.
if (arc_traversing_costs_.size() == NumberOfArcs()) continue;
break;
default:
break;
}
// Data is still expected: parse the current line according to the state.
switch (section_) {
case NODES_WITH_SERVICING:
if (!ParseNode(line)) {
LOG(ERROR) << "Could not parse line in required nodes: " << line;
return false;
}
break;
case EDGES_WITH_SERVICING:
if (!ParseEdge(line, true)) {
LOG(ERROR) << "Could not parse line in required edges: " << line;
return false;
}
break;
case EDGES_WITHOUT_SERVICING:
if (!ParseEdge(line, false)) {
LOG(ERROR) << "Could not parse line in edges: " << line;
return false;
}
break;
case ARCS_WITH_SERVICING:
if (!ParseArc(line, true)) {
LOG(ERROR) << "Could not parse line in required arcs: " << line;
return false;
}
break;
case ARCS_WITHOUT_SERVICING:
if (!ParseArc(line, false)) {
LOG(ERROR) << "Could not parse line in arcs: " << line;
return false;
}
break;
default:
LOG(ERROR) << "Could not parse line outside node-edge-arc lists: "
<< line;
return false;
}
}
}
return section_ != METADATA;
}
namespace {
std::optional<int64_t> ParseNodeIndex(std::string_view text);
struct ArcOrEdge {
const int64_t tail;
const int64_t head;
const int64_t traversing_cost;
const int64_t servicing_demand;
const int64_t servicing_cost;
};
std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
bool with_servicing);
} // namespace
bool NearpParser::ParseMetadataLine(const std::vector<std::string>& words) {
if (words[0] == "Name") {
name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
} else if (words[0] == "Optimal" && words[1] == "value") {
comment_ = absl::StrJoin(words.begin() + 2, words.end(), " ");
} else if (words[0] == "#Vehicles") {
num_vehicles_ = strings::ParseLeadingInt32Value(words[1], -1);
// -1 indicates that the number of vehicles is unknown.
if (num_vehicles_ < -1) {
LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
return false;
}
} else if (words[0] == "Capacity") {
capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
if (capacity_ <= 0) {
LOG(ERROR) << "Error when parsing the capacity: " << words[1];
return false;
}
} else if (words[0] == "Depot" && words[1] == "Node") {
const std::optional<int64_t> depot = ParseNodeIndex(words[2]);
if (!depot.has_value()) {
LOG(ERROR) << "Error when parsing the depot: " << words[1];
return false;
}
depot_ = depot.value();
} else if (words[0] == "#Nodes") {
num_nodes_ = strings::ParseLeadingInt32Value(words[1], -1);
if (num_nodes_ <= 0) {
LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
return false;
}
} else if (words[0] == "#Edges") {
num_edges_ = strings::ParseLeadingInt32Value(words[1], -1);
if (num_edges_ <= 0) {
LOG(ERROR) << "Error when parsing the number of edges: " << words[1];
return false;
}
} else if (words[0] == "#Arcs") {
num_arcs_ = strings::ParseLeadingInt32Value(words[1], -1);
if (num_arcs_ <= 0) {
LOG(ERROR) << "Error when parsing the number of arcs: " << words[1];
return false;
}
} else if (words[0] == "#Required" && words[1] == "N") {
num_nodes_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
if (num_nodes_with_servicing_ <= 0) {
LOG(ERROR) << "Error when parsing the number of nodes with servicing: "
<< words[1];
return false;
}
} else if (words[0] == "#Required" && words[1] == "E") {
num_edges_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
if (num_edges_with_servicing_ <= 0) {
LOG(ERROR) << "Error when parsing the number of edges with servicing: "
<< words[1];
return false;
}
} else if (words[0] == "#Required" && words[1] == "A") {
num_arcs_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
if (num_arcs_with_servicing_ <= 0) {
LOG(ERROR) << "Error when parsing the number of arcs with servicing: "
<< words[1];
return false;
}
} else {
LOG(ERROR) << "Unrecognized metadata line: " << absl::StrJoin(words, " ");
return false;
}
return true;
}
bool NearpParser::ParseArc(std::string_view line, bool with_servicing) {
std::optional<ArcOrEdge> parsed_arc = ParseArcOrEdge(line, with_servicing);
if (!parsed_arc.has_value()) {
return false;
}
Arc arc{parsed_arc->tail, parsed_arc->head};
arc_traversing_costs_[arc] = parsed_arc->traversing_cost;
if (with_servicing) {
arc_servicing_demands_[arc] = parsed_arc->servicing_demand;
arc_servicing_costs_[arc] = parsed_arc->servicing_cost;
}
return true;
}
bool NearpParser::ParseEdge(std::string_view line, bool with_servicing) {
std::optional<ArcOrEdge> parsed_edge = ParseArcOrEdge(line, with_servicing);
if (!parsed_edge.has_value()) {
return false;
}
Edge edge{parsed_edge->tail, parsed_edge->head};
edge_traversing_costs_[edge] = parsed_edge->traversing_cost;
if (with_servicing) {
edge_servicing_demands_[edge] = parsed_edge->servicing_demand;
edge_servicing_costs_[edge] = parsed_edge->servicing_cost;
}
return true;
}
bool NearpParser::ParseNode(std::string_view line) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
// Parse the name and ID.
const int64_t id = strings::ParseLeadingInt64Value(words[0].substr(1), 0) - 1;
if (id < 0) {
LOG(ERROR) << "Error when parsing the node name: " << words[0];
return false;
}
// Parse the servicing details if needed.
const int64_t servicing_demand =
strings::ParseLeadingInt64Value(words[1], -1);
if (servicing_demand < 0) {
LOG(ERROR) << "Error when parsing the node servicing demand: " << words[1];
return false;
}
const int64_t servicing_cost = strings::ParseLeadingInt64Value(words[2], -1);
if (servicing_cost < 0) {
LOG(ERROR) << "Error when parsing the node servicing cost: " << words[1];
return false;
}
// Once the values have been parsed successfully, save them.
node_servicing_demands_.insert({id, servicing_demand});
node_servicing_costs_.insert({id, servicing_cost});
return true;
}
namespace {
std::optional<int64_t> ParseNodeIndex(std::string_view text) {
const int64_t node = strings::ParseLeadingInt64Value(text, -1);
if (node < 0) {
LOG(ERROR) << "Could not parse node index: " << text;
return std::nullopt;
}
return {node - 1};
}
std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
bool with_servicing) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
// Parse the name.
const std::string name = words[0];
// Parse the tail and the head of the arc/edge.
std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
if (!opt_tail.has_value()) {
LOG(ERROR) << "Error when parsing the tail node: " << words[1];
return {};
}
const int64_t tail = opt_tail.value();
std::optional<int64_t> opt_head = ParseNodeIndex(words[2]);
if (!opt_head.has_value()) {
LOG(ERROR) << "Error when parsing the head node: " << words[2];
return {};
}
const int64_t head = opt_head.value();
if (tail == head) {
LOG(ERROR) << "The head and tail nodes are identical: " << line;
return {};
}
// Parse the traversing cost.
const int64_t traversing_cost = strings::ParseLeadingInt64Value(words[3], -1);
if (traversing_cost < 0) {
LOG(ERROR) << "Error when parsing the traversing cost: " << words[3];
return {};
}
// Ensure there are no extraneous elements.
const int64_t next_id = (with_servicing) ? 6 : 4;
if (words.size() > next_id) {
LOG(ERROR) << "Extraneous elements in line, starting with: "
<< words[next_id];
return {};
}
// Parse the servicing details if needed and return the elements.
if (!with_servicing) {
return ArcOrEdge{tail, head, traversing_cost,
/*servicing_demand=*/-1,
/*servicing_cost=*/-1};
} else {
const int64_t servicing_demand =
strings::ParseLeadingInt64Value(words[4], -1);
if (servicing_demand < 0) {
LOG(ERROR) << "Error when parsing the servicing demand: " << words[4];
return {};
}
const int64_t servicing_cost =
strings::ParseLeadingInt64Value(words[5], -1);
if (servicing_cost < 0) {
LOG(ERROR) << "Error when parsing the servicing cost: " << words[5];
return {};
}
return ArcOrEdge{tail, head, traversing_cost, servicing_demand,
servicing_cost};
}
}
} // namespace
std::string NearpParser::GetArcName(Arc arc) const {
if (arc_servicing_costs_.contains(arc)) {
int64_t arc_position = std::distance(arc_servicing_demands_.begin(),
arc_servicing_demands_.find(arc));
CHECK_LT(arc_position, arc_servicing_demands_.size());
return absl::StrCat("A", arc_position + 1);
} else {
int64_t arc_position = std::distance(arc_traversing_costs_.begin(),
arc_traversing_costs_.find(arc));
CHECK_LT(arc_position, arc_traversing_costs_.size());
return absl::StrCat("NrA", arc_position - num_arcs_with_servicing_ + 1);
}
}
std::string NearpParser::GetEdgeName(Edge edge) const {
if (edge_servicing_costs_.contains(edge)) {
int64_t edge_position = std::distance(edge_servicing_demands_.begin(),
edge_servicing_demands_.find(edge));
CHECK_LT(edge_position, edge_servicing_demands_.size());
return absl::StrCat("E", edge_position + 1);
} else {
int64_t edge_position = std::distance(edge_traversing_costs_.begin(),
edge_traversing_costs_.find(edge));
CHECK_LT(edge_position, edge_traversing_costs_.size());
return absl::StrCat("NrE", edge_position - num_edges_with_servicing_ + 1);
}
}
} // namespace operations_research

View File

@@ -0,0 +1,257 @@
// Copyright 2010-2022 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.
// A parser for NEARPLIB instances. The base files are available online, as well
// as a description of the format:
// https://www.sintef.no/projectweb/top/nearp/documentation/
//
// The goal is to find routes starting and ending at a depot which visit a
// set of arcs (directed), edges (undirected), and nodes, whereas a VRP only
// visits nodes. The objective is to minimize the total cost, which is due to
// either servicing a part of the graph (i.e. performing the required action)
// or traversing an edge (to get to another point in space). Not all arcs/edges
// in the graph must be serviced. These components are summarized in NEARP:
// node-edge-arc routing problem. The problem is sometimes also called MCGRP:
// mixed capacitated generalized routing problem.
//
// All vehicles start at the same node, the depot. Its index is often 1, but
// many instances have another depot.
// Servicing a part of the graph requires resources, vehicles have a limited
// capacity. All vehicles have the same capacity.
//
// The format of the data is the following (from
// https://www.sintef.no/projectweb/top/nearp/documentation/):
//
// Name: <Instance name>
// Optimal value: <Optimal value, -1 if unknown>
// #Vehicles: <Max. number of vehicles, -1 if unconstrained>
// Capacity: <Vehicle capacity Q>
// Depot: <Index of depot node>
// #Nodes: <number of nodes>
// #Edges: <number of edges>
// #Arcs: <number of arcs>
// #Required N: <number of required nodes>
// #Required E: <number of required edges>
// #Required A: <number of required arcs>
//
// % Required nodes: Ni q_i s_i
// NODE INDEX, DEMAND, SERVICE COST
//
// % Required edges: Ek i j q_ij c_ij s_ij
// EDGE INDEX, FROM NODE, TO NODE, TRAVERSAL COST, DEMAND, SERVICE COST
//
// % Non-required edges: NrEl i j c_ij
// EDGE INDEX, FROM NODE, TO NODE, TRAVERSAL COST
//
// % Required arcs: Ar i j q_ij c_ij
// ARC INDEX, FROM NODE, TO NODE, TRAVERSAL COST, DEMAND, SERVICE COST
//
// % Non-required arcs: NrAs i j c_ij
// ARC INDEX, FROM NODE, TO NODE, TRAVERSAL COST
//
// For nodes, the index is of the form NX, where X is the node index (for
// instance, N1 is the first node that requires servicing). The elements of
// each section are not necessarily sorted. Nodes are indexed together, with no
// separation between those that require servicing and those that do not,
// from 1 to the number of nodes. Conversely, arcs and edges have separate
// indexing depending on whether they require indexing: E1 to EM all require
// servicing, NrE1 to NrEN do not, for a total of M + N edges (respectively,
// for arcs, A1 to AK and NrA1 to NrAL for K + L arcs).
//
// While the file format is defined with 1-based indexing, the output of the
// parser is always 0-based. Users of this parser should never see any 1-based
// index; only 0-based index should be used to query values.
#ifndef OR_TOOLS_ROUTING_NEARP_PARSER_H_
#define OR_TOOLS_ROUTING_NEARP_PARSER_H_
#include <algorithm>
#include <string>
#include <string_view>
#include <vector>
#include "ortools/base/linked_hash_map.h"
#include "ortools/base/logging.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
class NearpParser {
public:
NearpParser();
#ifndef SWIG
NearpParser(const NearpParser&) = delete;
const NearpParser& operator=(const NearpParser&) = delete;
#endif
// Loads instance from a file into this parser object.
bool LoadFile(const std::string& file_name);
// Returns the name of the instance being solved.
const std::string& name() const { return name_; }
// Returns the comment of the instance being solved, typically an upper bound.
const std::string& comment() const { return comment_; }
// Returns the index of the depot.
int64_t depot() const { return depot_; }
// Returns the maximum number of vehicles to use.
int NumberOfVehicles() const { return num_vehicles_; }
// Returns the capacity of the vehicles.
int64_t capacity() const { return capacity_; }
// Returns the number of nodes in the current routing problem.
int NumberOfNodes() const { return num_nodes_; }
// Returns the number of arc in the current routing problem, with or
// without servicing required.
int NumberOfArcs() const { return num_arcs_; }
// Returns the number of edges in the current routing problem, with or
// without servicing required.
int NumberOfEdges() const { return num_edges_; }
// Returns the number of arcs in the current routing problem that require
// servicing.
int NumberOfArcsWithServicing() const { return num_arcs_with_servicing_; }
// Returns the number of edges in the current routing problem that require
// servicing.
int NumberOfEdgesWithServicing() const { return num_edges_with_servicing_; }
// Returns the number of nodes in the current routing problem that require
// servicing.
int NumberOfNodesWithServicing() const { return num_nodes_with_servicing_; }
// Returns the number of arcs in the current routing problem that do not
// require servicing.
int NumberOfArcsWithoutServicing() const {
return NumberOfArcs() - NumberOfArcsWithServicing();
}
// Returns the number of edges in the current routing problem that do not
// require servicing.
int NumberOfEdgesWithoutServicing() const {
return NumberOfEdges() - NumberOfEdgesWithServicing();
}
// Returns the number of nodes in the current routing problem that do not
// require servicing.
int64_t NumberOfNodesWithoutServicing() const {
return NumberOfNodes() - NumberOfNodesWithServicing();
}
// Returns the servicing demands of the arcs in the current routing problem.
const gtl::linked_hash_map<Arc, int64_t>& arc_servicing_demands() const {
return arc_servicing_demands_;
}
// Returns the servicing demands of the edges in the current routing problem.
const gtl::linked_hash_map<Edge, int64_t>& edge_servicing_demands() const {
return edge_servicing_demands_;
}
// Returns the servicing demands of the nodes in the current routing problem.
const gtl::linked_hash_map<int64_t, int64_t>& node_servicing_demands() const {
return node_servicing_demands_;
}
// Returns the servicing costs of the arcs in the current routing problem.
const gtl::linked_hash_map<Arc, int64_t>& arc_servicing_costs() const {
return arc_servicing_costs_;
}
// Returns the servicing costs of the edges in the current routing problem.
const gtl::linked_hash_map<Edge, int64_t>& edge_servicing_costs() const {
return edge_servicing_costs_;
}
// Returns the servicing costs of the nodes in the current routing problem.
const gtl::linked_hash_map<int64_t, int64_t>& node_servicing_costs() const {
return node_servicing_costs_;
}
// Returns the traversing costs of the arcs in the current routing problem.
const gtl::linked_hash_map<Arc, int64_t>& arc_traversing_costs() const {
return arc_traversing_costs_;
}
// Returns the traversing costs of the edges in the current routing problem.
const gtl::linked_hash_map<Edge, int64_t>& edge_traversing_costs() const {
return edge_traversing_costs_;
}
// Returns the name of graph objects. The implementations should fit all
// instances of NEARP files,
std::string GetArcName(Arc arc) const;
std::string GetArcName(int64_t tail, int64_t head) const {
return GetArcName({tail, head});
}
std::string GetEdgeName(Edge edge) const;
std::string GetEdgeName(int64_t tail, int64_t head) const {
return GetEdgeName({tail, head});
}
std::string GetNodeName(int64_t node) const {
CHECK_GE(node, 0);
CHECK_LT(node, NumberOfNodes());
return absl::StrCat("N", node + 1);
}
private:
// Parsing.
enum Section {
METADATA,
ARCS_WITH_SERVICING,
ARCS_WITHOUT_SERVICING,
EDGES_WITH_SERVICING,
EDGES_WITHOUT_SERVICING,
NODES_WITH_SERVICING,
// No need for a state to parse nodes without servicing demands: they do
// not have any data associated with them (their number is known in the
// header of the data file).
UNDEFINED_SECTION
};
void Initialize();
bool ParseFile(const std::string& file_name);
bool ParseMetadataLine(const std::vector<std::string>& words);
bool ParseArc(std::string_view line, bool with_servicing);
bool ParseEdge(std::string_view line, bool with_servicing);
bool ParseNode(std::string_view line);
// Parsing data.
Section section_;
// Instance data:
// - metadata
std::string name_;
std::string comment_;
int num_arcs_;
int num_edges_;
int num_nodes_;
int num_arcs_with_servicing_;
int num_edges_with_servicing_;
int num_nodes_with_servicing_;
int64_t depot_;
// - graph costs and servicing demands. Keep track of the order of the
// demands: the output format requires to use the servicing-demands IDs,
// which are indices when iterating through these maps.
// Specifically, for nodes, a vector is not suitable, as indices are not
// necessarily contiguous.
gtl::linked_hash_map<Arc, int64_t> arc_traversing_costs_;
gtl::linked_hash_map<Edge, int64_t> edge_traversing_costs_;
gtl::linked_hash_map<Arc, int64_t> arc_servicing_demands_;
gtl::linked_hash_map<Edge, int64_t> edge_servicing_demands_;
gtl::linked_hash_map<int64_t, int64_t> node_servicing_demands_;
gtl::linked_hash_map<Arc, int64_t> arc_servicing_costs_;
gtl::linked_hash_map<Edge, int64_t> edge_servicing_costs_;
gtl::linked_hash_map<int64_t, int64_t> node_servicing_costs_;
// - vehicles
int num_vehicles_;
int64_t capacity_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_NEARP_PARSER_H_

View File

@@ -0,0 +1,134 @@
// Copyright 2010-2022 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/routing/nearp_parser.h"
#include <string>
#include "absl/flags/flag.h"
#include "gtest/gtest.h"
#include "ortools/base/mock-log.h"
#include "ortools/base/path.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
namespace operations_research {
namespace {
TEST(NearpParserTest, Constructor) {
NearpParser parser;
EXPECT_EQ(parser.name(), "");
EXPECT_EQ(parser.comment(), "");
EXPECT_EQ(parser.NumberOfNodes(), 0);
EXPECT_EQ(parser.NumberOfEdgesWithServicing(), 0);
EXPECT_EQ(parser.NumberOfEdgesWithoutServicing(), 0);
EXPECT_EQ(parser.NumberOfEdges(), 0);
EXPECT_EQ(parser.NumberOfVehicles(), 0);
EXPECT_EQ(parser.capacity(), 0);
EXPECT_EQ(parser.depot(), 0);
}
TEST(NearpParserTest, LoadEmptyFileName) {
std::string empty_file_name;
NearpParser parser;
EXPECT_FALSE(parser.LoadFile(empty_file_name));
}
TEST(NearpParserTest, LoadNonExistingFile) {
NearpParser parser;
EXPECT_FALSE(parser.LoadFile("google2/nonexistent.dat"));
}
TEST(NearpParserTest, LoadBHW1) {
std::string file_name =
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/nearp_BHW1.dat");
NearpParser parser;
EXPECT_TRUE(parser.LoadFile(file_name));
EXPECT_EQ(parser.name(), "BHW1");
EXPECT_EQ(parser.comment(), "-1");
EXPECT_EQ(parser.NumberOfNodes(), 12);
EXPECT_EQ(parser.NumberOfNodesWithServicing(), 7);
EXPECT_EQ(parser.NumberOfNodesWithoutServicing(), 5);
EXPECT_EQ(parser.NumberOfEdges(), 11);
EXPECT_EQ(parser.NumberOfEdgesWithServicing(), 11);
EXPECT_EQ(parser.NumberOfEdgesWithoutServicing(), 0);
EXPECT_EQ(parser.NumberOfArcs(), 22);
EXPECT_EQ(parser.NumberOfArcsWithServicing(), 11);
EXPECT_EQ(parser.NumberOfArcsWithoutServicing(), 11);
EXPECT_EQ(parser.NumberOfVehicles(), -1);
EXPECT_EQ(parser.capacity(), 5);
EXPECT_EQ(parser.depot(), 0);
EXPECT_EQ(parser.arc_traversing_costs().size(), 22);
EXPECT_EQ(parser.arc_servicing_costs().size(), 11);
EXPECT_EQ(parser.arc_servicing_demands().size(), 11);
EXPECT_EQ(parser.edge_traversing_costs().size(), 11);
EXPECT_EQ(parser.edge_servicing_demands().size(), 11);
EXPECT_EQ(parser.edge_servicing_costs().size(), 11);
EXPECT_EQ(parser.node_servicing_demands().size(), 7);
EXPECT_EQ(parser.node_servicing_costs().size(), 7);
EXPECT_EQ(parser.GetArcName(0, 1), "A1");
EXPECT_EQ(parser.GetArcName(Arc(0, 1)), "A1");
EXPECT_EQ(parser.GetArcName(3, 0), "NrA2");
EXPECT_EQ(parser.GetArcName(Arc(3, 0)), "NrA2");
EXPECT_EQ(parser.GetEdgeName(2, 1), "E1");
EXPECT_EQ(parser.GetEdgeName(Edge(2, 1)), "E1");
EXPECT_EQ(parser.GetEdgeName(1, 2), "E1");
EXPECT_EQ(parser.GetEdgeName(Edge(1, 2)), "E1");
EXPECT_EQ(parser.GetNodeName(3), "N4");
}
TEST(NearpParserTest, LoadToy) {
std::string file_name =
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/nearp_toy.dat");
NearpParser parser;
EXPECT_TRUE(parser.LoadFile(file_name));
EXPECT_EQ(parser.name(), "Toy");
EXPECT_EQ(parser.comment(), "-1");
EXPECT_EQ(parser.NumberOfNodes(), 4);
EXPECT_EQ(parser.NumberOfNodesWithServicing(), 1);
EXPECT_EQ(parser.NumberOfNodesWithoutServicing(), 3);
EXPECT_EQ(parser.NumberOfEdges(), 3);
EXPECT_EQ(parser.NumberOfEdgesWithServicing(), 2);
EXPECT_EQ(parser.NumberOfEdgesWithoutServicing(), 1);
EXPECT_EQ(parser.NumberOfArcs(), 3);
EXPECT_EQ(parser.NumberOfArcsWithServicing(), 2);
EXPECT_EQ(parser.NumberOfArcsWithoutServicing(), 1);
EXPECT_EQ(parser.NumberOfVehicles(), -1);
EXPECT_EQ(parser.capacity(), 5);
EXPECT_EQ(parser.depot(), 0);
EXPECT_EQ(parser.arc_traversing_costs().size(), 3);
EXPECT_EQ(parser.arc_servicing_costs().size(), 2);
EXPECT_EQ(parser.arc_servicing_demands().size(), 2);
EXPECT_EQ(parser.edge_traversing_costs().size(), 3);
EXPECT_EQ(parser.edge_servicing_demands().size(), 2);
EXPECT_EQ(parser.edge_servicing_costs().size(), 2);
EXPECT_EQ(parser.node_servicing_demands().size(), 1);
EXPECT_EQ(parser.node_servicing_costs().size(), 1);
EXPECT_DEATH(parser.GetArcName(0, 1), "");
EXPECT_DEATH(parser.GetArcName(3, 0), "");
EXPECT_DEATH(parser.GetEdgeName(3, 1), "");
EXPECT_DEATH(parser.GetEdgeName(1, 3), "");
EXPECT_EQ(parser.GetArcName(1, 3), "A1");
EXPECT_EQ(parser.GetArcName(3, 1), "NrA1");
EXPECT_EQ(parser.GetEdgeName(2, 1), "E2");
EXPECT_EQ(parser.GetEdgeName(1, 2), "E2");
EXPECT_EQ(parser.GetNodeName(3), "N4");
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,106 @@
// Copyright 2010-2022 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/routing/pdtsp_parser.h"
#include <functional>
#include <string>
#include <vector>
#include "absl/strings/str_split.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 {
namespace {
using absl::ByAnyChar;
File* OpenReadOnly(const std::string& 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) {}
bool PdTspParser::LoadFile(const std::string& file_name) {
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
ProcessNewLine(line);
}
return true;
}
std::function<int64_t(int, int)> PdTspParser::Distances() const {
std::function<int64_t(int, int)> distances = [this](int from, int to) {
const double xd = x_[from] - x_[to];
const double yd = y_[from] - y_[to];
const double d = sqrt(xd * xd + yd * yd);
return MathUtil::FastInt64Round(d);
};
return distances;
}
void PdTspParser::ProcessNewLine(const std::string& line) {
const std::vector<std::string> words =
absl::StrSplit(line, ByAnyChar(" :\t"), absl::SkipEmpty());
if (!words.empty()) {
switch (section_) {
case SIZE_SECTION: {
const int size = atoi64(words[0]);
x_.resize(size, 0);
y_.resize(size, 0);
deliveries_.resize(size, -1);
section_ = DEPOT_SECTION;
break;
}
case DEPOT_SECTION:
depot_ = atoi64(words[0]) - 1;
x_[depot_] = atoi64(words[1]);
y_[depot_] = atoi64(words[2]);
deliveries_[depot_] = -1;
section_ = NODE_SECTION;
break;
case NODE_SECTION: {
const int kEof = -999;
const int id = atoi64(words[0]) - 1;
if (id + 1 == kEof) {
section_ = EOF_SECTION;
} else {
x_[id] = atoi64(words[1]);
y_[id] = atoi64(words[2]);
const bool is_pickup = atoi64(words[3]) == 0;
if (is_pickup) {
deliveries_[id] = atoi64(words[4]) - 1;
}
}
break;
}
case EOF_SECTION:
break;
default:
break;
}
}
}
} // namespace operations_research

View File

@@ -0,0 +1,59 @@
// Copyright 2010-2022 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.
// A TSPPD parser used to parse instances of Traveling Salesman Problems with
// pickup and delivery constraints. This format was created by Stefan Ropke.
// https://link.springer.com/article/10.1007%2Fs10107-008-0234-9
#ifndef OR_TOOLS_ROUTING_PDTSP_PARSER_H_
#define OR_TOOLS_ROUTING_PDTSP_PARSER_H_
#include <functional>
#include <string>
#include <vector>
#include "ortools/base/integral_types.h"
namespace operations_research {
class PdTspParser {
public:
PdTspParser();
~PdTspParser() = default;
// Loads and parse a PDTSP from a given file.
bool LoadFile(const std::string& file_name);
// Returns the index of the depot.
int depot() const { return depot_; }
// Returns the number of nodes in the PDTSP.
int Size() const { return x_.size(); }
// Returns true if the index corresponding to a node is a pickup.
bool IsPickup(int index) const { return deliveries_[index] >= 0; }
// Returns the delivery corresponding to a pickup.
int DeliveryFromPickup(int index) const { return deliveries_[index]; }
// Returns a function returning distances between nodes.
std::function<int64_t(int, int)> Distances() const;
private:
enum Sections { SIZE_SECTION, DEPOT_SECTION, NODE_SECTION, EOF_SECTION };
void ProcessNewLine(const std::string& line);
int depot_;
Sections section_;
std::vector<double> x_;
std::vector<double> y_;
std::vector<int> deliveries_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_PDTSP_PARSER_H_

View File

@@ -0,0 +1,50 @@
// Copyright 2010-2022 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/routing/pdtsp_parser.h"
#include <functional>
#include <string>
#include "absl/flags/flag.h"
#include "gtest/gtest.h"
#include "ortools/base/helpers.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/path.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
namespace operations_research {
namespace {
TEST(PdTspParserTest, LoadDataSet) {
for (const std::string& data : {"ortools/routing/testdata/pdtsp_prob10b.txt",
"ortools/routing/testdata/"
"pdtsp_prob10b.txt.gz"}) {
PdTspParser parser;
EXPECT_TRUE(parser.LoadFile(
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir), data)));
EXPECT_EQ(0, parser.depot());
EXPECT_EQ(21, parser.Size());
EXPECT_FALSE(parser.IsPickup(0)); // depot
EXPECT_FALSE(parser.IsPickup(11)); // delivery
EXPECT_TRUE(parser.IsPickup(2)); // pickup
EXPECT_EQ(12, parser.DeliveryFromPickup(2));
std::function<int64_t(int, int)> distances = parser.Distances();
for (int i = 0; i < parser.Size(); ++i) {
EXPECT_EQ(0, distances(i, i));
}
EXPECT_EQ(557, distances(1, 20));
}
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,81 @@
# Copyright 2010-2022 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.
cc_binary(
name = "cvrptw",
srcs = ["cvrptw.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
],
)
cc_binary(
name = "cvrp_disjoint_tw",
srcs = ["cvrp_disjoint_tw.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
],
)
cc_binary(
name = "cvrptw_with_breaks",
srcs = ["cvrptw_with_breaks.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_enums_cc_proto",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
"@com_google_absl//absl/strings",
],
)
cc_binary(
name = "cvrptw_with_resources",
srcs = ["cvrptw_with_resources.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
],
)
cc_binary(
name = "cvrptw_with_stop_times_and_resources",
srcs = ["cvrptw_with_stop_times_and_resources.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
"@com_google_absl//absl/strings",
],
)
cc_binary(
name = "cvrptw_with_refueling",
srcs = ["cvrptw_with_refueling.cc"],
deps = [
"//ortools/base",
"//ortools/constraint_solver:routing",
"//ortools/constraint_solver:routing_flags",
"//ortools/routing:cvrptw_lib",
],
)

View File

@@ -0,0 +1,197 @@
// Copyright 2010-2022 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.
//
// Capacitated Vehicle Routing Problem with Disjoint Time Windows (and optional
// orders).
// A description of the problem can be found here:
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
// The variant which is tackled by this model includes a capacity dimension,
// disjoint time windows and optional orders, with a penalty cost if orders are
// not performed. For the sake of simplicity, orders are randomly located and
// distances are computed using the Manhattan distance. Distances are assumed
// to be in meters and times in seconds.
#include <algorithm>
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
using operations_research::Solver;
ABSL_FLAG(int, vrp_orders, 100, "Number of nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20, "Number of vehicles in the problem.");
ABSL_FLAG(int, vrp_windows, 5, "Number of disjoint windows per node.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(bool, vrp_use_same_vehicle_costs, false,
"Use same vehicle costs in the routing model");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
const int64_t kMaxNodesPerGroup = 10;
const int64_t kSameVehicleCost = 1000;
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/false, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding disjoint time windows.
Solver* solver = routing.solver();
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
for (int order = 1; order < manager.num_nodes(); ++order) {
std::vector<int64_t> forbid_points(2 * absl::GetFlag(FLAGS_vrp_windows), 0);
for (int i = 0; i < forbid_points.size(); ++i) {
forbid_points[i] = absl::Uniform<int32_t>(randomizer, 0, kHorizon);
}
std::sort(forbid_points.begin(), forbid_points.end());
std::vector<int64_t> forbid_starts(1, 0);
std::vector<int64_t> forbid_ends;
for (int i = 0; i < forbid_points.size(); i += 2) {
forbid_ends.push_back(forbid_points[i]);
forbid_starts.push_back(forbid_points[i + 1]);
}
forbid_ends.push_back(kHorizon);
solver->AddConstraint(solver->MakeNotMemberCt(
time_dimension.CumulVar(order), forbid_starts, forbid_ends));
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10000000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Adding same vehicle constraint costs for consecutive nodes.
if (absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs)) {
std::vector<int64_t> group;
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
group.push_back(manager.NodeToIndex(order));
if (group.size() == kMaxNodesPerGroup) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
group.clear();
}
}
if (!group.empty()) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
}
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution,
absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs),
kMaxNodesPerGroup, kSameVehicleCost,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED '${DIR}/cvrp_disjoint_tw --vrp_use_deterministic_random_seed'
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,182 @@
// Copyright 2010-2022 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.
//
// Capacitated Vehicle Routing Problem with Time Windows (and optional orders).
// A description of the problem can be found here:
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
// The variant which is tackled by this model includes a capacity dimension,
// time windows and optional orders, with a penalty cost if orders are not
// performed. For the sake of simplicity, orders are randomly located and
// distances are computed using the Manhattan distance. Distances are assumed
// to be in meters and times in seconds.
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
ABSL_FLAG(int, vrp_orders, 100, "Number of nodes in the problem");
ABSL_FLAG(int, vrp_vehicles, 20, "Number of vehicles in the problem");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds");
ABSL_FLAG(bool, vrp_use_same_vehicle_costs, false,
"Use same vehicle costs in the routing model");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
const int64_t kMaxNodesPerGroup = 10;
const int64_t kSameVehicleCost = 1000;
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/true, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding time windows.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10000000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Adding same vehicle constraint costs for consecutive nodes.
if (absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs)) {
std::vector<int64_t> group;
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
group.push_back(manager.NodeToIndex(order));
if (group.size() == kMaxNodesPerGroup) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
group.clear();
}
}
if (!group.empty()) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
}
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution,
absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs),
kMaxNodesPerGroup, kSameVehicleCost,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,206 @@
// Copyright 2010-2022 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.
// Soft-Capacitated Vehicle Routing Problem.
// A description of the problem can be found here:
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
// The variant which is tackled by this model includes a capacity dimension,
// implemented as a soft constraint: using more than the available capacity is
// penalized (i.e. "costs" more) but not forbidden. For the sake of simplicity,
// orders are randomly located and distances are computed using the Manhattan
// distance. Distances are assumed to be in meters and times in seconds.
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
ABSL_FLAG(int, vrp_orders, 100, "Number of nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20, "Number of vehicles in the problem.");
ABSL_FLAG(int, vrp_vehicle_hard_capacity, 80,
"Hard capacity for a vehicle; set to 0 to disable the hard capacity "
"constraint");
ABSL_FLAG(int, vrp_vehicle_soft_capacity, 40,
"Soft capacity for a vehicle; set to 0 to disable the soft capacity "
"constraint");
ABSL_FLAG(int, vrp_vehicle_soft_capacity_cost, 5000,
"Cost of using a vehicle beyond its soft capacity (per unit "
"of storage over the soft capacity)");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(bool, vrp_use_same_vehicle_costs, false,
"Use same vehicle costs in the routing model");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
const int64_t kMaxNodesPerGroup = 10;
const int64_t kSameVehicleCost = 1000;
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
if (absl::GetFlag(FLAGS_vrp_vehicle_hard_capacity) > 0 &&
absl::GetFlag(FLAGS_vrp_vehicle_soft_capacity) > 0) {
CHECK_LT(absl::GetFlag(FLAGS_vrp_vehicle_soft_capacity),
absl::GetFlag(FLAGS_vrp_vehicle_hard_capacity))
<< "The hard capacity must be higher than the soft capacity.";
}
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100'000;
const int64_t kYMax = 100'000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints with slacks.
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(
routing.RegisterTransitCallback([&demand, &manager](int64_t i,
int64_t j) {
return demand.Demand(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kNullCapacitySlack, absl::GetFlag(FLAGS_vrp_vehicle_hard_capacity),
/*fix_start_cumul_to_zero=*/true, kCapacity);
RoutingDimension* capacity_dimension = routing.GetMutableDimension(kCapacity);
// Penalise the capacity slacks to implement the soft constraint (a hard
// constraint has a zero slack).
const int num_vehicles = absl::GetFlag(FLAGS_vrp_vehicles);
for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) {
capacity_dimension->SetCumulVarSoftUpperBound(
routing.End(vehicle), absl::GetFlag(FLAGS_vrp_vehicle_soft_capacity),
absl::GetFlag(FLAGS_vrp_vehicle_soft_capacity_cost));
}
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/true, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding time windows.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10'000'000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Adding same vehicle constraint costs for consecutive nodes.
if (absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs)) {
std::vector<int64_t> group;
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
group.push_back(manager.NodeToIndex(order));
if (group.size() == kMaxNodesPerGroup) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
group.clear();
}
}
if (!group.empty()) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
}
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution,
absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs),
kMaxNodesPerGroup, kSameVehicleCost,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED '${DIR}/cvrptw --vrp_use_deterministic_random_seed'
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,237 @@
// Copyright 2010-2022 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.
//
// Capacitated Vehicle Routing Problem with Time Windows and Breaks.
// A description of the Capacitated Vehicle Routing Problem with Time Windows
// can be found here:
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
// The variant which is tackled by this model includes a capacity dimension,
// time windows and optional orders, with a penalty cost if orders are not
// performed. For the sake of simplicty, orders are randomly located and
// distances are computed using the Manhattan distance. Distances are assumed
// to be in meters and times in seconds.
// This variant also includes vehicle breaks which must happen during the day
// with two alternate breaks schemes: either a long break in the middle of the
// day or two smaller ones which can be taken during a longer period of the day.
#include <cstdint>
#include <random>
#include <utility>
#include <vector>
#include "absl/random/random.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_enums.pb.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::FirstSolutionStrategy;
using operations_research::GetSeed;
using operations_research::IntervalVar;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
using operations_research::Solver;
ABSL_FLAG(int, vrp_orders, 100, "Nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
parameters.set_first_solution_strategy(
FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/false, kTime);
RoutingDimension* const time_dimension = routing.GetMutableDimension(kTime);
// Adding time windows.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension->CumulVar(order)->SetRange(start, start + kTWDuration);
routing.AddToAssignment(time_dimension->SlackVar(order));
}
// Minimize time variables.
for (int i = 0; i < routing.Size(); ++i) {
routing.AddVariableMinimizedByFinalizer(time_dimension->CumulVar(i));
}
for (int j = 0; j < absl::GetFlag(FLAGS_vrp_vehicles); ++j) {
routing.AddVariableMinimizedByFinalizer(
time_dimension->CumulVar(routing.Start(j)));
routing.AddVariableMinimizedByFinalizer(
time_dimension->CumulVar(routing.End(j)));
}
// Adding vehicle breaks:
// - 40min breaks between 11:00am and 1:00pm
// or
// - 2 x 30min breaks between 10:00am and 3:00pm, at least 1h apart
// First, fill service time vector.
std::vector<int64_t> service_times(routing.Size());
for (int node = 0; node < routing.Size(); node++) {
if (node >= routing.nodes()) {
service_times[node] = 0;
} else {
const RoutingIndexManager::NodeIndex index(node);
service_times[node] = kTimePerDemandUnit * demand.Demand(index, index);
}
}
const std::vector<std::vector<int>> break_data = {
{/*start_min*/ 11, /*start_max*/ 13, /*duration*/ 2400},
{/*start_min*/ 10, /*start_max*/ 15, /*duration*/ 1800},
{/*start_min*/ 10, /*start_max*/ 15, /*duration*/ 1800}};
Solver* const solver = routing.solver();
for (int vehicle = 0; vehicle < absl::GetFlag(FLAGS_vrp_vehicles);
++vehicle) {
std::vector<IntervalVar*> breaks;
for (int i = 0; i < break_data.size(); ++i) {
IntervalVar* const break_interval = solver->MakeFixedDurationIntervalVar(
break_data[i][0] * 3600, break_data[i][1] * 3600, break_data[i][2],
true, absl::StrCat("Break ", i, " on vehicle ", vehicle));
breaks.push_back(break_interval);
}
// break1 performed iff break2 performed
solver->AddConstraint(solver->MakeEquality(breaks[1]->PerformedExpr(),
breaks[2]->PerformedExpr()));
// break2 start 1h after break1.
solver->AddConstraint(solver->MakeIntervalVarRelationWithDelay(
breaks[2], Solver::STARTS_AFTER_END, breaks[1], 3600));
// break0 performed iff break2 unperformed
solver->AddConstraint(solver->MakeNonEquality(breaks[0]->PerformedExpr(),
breaks[2]->PerformedExpr()));
time_dimension->SetBreakIntervalsOfVehicle(std::move(breaks), vehicle,
service_times);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10000000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < routing.nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Solve, returns a solution if any (owned by RoutingModel).
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
LOG(INFO) << "Breaks: ";
for (const auto& break_interval :
solution->IntervalVarContainer().elements()) {
if (break_interval.PerformedValue() == 1) {
LOG(INFO) << break_interval.Var()->name() << " "
<< break_interval.DebugString();
} else {
LOG(INFO) << break_interval.Var()->name() << " unperformed";
}
}
DisplayPlan(manager, routing, *solution, false, 0, 0,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,212 @@
// Copyright 2010-2022 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.
//
// Capacitated Vehicle Routing Problem with Time Windows (and optional orders).
// A description of the problem can be found here:
// http://en.wikipedia.org/wiki/Vehicle_routing_problem.
// The variant which is tackled by this model includes a capacity dimension,
// time windows and optional orders, with a penalty cost if orders are not
// performed. For the sake of simplicty, orders are randomly located and
// distances are computed using the Manhattan distance. Distances are assumed
// to be in meters and times in seconds.
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/graph/graph_builder.h"
#include "ortools/routing/cvrptw_lib.h"
#include "ortools/util/random_engine.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
ABSL_FLAG(int, vrp_orders, 100, "Nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(bool, vrp_use_same_vehicle_costs, false,
"Use same vehicle costs in the routing model");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
ABSL_FLAG(int, vrp_precedences, 5,
"Number of precedence indices. Precedences will be chosen "
"randomly with the constraint that they don't form cycles.");
ABSL_FLAG(int64_t, vrp_precedence_offset, 100,
"The offset that applies to the precedences. For each pair linked "
"by a precedence constraint, pair.second can only start after the "
"start of pair.first + offset.");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
const int64_t kMaxNodesPerGroup = 10;
const int64_t kSameVehicleCost = 1000;
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/true, kTime);
RoutingDimension* time_dimension = routing.GetMutableDimension(kTime);
// Adding time windows.
random_engine_t randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension->CumulVar(order)->SetRange(start, start + kTWDuration);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10000000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Adding same vehicle constraint costs for consecutive nodes.
if (absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs)) {
std::vector<int64_t> group;
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
group.push_back(manager.NodeToIndex(order));
if (group.size() == kMaxNodesPerGroup) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
group.clear();
}
}
if (!group.empty()) {
routing.AddSoftSameVehicleConstraint(group, kSameVehicleCost);
}
}
// If the flag is > 0, we create a DAG with random edges representing
// precedences. If it is not possible to meet the precedence constraints, for
// instance if the generated time window are incompatible, we expect one of
// the underlying orders to be skipped.
if (absl::GetFlag(FLAGS_vrp_precedences) > 0) {
// Randomly select edges in a graph that will act as precedences.
std::vector<std::pair<int, int>> precedences;
GraphBuilder::RandomEdges(
GraphBuilder::DISALLOW_ALL_CYCLES, absl::GetFlag(FLAGS_vrp_orders),
absl::GetFlag(FLAGS_vrp_precedences), &randomizer, &precedences);
LOG(INFO) << "Adding precedences: ";
for (const std::pair<int, int>& precedence : precedences) {
LOG(INFO) << precedence.first << " -> " << precedence.second;
time_dimension->AddNodePrecedence(
{precedence.first, precedence.second,
absl::GetFlag(FLAGS_vrp_precedence_offset)});
}
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution,
absl::GetFlag(FLAGS_vrp_use_same_vehicle_costs),
kMaxNodesPerGroup, kSameVehicleCost,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return 0;
}

View File

@@ -0,0 +1,193 @@
// Copyright 2010-2022 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.
// Capacitated Vehicle Routing Problem with Time Windows and refueling
// constraints.
// This is an extension to the model in cvrptw.cc so refer to that file for
// more information on the common part of the model. The model implemented here
// takes into account refueling constraints using a specific dimension: vehicles
// must visit certain nodes (refueling nodes) before the quantity of fuel
// reaches zero. Fuel consumption is proportional to the distance traveled.
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
ABSL_FLAG(int, vrp_orders, 100, "Nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
const char* kFuel = "Fuel";
// Returns true if node is a refueling node (based on node / refuel node ratio).
bool IsRefuelNode(int64_t node) {
const int64_t kRefuelNodeRatio = 10;
return (node % kRefuelNodeRatio == 0);
}
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/true, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding time windows.
// NOTE(user): This randomized test case is quite sensible to the seed:
// the generated model can be much easier or harder to solve, depending on
// the seed. It turns out that most seeds yield pretty slow/bad solver
// performance: I got good performance for about 10% of the seeds.
std::mt19937 randomizer(
144 + GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
if (!IsRefuelNode(order)) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
}
// Adding fuel dimension. This dimension consumes a quantity equal to the
// distance traveled. Only refuel nodes can make the quantity of dimension
// increase by letting slack variable replenish the fuel.
const int64_t kFuelCapacity = kXMax + kYMax;
routing.AddDimension(
routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.NegManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kFuelCapacity, kFuelCapacity, /*fix_start_cumul_to_zero=*/false, kFuel);
const RoutingDimension& fuel_dimension = routing.GetDimensionOrDie(kFuel);
for (int order = 0; order < routing.Size(); ++order) {
// Only let slack free for refueling nodes.
if (!IsRefuelNode(order) || routing.IsStart(order)) {
fuel_dimension.SlackVar(order)->SetValue(0);
}
// Needed to instantiate fuel quantity at each node.
routing.AddVariableMinimizedByFinalizer(fuel_dimension.CumulVar(order));
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 100000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < routing.nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution, /*use_same_vehicle_costs=*/false,
/*max_nodes_per_group=*/0, /*same_vehicle_cost=*/0,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw_with_refueling() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED "${DIR}/cvrptw_with_refueling\
--vrp_use_deterministic_random_seed --cp_random_seed=144"
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,188 @@
// Copyright 2010-2022 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.
// Capacitated Vehicle Routing Problem with Time Windows and capacitated
// resources.
// This is an extension to the model in cvrptw.cc so refer to that file for
// more information on the common part of the model. The model implemented here
// limits the number of vehicles which can simultaneously leave or enter the
// depot due to limited resources (or capacity) available.
// TODO(user): The current model consumes resources even for vehicles with
// empty routes; fix this when we have an API on the cumulative constraints
// with variable demands.
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::IntervalVar;
using operations_research::IntVar;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
using operations_research::Solver;
ABSL_FLAG(int, vrp_orders, 100, "Nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 20,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 300;
const int64_t kHorizon = 24 * 3600;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/false, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding time windows.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
// Adding resource constraints at the depot (start and end location of
// routes).
std::vector<IntVar*> start_end_times;
for (int i = 0; i < absl::GetFlag(FLAGS_vrp_vehicles); ++i) {
start_end_times.push_back(time_dimension.CumulVar(routing.End(i)));
start_end_times.push_back(time_dimension.CumulVar(routing.Start(i)));
}
// Build corresponding time intervals.
const int64_t kVehicleSetup = 180;
Solver* const solver = routing.solver();
std::vector<IntervalVar*> intervals;
solver->MakeFixedDurationIntervalVarArray(start_end_times, kVehicleSetup,
"depot_interval", &intervals);
// Constrain the number of maximum simultaneous intervals at depot.
const int64_t kDepotCapacity = 5;
std::vector<int64_t> depot_usage(start_end_times.size(), 1);
solver->AddConstraint(
solver->MakeCumulative(intervals, depot_usage, kDepotCapacity, "depot"));
// Instantiate route start and end times to produce feasible times.
for (int i = 0; i < start_end_times.size(); ++i) {
routing.AddVariableMinimizedByFinalizer(start_end_times[i]);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 100000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < manager.num_nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution, /*use_same_vehicle_costs=*/false,
/*max_nodes_per_group=*/0, /*same_vehicle_cost=*/0,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw_with_resources() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED "${DIR}/cvrptw_with_resources\
--vrp_use_deterministic_random_seed"
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,224 @@
// Copyright 2010-2022 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.
// Capacitated Vehicle Routing Problem with Time Windows, fixed stop times and
// capacitated resources. A stop is defined as consecutive nodes at the same
// location.
// This is an extension to the model in cvrptw.cc so refer to that file for
// more information on the common part of the model. The model implemented here
// limits the number of vehicles which can simultaneously leave or enter a node
// to one.
#include <cstdint>
#include <random>
#include <vector>
#include "absl/random/random.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::IntervalVar;
using operations_research::IntVar;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::Solver;
using operations_research::StopServiceTimePlusTransition;
ABSL_FLAG(int, vrp_stops, 25, "Stop locations in the problem.");
ABSL_FLAG(int, vrp_orders_per_stop, 5, "Nodes for each stop.");
ABSL_FLAG(int, vrp_vehicles, 20,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
const char* kTime = "Time";
const char* kCapacity = "Capacity";
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_stops))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders_per_stop))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
const int vrp_orders =
absl::GetFlag(FLAGS_vrp_stops) * absl::GetFlag(FLAGS_vrp_orders_per_stop);
// Nodes are indexed from 0 to vrp_orders, the starts and ends of the routes
// are at node 0.
const RoutingIndexManager::NodeIndex kDepot(0);
RoutingIndexManager manager(vrp_orders + 1, absl::GetFlag(FLAGS_vrp_vehicles),
kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 100000;
const int64_t kYMax = 100000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int stop = 0; stop <= absl::GetFlag(FLAGS_vrp_stops); ++stop) {
const int num_orders =
stop == 0 ? 1 : absl::GetFlag(FLAGS_vrp_orders_per_stop);
locations.AddRandomLocation(kXMax, kYMax, num_orders);
}
// Setting the cost function.
const int vehicle_cost = routing.RegisterTransitCallback(
[&locations, &manager](int64_t i, int64_t j) {
return locations.ManhattanDistance(manager.IndexToNode(i),
manager.IndexToNode(j));
});
routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost);
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kStopTime = 300;
const int64_t kHorizon = 24 * 3600;
StopServiceTimePlusTransition time(
kStopTime, locations,
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/false, kTime);
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
// Adding time windows, for the sake of simplicty same for each stop.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const int64_t kTWDuration = 5 * 3600;
for (int stop = 0; stop < absl::GetFlag(FLAGS_vrp_stops); ++stop) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
for (int stop_order = 0;
stop_order < absl::GetFlag(FLAGS_vrp_orders_per_stop); ++stop_order) {
const int order =
stop * absl::GetFlag(FLAGS_vrp_orders_per_stop) + stop_order + 1;
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
}
// Adding resource constraints at order locations.
Solver* const solver = routing.solver();
std::vector<IntervalVar*> intervals;
for (int stop = 0; stop < absl::GetFlag(FLAGS_vrp_stops); ++stop) {
std::vector<IntervalVar*> stop_intervals;
for (int stop_order = 0;
stop_order < absl::GetFlag(FLAGS_vrp_orders_per_stop); ++stop_order) {
const int order =
stop * absl::GetFlag(FLAGS_vrp_orders_per_stop) + stop_order + 1;
IntervalVar* const interval = solver->MakeFixedDurationIntervalVar(
0, kHorizon, kStopTime, true, absl::StrCat("Order", order));
intervals.push_back(interval);
stop_intervals.push_back(interval);
// Link order and interval.
IntVar* const order_start = time_dimension.CumulVar(order);
solver->AddConstraint(
solver->MakeIsEqualCt(interval->SafeStartExpr(0), order_start,
interval->PerformedExpr()->Var()));
// Make interval performed iff corresponding order has service time.
// An order has no service time iff it is at the same location as the
// next order on the route.
IntVar* const is_null_duration =
solver
->MakeElement(
[&locations, order](int64_t index) {
return locations.SameLocationFromIndex(order, index);
},
routing.NextVar(order))
->Var();
solver->AddConstraint(
solver->MakeNonEquality(interval->PerformedExpr(), is_null_duration));
routing.AddIntervalToAssignment(interval);
// We are minimizing route durations by minimizing route ends; so we can
// maximize order starts to pack them together.
routing.AddVariableMaximizedByFinalizer(order_start);
}
// Only one order can happen at the same time at a given location.
std::vector<int64_t> location_usage(stop_intervals.size(), 1);
solver->AddConstraint(solver->MakeCumulative(
stop_intervals, location_usage, 1, absl::StrCat("Client", stop)));
}
// Minimizing route duration.
for (int vehicle = 0; vehicle < manager.num_vehicles(); ++vehicle) {
routing.AddVariableMinimizedByFinalizer(
time_dimension.CumulVar(routing.End(vehicle)));
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 100000;
const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < routing.nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution, /*use_same_vehicle_costs=*/false,
/*max_nodes_per_group=*/0, /*same_vehicle_cost=*/0,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
LOG(INFO) << "Stop intervals:";
for (IntervalVar* const interval : intervals) {
if (solution->PerformedValue(interval)) {
LOG(INFO) << interval->name() << ": " << solution->StartValue(interval);
}
}
} else {
LOG(INFO) << "No solution found.";
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw_with_stop_times_and_resources() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED "${DIR}/cvrptw_with_stop_times_and_resources\
--vrp_use_deterministic_random_seed"
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,249 @@
// Copyright 2010-2022 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.
// This example is very similar to cvrptw.cc, but distances are time dependent.
// The function RandomStepFunction is used to add random noise to each transit.
#include <cmath>
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <set>
#include <vector>
#include "absl/functional/bind_front.h"
#include "absl/random/random.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/init_google.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/routing/cvrptw_lib.h"
#include "ortools/util/random_engine.h"
#include "ortools/util/range_query_function.h"
#include "ortools/util/step_function.h"
using operations_research::Assignment;
using operations_research::DefaultRoutingSearchParameters;
using operations_research::GetSeed;
using operations_research::LocationContainer;
using operations_research::RandomDemand;
using operations_research::RoutingDimension;
using operations_research::RoutingIndexManager;
using operations_research::RoutingModel;
using operations_research::RoutingNodeIndex;
using operations_research::RoutingSearchParameters;
using operations_research::ServiceTimePlusTransition;
using operations_research::StepFunction;
ABSL_FLAG(int, vrp_orders, 25, "Nodes in the problem.");
ABSL_FLAG(int, vrp_vehicles, 10,
"Size of Traveling Salesman Problem instance.");
ABSL_FLAG(bool, vrp_use_deterministic_random_seed, false,
"Use deterministic random seeds.");
ABSL_FLAG(std::string, routing_search_parameters, "",
"Text proto RoutingSearchParameters (possibly partial) that will "
"override the DefaultRoutingSearchParameters()");
static const char kTime[] = "Time";
static const char kCapacity[] = "Capacity";
static const char kTimeDepedentCost[] = "TimeDependentCost";
// This class implements the Pólya urn stochastic process, for more information:
// https://en.wikipedia.org/wiki/P%C3%B3lya_urn_model
// Basically, the polya urn is a martingale that converges almost surely to a
// uniform random variable over [0, 1]. It is questionable if it's realistic to
// model traffic deviations with this process, but traffic is hard to model in
// general.
class PolyaUrn {
public:
PolyaUrn(int red_balls, int blue_balls, int seed)
: red_balls_(red_balls),
all_balls_(red_balls + blue_balls),
generator_(seed) {
CHECK_LT(0, red_balls_);
CHECK_LT(red_balls_, all_balls_);
}
// Every call to Next moves the process one step forward and returns the
// current value.
double Next() {
CHECK_LT(0, red_balls_);
CHECK_LT(red_balls_, all_balls_);
const double return_value = static_cast<double>(red_balls_) / all_balls_;
red_balls_ += (generator_.Uniform(all_balls_) < red_balls_);
all_balls_ += 1;
CHECK_LT(0, return_value);
CHECK_LT(return_value, 1);
return return_value - 0.5;
}
private:
int red_balls_;
int all_balls_;
random_engine_t generator_;
};
// Creates a random histogram over the interval [0, interval_end) using the urn.
StepFunction RandomStepFunction(int64_t mean, int64_t step_size,
int64_t interval_end, int seed) {
PolyaUrn random_generator(1, 1, seed);
StepFunction result;
for (int64_t step = 0; step < interval_end; step += step_size) {
result.AddStepToEnd(step, 2 * mean * random_generator.Next() - mean);
}
result.AddStepToEnd(interval_end, 0);
return result;
}
class TrafficTransitionEvaluator {
public:
TrafficTransitionEvaluator(const LocationContainer& distance_evaluator,
int64_t max_time)
: distance_evaluator_(distance_evaluator), max_time_(max_time) {}
RoutingModel::StateDependentTransit Run(const RoutingIndexManager& manager,
int64_t from_index,
int64_t to_index) {
const RoutingIndexManager::NodeIndex from = manager.IndexToNode(from_index);
const RoutingIndexManager::NodeIndex to = manager.IndexToNode(to_index);
static const int magic_number = 0xfe3498aa;
const int64_t seed =
(from.value() ^ magic_number) * (to.value() ^ (~magic_number));
const int64_t distance = distance_evaluator_.ManhattanDistance(from, to);
const int64_t mean_deviation = sqrt(distance);
const StepFunction deviation =
RandomStepFunction(mean_deviation, sqrt(max_time_), max_time_, seed);
const std::function<int64_t(int64_t)> travel_time =
[distance, &deviation](int64_t time) -> int64_t {
return distance + deviation.GetValue(time);
};
return RoutingModel::MakeStateDependentTransit(travel_time, 0, max_time_);
// Now the local variables deviation and travel_time are going to be
// destroyed, but MakeStateDependentTransit does not store either and it
// uses its own caches.
}
private:
const LocationContainer& distance_evaluator_;
const int64_t max_time_;
};
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_orders))
<< "Specify an instance size greater than 0.";
CHECK_LT(0, absl::GetFlag(FLAGS_vrp_vehicles))
<< "Specify a non-null vehicle fleet size.";
// VRP of size absl::GetFlag(FLAGS_vrp_size).
// Nodes are indexed from 0 to absl::GetFlag(FLAGS_vrp_orders), the starts and
// ends of the routes are at node 0.
static const RoutingIndexManager::NodeIndex kDepot(0);
static const RoutingIndexManager::NodeIndex kFirstNodeAfterDepot(1);
RoutingIndexManager manager(absl::GetFlag(FLAGS_vrp_orders) + 1,
absl::GetFlag(FLAGS_vrp_vehicles), kDepot);
RoutingModel routing(manager);
// Setting up locations.
const int64_t kXMax = 1000;
const int64_t kYMax = 1000;
const int64_t kSpeed = 10;
LocationContainer locations(
kSpeed, absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
for (int location = 0; location <= absl::GetFlag(FLAGS_vrp_orders);
++location) {
locations.AddRandomLocation(kXMax, kYMax);
}
// Adding capacity dimension constraints.
const int64_t kVehicleCapacity = 40;
const int64_t kNullCapacitySlack = 0;
RandomDemand demand(manager.num_nodes(), kDepot,
absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed));
demand.Initialize();
routing.AddDimension(routing.RegisterTransitCallback(
[&demand, &manager](int64_t i, int64_t j) {
return demand.Demand(manager.IndexToNode(i),
manager.IndexToNode(j));
}),
kNullCapacitySlack, kVehicleCapacity,
/*fix_start_cumul_to_zero=*/true, kCapacity);
// Adding time dimension constraints.
const int64_t kTimePerDemandUnit = 3;
const int64_t kHorizon = 24 * 36;
ServiceTimePlusTransition time(
kTimePerDemandUnit,
[&demand](RoutingNodeIndex i, RoutingNodeIndex j) {
return demand.Demand(i, j);
},
[&locations](RoutingNodeIndex i, RoutingNodeIndex j) {
return locations.ManhattanTime(i, j);
});
routing.AddDimension(
routing.RegisterTransitCallback([&time, &manager](int64_t i, int64_t j) {
return time.Compute(manager.IndexToNode(i), manager.IndexToNode(j));
}),
kHorizon, kHorizon, /*fix_start_cumul_to_zero=*/true, kTime);
// Setting the cost function. In fact, we create a time dependent dimension.
const int64_t max_time = manager.num_nodes() * (kXMax + kYMax) / kSpeed;
TrafficTransitionEvaluator traffic_evaluator(locations, max_time);
routing.AddDimensionDependentDimensionWithVehicleCapacity(
routing.RegisterStateDependentTransitCallback(::absl::bind_front(
&TrafficTransitionEvaluator::Run, &traffic_evaluator, manager)),
&routing.GetDimensionOrDie(kTime), kHorizon, kHorizon,
/*fix_start_cumul_to_zero=*/true, kTimeDepedentCost);
routing.GetMutableDimension(kTimeDepedentCost)
->SetSpanCostCoefficientForAllVehicles(1);
// Adding time windows.
std::mt19937 randomizer(
GetSeed(absl::GetFlag(FLAGS_vrp_use_deterministic_random_seed)));
const RoutingDimension& time_dimension = routing.GetDimensionOrDie(kTime);
const int64_t kTWDuration = 5 * 36;
for (int order = 1; order < manager.num_nodes(); ++order) {
const int64_t start =
absl::Uniform<int32_t>(randomizer, 0, kHorizon - kTWDuration);
time_dimension.CumulVar(order)->SetRange(start, start + kTWDuration);
}
// Adding penalty costs to allow skipping orders.
const int64_t kPenalty = 10000000;
for (RoutingIndexManager::NodeIndex order = kFirstNodeAfterDepot;
order < routing.nodes(); ++order) {
std::vector<int64_t> orders(1, manager.NodeToIndex(order));
routing.AddDisjunction(orders, kPenalty);
}
// Solve, returns a solution if any (owned by RoutingModel).
RoutingSearchParameters parameters = DefaultRoutingSearchParameters();
CHECK(google::protobuf::TextFormat::MergeFromString(
absl::GetFlag(FLAGS_routing_search_parameters), &parameters));
const Assignment* solution = routing.SolveWithParameters(parameters);
if (solution != nullptr) {
DisplayPlan(manager, routing, *solution, /*use_same_vehicle_costs=*/false,
/*max_nodes_per_group=*/0, /*same_vehicle_cost=*/0,
routing.GetDimensionOrDie(kCapacity),
routing.GetDimensionOrDie(kTime));
} else {
LOG(INFO) << "No solution found.";
}
return 0;
}

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright 2010-2022 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.
source gbash.sh || exit
source module gbash_unit.sh
function test::operations_research_examples::cvrptw_with_time_dependent_costs() {
declare -r DIR="${TEST_SRCDIR}/ortools/routing/samples"
EXPECT_SUCCEED '${DIR}/cvrptw_with_time_dependent_costs --vrp_use_deterministic_random_seed'
}
gbash::unit::main "$@"

View File

@@ -0,0 +1,21 @@
// Copyright 2010-2022 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/routing/simple_graph.h"
namespace operations_research {
Edge::Edge(const Arc& arc) : tail_(arc.tail()), head_(arc.head()) {}
Arc::Arc(const Edge& edge) : tail_(edge.tail()), head_(edge.head()) {}
} // namespace operations_research

View File

@@ -0,0 +1,155 @@
// Copyright 2010-2022 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.
// Common utilities for parsing routing instances.
#ifndef OR_TOOLS_ROUTING_SIMPLE_GRAPH_H_
#define OR_TOOLS_ROUTING_SIMPLE_GRAPH_H_
#include <algorithm>
#include <functional>
#include <ostream>
#include <string>
#include "absl/hash/hash.h"
namespace operations_research {
class Arc;
// Edge, undirected, between the head to the tail.
// With a few bells and whistles to allow its use within hash tables.
class Edge {
public:
Edge(int64_t tail, int64_t head) : tail_(tail), head_(head) {}
explicit Edge(const Arc& arc);
int64_t tail() const { return tail_; }
int64_t head() const { return head_; }
bool operator==(const Edge& other) const {
return (head_ == other.head_ && tail_ == other.tail_) ||
(head_ == other.tail_ && tail_ == other.head_);
}
bool operator!=(const Edge& other) const { return !this->operator==(other); }
template <typename H>
friend H AbslHashValue(H h, const Edge& a) {
// This hash value should not depend on the direction of the edge, hence
// the use of min and max.
return H::combine(std::move(h), std::min(a.head_, a.tail_),
std::max(a.head_, a.tail_));
}
private:
const int64_t tail_;
const int64_t head_;
};
// Arc, directed, from the tail to the head.
// With a few bells and whistles to allow its use within hash tables.
class Arc {
public:
Arc(int64_t tail, int64_t head) : tail_(tail), head_(head) {}
explicit Arc(const Edge& edge);
int64_t tail() const { return tail_; }
int64_t head() const { return head_; }
Arc reversed() const { return {head_, tail_}; }
bool operator==(const Arc& other) const {
return head_ == other.head_ && tail_ == other.tail_;
}
bool operator!=(const Arc& other) const { return !this->operator==(other); }
template <typename H>
friend H AbslHashValue(H h, const Arc& a) {
// Unlike the edge, this value *must* depend on the direction of the arc.
return H::combine(std::move(h), a.tail_, a.head_);
}
private:
const int64_t tail_;
const int64_t head_;
};
// Mapping between an edge (given by its tail and its head) and its weight.
typedef std::function<int64_t(int, int)> EdgeWeights;
// Real-world coordinates.
template <typename T>
struct Coordinates2 {
T x = {};
T y = {};
Coordinates2() = default;
Coordinates2(T x, T y) : x(x), y(y) {}
friend bool operator==(const Coordinates2& a, const Coordinates2& b) {
return a.x == b.x && a.y == b.y;
}
friend bool operator!=(const Coordinates2& a, const Coordinates2& b) {
return !(a == b);
}
friend std::ostream& operator<<(std::ostream& stream,
const Coordinates2& coordinates) {
return stream << "{x = " << coordinates.x << ", y = " << coordinates.y
<< "}";
}
template <typename H>
friend H AbslHashValue(H h, const Coordinates2& coordinates) {
return H::combine(std::move(h), coordinates.x, coordinates.y);
}
};
template <typename T>
struct Coordinates3 {
T x = {};
T y = {};
T z = {};
Coordinates3() = default;
Coordinates3(T x, T y, T z) : x(x), y(y), z(z) {}
friend bool operator==(const Coordinates3& a, const Coordinates3& b) {
return a.x == b.x && a.y == b.y && a.z == b.z;
}
friend bool operator!=(const Coordinates3& a, const Coordinates3& b) {
return !(a == b);
}
friend std::ostream& operator<<(std::ostream& stream,
const Coordinates3& coordinates) {
return stream << "{x = " << coordinates.x << ", y = " << coordinates.y
<< ", z = " << coordinates.z << "}";
}
template <typename H>
friend H AbslHashValue(H h, const Coordinates3& coordinates) {
return H::combine(std::move(h), coordinates.x, coordinates.y,
coordinates.z);
}
};
// Time window, typically used for a node.
// Name chosen to avoid clash with tour_optimization.proto, defining a
// TimeWindow message with more fields.
template <typename T>
struct SimpleTimeWindow {
T start;
T end;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_SIMPLE_GRAPH_H_

View File

@@ -0,0 +1,103 @@
// Copyright 2010-2022 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/routing/simple_graph.h"
#include <sstream>
#include <string>
#include "absl/hash/hash_testing.h"
#include "gtest/gtest.h"
namespace operations_research {
namespace {
TEST(SimpleGraphTest, EdgeHashing) {
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
Edge(0, 0),
Edge(1, 2),
Edge(2, 1),
}));
}
TEST(SimpleGraphTest, EdgeEquality) {
EXPECT_EQ(Edge(0, 0), Edge(0, 0));
EXPECT_NE(Edge(0, 0), Edge(1, 2));
EXPECT_EQ(Edge(2, 1), Edge(1, 2)); // Undirected edge.
}
TEST(SimpleGraphTest, ArcHashing) {
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
Arc(0, 0),
Arc(1, 2),
Arc(2, 1),
}));
}
TEST(SimpleGraphTest, ArcEquality) {
EXPECT_EQ(Arc(0, 0), Arc(0, 0));
EXPECT_NE(Arc(0, 0), Arc(1, 2));
EXPECT_NE(Arc(2, 1), Arc(1, 2)); // Directed edge.
}
TEST(Coordinates2Test, Int) {
EXPECT_EQ(Coordinates2<int>(0, 0), Coordinates2<int>(0, 0));
EXPECT_NE(Coordinates2<int>(0, 0), Coordinates2<int>(1, 2));
std::stringstream generated;
generated << Coordinates2<int>(0, 1);
EXPECT_EQ(generated.str(), "{x = 0, y = 1}");
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
{Coordinates2<int>(), Coordinates2<int>(0, 0), Coordinates2<int>(1, 2)}));
}
TEST(Coordinates2Test, Double) {
EXPECT_EQ(Coordinates2<double>(0, 0), Coordinates2<double>(0, 0));
EXPECT_NE(Coordinates2<double>(0, 0), Coordinates2<double>(1, 2));
std::stringstream generated;
generated << Coordinates2<double>(0.0, 1.0);
EXPECT_EQ(generated.str(), "{x = 0, y = 1}");
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
{Coordinates2<double>(), Coordinates2<double>(0, 0),
Coordinates2<double>(1, 2)}));
}
TEST(Coordinates3Test, Int) {
EXPECT_EQ(Coordinates3<int>(0, 0, 0), Coordinates3<int>(0, 0, 0));
EXPECT_NE(Coordinates3<int>(0, 0, 0), Coordinates3<int>(1, 2, 3));
std::stringstream generated;
generated << Coordinates3<int>(0, 1, 2);
EXPECT_EQ(generated.str(), "{x = 0, y = 1, z = 2}");
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
{Coordinates3<int>(), Coordinates3<int>(0, 0, 0),
Coordinates3<int>(1, 2, 3)}));
}
TEST(Coordinates3Test, Double) {
EXPECT_EQ(Coordinates3<double>(0, 0, 0), Coordinates3<double>(0, 0, 0));
EXPECT_NE(Coordinates3<double>(0, 0, 0), Coordinates3<double>(1, 2, 3));
std::stringstream generated;
generated << Coordinates3<double>(0.0, 1.0, 2.0);
EXPECT_EQ(generated.str(), "{x = 0, y = 1, z = 2}");
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
{Coordinates3<double>(), Coordinates3<double>(0.0, 0.0, 0.0),
Coordinates3<double>(1.0, 2.0, 3.0)}));
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,137 @@
// Copyright 2010-2022 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/routing/solomon_parser.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/base/map_util.h"
#include "ortools/base/numbers.h"
#include "ortools/base/path.h"
#include "ortools/base/zipfile.h"
#include "ortools/util/filelineiter.h"
namespace operations_research {
SolomonParser::SolomonParser()
: sections_({{"VEHICLE", VEHICLE}, {"CUSTOMER", CUSTOMER}}) {
Initialize();
}
bool SolomonParser::LoadFile(const std::string& file_name) {
Initialize();
return ParseFile(file_name);
}
bool SolomonParser::LoadFile(const std::string& file_name,
const std::string& archive_name) {
Initialize();
if (!absl::StartsWith(archive_name, "/")) {
return false;
}
const std::string fake_zip_path = "/zip" + archive_name;
std::shared_ptr<zipfile::ZipArchive> fake_zip_closer(
zipfile::OpenZipArchive(archive_name));
if (nullptr == fake_zip_closer) return false;
const std::string zip_filename = file::JoinPath(fake_zip_path, file_name);
return ParseFile(zip_filename);
}
void SolomonParser::Initialize() {
name_.clear();
vehicles_ = 0;
coordinates_.clear();
capacity_ = 0;
demands_.clear();
time_windows_.clear();
service_times_.clear();
section_ = NAME;
to_read_ = 1;
}
bool SolomonParser::ParseFile(const std::string& file_name) {
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
// Skip blank lines
if (words.empty()) continue;
if (to_read_ > 0) {
switch (section_) {
case NAME: {
name_ = words[0];
break;
}
case VEHICLE: {
if (to_read_ == 1) {
if (words.size() != 2) return false;
vehicles_ = strings::ParseLeadingInt32Value(words[0], -1);
if (vehicles_ < 0) return false;
capacity_ = strings::ParseLeadingInt32Value(words[1], -1);
if (capacity_ < 0) return false;
}
break;
}
case CUSTOMER: {
if (to_read_ < 2) {
std::vector<int64_t> values;
for (int i = 1; i < words.size(); ++i) {
const int64_t value =
strings::ParseLeadingInt64Value(words[i], -1);
if (value < 0) return false;
values.push_back(value);
}
coordinates_.push_back({values[0], values[1]});
demands_.push_back(values[2]);
time_windows_.push_back({values[3], values[4]});
service_times_.push_back(values[5]);
++to_read_;
}
break;
}
default: {
LOG(ERROR) << "Reading data outside section";
return false;
}
}
--to_read_;
} else { // New section
section_ = gtl::FindWithDefault(sections_, words[0], UNKNOWN);
switch (section_) {
case VEHICLE: {
// Two rows: header and data.
to_read_ = 2;
break;
}
case CUSTOMER: {
to_read_ = 2;
break;
}
default: {
LOG(ERROR) << "Unknown section: " << section_;
return false;
}
}
}
}
return section_ == CUSTOMER;
}
} // namespace operations_research

View File

@@ -0,0 +1,138 @@
// Copyright 2010-2022 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.
// A parser for "Solomon" instances. The Solomon file library is a set of
// Capacitated Vehicle Routing Problems with Time Windows created by
// Pr. Marius Solomon.
//
// The goal is to find routes starting and ending at a depot which visit a
// set of nodes. The objective is first to minimize the number of routes and
// then to minimize the total distance traveled, distances being measured with
// the Euclidean distance. There are two types of constraints limiting the
// route lengths:
// - time windows restricting the time during which a node can be visited
// - vehicle capacity which limits the load of the vehicles performing the
// routes (each node has a corresponding demand which must be picked up
// by the vehicle).
//
// The format of the data is the following:
//
// <instance_name>
// VEHICLE
// NUMBER CAPACITY
// <number of nodes> <vehicle capacity>
// CUSTOMER
// CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE DATE SERVICE TIME
// <node id> <x> <y> <demand> <ready time> <due date> <service time>
//
// The parser supports both standard instance files and zipped archives
// containing multiple instances.
//
#ifndef OR_TOOLS_ROUTING_SOLOMON_PARSER_H_
#define OR_TOOLS_ROUTING_SOLOMON_PARSER_H_
#include <math.h>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include "ortools/base/integral_types.h"
#include "ortools/base/macros.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
// Solomon parser class.
class SolomonParser {
public:
SolomonParser();
#ifndef SWIG
SolomonParser(const SolomonParser&) = delete;
const SolomonParser& operator=(const SolomonParser&) = delete;
#endif
// Loading an instance. Both methods return false in case of failure to read
// the instance. Loading a new instance clears the previously loaded instance.
// Loads instance from a file.
bool LoadFile(const std::string& file_name);
// Loads instance from a file contained in a zipped archive; the archive can
// contain multiple files.
bool LoadFile(const std::string& file_name, const std::string& archive_name);
// Returns the name of the instance being solved.
const std::string& name() const { return name_; }
// Returns the index of the depot.
int Depot() const { return 0; }
// Returns the number of nodes in the current routing problem.
int NumberOfNodes() const { return coordinates_.size(); }
// Returns the maximum number of vehicles to use.
int NumberOfVehicles() const { return vehicles_; }
// Returns the coordinates of the nodes in the current routing problem.
const std::vector<Coordinates2<int64_t>>& coordinates() const {
return coordinates_;
}
// Returns the capacity of the vehicles.
int64_t capacity() const { return capacity_; }
// Returns the demand of the nodes in the current routing problem.
const std::vector<int64_t>& demands() const { return demands_; }
// Returns the time windows of the nodes in the current routing problem.
const std::vector<SimpleTimeWindow<int64_t>>& time_windows() const {
return time_windows_;
}
// Returns the service times of the nodes in the current routing problem.
const std::vector<int64_t>& service_times() const { return service_times_; }
// Returns the distance between two nodes.
double GetDistance(int from, int to) const {
const Coordinates2<int64_t>& from_coords = coordinates_[from];
const Coordinates2<int64_t>& to_coords = coordinates_[to];
const double xd = from_coords.x - to_coords.x;
const double yd = from_coords.y - to_coords.y;
return sqrt(xd * xd + yd * yd);
}
// Returns the travel time between two nodes.
double GetTravelTime(int from, int to) const {
return service_times_[from] + GetDistance(from, to);
}
private:
enum Section { UNKNOWN, NAME, VEHICLE, CUSTOMER };
// Parsing
void Initialize();
bool ParseFile(const std::string& file_name);
// Parsing data
const std::map<std::string, Section> sections_;
Section section_;
int to_read_;
// Input data
// Using int64_t to represent different dimension values of the problem:
// - demand and vehicle capacity,
// - distances and node coordinates,
// - time, including time window values and service times.
std::string name_;
int vehicles_;
std::vector<Coordinates2<int64_t>> coordinates_;
int64_t capacity_;
std::vector<int64_t> demands_;
std::vector<SimpleTimeWindow<int64_t>> time_windows_;
std::vector<int64_t> service_times_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_SOLOMON_PARSER_H_

View File

@@ -0,0 +1,68 @@
// Copyright 2010-2022 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/routing/solomon_parser.h"
#include <memory>
#include <string>
#include "absl/flags/flag.h"
#include "gtest/gtest.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/file.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/path.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
ABSL_FLAG(std::string, solomon_test_archive,
"ortools/bench/solomon/"
"testdata/solomon.zip",
"Solomon: testing archive");
ABSL_FLAG(std::string, solomon_test_instance, "google2.txt",
"Solomon: testing instance");
namespace operations_research {
namespace {
TEST(SolomonParserTest, LoadEmptyFileName) {
std::string empty_file_name;
SolomonParser parser;
EXPECT_FALSE(parser.LoadFile(empty_file_name));
}
TEST(SolomonParserTest, LoadNonExistingFile) {
SolomonParser parser;
EXPECT_FALSE(parser.LoadFile(""));
}
TEST(SolomonParserTest, LoadNonEmptyArchive) {
std::string empty_archive_name;
SolomonParser parser;
EXPECT_FALSE(parser.LoadFile(absl::GetFlag(FLAGS_solomon_test_instance),
empty_archive_name));
}
TEST(SolomonParserTest, LoadNonExistingArchive) {
SolomonParser parser;
EXPECT_FALSE(parser.LoadFile(absl::GetFlag(FLAGS_solomon_test_instance), ""));
}
TEST(SolomonParserTest, LoadNonExistingInstance) {
SolomonParser parser;
EXPECT_FALSE(parser.LoadFile(
"doesnotexist.txt",
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
absl::GetFlag(FLAGS_solomon_test_archive))));
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,424 @@
// Copyright 2010-2022 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//routing/solution_serializer.h"
#include <algorithm>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "absl/strings/ascii.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "ortools/base/logging.h"
namespace operations_research {
RoutingOutputFormat RoutingOutputFormatFromString(std::string_view format) {
const std::string format_normalized =
absl::AsciiStrToLower(absl::StripAsciiWhitespace(format));
if (format_normalized == "tsplib") return RoutingOutputFormat::kTSPLIB;
if (format_normalized == "cvrplib") return RoutingOutputFormat::kCVRPLIB;
if (format_normalized == "carplib") return RoutingOutputFormat::kCARPLIB;
if (format_normalized == "nearplib") return RoutingOutputFormat::kNEARPLIB;
return RoutingOutputFormat::kNone;
}
// Helper for FromSplitRoutes.
namespace {
std::vector<RoutingSolution::Route> RoutesFromVector(
const std::vector<std::vector<int64_t>>& routes,
std::optional<int64_t> depot = std::nullopt);
} // namespace
std::vector<std::vector<int64_t>> RoutingSolution::SplitRoutes(
const std::vector<int64_t>& solution, int64_t separator) {
// The solution vector separates routes by -1: split this vector into a vector
// per route, where the other helpers can make the rest of the way to a proper
// RoutingSolution object.
std::vector<std::vector<int64_t>> routes;
int64_t current_route = 0;
for (int64_t node : solution) {
if (routes.size() == current_route) {
routes.emplace_back(std::vector<int64_t>());
}
if (node == separator) {
current_route += 1;
} else {
routes[current_route].emplace_back(node);
}
}
return routes;
}
RoutingSolution RoutingSolution::FromSplitRoutes(
const std::vector<std::vector<int64_t>>& routes,
std::optional<int64_t> depot) {
std::vector<int64_t> total_demands(routes.size(), -1);
std::vector<int64_t> total_distances(routes.size(), -1);
return {RoutesFromVector(routes, depot), total_demands, total_distances};
}
int64_t RoutingSolution::NumberOfNonemptyRoutes() const {
int64_t num_nonempty_routes = 0;
for (const Route& route : routes_) {
if (!route.empty()) num_nonempty_routes++;
}
return num_nonempty_routes;
}
void RoutingSolution::WriteToSolutionFile(RoutingOutputFormat format,
const std::string& file_name) const {
File* file;
CHECK_OK(file::Open(file_name, "w", &file, file::Defaults()))
<< "Could not open the solution file '" << file_name << "'";
CHECK_OK(file::WriteString(file, SerializeToSolutionFile(format),
file::Defaults()))
<< "Could not write the solution file '" << file_name << "'";
CHECK_OK(file->Close(file::Defaults()));
}
std::string RoutingSolution::SerializeToTSPLIBString() const {
std::string tour_out;
for (const Route& route : routes_) {
if (route.empty()) continue;
for (const Event& event : route) {
if (event.type != RoutingSolution::Event::Type::kEnd) {
absl::StrAppendFormat(&tour_out, "%d\n", event.arc.head());
}
}
absl::StrAppendFormat(&tour_out, "-1\n");
}
return tour_out;
}
std::string RoutingSolution::SerializeToTSPLIBSolutionFile() const {
// Determine the number of nodes as the maximum index of a node in the
// solution, plus one (due to TSPLIB being 1-based and C++ 0-based).
int64_t number_of_nodes = 0;
for (const Route& route : routes_) {
for (const Event& event : route) {
if (event.arc.tail() > number_of_nodes) {
number_of_nodes = event.arc.tail();
}
if (event.arc.head() > number_of_nodes) {
number_of_nodes = event.arc.head();
}
}
}
number_of_nodes += 1;
std::string tour_out;
absl::StrAppendFormat(&tour_out, "NAME : %s\n", name_);
absl::StrAppendFormat(&tour_out, "COMMENT : Length = %d; Total time = %f s\n",
total_distance_, total_time_);
absl::StrAppendFormat(&tour_out, "TYPE : TOUR\n");
absl::StrAppendFormat(&tour_out, "DIMENSION : %d\n", number_of_nodes);
absl::StrAppendFormat(&tour_out, "TOUR_SECTION\n");
absl::StrAppendFormat(&tour_out, "%s", SerializeToTSPLIBString());
absl::StrAppendFormat(&tour_out, "EOF");
return tour_out;
}
namespace {
std::string SerializeRouteToCVRPLIBString(const RoutingSolution::Route& route);
} // namespace
std::string RoutingSolution::SerializeToCVRPLIBString() const {
std::string tour_out; // The complete solution.
int route_index = 1; // Index of the route being written.
for (const Route& route : routes_) {
if (route.empty()) continue;
std::string current_route = SerializeRouteToCVRPLIBString(route);
// Output the current route only if it is not empty.
if (!current_route.empty()) {
absl::StrAppendFormat(&tour_out, "Route #%d: %s\n", route_index++,
absl::StripAsciiWhitespace(current_route));
}
}
return tour_out;
}
std::string RoutingSolution::SerializeToCVRPLIBSolutionFile() const {
std::string tour_out = SerializeToCVRPLIBString();
absl::StrAppendFormat(&tour_out, "Cost %d", total_cost_);
return tour_out;
}
std::string RoutingSolution::SerializeToCARPLIBString() const {
std::string tour_out; // The complete solution.
int64_t num_out_route = 1; // Index of the route being written.
int64_t num_iteration_route = 0; // Index of the route being considered.
int64_t depot;
for (const Route& route : routes_) {
std::string current_route;
for (const RoutingSolution::Event& event : route) {
std::string type;
switch (event.type) {
case RoutingSolution::Event::Type::kStart:
ABSL_FALLTHROUGH_INTENDED;
case RoutingSolution::Event::Type::kEnd:
CHECK_EQ(event.arc.tail(), event.arc.head());
depot = event.arc.tail();
type = "D";
break;
case RoutingSolution::Event::Type::kServeArc:
case RoutingSolution::Event::Type::kServeEdge:
ABSL_FALLTHROUGH_INTENDED;
case RoutingSolution::Event::Type::kServeNode:
// The only difference is in the arc: when serving a node, both the
// head and the tail are the node being served.
type = "S";
break;
case RoutingSolution::Event::Type::kTransit:
// Not present in CARPLIB output.
break;
}
if (!type.empty()) {
absl::StrAppendFormat(&current_route, "(%s %d,%d,%d) ", type,
event.demand_id, event.arc.tail() + 1,
event.arc.head() + 1);
}
}
// Output the current route only if it is not empty.
if (!route.empty()) {
const int64_t day = 1;
const int64_t num_events = std::count_if(
route.begin(), route.end(), [](const RoutingSolution::Event& event) {
// Bare transitions are not output in CARPLIB, don't count them.
return event.type != RoutingSolution::Event::Type::kTransit;
});
absl::StrAppendFormat(
&tour_out, "%d %d %d %d %d %d %s\n",
depot, // Use a 0-based encoding for the depot here.
day, num_out_route, total_demands_[num_iteration_route],
total_distances_[num_iteration_route], num_events,
absl::StripAsciiWhitespace(current_route));
num_out_route += 1;
}
num_iteration_route += 1;
}
absl::StripTrailingAsciiWhitespace(&tour_out);
return tour_out;
}
std::string RoutingSolution::SerializeToCARPLIBSolutionFile() const {
std::string solution;
absl::StrAppendFormat(&solution, "%d\n", total_cost_);
absl::StrAppendFormat(&solution, "%d\n", NumberOfNonemptyRoutes());
absl::StrAppendFormat(&solution, "%f\n", total_time_);
absl::StrAppend(&solution, SerializeToCARPLIBString());
return solution;
}
std::string RoutingSolution::SerializeToNEARPLIBString() const {
std::string tour_out; // The complete solution.
int64_t route_index = 1; // Index of the route being written.
for (const Route& route : routes_) {
std::string current_route;
int64_t current_node = -2; // Holds the last node that was output, i.e.
// where the vehicle is located at the beginning of each iteration. -1 is
// used for the depot, hence an even lower value.
// Skip empty routes.
if (route.size() <= 1) continue;
if (route.size() == 2 &&
route[0].type == RoutingSolution::Event::Type::kStart &&
route[1].type == RoutingSolution::Event::Type::kEnd)
continue;
// Print the nodes that are traversed only when they are a depot or some end
// of a serviced arc/edge, without repeating nodes when two consecutive
// serviced arcs/edges are incident to the same node in the middle.
// Hence, current_node is used to determine whether the sequence of
// arcs/edges is continued or should start over.
// Only set current_node when a sequence should be continued (e.g., not
// when only traversing an arc/edge).
for (const RoutingSolution::Event& event : route) {
switch (event.type) {
case RoutingSolution::Event::Type::kStart:
// Always start at the depot.
CHECK_EQ(event.arc.tail(), event.arc.head());
current_node = event.arc.tail();
absl::StrAppendFormat(&current_route, "%d", event.arc.tail() + 1);
break;
case RoutingSolution::Event::Type::kEnd:
// Always print the end depot.
CHECK_EQ(event.arc.tail(), event.arc.head());
if (current_node != event.arc.tail()) {
absl::StrAppendFormat(&current_route, " %d", event.arc.tail() + 1);
}
break;
case RoutingSolution::Event::Type::kServeArc:
ABSL_FALLTHROUGH_INTENDED;
case RoutingSolution::Event::Type::kServeEdge:
CHECK(!event.arc_name.empty())
<< "Arc " << event.arc.tail() << "-" << event.arc.head()
<< " does not have a name in the solution object.";
// TODO(user): print the name of the node when it is served
// (i.e. there is a kServeNode event just after). For now, it's only
// done when the node happens before.
if (current_node == event.arc.tail()) {
// Direct continuation of the path: just add a hyphen and go on.
absl::StrAppendFormat(&current_route, "-%s-%d", event.arc_name,
event.arc.head() + 1);
} else {
// Some part of the path is not explicitly output before the
// previous node and the one after this edge is served.
absl::StrAppendFormat(&current_route, " %d-%s-%d",
event.arc.tail() + 1, event.arc_name,
event.arc.head() + 1);
}
current_node = event.arc.head();
break;
case RoutingSolution::Event::Type::kServeNode:
CHECK_EQ(event.arc.tail(), event.arc.head());
absl::StrAppendFormat(&current_route, " N%d", event.arc.head() + 1);
current_node = event.arc.head();
break;
case RoutingSolution::Event::Type::kTransit:
current_node = -2;
break;
}
}
// Output the current route only if it is not empty.
if (!current_route.empty()) {
absl::StrAppendFormat(&tour_out, "Route #%d : %s\n", route_index++,
absl::StripAsciiWhitespace(current_route));
}
}
absl::StripTrailingAsciiWhitespace(&tour_out);
return tour_out;
}
std::string RoutingSolution::SerializeToNEARPLIBSolutionFile() const {
const std::string date =
absl::FormatTime("%B %d, %E4Y", absl::Now(), absl::LocalTimeZone());
std::string solution;
absl::StrAppendFormat(&solution, "Instance name: %s\n", name_);
absl::StrAppendFormat(&solution, "Authors: %s\n", authors_);
absl::StrAppendFormat(&solution, "Date: %s\n", date);
absl::StrAppendFormat(&solution, "Reference: OR-Tools\n");
absl::StrAppendFormat(&solution, "Solution\n");
absl::StrAppendFormat(&solution, "%s\n", SerializeToNEARPLIBString());
absl::StrAppendFormat(&solution, "Total cost: %d", total_cost_);
// Official solutions for CBMix use "total cost", whereas the definition of
// the output format rather uses "cost":
// https://www.sintef.no/globalassets/project/top/nearp/cbmix-results/cbmix22.txt
// https://www.sintef.no/globalassets/project/top/nearp/solutionformat.txt
return solution;
}
namespace {
RoutingSolution::Route RouteFromVector(
const std::vector<int64_t>& route_int,
std::optional<int64_t> depot = std::nullopt);
std::vector<RoutingSolution::Route> RoutesFromVector(
const std::vector<std::vector<int64_t>>& routes,
std::optional<int64_t> depot) {
std::vector<RoutingSolution::Route> solution_routes;
solution_routes.reserve(routes.size());
for (const std::vector<int64_t>& route : routes) {
// TODO(user): explore merging RouteFromVector in this function.
solution_routes.emplace_back(RouteFromVector(route, depot));
}
return solution_routes;
}
RoutingSolution::Route RouteFromVector(const std::vector<int64_t>& route_int,
std::optional<int64_t> forced_depot) {
// One route in input: from the node indices, create a Route object (not yet
// a RoutingSolution one).
RoutingSolution::Route route;
// If no depot is given, guess one.
int64_t depot =
(forced_depot.has_value()) ? forced_depot.value() : route_int[0];
route.emplace_back(
RoutingSolution::Event{/*type=*/RoutingSolution::Event::Type::kStart,
/*demand_id=*/-1, /*arc=*/Arc{depot, depot}});
for (int64_t i = 0; i < route_int.size() - 1; ++i) {
int64_t tail = route_int[i];
int64_t head = route_int[i + 1];
route.emplace_back(RoutingSolution::Event{
RoutingSolution::Event::Type::kTransit, -1, Arc{tail, head}});
}
route.emplace_back(RoutingSolution::Event{RoutingSolution::Event::Type::kEnd,
-1, Arc{depot, depot}});
return route;
}
std::string SerializeRouteToCVRPLIBString(const RoutingSolution::Route& route) {
// Before serializing the route, make some tests to check that the hypotheses
// are respected (otherwise, the output of the function is highly likely
// pure garbage).
RoutingSolution::Event first_event = route[0];
CHECK(first_event.type == RoutingSolution::Event::Type::kStart)
<< "The route does not begin with a Start event to indicate "
"the depot.";
const int64_t depot = first_event.arc.tail();
CHECK_GE(depot, 0) << "The given depot is negative: " << depot;
CHECK_LE(depot, 1) << "The given depot is greater than 1: " << depot;
// Serialize this route, ignoring the depot (already dealt with).
std::string current_route;
for (int64_t i = 1; i < route.size() - 1; ++i) {
RoutingSolution::Event event = route[i];
// Ignore the depot, as CVRPLIB doesn't output the depot in the routes
// (all routes implicitly start and end at the depot).
int64_t node = event.arc.head();
if (node > depot) {
absl::StrAppendFormat(&current_route, "%d ", node - depot);
}
}
// Last event: end at a depot. Due to the strange way CVRPLIB
// outputs nodes, the depot must be the same at the beginning and the
// end of the route.
RoutingSolution::Event last_event = route.back();
if (last_event.type == RoutingSolution::Event::Type::kEnd) {
CHECK_EQ(depot, last_event.arc.tail());
CHECK_EQ(last_event.arc.tail(), last_event.arc.head());
} else {
LOG(FATAL) << "The route does not finish with an End event to "
"indicate the depot.";
}
return current_route;
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,294 @@
// Copyright 2010-2022 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.
// Utilities to serialize VRP-like solutions in standardised formats: either
// TSPLIB or CVRPLIB.
#ifndef OR_TOOLS_ROUTING_SOLUTION_SERIALIZER_H_
#define OR_TOOLS_ROUTING_SOLUTION_SERIALIZER_H_
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "ortools/base/file.h"
#include "ortools/base/helpers.h"
#include "ortools/base/logging.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
// Indicates the format in which the output should be done. This enumeration is
// used for solutions and solver statistics.
enum class RoutingOutputFormat {
kNone,
kTSPLIB,
kCVRPLIB,
kCARPLIB,
kNEARPLIB
};
// Parses a user-provided description of the output format. Expected inputs look
// like (without quotes): "tsplib", "cvrplib", "carplib". Unrecognized strings
// are parsed as kNone.
RoutingOutputFormat RoutingOutputFormatFromString(std::string_view format);
// Describes completely a solution to a routing problem in preparation of its
// serialization as a string.
class RoutingSolution {
public:
// Describes a state transition performed by a vehicle: starting from/ending
// at a given depot, serving a given customer, etc.
// When need be, each event can have a specific demand ID (this is mostly
// useful when servicing arcs and edges). An event always stores an arc:
// this is simply the edge when servicing the edge (it should correspond to
// the direction in which the edge is traversed); when the event is about
// a node (either a depot or a demand), both ends of the arc should be the
// node the event is about.
struct Event {
// Describes the type of events that occur along a route.
enum class Type {
// The vehicle starts its route at a depot.
kStart,
// The vehicle ends its route at a depot (not necessarily the same as the
// starting one).
kEnd,
// The vehicle traverses the arc while servicing it.
kServeArc,
// The vehicle traverses the edge while servicing it.
kServeEdge,
// The vehicle serves the demand of the node.
kServeNode,
// The vehicle simply goes through an edge or an arc without servicing.
kTransit
};
Type type;
int64_t demand_id;
Arc arc;
std::string arc_name;
Event(Type type, int64_t demand_id, Arc arc)
: type(type), demand_id(demand_id), arc(arc) {}
Event(Type type, int64_t demand_id, Arc arc, std::string_view arc_name)
: type(type), demand_id(demand_id), arc(arc), arc_name(arc_name) {}
bool operator==(const Event& other) const {
return type == other.type && demand_id == other.demand_id &&
arc == other.arc && arc_name == other.arc_name;
}
bool operator!=(const Event& other) const { return !(*this == other); }
};
using Route = std::vector<Event>;
RoutingSolution(std::vector<Route> routes, std::vector<int64_t> total_demands,
std::vector<int64_t> total_distances, int64_t total_cost = -1,
int64_t total_distance = -1, double total_time = -1.0,
std::string_view name = "")
: routes_(std::move(routes)),
total_demands_(std::move(total_demands)),
total_distances_(std::move(total_distances)),
total_cost_(total_cost),
total_distance_(total_distance),
total_time_(total_time),
name_(name) {
CHECK_EQ(routes_.size(), total_demands_.size());
CHECK_EQ(routes_.size(), total_distances_.size());
}
bool operator==(const RoutingSolution& other) const {
return routes_ == other.routes_ && total_demands_ == other.total_demands_ &&
total_distances_ == other.total_distances_ &&
total_cost_ == other.total_cost_ && total_time_ == other.total_time_;
}
bool operator!=(const RoutingSolution& other) const {
return !(*this == other);
}
// Setters for solution metadata.
void SetTotalTime(double total_time) { total_time_ = total_time; }
void SetTotalCost(int64_t total_cost) { total_cost_ = total_cost; }
void SetTotalDistance(int64_t total_distance) {
total_distance_ = total_distance;
}
void SetName(std::string_view name) { name_ = name; }
void SetAuthors(std::string_view authors) { authors_ = authors; }
// Public-facing builders.
// Splits a list of nodes whose routes are separated by the given separator
// (TSPLIB uses -1; it is crucial that the separator cannot be a node) into
// a vector per route, for use in FromSplit* functions.
static std::vector<std::vector<int64_t>> SplitRoutes(
const std::vector<int64_t>& solution, int64_t separator);
// Builds a RoutingSolution object from a vector of routes, each represented
// as a vector of nodes being traversed. All the routes are supposed to start
// and end at the depot if specified.
static RoutingSolution FromSplitRoutes(
const std::vector<std::vector<int64_t>>& routes,
std::optional<int64_t> depot = std::nullopt);
// Serializes the bare solution to a string, i.e. only the routes for the
// vehicles, without other metadata that is typically present in solution
// files.
std::string SerializeToString(RoutingOutputFormat format) const {
switch (format) {
case RoutingOutputFormat::kNone:
return "";
case RoutingOutputFormat::kTSPLIB:
return SerializeToTSPLIBString();
case RoutingOutputFormat::kCVRPLIB:
return SerializeToCVRPLIBString();
case RoutingOutputFormat::kCARPLIB:
return SerializeToCARPLIBString();
case RoutingOutputFormat::kNEARPLIB:
return SerializeToNEARPLIBString();
}
}
// Serializes the full solution to the given file, including metadata like
// instance name or total cost, depending on the format.
// For TSPLIB, solution files are typically called "tours".
std::string SerializeToSolutionFile(RoutingOutputFormat format) const {
switch (format) {
case RoutingOutputFormat::kNone:
return "";
case RoutingOutputFormat::kTSPLIB:
return SerializeToTSPLIBSolutionFile();
case RoutingOutputFormat::kCVRPLIB:
return SerializeToCVRPLIBSolutionFile();
case RoutingOutputFormat::kCARPLIB:
return SerializeToCARPLIBSolutionFile();
case RoutingOutputFormat::kNEARPLIB:
return SerializeToNEARPLIBSolutionFile();
}
}
// Serializes the full solution to the given file, including metadata like
// instance name or total cost, depending on the format.
void WriteToSolutionFile(RoutingOutputFormat format,
const std::string& file_name) const;
private:
// Description of the solution. Typically, one element per route (e.g., one
// vector of visited nodes per route). These elements are supposed to be
// returned by a solver.
// Depots are not explicitly stored as a route-level attribute, but rather by
// specific transitions (starting or ending at a depot).
std::vector<std::vector<Event>> routes_;
std::vector<int64_t> total_demands_;
std::vector<int64_t> total_distances_;
// Solution metadata. These elements could be set either by the solver or by
// the caller.
int64_t total_cost_;
int64_t total_distance_;
double total_time_;
std::string name_;
std::string authors_;
int64_t NumberOfNonemptyRoutes() const;
// The various implementations of SerializeToString depending on the format.
// Generates a string representation of a solution in the TSPLIB format.
// TSPLIB explicitly outputs the depot in its tours.
// It has been defined in
// http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/ where solutions are
// referred to as "tours".
std::string SerializeToTSPLIBString() const;
// Generates a string representation of a solution in the CVRPLIB format.
// CVRPLIB doesn't explicitly output the depot in its tours.
// Format used in http://vrp.atd-lab.inf.puc-rio.br/
// Better description of the format:
// http://dimacs.rutgers.edu/programs/challenge/vrp/cvrp/
std::string SerializeToCVRPLIBString() const;
// Generates a string representation of a solution in the CARPLIB format.
// Format used in https://www.uv.es/belengue/carp.html
// Formal description of the format: https://www.uv.es/~belengue/carp/READ_ME
// Another description of the format:
// http://dimacs.rutgers.edu/programs/challenge/vrp/carp/
std::string SerializeToCARPLIBString() const;
// Generates a string representation of a solution in the NEARPLIB format.
// Format used in https://www.sintef.no/projectweb/top/nearp/
// Formal description of the format:
// https://www.sintef.no/projectweb/top/nearp/documentation/
// Example:
// https://www.sintef.no/globalassets/project/top/nearp/solutionformat.txt
std::string SerializeToNEARPLIBString() const;
// The various implementations of SerializeToSolutionFile depending on the
// format. These methods are highly similar to the previous ones.
std::string SerializeToTSPLIBSolutionFile() const;
std::string SerializeToCVRPLIBSolutionFile() const;
std::string SerializeToCARPLIBSolutionFile() const;
std::string SerializeToNEARPLIBSolutionFile() const;
};
// Formats a solution or solver statistic according to the given format.
template <typename T>
std::string FormatStatistic(const std::string& name, T value,
RoutingOutputFormat format) {
// TODO(user): think about using an enum instead of names (or even a
// full-fledged struct/class) for the various types of fields.
switch (format) {
case RoutingOutputFormat::kNone:
ABSL_FALLTHROUGH_INTENDED;
case RoutingOutputFormat::kTSPLIB:
return absl::StrCat(name, " = ", value);
case RoutingOutputFormat::kCVRPLIB:
return absl::StrCat(name, " ", value);
case RoutingOutputFormat::kCARPLIB:
// For CARPLIB, the statistics do not have names, it's up to the user to
// memorize their order.
return absl::StrCat(value);
case RoutingOutputFormat::kNEARPLIB:
return absl::StrCat(name, " : ", value);
}
}
// Specialization for doubles to show a higher precision: without this
// specialization, 591.556557 is displayed as 591.557.
template <>
inline std::string FormatStatistic(const std::string& name, double value,
RoutingOutputFormat format) {
switch (format) {
case RoutingOutputFormat::kNone:
ABSL_FALLTHROUGH_INTENDED;
case RoutingOutputFormat::kTSPLIB:
return absl::StrFormat("%s = %f", name, value);
case RoutingOutputFormat::kCVRPLIB:
return absl::StrFormat("%s %f", name, value);
case RoutingOutputFormat::kCARPLIB:
return absl::StrFormat("%f", value);
case RoutingOutputFormat::kNEARPLIB:
return absl::StrFormat("%s : %f", name, value);
}
}
// Prints a formatted solution or solver statistic according to the given
// format.
template <typename T>
void PrintStatistic(const std::string& name, T value,
RoutingOutputFormat format) {
absl::PrintF("%s\n", FormatStatistic(name, value, format));
}
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_SOLUTION_SERIALIZER_H_

View File

@@ -0,0 +1,611 @@
// Copyright 2010-2022 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/routing/solution_serializer.h"
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "ortools/base/mutable_memfile.h"
namespace operations_research {
namespace {
TEST(RoutingSolutionSerializerTest, RoutingSolutionEventComparison) {
RoutingSolution::Event t1 = {RoutingSolution::Event::Type::kStart, 0,
Arc{0, 0}};
RoutingSolution::Event t2 = {RoutingSolution::Event::Type::kStart, 0,
Arc{0, 0}}; // Same as t1.
RoutingSolution::Event t3 = {RoutingSolution::Event::Type::kEnd, 0,
Arc{0, 0}};
RoutingSolution::Event t4 = {RoutingSolution::Event::Type::kStart, 1,
Arc{0, 0}};
RoutingSolution::Event t5 = {RoutingSolution::Event::Type::kStart, 0,
Arc{1, 0}};
RoutingSolution::Event t6 = {RoutingSolution::Event::Type::kStart, 0,
Arc{0, 1}};
EXPECT_EQ(t1, t1);
EXPECT_EQ(t1, t2);
EXPECT_NE(t1, t3);
EXPECT_NE(t1, t4);
EXPECT_NE(t1, t5);
EXPECT_NE(t1, t6);
}
TEST(RoutingSolutionSerializerTest, ParseEmptyString) {
EXPECT_EQ(RoutingOutputFormatFromString(""), RoutingOutputFormat::kNone);
}
TEST(RoutingSolutionSerializerTest, ParseUnrecognizedString) {
EXPECT_EQ(RoutingOutputFormatFromString("ThisIsPureGarbage"),
RoutingOutputFormat::kNone);
}
TEST(RoutingSolutionSerializerTest, ParseNoneString) {
EXPECT_EQ(RoutingOutputFormatFromString("NONE"), RoutingOutputFormat::kNone);
}
TEST(RoutingSolutionSerializerTest, ParseTsplibString) {
EXPECT_EQ(RoutingOutputFormatFromString("tsplib"),
RoutingOutputFormat::kTSPLIB);
EXPECT_EQ(RoutingOutputFormatFromString("TSPLIB"),
RoutingOutputFormat::kTSPLIB);
}
TEST(RoutingSolutionSerializerTest, ParseCvrplibString) {
EXPECT_EQ(RoutingOutputFormatFromString("cvrplib"),
RoutingOutputFormat::kCVRPLIB);
EXPECT_EQ(RoutingOutputFormatFromString("CVRPLIB"),
RoutingOutputFormat::kCVRPLIB);
}
TEST(RoutingSolutionSerializerTest, ParseCarplibString) {
EXPECT_EQ(RoutingOutputFormatFromString("carplib"),
RoutingOutputFormat::kCARPLIB);
EXPECT_EQ(RoutingOutputFormatFromString("CARPLIB"),
RoutingOutputFormat::kCARPLIB);
}
TEST(RoutingSolutionSerializerTest, ParseNearplibString) {
EXPECT_EQ(RoutingOutputFormatFromString("nearplib"),
RoutingOutputFormat::kNEARPLIB);
EXPECT_EQ(RoutingOutputFormatFromString("NEARPLIB"),
RoutingOutputFormat::kNEARPLIB);
}
TEST(RoutingSolutionSerializerTest, FromSplitRoutesWithOneRoute) {
// Specifically test RouteFromVector in the implementation.
const std::vector<std::vector<int64_t>> routes{{0, 1, 3, 0}};
const RoutingSolution result = RoutingSolution::FromSplitRoutes(routes);
const RoutingSolution expected_output = RoutingSolution{
std::vector<RoutingSolution::Route>{RoutingSolution::Route{
RoutingSolution::Event{RoutingSolution::Event::Type::kStart, -1,
Arc{0, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{0, 1}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{1, 3}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{3, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kEnd, -1,
Arc{0, 0}},
}},
std::vector<int64_t>{-1},
std::vector<int64_t>{-1},
};
EXPECT_EQ(result, expected_output);
}
TEST(RoutingSolutionSerializerTest, FromSplitRoutesWithTwoRoutes) {
// Specifically test RoutesFromVector in the implementation.
const std::vector<std::vector<int64_t>> routes{
{0, 1, 3, 0},
{0, 2, 0},
};
const RoutingSolution result = RoutingSolution::FromSplitRoutes(routes);
const RoutingSolution expected_output = {RoutingSolution{
std::vector<RoutingSolution::Route>{
RoutingSolution::Route{
RoutingSolution::Event{RoutingSolution::Event::Type::kStart, -1,
Arc{0, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{0, 1}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{1, 3}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{3, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kEnd, -1,
Arc{0, 0}},
},
RoutingSolution::Route{
RoutingSolution::Event{RoutingSolution::Event::Type::kStart, -1,
Arc{0, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{0, 2}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{2, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kEnd, -1,
Arc{0, 0}},
},
},
std::vector<int64_t>{-1, -1},
std::vector<int64_t>{-1, -1},
}};
EXPECT_EQ(result, expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToTsplib) {
const std::vector<int64_t> solution{0, 1, 2, 3, 0, -1, 0, 4, 5, 6, 0, -1};
const std::string expected_output = "0\n1\n2\n3\n0\n-1\n0\n4\n5\n6\n0\n-1\n";
EXPECT_EQ(RoutingSolution::FromSplitRoutes(
RoutingSolution::SplitRoutes(solution, -1), 0)
.SerializeToString(RoutingOutputFormat::kTSPLIB),
expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToTsplibFile) {
std::string file_name = "/mmemfile/sol_tsplib";
RegisteredMutableMemFile registered(file_name);
const std::vector<std::vector<int64_t>> solution_vector{{0, 1, 2, 3, 0},
{0, 4, 5, 6, 0}};
const std::string expected_output =
"NAME : Test name\n"
"COMMENT : Length = -1; Total time = -1.000000 s\n"
"TYPE : TOUR\n"
"DIMENSION : 7\n"
"TOUR_SECTION\n"
"0\n1\n2\n3\n0\n-1\n0\n4\n5\n6\n0\n-1\n"
"EOF";
RoutingSolution solution =
RoutingSolution::FromSplitRoutes(solution_vector, 0);
solution.SetName("Test name");
solution.WriteToSolutionFile(RoutingOutputFormat::kTSPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplib) {
// Depot: 1.
const std::vector<int64_t> solution{1, 2, 3, 1, -1, 1, 4, 5, 6, 1, -1};
const std::string expected_output = "Route #1: 1 2\nRoute #2: 3 4 5\n";
EXPECT_EQ(RoutingSolution::FromSplitRoutes(
RoutingSolution::SplitRoutes(solution, -1), 1)
.SerializeToString(RoutingOutputFormat::kCVRPLIB),
expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplibInvalidNoStart) {
const std::vector<RoutingSolution::Route> routes = {
RoutingSolution::Route{
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{0, 1}},
RoutingSolution::Event{RoutingSolution::Event::Type::kEnd, -1,
Arc{0, 0}},
},
};
const RoutingSolution solution{routes, {4}, {4}};
std::string solution_str;
EXPECT_DEATH(
solution_str = solution.SerializeToString(RoutingOutputFormat::kCVRPLIB),
"");
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplibInvalidNoEnd) {
const std::vector<RoutingSolution::Route> routes = {
RoutingSolution::Route{
RoutingSolution::Event{RoutingSolution::Event::Type::kStart, -1,
Arc{0, 0}},
RoutingSolution::Event{RoutingSolution::Event::Type::kTransit, -1,
Arc{0, 1}},
},
};
const RoutingSolution solution{routes, {4}, {4}};
std::string solution_str;
EXPECT_DEATH(
solution_str = solution.SerializeToString(RoutingOutputFormat::kCVRPLIB),
"");
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplibDepot0Dimacs) {
// Section 7 from
// http://dimacs.rutgers.edu/files/6916/3848/0327/CVRP_Competition_Rules.pdf
const std::vector<int64_t> solution{0, 1, 4, 0, -1, 0, 3, 2, 5, 0, -1};
const std::string expected_output = "Route #1: 1 4\nRoute #2: 3 2 5\n";
EXPECT_EQ(RoutingSolution::FromSplitRoutes(
RoutingSolution::SplitRoutes(solution, -1), 0)
.SerializeToString(RoutingOutputFormat::kCVRPLIB),
expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplibDepot1Dimacs) {
// Section 7 from
// http://dimacs.rutgers.edu/files/6916/3848/0327/CVRP_Competition_Rules.pdf
const std::vector<int64_t> solution{1, 2, 5, 1, -1, 1, 4, 3, 6, 1, -1};
const std::string expected_output = "Route #1: 1 4\nRoute #2: 3 2 5\n";
EXPECT_EQ(RoutingSolution::FromSplitRoutes(
RoutingSolution::SplitRoutes(solution, -1), 1)
.SerializeToString(RoutingOutputFormat::kCVRPLIB),
expected_output);
}
TEST(RoutingSolutionSerializerTest, SolutionToCvrplibFile) {
std::string file_name = "/mmemfile/sol_cvrplib";
RegisteredMutableMemFile registered(file_name);
const std::vector<std::vector<int64_t>> solution_vector{{0, 1, 2, 3, 0},
{0, 4, 5, 6, 0}};
const std::string expected_output =
"Route #1: 1 2 3\n"
"Route #2: 4 5 6\n"
"Cost 4857";
RoutingSolution solution =
RoutingSolution::FromSplitRoutes(solution_vector, 0);
solution.SetTotalCost(4857);
solution.WriteToSolutionFile(RoutingOutputFormat::kCVRPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
RoutingSolution MakeTestArcRoutingInstance() {
using Event = RoutingSolution::Event;
using Type = Event::Type;
return {std::vector<RoutingSolution::Route>{
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeArc, 12, Arc{4, 10}, "A1"},
Event{Type::kServeArc, 21, Arc{10, 8}, "A2"},
Event{Type::kServeArc, 8, Arc{8, 1}, "A3"},
Event{Type::kServeArc, 7, Arc{1, 3}, "A4"},
Event{Type::kServeArc, 2, Arc{3, 0}, "A5"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeArc, 5, Arc{0, 11}, "A6"},
Event{Type::kServeArc, 14, Arc{5, 6}, "A7"},
Event{Type::kServeArc, 19, Arc{7, 10}, "A8"},
Event{Type::kServeArc, 22, Arc{10, 9}, "A9"},
Event{Type::kServeArc, 4, Arc{9, 0}, "A10"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeArc, 13, Arc{11, 4}, "A11"},
Event{Type::kServeArc, 9, Arc{2, 3}, "A12"},
Event{Type::kServeArc, 6, Arc{1, 2}, "A13"},
Event{Type::kServeArc, 10, Arc{2, 4}, "A14"},
Event{Type::kServeArc, 11, Arc{4, 5}, "A15"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeArc, 15, Arc{11, 5}, "A16"},
Event{Type::kServeArc, 16, Arc{6, 7}, "A17"},
Event{Type::kServeArc, 18, Arc{7, 9}, "A18"},
Event{Type::kServeArc, 20, Arc{9, 8}, "A19"},
Event{Type::kServeArc, 1, Arc{1, 0}, "A20"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeArc, 17, Arc{11, 6}, "A21"},
Event{Type::kServeArc, 3, Arc{6, 0}, "A22"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
},
std::vector<int64_t>{5, 5, 5, 5, 2},
std::vector<int64_t>{76, 60, 86, 53, 41},
7,
6,
30.84};
}
RoutingSolution MakeTestEdgeNodeArcRoutingInstance() {
using Event = RoutingSolution::Event;
using Type = Event::Type;
return {std::vector<RoutingSolution::Route>{
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kTransit, -1, Arc{0, 4}},
Event{Type::kServeEdge, 12, Arc{4, 10}, "E1"},
Event{Type::kServeArc, 21, Arc{10, 8}, "A2"},
Event{Type::kServeNode, 8, Arc{8, 8}},
Event{Type::kTransit, -1, Arc{8, 1}},
Event{Type::kServeEdge, 7, Arc{1, 3}, "E3"},
Event{Type::kServeArc, 2, Arc{3, 0}, "A4"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kServeEdge, 5, Arc{0, 11}, "E5"},
Event{Type::kTransit, -1, Arc{11, 5}},
Event{Type::kServeEdge, 14, Arc{5, 6}, "E6"},
Event{Type::kTransit, -1, Arc{6, 7}},
Event{Type::kServeEdge, 19, Arc{7, 10}, "E7"},
Event{Type::kServeEdge, 22, Arc{10, 9}, "E8"},
Event{Type::kServeEdge, 4, Arc{9, 0}, "E9"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kTransit, -1, Arc{0, 11}},
Event{Type::kServeArc, 13, Arc{11, 4}, "A10"},
Event{Type::kTransit, -1, Arc{4, 2}},
Event{Type::kServeEdge, 9, Arc{2, 3}, "E11"},
Event{Type::kTransit, -1, Arc{3, 1}},
Event{Type::kServeArc, 6, Arc{1, 2}, "A12"},
Event{Type::kServeNode, 10, Arc{2, 2}},
Event{Type::kTransit, -1, Arc{2, 4}},
Event{Type::kServeEdge, 11, Arc{4, 5}, "E13"},
Event{Type::kTransit, -1, Arc{5, 0}},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kTransit, -1, Arc{0, 11}},
Event{Type::kServeNode, 15, Arc{11, 11}},
Event{Type::kServeEdge, 16, Arc{11, 7}, "E14"},
Event{Type::kServeEdge, 18, Arc{7, 9}, "E15"},
Event{Type::kServeEdge, 20, Arc{9, 8}, "E16"},
Event{Type::kTransit, -1, Arc{8, 1}},
Event{Type::kServeEdge, 1, Arc{1, 0}, "E17"},
Event{Type::kEnd, 0, Arc{0, 0}},
},
RoutingSolution::Route{
Event{Type::kStart, 0, Arc{0, 0}},
Event{Type::kTransit, -1, Arc{0, 11}},
Event{Type::kServeNode, 17, Arc{11, 11}},
Event{Type::kTransit, -1, Arc{11, 6}},
Event{Type::kServeNode, 3, Arc{6, 6}},
Event{Type::kTransit, -1, Arc{6, 0}},
Event{Type::kEnd, 0, Arc{0, 0}},
},
},
std::vector<int64_t>{5, 5, 5, 5, 2},
std::vector<int64_t>{76, 60, 86, 53, 41},
7,
6,
30.84};
}
TEST(RoutingSolutionSerializerTest, CarpSolutionToCarplib) {
// http://dimacs.rutgers.edu/programs/challenge/vrp/carp/
const std::string expected_solution_output =
"0 1 1 5 76 7 (D 0,1,1) (S 12,5,11) (S 21,11,9) (S 8,9,2) (S 7,2,4) "
"(S 2,4,1) (D 0,1,1)\n"
"0 1 2 5 60 7 (D 0,1,1) (S 5,1,12) (S 14,6,7) (S 19,8,11) (S 22,11,10) "
"(S 4,10,1) (D 0,1,1)\n"
"0 1 3 5 86 7 (D 0,1,1) (S 13,12,5) (S 9,3,4) (S 6,2,3) (S 10,3,5) "
"(S 11,5,6) (D 0,1,1)\n"
"0 1 4 5 53 7 (D 0,1,1) (S 15,12,6) (S 16,7,8) (S 18,8,10) (S 20,10,9) "
"(S 1,2,1) (D 0,1,1)\n"
"0 1 5 2 41 4 (D 0,1,1) (S 17,12,7) (S 3,7,1) (D 0,1,1)";
const RoutingSolution solution = MakeTestArcRoutingInstance();
EXPECT_EQ(solution.SerializeToString(RoutingOutputFormat::kCARPLIB),
expected_solution_output);
}
TEST(RoutingSolutionSerializerTest, CarpSolutionToCarplibFile) {
std::string file_name = "/mmemfile/sol_carp_carplib";
RegisteredMutableMemFile registered(file_name);
RoutingSolution solution = MakeTestArcRoutingInstance();
const std::string expected_output =
"7\n"
"5\n"
"30.840000\n"
"0 1 1 5 76 7 (D 0,1,1) (S 12,5,11) (S 21,11,9) (S 8,9,2) (S 7,2,4) "
"(S 2,4,1) (D 0,1,1)\n"
"0 1 2 5 60 7 (D 0,1,1) (S 5,1,12) (S 14,6,7) (S 19,8,11) (S 22,11,10) "
"(S 4,10,1) (D 0,1,1)\n"
"0 1 3 5 86 7 (D 0,1,1) (S 13,12,5) (S 9,3,4) (S 6,2,3) (S 10,3,5) "
"(S 11,5,6) (D 0,1,1)\n"
"0 1 4 5 53 7 (D 0,1,1) (S 15,12,6) (S 16,7,8) (S 18,8,10) (S 20,10,9) "
"(S 1,2,1) (D 0,1,1)\n"
"0 1 5 2 41 4 (D 0,1,1) (S 17,12,7) (S 3,7,1) (D 0,1,1)";
solution.SetName("Test name");
solution.WriteToSolutionFile(RoutingOutputFormat::kCARPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
TEST(RoutingSolutionSerializerTest, NearpSolutionToCarplib) {
const std::string expected_solution_output =
"0 1 1 5 76 7 (D 0,1,1) (S 12,5,11) (S 21,11,9) (S 8,9,9) (S 7,2,4) "
"(S 2,4,1) (D 0,1,1)\n"
"0 1 2 5 60 7 (D 0,1,1) (S 5,1,12) (S 14,6,7) (S 19,8,11) (S 22,11,10) "
"(S 4,10,1) (D 0,1,1)\n"
"0 1 3 5 86 7 (D 0,1,1) (S 13,12,5) (S 9,3,4) (S 6,2,3) (S 10,3,3) "
"(S 11,5,6) (D 0,1,1)\n"
"0 1 4 5 53 7 (D 0,1,1) (S 15,12,12) (S 16,12,8) (S 18,8,10) (S 20,10,9) "
"(S 1,2,1) (D 0,1,1)\n"
"0 1 5 2 41 4 (D 0,1,1) (S 17,12,12) (S 3,7,7) (D 0,1,1)";
const RoutingSolution solution = MakeTestEdgeNodeArcRoutingInstance();
EXPECT_EQ(solution.SerializeToString(RoutingOutputFormat::kCARPLIB),
expected_solution_output);
}
TEST(RoutingSolutionSerializerTest, NearpSolutionToCarplibFile) {
std::string file_name = "/mmemfile/sol_nearp_carplib";
RegisteredMutableMemFile registered(file_name);
RoutingSolution solution = MakeTestEdgeNodeArcRoutingInstance();
const std::string expected_output =
"7\n"
"5\n"
"30.840000\n"
"0 1 1 5 76 7 (D 0,1,1) (S 12,5,11) (S 21,11,9) (S 8,9,9) (S 7,2,4) "
"(S 2,4,1) (D 0,1,1)\n"
"0 1 2 5 60 7 (D 0,1,1) (S 5,1,12) (S 14,6,7) (S 19,8,11) (S 22,11,10) "
"(S 4,10,1) (D 0,1,1)\n"
"0 1 3 5 86 7 (D 0,1,1) (S 13,12,5) (S 9,3,4) (S 6,2,3) (S 10,3,3) "
"(S 11,5,6) (D 0,1,1)\n"
"0 1 4 5 53 7 (D 0,1,1) (S 15,12,12) (S 16,12,8) (S 18,8,10) (S 20,10,9) "
"(S 1,2,1) (D 0,1,1)\n"
"0 1 5 2 41 4 (D 0,1,1) (S 17,12,12) (S 3,7,7) (D 0,1,1)";
solution.SetName("Test name");
solution.WriteToSolutionFile(RoutingOutputFormat::kCARPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
TEST(RoutingSolutionSerializerTest, CarpSolutionToNearplib) {
const std::string expected_solution_output =
"Route #1 : 1 5-A1-11-A2-9-A3-2-A4-4-A5-1\n"
"Route #2 : 1-A6-12 6-A7-7 8-A8-11-A9-10-A10-1\n"
"Route #3 : 1 12-A11-5 3-A12-4 2-A13-3-A14-5-A15-6 1\n"
"Route #4 : 1 12-A16-6 7-A17-8-A18-10-A19-9 2-A20-1\n"
"Route #5 : 1 12-A21-7-A22-1";
const RoutingSolution solution = MakeTestArcRoutingInstance();
EXPECT_EQ(solution.SerializeToString(RoutingOutputFormat::kNEARPLIB),
expected_solution_output);
}
TEST(RoutingSolutionSerializerTest, CarpSolutionToNearplibFile) {
std::string file_name = "/mmemfile/sol_carp_nearplib";
RegisteredMutableMemFile registered(file_name);
RoutingSolution solution = MakeTestArcRoutingInstance();
const std::string date =
absl::FormatTime("%B %d, %E4Y", absl::Now(), absl::LocalTimeZone());
const std::string expected_output =
"Instance name: Test name\n"
"Authors: DIMACS CARP\n"
"Date: " +
date +
"\n"
"Reference: OR-Tools\n"
"Solution\n"
"Route #1 : 1 5-A1-11-A2-9-A3-2-A4-4-A5-1\n"
"Route #2 : 1-A6-12 6-A7-7 8-A8-11-A9-10-A10-1\n"
"Route #3 : 1 12-A11-5 3-A12-4 2-A13-3-A14-5-A15-6 1\n"
"Route #4 : 1 12-A16-6 7-A17-8-A18-10-A19-9 2-A20-1\n"
"Route #5 : 1 12-A21-7-A22-1\n"
"Total cost: 7";
solution.SetName("Test name");
solution.SetAuthors("DIMACS CARP");
solution.WriteToSolutionFile(RoutingOutputFormat::kNEARPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
TEST(RoutingSolutionSerializerTest, NearpSolutionToNearplib) {
const std::string expected_solution_output =
"Route #1 : 1 5-E1-11-A2-9 N9 2-E3-4-A4-1\n"
"Route #2 : 1-E5-12 6-E6-7 8-E7-11-E8-10-E9-1\n"
"Route #3 : 1 12-A10-5 3-E11-4 2-A12-3 N3 5-E13-6 1\n"
"Route #4 : 1 N12-E14-8-E15-10-E16-9 2-E17-1\n"
"Route #5 : 1 N12 N7 1";
// TODO(user): the following output would be ideal (because shorter). It
// would be achieved by implementing the relevant TODO in
// SerializeToNEARPLIBString.
// Route #1 : 1 5-E1-11-A2-N9 2-E3-4-A4-1
// Route #3 : 1 12-A10-5 3-E11-4 2-A12-N3 5-E13-6 1
const RoutingSolution solution = MakeTestEdgeNodeArcRoutingInstance();
EXPECT_EQ(solution.SerializeToString(RoutingOutputFormat::kNEARPLIB),
expected_solution_output);
}
TEST(RoutingSolutionSerializerTest, NearpSolutionToNearplibFile) {
std::string file_name = "/mmemfile/sol_nearp_nearplib";
RegisteredMutableMemFile registered(file_name);
RoutingSolution solution = MakeTestEdgeNodeArcRoutingInstance();
const std::string date =
absl::FormatTime("%B %d, %E4Y", absl::Now(), absl::LocalTimeZone());
const std::string expected_output =
"Instance name: Test name\n"
"Authors: Based on DIMACS CARP\n"
"Date: " +
date +
"\n"
"Reference: OR-Tools\n"
"Solution\n"
"Route #1 : 1 5-E1-11-A2-9 N9 2-E3-4-A4-1\n"
"Route #2 : 1-E5-12 6-E6-7 8-E7-11-E8-10-E9-1\n"
"Route #3 : 1 12-A10-5 3-E11-4 2-A12-3 N3 5-E13-6 1\n"
"Route #4 : 1 N12-E14-8-E15-10-E16-9 2-E17-1\n"
"Route #5 : 1 N12 N7 1\n"
"Total cost: 7";
solution.SetName("Test name");
solution.SetAuthors("Based on DIMACS CARP");
solution.WriteToSolutionFile(RoutingOutputFormat::kNEARPLIB, file_name);
std::string written_solution;
CHECK_OK(file::GetContents(file_name, &written_solution, file::Defaults()));
EXPECT_EQ(written_solution, expected_output);
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsTsplib) {
EXPECT_EQ(FormatStatistic("STAT", 4, RoutingOutputFormat::kTSPLIB),
"STAT = 4");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsCvrplib) {
EXPECT_EQ(FormatStatistic("STAT", 4, RoutingOutputFormat::kCVRPLIB),
"STAT 4");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsCarplib) {
EXPECT_EQ(FormatStatistic("STAT", 4, RoutingOutputFormat::kCARPLIB), "4");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsNearplib) {
EXPECT_EQ(FormatStatistic("STAT", 4, RoutingOutputFormat::kNEARPLIB),
"STAT : 4");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsTsplibLongPrecision) {
EXPECT_EQ(FormatStatistic("STAT", 591.556557, RoutingOutputFormat::kTSPLIB),
"STAT = 591.556557");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsCvrplibLongPrecision) {
EXPECT_EQ(FormatStatistic("STAT", 591.556557, RoutingOutputFormat::kCVRPLIB),
"STAT 591.556557");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsCarplibLongPrecision) {
EXPECT_EQ(FormatStatistic("STAT", 591.556557, RoutingOutputFormat::kCARPLIB),
"591.556557");
}
TEST(RoutingSolutionSerializerTest, FormatStatisticAsNearplibLongPrecision) {
EXPECT_EQ(FormatStatistic("STAT", 591.556557, RoutingOutputFormat::kNEARPLIB),
"STAT : 591.556557");
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,855 @@
// Copyright 2010-2022 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/routing/tsplib_parser.h"
#include <algorithm>
#include <cmath>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "ortools/base/check.h"
#include "ortools/base/map_util.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"
#include "re2/re2.h"
namespace operations_research {
namespace {
// ----- Distances -----
// As defined by the TSPLIB95 doc
static int64_t ATTDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = from.x - to.x;
const double yd = from.y - to.y;
const double euc = sqrt((xd * xd + yd * yd) / 10.0);
int64_t distance = std::round(euc);
if (distance < euc) ++distance;
return distance;
}
static double DoubleEuc2DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = from.x - to.x;
const double yd = from.y - to.y;
return sqrt(xd * xd + yd * yd);
}
static int64_t Euc2DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
return std::round(DoubleEuc2DDistance(from, to));
}
static int64_t Euc3DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = from.x - to.x;
const double yd = from.y - to.y;
const double zd = from.z - to.z;
const double euc = sqrt(xd * xd + yd * yd + zd * zd);
return std::round(euc);
}
static int64_t Ceil2DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
return static_cast<int64_t>(ceil(DoubleEuc2DDistance(from, to)));
}
static double ToRad(double x) {
static const double kPi = 3.141592;
const int64_t deg = static_cast<int64_t>(x);
const double min = x - deg;
return kPi * (deg + 5.0 * min / 3.0) / 180.0;
}
static int64_t GeoDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
static const double kRadius = 6378.388;
const double q1 = cos(ToRad(from.y) - ToRad(to.y));
const double q2 = cos(ToRad(from.x) - ToRad(to.x));
const double q3 = cos(ToRad(from.x) + ToRad(to.x));
return static_cast<int64_t>(
kRadius * acos(0.5 * ((1.0 + q1) * q2 - (1.0 - q1) * q3)) + 1.0);
}
static int64_t GeoMDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
static const double kPi = 3.14159265358979323846264;
static const double kRadius = 6378388.0;
const double from_lat = kPi * from.x / 180.0;
const double to_lat = kPi * to.x / 180.0;
const double from_lng = kPi * from.y / 180.0;
const double to_lng = kPi * to.y / 180.0;
const double q1 = cos(to_lat) * sin(from_lng - to_lng);
const double q3 = sin((from_lng - to_lng) / 2.0);
const double q4 = cos((from_lng - to_lng) / 2.0);
const double q2 =
sin(from_lat + to_lat) * q3 * q3 - sin(from_lat - to_lat) * q4 * q4;
const double q5 =
cos(from_lat - to_lat) * q4 * q4 - cos(from_lat + to_lat) * q3 * q3;
return static_cast<int64_t>(kRadius * atan2(sqrt(q1 * q1 + q2 * q2), q5) +
1.0);
}
static int64_t Man2DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = fabs(from.x - to.x);
const double yd = fabs(from.y - to.y);
return std::round(xd + yd);
}
static int64_t Man3DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = fabs(from.x - to.x);
const double yd = fabs(from.y - to.y);
const double zd = fabs(from.z - to.z);
return std::round(xd + yd + zd);
}
static int64_t Max2DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = fabs(from.x - to.x);
const double yd = fabs(from.y - to.y);
return std::round(std::max(xd, yd));
}
static int64_t Max3DDistance(const Coordinates3<double>& from,
const Coordinates3<double>& to) {
const double xd = fabs(from.x - to.x);
const double yd = fabs(from.y - to.y);
const double zd = fabs(from.z - to.z);
return std::round(std::max(xd, std::max(yd, zd)));
}
std::shared_ptr<zipfile::ZipArchive> OpenZipArchiveIfItExists(
const std::string& file_name) {
const absl::string_view archive_name = file::Dirname(file_name);
if (file::Extension(archive_name) == "zip") {
return zipfile::OpenZipArchive(archive_name);
} else {
return nullptr;
}
}
} // namespace
TspLibParser::TspLibParser()
: size_(0),
capacity_(kint64max),
max_distance_(kint64max),
distance_function_(nullptr),
explicit_costs_(),
depot_(0),
section_(UNDEFINED_SECTION),
type_(Types::UNDEFINED_TYPE),
edge_weight_type_(UNDEFINED_EDGE_WEIGHT_TYPE),
edge_weight_format_(UNDEFINED_EDGE_WEIGHT_FORMAT),
edge_row_(0),
edge_column_(0),
to_read_(0) {}
bool TspLibParser::LoadFile(const std::string& file_name) {
std::shared_ptr<zipfile::ZipArchive> zip_archive(
OpenZipArchiveIfItExists(file_name));
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
ProcessNewLine(line);
}
FinalizeEdgeWeights();
return true;
}
int TspLibParser::SizeFromFile(const std::string& file_name) const {
std::shared_ptr<zipfile::ZipArchive> zip_archive(
OpenZipArchiveIfItExists(file_name));
int size = 0;
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
if (RE2::PartialMatch(line, "DIMENSION\\s*:\\s*(\\d+)", &size)) {
break;
}
}
return size;
}
void TspLibParser::ParseExplicitFullMatrix(
const std::vector<std::string>& words) {
CHECK_LT(edge_row_, size_);
if (type_ == Types::SOP && to_read_ == size_ * size_) {
// Matrix size is present in SOP which is redundant with dimension and must
// not be confused with the first cell of the matrix.
return;
}
for (const std::string& word : words) {
SetExplicitCost(edge_row_, edge_column_, atoi64(word));
++edge_column_;
if (edge_column_ >= size_) {
edge_column_ = 0;
++edge_row_;
}
--to_read_;
}
}
void TspLibParser::ParseExplicitUpperRow(
const std::vector<std::string>& words) {
CHECK_LT(edge_row_, size_);
for (const std::string& word : words) {
SetExplicitCost(edge_row_, edge_column_, atoi64(word));
SetExplicitCost(edge_column_, edge_row_, atoi64(word));
++edge_column_;
if (edge_column_ >= size_) {
++edge_row_;
SetExplicitCost(edge_row_, edge_row_, 0);
edge_column_ = edge_row_ + 1;
}
--to_read_;
}
}
void TspLibParser::ParseExplicitLowerRow(
const std::vector<std::string>& words) {
CHECK_LT(edge_row_, size_);
for (const std::string& word : words) {
SetExplicitCost(edge_row_, edge_column_, atoi64(word));
SetExplicitCost(edge_column_, edge_row_, atoi64(word));
++edge_column_;
if (edge_column_ >= edge_row_) {
SetExplicitCost(edge_column_, edge_column_, 0);
edge_column_ = 0;
++edge_row_;
}
--to_read_;
}
}
void TspLibParser::ParseExplicitUpperDiagRow(
const std::vector<std::string>& words) {
CHECK_LT(edge_row_, size_);
for (const std::string& word : words) {
SetExplicitCost(edge_row_, edge_column_, atoi64(word));
SetExplicitCost(edge_column_, edge_row_, atoi64(word));
++edge_column_;
if (edge_column_ >= size_) {
++edge_row_;
edge_column_ = edge_row_;
}
--to_read_;
}
}
void TspLibParser::ParseExplicitLowerDiagRow(
const std::vector<std::string>& words) {
CHECK_LT(edge_row_, size_);
for (const std::string& word : words) {
SetExplicitCost(edge_row_, edge_column_, atoi64(word));
SetExplicitCost(edge_column_, edge_row_, atoi64(word));
++edge_column_;
if (edge_column_ > edge_row_) {
edge_column_ = 0;
++edge_row_;
}
--to_read_;
}
}
void TspLibParser::ParseNodeCoord(const std::vector<std::string>& words) {
CHECK_LE(3, words.size()) << words[0];
CHECK_GE(4, words.size()) << words[4];
const int node(atoi32(words[0]) - 1);
coords_[node].x = strings::ParseLeadingDoubleValue(words[1].c_str(), 0);
coords_[node].y = strings::ParseLeadingDoubleValue(words[2].c_str(), 0);
if (4 == words.size()) {
coords_[node].z = strings::ParseLeadingDoubleValue(words[3].c_str(), 0);
} else {
coords_[node].z = 0;
}
--to_read_;
}
void TspLibParser::SetUpEdgeWeightSection() {
edge_row_ = 0;
edge_column_ = 0;
switch (edge_weight_format_) {
case FULL_MATRIX:
to_read_ = size_ * size_;
break;
case LOWER_COL:
case UPPER_ROW:
SetExplicitCost(0, 0, 0);
++edge_column_;
to_read_ = ((size_ - 1) * size_) / 2;
break;
case UPPER_COL:
case LOWER_ROW:
SetExplicitCost(0, 0, 0);
++edge_row_;
to_read_ = ((size_ - 1) * size_) / 2;
break;
case LOWER_DIAG_COL:
case UPPER_DIAG_ROW:
to_read_ = ((size_ + 1) * size_) / 2;
break;
case UPPER_DIAG_COL:
case LOWER_DIAG_ROW:
to_read_ = ((size_ + 1) * size_) / 2;
break;
default:
LOG(WARNING) << "Unknown EDGE_WEIGHT_FORMAT: " << edge_weight_format_;
}
}
// Fill in the edge weight matrix.
void TspLibParser::FinalizeEdgeWeights() {
distance_function_ = nullptr;
if (type_ == Types::HCP) {
VLOG(2) << "No edge weights";
return;
}
VLOG(2) << "Edge weight type: " << edge_weight_type_;
switch (edge_weight_type_) {
case EXPLICIT:
distance_function_ = [this](int from, int to) {
return explicit_costs_[from * size_ + to];
};
break;
case EUC_2D:
distance_function_ = [this](int from, int to) {
return Euc2DDistance(coords_[from], coords_[to]);
};
break;
case EUC_3D:
distance_function_ = [this](int from, int to) {
return Euc3DDistance(coords_[from], coords_[to]);
};
break;
case MAX_2D:
distance_function_ = [this](int from, int to) {
return Max2DDistance(coords_[from], coords_[to]);
};
break;
case MAX_3D:
distance_function_ = [this](int from, int to) {
return Max3DDistance(coords_[from], coords_[to]);
};
break;
case MAN_2D:
distance_function_ = [this](int from, int to) {
return Man2DDistance(coords_[from], coords_[to]);
};
break;
case MAN_3D:
distance_function_ = [this](int from, int to) {
return Man3DDistance(coords_[from], coords_[to]);
};
break;
case CEIL_2D:
distance_function_ = [this](int from, int to) {
return Ceil2DDistance(coords_[from], coords_[to]);
};
break;
case GEO:
distance_function_ = [this](int from, int to) {
return GeoDistance(coords_[from], coords_[to]);
};
break;
case GEOM:
distance_function_ = [this](int from, int to) {
return GeoMDistance(coords_[from], coords_[to]);
};
break;
case ATT:
distance_function_ = [this](int from, int to) {
return ATTDistance(coords_[from], coords_[to]);
};
break;
case XRAY1:
LOG(WARNING) << "XRAY1 not supported for EDGE_WEIGHT_TYPE";
break;
case XRAY2:
LOG(WARNING) << "XRAY2 not supported for EDGE_WEIGHT_TYPE";
break;
case SPECIAL:
LOG(WARNING) << "SPECIAL not supported for EDGE_WEIGHT_TYPE";
break;
default:
LOG(WARNING) << "Unknown EDGE_WEIGHT_TYPE: " << edge_weight_type_;
}
}
void TspLibParser::ParseSections(const std::vector<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;
}
const std::string& last_word = words[words_size - 1];
switch (section_) {
case NAME: {
name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
break;
}
case TYPE: {
CHECK_LE(2, words.size());
const std::string& type = words[1];
if (!gtl::FindCopy(*kTypes, type, &type_)) {
LOG(WARNING) << "Unknown TYPE: " << type;
}
break;
}
case COMMENT: {
if (!comments_.empty()) {
comments_ += "\n";
}
comments_ += absl::StrJoin(words.begin() + 1, words.end(), " ");
break;
}
case DIMENSION: {
size_ = atoi32(last_word);
coords_.resize(size_);
break;
}
case DISTANCE: {
// This is non-standard but is supported as an upper bound on the length
// of each route.
max_distance_ = atoi64(last_word);
break;
}
case CAPACITY: {
capacity_ = atoi64(last_word);
break;
}
case EDGE_DATA_FORMAT: {
CHECK(Types::HCP == type_);
if (!gtl::FindCopy(*kEdgeDataFormats, last_word, &edge_data_format_)) {
LOG(WARNING) << "Unknown EDGE_DATA_FORMAT: " << last_word;
}
break;
}
case EDGE_DATA_SECTION: {
CHECK(Types::HCP == type_);
edges_.resize(size_);
to_read_ = 1;
break;
}
case EDGE_WEIGHT_TYPE: {
if (!gtl::FindCopy(*kEdgeWeightTypes, last_word, &edge_weight_type_)) {
// Some data sets invert EDGE_WEIGHT_TYPE and EDGE_WEIGHT_FORMAT.
LOG(WARNING) << "Unknown EDGE_WEIGHT_TYPE: " << last_word;
LOG(WARNING) << "Trying in EDGE_WEIGHT_FORMAT formats";
if (!gtl::FindCopy(*kEdgeWeightFormats, last_word,
&edge_weight_format_)) {
LOG(WARNING) << "Unknown EDGE_WEIGHT_FORMAT: " << last_word;
}
}
break;
}
case EDGE_WEIGHT_FORMAT: {
if (!gtl::FindCopy(*kEdgeWeightFormats, last_word,
&edge_weight_format_)) {
// Some data sets invert EDGE_WEIGHT_TYPE and EDGE_WEIGHT_FORMAT.
LOG(WARNING) << "Unknown EDGE_WEIGHT_FORMAT: " << last_word;
LOG(WARNING) << "Trying in EDGE_WEIGHT_TYPE types";
if (!gtl::FindCopy(*kEdgeWeightTypes, last_word, &edge_weight_type_)) {
LOG(WARNING) << "Unknown EDGE_WEIGHT_TYPE: " << last_word;
}
}
break;
}
case EDGE_WEIGHT_SECTION: {
SetUpEdgeWeightSection();
break;
}
case FIXED_EDGES_SECTION: {
to_read_ = kint64max;
break;
}
case NODE_COORD_TYPE: {
break;
}
case DISPLAY_DATA_TYPE: {
break;
}
case DISPLAY_DATA_SECTION: {
to_read_ = size_;
break;
}
case NODE_COORD_SECTION: {
to_read_ = size_;
break;
}
case DEPOT_SECTION: {
to_read_ = kint64max;
break;
}
case DEMAND_SECTION: {
demands_.resize(size_, 0);
to_read_ = size_;
break;
}
case END_OF_FILE: {
break;
}
default: {
LOG(WARNING) << "Unknown section: " << words[0];
}
}
}
void TspLibParser::ProcessNewLine(const std::string& line) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
if (!words.empty()) {
// New section detected.
if (kSections->contains(words[0])) {
to_read_ = 0;
}
if (to_read_ > 0) {
switch (section_) {
case EDGE_DATA_SECTION: {
CHECK(!words.empty());
switch (edge_data_format_) {
case EDGE_LIST: {
if (words[0] == "-1") {
CHECK_EQ(words.size(), 1);
// Remove duplicate edges
for (auto& edges : edges_) {
std::sort(edges.begin(), edges.end());
const auto it = std::unique(edges.begin(), edges.end());
edges.resize(std::distance(edges.begin(), it));
}
to_read_ = 0;
} else {
CHECK_EQ(words.size(), 2);
const int from = atoi64(words[0]) - 1;
const int to = atoi64(words[1]) - 1;
edges_[std::min(from, to)].push_back(std::max(from, to));
}
break;
}
case ADJ_LIST: {
const int from = atoi64(words[0]) - 1;
for (int i = 1; i < words.size(); ++i) {
const int to = atoi64(words[i]) - 1;
if (to != -2) {
edges_[std::min(from, to)].push_back(std::max(from, to));
} else {
CHECK_EQ(i, words.size() - 1);
}
}
if (atoi64(words.back()) != -1) {
LOG(WARNING) << "Missing -1 at the end of ADJ_LIST";
}
break;
}
default:
LOG(WARNING) << "Unknown EDGE_DATA_FORMAT: " << edge_data_format_;
}
break;
}
case EDGE_WEIGHT_SECTION: {
switch (edge_weight_format_) {
case FULL_MATRIX:
ParseExplicitFullMatrix(words);
break;
case UPPER_ROW:
case LOWER_COL:
ParseExplicitUpperRow(words);
break;
case LOWER_ROW:
case UPPER_COL:
ParseExplicitLowerRow(words);
break;
case UPPER_DIAG_ROW:
case LOWER_DIAG_COL:
ParseExplicitUpperDiagRow(words);
break;
case LOWER_DIAG_ROW:
case UPPER_DIAG_COL:
ParseExplicitLowerDiagRow(words);
break;
default:
LOG(WARNING) << "Unknown EDGE_WEIGHT_FORMAT: "
<< edge_weight_format_;
}
break;
}
case FIXED_EDGES_SECTION: {
if (words.size() == 1) {
CHECK_EQ(-1, atoi64(words[0]));
to_read_ = 0;
} else {
CHECK_EQ(2, words.size());
fixed_edges_.insert(
std::make_pair(atoi64(words[0]) - 1, atoi64(words[1]) - 1));
}
break;
}
case NODE_COORD_SECTION: {
ParseNodeCoord(words);
break;
}
case DEPOT_SECTION: {
if (words.size() == 1) {
const int depot(atoi64(words[0]) - 1);
if (depot >= 0) {
VLOG(2) << "Depot: " << depot;
depot_ = depot;
} else {
to_read_ = 0;
}
} else if (words.size() >= 2) {
CHECK_GE(3, words.size()) << words[3];
const int depot(size_ - 1);
VLOG(2) << "Depot: " << depot;
depot_ = depot;
coords_[depot].x =
strings::ParseLeadingDoubleValue(words[0].c_str(), 0);
coords_[depot].y =
strings::ParseLeadingDoubleValue(words[1].c_str(), 0);
if (3 == words.size()) {
coords_[depot].z =
strings::ParseLeadingDoubleValue(words[2].c_str(), 0);
} else {
coords_[depot].z = 0;
}
}
break;
}
case DEMAND_SECTION: {
const int64_t node = atoi64(words[0]) - 1;
demands_[node] = atoi64(words[1]);
--to_read_;
break;
}
case DISPLAY_DATA_SECTION: {
ParseNodeCoord(words);
break;
}
default: {
LOG(ERROR) << "Reading data outside section";
}
}
} else {
ParseSections(words);
}
}
}
std::string TspLibParser::BuildTourFromRoutes(
const std::vector<std::vector<int>>& routes) const {
std::string tours = absl::StrCat(
"NAME : ", name_, "\nCOMMENT :\nTYPE : TOUR\nDIMENSION : ", size(),
"\nTOUR_SECTION\n");
for (const auto& route : routes) {
for (const int node : route) {
absl::StrAppend(&tours, node + 1, "\n");
}
absl::StrAppend(&tours, "-1\n");
}
return absl::StrCat(tours, "EOF");
}
const absl::flat_hash_map<std::string, TspLibParser::Sections>* const
TspLibParser::kSections =
new absl::flat_hash_map<std::string, TspLibParser::Sections>(
{{"NAME", NAME},
{"TYPE", TYPE},
{"COMMENT", COMMENT},
{"DIMENSION", DIMENSION},
{"DISTANCE", DISTANCE},
{"CAPACITY", CAPACITY},
{"EDGE_DATA_FORMAT", EDGE_DATA_FORMAT},
{"EDGE_DATA_SECTION", EDGE_DATA_SECTION},
{"EDGE_WEIGHT_TYPE", EDGE_WEIGHT_TYPE},
{"EDGE_WEIGHT_FORMAT", EDGE_WEIGHT_FORMAT},
{"EDGE_WEIGHT_SECTION", EDGE_WEIGHT_SECTION},
{"FIXED_EDGES_SECTION", FIXED_EDGES_SECTION},
{"FIXED_EDGES", FIXED_EDGES_SECTION},
{"DISPLAY_DATA_SECTION", DISPLAY_DATA_SECTION},
{"NODE_COORD_TYPE", NODE_COORD_TYPE},
{"DISPLAY_DATA_TYPE", DISPLAY_DATA_TYPE},
{"NODE_COORD_SECTION", NODE_COORD_SECTION},
{"DEPOT_SECTION", DEPOT_SECTION},
{"DEMAND_SECTION", DEMAND_SECTION},
{"EOF", END_OF_FILE}});
const absl::flat_hash_map<std::string, TspLibParser::Types>* const
TspLibParser::kTypes =
new absl::flat_hash_map<std::string, TspLibParser::Types>(
{{"TSP", Types::TSP},
{"ATSP", Types::ATSP},
{"SOP", Types::SOP},
{"HCP", Types::HCP},
{"CVRP", Types::CVRP},
{"TOUR", Types::TOUR}});
const absl::flat_hash_map<std::string, TspLibParser::EdgeDataFormat>* const
TspLibParser::kEdgeDataFormats =
new absl::flat_hash_map<std::string, TspLibParser::EdgeDataFormat>(
{{"EDGE_LIST", EDGE_LIST}, {"ADJ_LIST", ADJ_LIST}});
const absl::flat_hash_map<std::string, TspLibParser::EdgeWeightTypes>* const
TspLibParser::kEdgeWeightTypes =
new absl::flat_hash_map<std::string, TspLibParser::EdgeWeightTypes>(
{{"EXPLICIT", EXPLICIT},
{"EUC_2D", EUC_2D},
{"EUC_3D", EUC_3D},
{"MAX_2D", MAX_2D},
{"MAX_3D", MAX_3D},
{"MAN_2D", MAN_2D},
{"MAN_3D", MAN_3D},
{"CEIL_2D", CEIL_2D},
{"GEO", GEO},
{"GEOM", GEOM},
{"ATT", ATT},
{"XRAY1", XRAY1},
{"XRAY2", XRAY2},
{"SPECIAL", SPECIAL}});
const absl::flat_hash_map<std::string, TspLibParser::EdgeWeightFormats>* const
TspLibParser::kEdgeWeightFormats =
new absl::flat_hash_map<std::string, TspLibParser::EdgeWeightFormats>(
{{"FUNCTION", FUNCTION},
{"FULL_MATRIX", FULL_MATRIX},
{"UPPER_ROW", UPPER_ROW},
{"LOWER_ROW", LOWER_ROW},
{"UPPER_DIAG_ROW", UPPER_DIAG_ROW},
{"LOWER_DIAG_ROW", LOWER_DIAG_ROW},
{"UPPER_COL", UPPER_COL},
{"LOWER_COL", LOWER_COL},
{"UPPER_DIAG_COL", UPPER_DIAG_COL},
{"LOWER_DIAG_COL", LOWER_DIAG_COL}});
TspLibTourParser::TspLibTourParser() : section_(UNDEFINED_SECTION), size_(0) {}
// TODO(user): Return false when issues were encountered while parsing the
// file.
bool TspLibTourParser::LoadFile(const std::string& file_name) {
section_ = UNDEFINED_SECTION;
comments_.clear();
tour_.clear();
std::shared_ptr<zipfile::ZipArchive> zip_archive(
OpenZipArchiveIfItExists(file_name));
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
ProcessNewLine(line);
}
return true;
}
void TspLibTourParser::ProcessNewLine(const std::string& line) {
const std::vector<std::string> words =
absl::StrSplit(line, ' ', absl::SkipEmpty());
const int word_size = words.size();
if (word_size > 0) {
if (section_ == TOUR_SECTION) {
for (const std::string& word : words) {
const int node = atoi32(word);
if (node >= 0) {
tour_.push_back(atoi32(word) - 1);
} else {
section_ = UNDEFINED_SECTION;
}
}
} else {
const std::string& last_word = words[word_size - 1];
if (!gtl::FindCopy(*kSections, words[0], &section_)) {
LOG(WARNING) << "Unknown section: " << words[0];
return;
}
switch (section_) {
case NAME:
break;
case TYPE:
CHECK_EQ("TOUR", last_word);
break;
case COMMENT: {
comments_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
break;
}
case DIMENSION:
size_ = atoi32(last_word);
break;
case TOUR_SECTION:
break;
case END_OF_FILE:
break;
default:
LOG(WARNING) << "Unknown key word: " << words[0];
}
}
}
}
const absl::flat_hash_map<std::string, TspLibTourParser::Sections>* const
TspLibTourParser::kSections =
new absl::flat_hash_map<std::string, TspLibTourParser::Sections>(
{{"NAME", NAME},
{"TYPE", TYPE},
{"COMMENT", COMMENT},
{"DIMENSION", DIMENSION},
{"TOUR_SECTION", TOUR_SECTION},
{"EOF", END_OF_FILE}});
CVRPToursParser::CVRPToursParser() : cost_(0) {}
// TODO(user): Return false when issues were encountered while parsing the
// file.
bool CVRPToursParser::LoadFile(const std::string& file_name) {
tours_.clear();
cost_ = 0;
std::shared_ptr<zipfile::ZipArchive> zip_archive(
OpenZipArchiveIfItExists(file_name));
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
ProcessNewLine(line);
}
return true;
}
void CVRPToursParser::ProcessNewLine(const std::string& line) {
const std::vector<std::string> words =
absl::StrSplit(line, ' ', absl::SkipEmpty());
const int word_size = words.size();
if (word_size > 0) {
if (absl::AsciiStrToUpper(words[0]) == "COST") {
CHECK_EQ(word_size, 2);
cost_ = atoi32(words[1]);
return;
}
if (absl::AsciiStrToUpper(words[0]) == "ROUTE") {
CHECK_GT(word_size, 2);
tours_.resize(tours_.size() + 1);
for (int i = 2; i < word_size; ++i) {
tours_.back().push_back(atoi32(words[i]));
}
return;
}
LOG(WARNING) << "Unknown key word: " << words[0];
}
}
} // namespace operations_research

View File

@@ -0,0 +1,253 @@
// Copyright 2010-2022 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.
// A TSPLIB parser. The TSPLIB is a library containing Traveling
// Salesman Problems and other vehicle routing problems.
// Limitations:
// - only TSP and CVRP files are currently supported.
// - XRAY1, XRAY2 and SPECIAL edge weight types are not supported.
//
// Takes as input a data file, potentially gzipped. The data must
// follow the TSPLIB95 format (described at
// http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/DOC.PS).
#ifndef OR_TOOLS_ROUTING_TSPLIB_PARSER_H_
#define OR_TOOLS_ROUTING_TSPLIB_PARSER_H_
#include <functional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "ortools/base/integral_types.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
class TspLibParser final {
public:
// Routing model types (cf. the link above for a description).
enum Types { TSP, ATSP, SOP, HCP, CVRP, TOUR, UNDEFINED_TYPE };
TspLibParser();
// Loads and parses a routing problem from a given file.
bool LoadFile(const std::string& file_name);
// Returns the number of nodes in the routing problem stored in a given file.
int SizeFromFile(const std::string& file_name) const;
// Returns a function returning edge weights between nodes.
EdgeWeights GetEdgeWeights() const { return distance_function_; }
// Returns the index of the depot.
int depot() const { return depot_; }
// Returns the number of nodes in the current routing problem.
int size() const { return size_; }
// Returns the type of the current routing problem.
Types type() const { return type_; }
// Returns the coordinates of the nodes in the current routing problem (if
// they exist).
const std::vector<Coordinates3<double>>& coordinates() const {
return coords_;
}
// Returns the capacity of the vehicles in the current routing problem.
int64_t capacity() const { return capacity_; }
// Returns the maximal distance vehicles can travel.
int64_t max_distance() const { return max_distance_; }
// Returns the demands (or quantities picked up) at each node.
const std::vector<int64_t>& demands() const { return demands_; }
// Returns the pairs of nodes corresponding to forced edges (second node is
// directly after the first).
const std::set<std::pair<int, int>> fixed_edges() const {
return fixed_edges_;
}
// Returns edges of the graph on which Hamiltonian cycles need to be built.
// Edges are represented as adjacency lists for each node.
const std::vector<std::vector<int>>& edges() const { return edges_; }
// Returns the name of the current routing model.
const std::string& name() const { return name_; }
// Returns the comments attached to the data.
const std::string& comments() const { return comments_; }
// Build a tour output in TSPLIB95 format from a vector of routes, a route
// being a sequence of node indices.
std::string BuildTourFromRoutes(
const std::vector<std::vector<int>>& routes) const;
private:
enum Sections {
NAME,
TYPE,
COMMENT,
DIMENSION,
DISTANCE,
CAPACITY,
EDGE_DATA_FORMAT,
EDGE_DATA_SECTION,
EDGE_WEIGHT_TYPE,
EDGE_WEIGHT_FORMAT,
EDGE_WEIGHT_SECTION,
FIXED_EDGES_SECTION,
NODE_COORD_TYPE,
DISPLAY_DATA_TYPE,
DISPLAY_DATA_SECTION,
NODE_COORD_SECTION,
DEPOT_SECTION,
DEMAND_SECTION,
END_OF_FILE,
UNDEFINED_SECTION
};
enum EdgeDataFormat { EDGE_LIST, ADJ_LIST };
enum EdgeWeightTypes {
EXPLICIT,
EUC_2D,
EUC_3D,
MAX_2D,
MAX_3D,
MAN_2D,
MAN_3D,
CEIL_2D,
GEO,
GEOM,
ATT,
XRAY1,
XRAY2,
SPECIAL,
UNDEFINED_EDGE_WEIGHT_TYPE
};
enum EdgeWeightFormats {
FUNCTION,
FULL_MATRIX,
UPPER_ROW,
LOWER_ROW,
UPPER_DIAG_ROW,
LOWER_DIAG_ROW,
UPPER_COL,
LOWER_COL,
UPPER_DIAG_COL,
LOWER_DIAG_COL,
UNDEFINED_EDGE_WEIGHT_FORMAT
};
#ifndef SWIG
TspLibParser(const TspLibParser&) = delete;
void operator=(const TspLibParser&) = delete;
#endif
void ParseExplicitFullMatrix(const std::vector<std::string>& words);
void ParseExplicitUpperRow(const std::vector<std::string>& words);
void ParseExplicitLowerRow(const std::vector<std::string>& words);
void ParseExplicitUpperDiagRow(const std::vector<std::string>& words);
void ParseExplicitLowerDiagRow(const std::vector<std::string>& words);
void ParseNodeCoord(const std::vector<std::string>& words);
void SetUpEdgeWeightSection();
void FinalizeEdgeWeights();
void ParseSections(const std::vector<std::string>& words);
void ProcessNewLine(const std::string& line);
void SetExplicitCost(int from, int to, int64_t cost) {
if (explicit_costs_.size() != size_ * size_) {
explicit_costs_.resize(size_ * size_, 0);
}
explicit_costs_[from * size_ + to] = cost;
}
// Model data
int64_t size_;
int64_t capacity_;
int64_t max_distance_;
std::vector<int64_t> demands_;
EdgeWeights distance_function_;
std::vector<int64_t> explicit_costs_;
std::set<std::pair<int, int>> fixed_edges_;
int depot_;
std::vector<std::vector<int>> edges_;
// Parsing data
static const absl::flat_hash_map<std::string, Sections>* const kSections;
Sections section_;
static const absl::flat_hash_map<std::string, Types>* const kTypes;
Types type_;
static const absl::flat_hash_map<std::string, EdgeDataFormat>* const
kEdgeDataFormats;
EdgeDataFormat edge_data_format_;
static const absl::flat_hash_map<std::string, EdgeWeightTypes>* const
kEdgeWeightTypes;
EdgeWeightTypes edge_weight_type_;
static const absl::flat_hash_map<std::string, EdgeWeightFormats>* const
kEdgeWeightFormats;
EdgeWeightFormats edge_weight_format_;
int edge_row_;
int edge_column_;
std::vector<Coordinates3<double>> coords_;
std::string name_;
std::string comments_;
int64_t to_read_;
};
// Class parsing tour (solution) data in TSLIB95 format.
class TspLibTourParser final {
public:
TspLibTourParser();
// Loads and parses a given tour file.
bool LoadFile(const std::string& 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.
int size() const { return size_; }
// Returns the comments attached to the data.
const std::string& comments() const { return comments_; }
private:
enum Sections {
NAME,
TYPE,
COMMENT,
DIMENSION,
TOUR_SECTION,
END_OF_FILE,
UNDEFINED_SECTION
};
#ifndef SWIG
TspLibTourParser(const TspLibTourParser&) = delete;
void operator=(const TspLibTourParser&) = delete;
#endif
void ProcessNewLine(const std::string& line);
static const absl::flat_hash_map<std::string, Sections>* const kSections;
Sections section_;
std::string comments_;
int64_t size_;
std::vector<int> tour_;
};
// Class parsing tours (solution) data in CVRPlib format.
class CVRPToursParser final {
public:
CVRPToursParser();
// Loads and parses a given tours file.
bool LoadFile(const std::string& 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_; }
private:
void ProcessNewLine(const std::string& line);
std::vector<std::vector<int>> tours_;
int64_t cost_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_TSPLIB_PARSER_H_

View File

@@ -0,0 +1,604 @@
// Copyright 2010-2022 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/routing/tsplib_parser.h"
#include <memory>
#include <string>
#include <vector>
#include "absl/container/btree_set.h"
#include "absl/flags/flag.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/helpers.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/map_util.h"
#include "ortools/base/memfile.h"
#include "ortools/base/path.h"
#include "ortools/base/zipfile.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
namespace operations_research {
namespace {
TEST(TspLibParserTest, LoadAllDataSets) {
static const char* kArchives[] = {
"operations_research_data/TSPLIB95/ALL_tsp.tar.gz",
"operations_research_data/TSPLIB95/ALL_vrp.tar",
"operations_research_data/TSPLIB95/ALL_atsp.tar",
"operations_research_data/TSPLIB95/ALL_sop.tar",
"operations_research_data/TSPLIB95/ALL_hcp.tar"};
static const char* kZipArchives[] = {
"operations_research_data/TSPLIB95/ALL_tsp.zip",
"operations_research_data/TSPLIB95/ALL_vrp.zip"};
static const char* kExpectedComments[] = {
"drilling problem (Ludwig)",
"535 Airports around the globe (Padberg/Rinaldi)",
"48 capitals of the US (Padberg/Rinaldi)",
"532-city problem (Padberg/Rinaldi)",
"29 Cities in Bavaria, " // NOLINT(bugprone-suspicious-missing-comma)
"geographical distances (Groetschel,Juenger,Reinelt)",
"29 cities in Bavaria, street distances (Groetschel,Juenger,Reinelt)",
"52 locations in Berlin (Groetschel)",
"127 Biergaerten in Augsburg (Juenger/Reinelt)",
"58 cities in Brazil (Ferreira)",
"BR Deutschland in den Grenzen von 1989 (Bachem/Wottawa)",
"Bridge tournament problem (Rinaldi)",
"14-Staedte in Burma (Zaw Win)",
"130 city problem (Churritz)",
"150 city Problem (churritz)",
"Drilling problem (Reinelt)",
"Deutschland-Problem (A.Rohe)",
"Drilling problem (Reinelt)",
"Bundesrepublik Deutschland (mit Ex-DDR) (Bachem/Wottawa)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"42 cities (Dantzig)",
"Clustered random problem (Johnson)",
"101-city problem (Christofides/Eilon)",
"51-city problem (Christofides/Eilon)",
"76-city problem (Christofides/Eilon)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Die 5 neuen Laender Deutschlands (Ex-DDR) (Bachem/Wottawa)",
"26 Staedte (Fricker)",
"262-city problem (Gillet/Johnson)",
"120 cities in Germany (Groetschel)",
"America-Subproblem of 666-city TSP (Groetschel)",
"17-city problem (Groetschel)",
"Europe-Subproblem of 666-city TSP (Groetschel)",
"21-city problem (Groetschel)",
"Asia/Australia-Subproblem of 666-city TSP (Groetschel)",
"24-city problem (Groetschel)",
"Europe/Asia/Australia-Subproblem of 666-city TSP (Groetschel)",
"48-city problem (Groetschel)",
"666 cities around the world (Groetschel)",
"Africa-Subproblem of 666-city TSP (Groetschel)",
"48-city problem (Held/Karp)",
"100-city problem A (Krolak/Felts/Nelson)",
"150-city problem A (Krolak/Felts/Nelson)",
"200-city problem A (Krolak/Felts/Nelson)",
"100-city problem B (Krolak/Felts/Nelson)",
"150-city problem B (Krolak/Felts/Nelson)",
"200-city problem B (Krolak/Felts/Nelson)",
"100-city problem C (Krolak/Felts/Nelson)",
"100-city problem D (Krolak/Felts/Nelson)",
"100-city problem E (Krolak/Felts/Nelson)",
"105-city problem (Subproblem of lin318)",
"318-city problem (Lin/Kernighan)",
"Original 318-city problem (Lin/Kernighan)",
"1379 Orte in Nordrhein-Westfalen (Bachem/Wottawa)",
"Drilling problem (Reinelt)",
"561-city problem (Kleinschmidt)",
"Drilling problem (Juenger/Reinelt)",
"Drilling problem (Junger/Reinelt)",
"Drilling problem (Groetschel/Juenger/Reinelt)",
"Programmed logic array (Johnson)",
"Programmed logic array (Johnson)",
"Programmed logic array (Johnson)",
"1002-city problem (Padberg/Rinaldi)",
"107-city problem (Padberg/Rinaldi)",
"124-city problem (Padberg/Rinaldi)",
"136-city problem (Padberg/Rinaldi)",
"144-city problem (Padberg/Rinaldi)",
"152-city problem (Padberg/Rinaldi)",
"226-city problem (Padberg/Rinaldi)",
"2392-city problem (Padberg/Rinaldi)",
"264-city problem (Padberg/Rinaldi)",
"299-city problem (Padberg/Rinaldi)",
"439-city problem (Padberg/Rinaldi)",
"76-city problem (Padberg/Rinaldi)",
"Rattled grid (Pulleyblank)",
"Rattled grid (Pulleyblank)",
"Rattled grid (Pulleyblank)",
"Rattled grid (Pulleyblank)",
"100-city random TSP (Reinelt)",
"400-city random TSP (Reinelt)",
"11849-city TSP (Reinelt)",
"1304-city TSP (Reinelt)",
"1323-city TSP (Reinelt)",
"1889-city TSP (Reinelt)",
"5915-city TSP (Reinelt)",
"5934-city TSP (Reinelt)",
"",
"",
"",
"70-city problem (Smith/Thompson)",
"42 Staedte Schweiz (Fricker)",
"225-city problem (Juenger,Raecke,Tschoecke)",
"A TSP problem (Reinelt)",
"Drilling problem problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Drilling problem (Reinelt)",
"Odyssey of Ulysses (Groetschel/Padberg)",
"Odyssey of Ulysses (Groetschel/Padberg)",
"Cities with population at least 500 in the continental US.\nContributed"
" by David Applegate and Andre Rohe, based on the\ndata set"
" \"US.lat-long\" from the ftp site ftp.cs.toronto.edu.\nThe file"
" US.lat-long.Z can be found in the directory /doc/geography.",
"1084-city problem (Reinelt)",
"1784-city problem (Reinelt)",
"(Rinaldi,Yarrow/Araque)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Eilon et al.)",
"(Gillet and Johnson)",
"17 city problem (Repetto)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Fischetti)",
"Asymmetric TSP (Repetto,Pekny)",
"Stacker crane application (Ascheuer)",
"Stacker crane application (Ascheuer)",
"Stacker crane application (Ascheuer)",
"Stacker crane application (Ascheuer)",
"Asymmetric TSP (Fischetti)",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"Received by Norbert Ascheuer / Laureano Escudero",
"br17.atsp plus random precedences (Norbert Ascheuer)",
"br17.atsp plus random precedences (Norbert Ascheuer)",
"Received by Norbert Ascheuer (based on ft53.atsp)",
"Received by Norbert Ascheuer (based on ft53.atsp)",
"Received by Norbert Ascheuer (based on ft53.atsp)",
"Received by Norbert Ascheuer (based on ft53.atsp)",
"Received by Norbert Ascheuer (based on ft70.atsp)",
"Received by Norbert Ascheuer (based on ft70.atsp)",
"Received by Norbert Ascheuer (based on ft70.atsp)",
"Received by Norbert Ascheuer (based on ft70.atsp)",
"Received by Norbert Ascheuer (based on kro124p.atsp)",
"Received by Norbert Ascheuer (based on kro124p.atsp)",
"Received by Norbert Ascheuer (based on kro124p.atsp)",
"Received by Norbert Ascheuer (based on kro124p.atsp)",
"Received by Norbert Ascheuer (based on p43.atsp)",
"Received by Norbert Ascheuer (based on p43.atsp)",
"Received by Norbert Ascheuer (based on p43.atsp)",
"Received by Norbert Ascheuer (based on p43.atsp)",
"Received by N. Ascheuer / M. Juenger / G. Reinelt",
"Received by N. Ascheuer / M. Juenger / G. Reinelt",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Stacker crane application (Norbert Ascheuer)",
"Received by Norbert Ascheuer (based on ry48p.atsp)",
"Received by Norbert Ascheuer (based on ry48p.atsp)",
"Received by Norbert Ascheuer (based on ry48p.atsp)",
"Received by Norbert Ascheuer (based on ry48p.atsp)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)",
"Hamiltonian cycle problem (Erbacci)"};
// Skipping unsupported instances.
const absl::btree_set<absl::string_view> exceptions = {"xray.problems",
"tspleap.c"};
// Parsing from tar archive
int file_index = 0;
for (const char* const archive : kArchives) {
std::vector<std::string> matches;
if (file::Match(file::JoinPath("/tarfs", absl::GetFlag(FLAGS_test_srcdir),
archive, "*"),
&matches, file::Defaults())
.ok()) {
for (const std::string& match : matches) {
const absl::string_view stem = file::Stem(match);
if (!exceptions.contains(stem) && file::Extension(stem) != "tour") {
TspLibParser parser;
EXPECT_TRUE(parser.LoadFile(match));
EXPECT_EQ(kExpectedComments[file_index], parser.comments());
EXPECT_LT(0, parser.SizeFromFile(match));
file_index++;
}
}
}
}
// Parsing from zip archive.
file_index = 0;
for (const char* const archive : kZipArchives) {
std::shared_ptr<zipfile::ZipArchive> zip_archive(zipfile::OpenZipArchive(
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir), archive)));
ASSERT_NE(nullptr, zip_archive);
std::vector<std::string> matches;
ASSERT_TRUE(
file::Match(file::JoinPath("/zip", absl::GetFlag(FLAGS_test_srcdir),
archive, "*"),
&matches, file::Defaults())
.ok());
for (const std::string& match : matches) {
const absl::string_view stem = file::Stem(match);
if (!exceptions.contains(stem) && file::Extension(stem) != "tour") {
TspLibParser parser;
EXPECT_TRUE(parser.LoadFile(match));
EXPECT_EQ(kExpectedComments[file_index], parser.comments());
EXPECT_LT(0, parser.SizeFromFile(match));
file_index++;
}
}
}
}
TEST(TspLibParserTest, GeneratedDataSets) {
static const char kName[] = "GoogleTest";
static const char* const kTypes[] = {"TSP", "CVRP"};
static const char kComment[] = "This is a test";
static const int kDimension = 4;
static const int kCoordSize = 2;
static const int kCapacity = 2;
static const char* const kEdgeWeightTypes[] = {
"EXPLICIT", "EUC_2D", "EUC_3D", "MAX_2D", "MAX_3D",
"MAN_2D", "MAN_3D", "CEIL_2D", "GEO", "ATT"};
static const char* const kEdgeWeightFormats[] = {
"FULL_MATRIX", "UPPER_ROW", "LOWER_ROW",
"UPPER_DIAG_ROW", "LOWER_DIAG_ROW", "UPPER_COL",
"LOWER_COL", "UPPER_DIAG_COL", "LOWER_DIAG_COL"};
static const char* const kNodeCoordTypes[] = {"TWOD_COORDS", "THREED_COORDS",
"NO_COORDS"};
static const char* const kDisplayDataTypes[] = {"COORD_DISPLAY",
"TWOD_DISPLAY", "NO_DISPLAY"};
for (int type = 0; type < ABSL_ARRAYSIZE(kTypes); ++type) {
for (int edge_type = 0; edge_type < ABSL_ARRAYSIZE(kEdgeWeightTypes);
++edge_type) {
for (int edge_format = 0;
edge_format < ABSL_ARRAYSIZE(kEdgeWeightFormats); ++edge_format) {
for (int node_type = 0; node_type < ABSL_ARRAYSIZE(kNodeCoordTypes);
++node_type) {
if (node_type == 2 && edge_type != 0) break;
if (node_type == 1 && edge_type != 2 && edge_type != 4 &&
edge_type != 6)
break;
if (node_type == 0 && edge_type != 1 && edge_type != 3 &&
edge_type != 5 && edge_type < 7)
break;
for (int display_type = 0;
display_type < ABSL_ARRAYSIZE(kDisplayDataTypes);
++display_type) {
if (display_type == 0 && node_type == 2) break;
std::string data = absl::StrFormat("NAME: %s\n", kName);
absl::StrAppendFormat(&data, "TYPE: %s\n", kTypes[type]);
absl::StrAppendFormat(&data, "COMMENT: %s\n", kComment);
absl::StrAppendFormat(&data, "DIMENSION: %d\n", kDimension);
if (type == 1) {
absl::StrAppendFormat(&data, "CAPACITY: %d\n", kCapacity);
}
absl::StrAppendFormat(&data, "EDGE_WEIGHT_TYPE: %s\n",
kEdgeWeightTypes[edge_type]);
if (edge_type == 0) {
absl::StrAppendFormat(&data, "EDGE_WEIGHT_FORMAT: %s\n",
kEdgeWeightFormats[edge_format]);
}
absl::StrAppendFormat(&data, "NODE_COORD_TYPE: %s\n",
kNodeCoordTypes[node_type]);
absl::StrAppendFormat(&data, "DISPLAY_DATA_TYPE: %s\n",
kDisplayDataTypes[display_type]);
if (node_type != 2) {
data += "NODE_COORD_SECTION\n";
for (int i = 0; i < kDimension; ++i) {
absl::StrAppendFormat(&data, "%d %d %d", i + 1, i % kCoordSize,
i / kCoordSize);
if (node_type == 1) {
data += " 0";
}
data += "\n";
}
}
if (type == 1) {
data += "DEPOT_SECTION\n1\n-1\n";
data += "DEMAND_SECTION\n";
for (int i = 0; i < kDimension; ++i) {
absl::StrAppendFormat(&data, "%d %d\n", i + 1, 1);
}
}
if (display_type == 1) {
data += "DISPLAY_DATA_SECTION\n";
for (int i = 0; i < kDimension; ++i) {
absl::StrAppendFormat(&data, "%d %d %d\n", i + 1,
i % kCoordSize, i / kCoordSize);
}
}
if (edge_type == 0) {
data += "EDGE_WEIGHT_SECTION\n";
// Manhattan distances
switch (edge_format) {
case 0:
for (int i = 0; i < kDimension; ++i) {
const int x = i % kCoordSize;
const int y = i / kCoordSize;
for (int j = 0; j < kDimension; ++j) {
const int distance =
abs(x - (j % kCoordSize)) + abs(y - (j / kCoordSize));
absl::StrAppendFormat(&data, "%d ", distance);
}
data += "\n";
}
break;
case 1:
case 6:
for (int i = 0; i < kDimension; ++i) {
const int x = i % kCoordSize;
const int y = i / kCoordSize;
for (int j = i + 1; j < kDimension; ++j) {
const int distance =
abs(x - (j % kCoordSize)) + abs(y - (j / kCoordSize));
absl::StrAppendFormat(&data, "%d ", distance);
}
data += "\n";
}
break;
case 2:
case 5:
for (int i = 0; i < kDimension; ++i) {
const int x = i % kCoordSize;
const int y = i / kCoordSize;
for (int j = 0; j < i; ++j) {
const int distance =
abs(x - (j % kCoordSize)) + abs(y - (j / kCoordSize));
absl::StrAppendFormat(&data, "%d ", distance);
}
data += "\n";
}
break;
case 3:
case 8:
for (int i = 0; i < kDimension; ++i) {
const int x = i % kCoordSize;
const int y = i / kCoordSize;
for (int j = i; j < kDimension; ++j) {
const int distance =
abs(x - (j % kCoordSize)) + abs(y - (j / kCoordSize));
absl::StrAppendFormat(&data, "%d ", distance);
}
data += "\n";
}
break;
case 4:
case 7:
for (int i = 0; i < kDimension; ++i) {
const int x = i % kCoordSize;
const int y = i / kCoordSize;
for (int j = 0; j <= i; ++j) {
const int distance =
abs(x - (j % kCoordSize)) + abs(y - (j / kCoordSize));
absl::StrAppendFormat(&data, "%d ", distance);
}
data += "\n";
}
break;
}
}
data += "EOF";
static const char kMMFileName[] = "/memfile/dummy.tsp";
RegisteredMemFile registered(kMMFileName, data);
TspLibParser parser;
EXPECT_TRUE(parser.LoadFile(kMMFileName));
EXPECT_EQ(kDimension, parser.SizeFromFile(kMMFileName));
}
}
}
}
}
}
TEST(TspLibParserTest, ParseHCPEdgeList) {
static const char* kData =
"NAME : test\n"
"COMMENT : Test\n"
"TYPE : HCP\n"
"DIMENSION : 3\n"
"EDGE_DATA_FORMAT : EDGE_LIST\n"
"EDGE_DATA_SECTION\n"
" 3 1\n"
" 2 1\n"
"-1\nEOF";
static const char kMMFileName[] = "/memfile/dummy.hcp";
RegisteredMemFile registered(kMMFileName, kData);
TspLibParser parser;
EXPECT_TRUE(parser.LoadFile(kMMFileName));
EXPECT_EQ(3, parser.SizeFromFile(kMMFileName));
EXPECT_EQ(2, parser.edges()[0].size());
EXPECT_EQ(1, parser.edges()[0][0]);
EXPECT_EQ(2, parser.edges()[0][1]);
EXPECT_EQ(0, parser.edges()[1].size());
EXPECT_EQ(0, parser.edges()[2].size());
}
TEST(TspLibParserTest, ParseHCPAdjList) {
static const char* kData =
"NAME : test\n"
"COMMENT : Test\n"
"TYPE : HCP\n"
"DIMENSION : 3\n"
"EDGE_DATA_FORMAT : ADJ_LIST\n"
"EDGE_DATA_SECTION\n"
" 3 1 2 -1\n"
"-1\nEOF";
static const char kMMFileName[] = "/memfile/dummy.hcp";
RegisteredMemFile registered(kMMFileName, kData);
TspLibParser parser;
EXPECT_TRUE(parser.LoadFile(kMMFileName));
EXPECT_EQ(3, parser.SizeFromFile(kMMFileName));
EXPECT_EQ(1, parser.edges()[0].size());
EXPECT_EQ(2, parser.edges()[0][0]);
EXPECT_EQ(1, parser.edges()[1].size());
EXPECT_EQ(2, parser.edges()[1][0]);
EXPECT_EQ(0, parser.edges()[2].size());
}
TEST(TspLibParserTest, ParseKytojoki33Depot) {
// This file inverts EDGE_WEIGHT_TYPE and EDGE_WEIGHT_FORMAT.
std::string file_name =
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir),
"ortools/routing/testdata/", "tsplib_Kytojoki_33.vrp");
TspLibParser parser;
EXPECT_TRUE(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());
EXPECT_EQ(0, parser.edges().size());
EXPECT_EQ(0.0, parser.coordinates()[parser.depot()].x);
EXPECT_EQ(0.0, parser.coordinates()[parser.depot()].y);
}
TEST(TspLibTourParserTest, LoadAllDataSets) {
static const char kArchive[] =
"operations_research_data/TSPLIB95/ALL_tsp.tar.gz";
static const char* kExpectedComments[] = {
"",
": Optimum solution for att48",
": Optimum solution of bayg29",
": Optimum solution of bays29",
"",
"",
": Length 6110",
": Length 6528",
": Optimum tour for eil101.tsp (Length 629)",
": Optimal tour for eil51.tsp (426)",
": Optimum tour for eil76.tsp (538)",
": optimal tour for fri26 (937)",
": Optimal tour for gr120 (6942)",
": Optimal solution for gr202 (40160)",
": Optimal solution for gr24 (1272)",
": Optimal solution for gr48 (5046)",
": Optimal solution of gr666 (294358)",
": Optimal tour for gr96 (55209)",
": Optimum tour for kroA100 (21282)",
": Optimal tour for kroC100 (20749)",
": Optimal tour for kroD100 (21294)",
": Optimal tour for lin105 (14379)",
": Optimal tour for pa561 (2763)",
": Optimal solution for pcb442 (50778)",
": optimal tour for pr1002 (259045)",
": Optimal solution for pr2392 (378032)",
": Optimal tour for pr76 (108159)",
": Optimal solution for rd100 (7910)",
": Optimal tour for st70 (675)",
": Optimal solution for tsp225 (3919)",
": Optimal solution for ulysses16 (6859)",
": Optimal solution of ulysses22 (7013)"};
int file_index = 0;
std::vector<std::string> matches;
if (file::Match(file::JoinPath("/tarfs", absl::GetFlag(FLAGS_test_srcdir),
kArchive, "*\\.opt\\.tour\\.gz"),
&matches, file::Defaults())
.ok()) {
for (const std::string& match : matches) {
TspLibTourParser parser;
EXPECT_TRUE(parser.LoadFile(match));
EXPECT_EQ(kExpectedComments[file_index], parser.comments());
file_index++;
}
}
}
TEST(CVRPToursParserTest, LoadAllDataSets) {
static const char kArchive[] =
"operations_research_data/CVRP/Augerat/A-VRP-sol.zip";
static const int kExpectedCosts[] = {/*opt-A-n32-k5*/ 784,
/*opt-A-n33-k5*/ 661,
/*opt-A-n33-k6*/ 742,
/*opt-A-n34-k5*/ 778,
/*opt-A-n36-k5*/ 799,
/*opt-A-n37-k5*/ 669,
/*opt-A-n37-k6*/ 949,
/*opt-A-n38-k5*/ 730,
/*opt-A-n39-k5*/ 822,
/*opt-A-n39-k6*/ 831,
/*opt-A-n44-k6*/ 937,
/*opt-A-n45-k6*/ 944,
/*opt-A-n45-k7*/ 1146,
/*opt-A-n46-k7*/ 914,
/*opt-A-n48-k7*/ 1073,
/*opt-A-n53-k7*/ 1010,
/*opt-A-n55-k9*/ 1073};
int file_index = 0;
std::vector<std::string> matches;
if (file::Match(file::JoinPath("/zip", absl::GetFlag(FLAGS_test_srcdir),
kArchive, "opt-A-\\.*"),
&matches, file::Defaults())
.ok()) {
for (const std::string& match : matches) {
CVRPToursParser parser;
EXPECT_TRUE(parser.LoadFile(match));
EXPECT_EQ(kExpectedCosts[file_index], parser.cost());
file_index++;
}
}
}
} // namespace
} // namespace operations_research

View File

@@ -0,0 +1,213 @@
// Copyright 2010-2022 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/routing/tsptw_parser.h"
#include <cmath>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#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"
namespace operations_research {
namespace {
double DoubleEuc2DDistance(const Coordinates2<double>& from,
const Coordinates2<double>& to) {
const double xd = from.x - to.x;
const double yd = from.y - to.y;
return sqrt(xd * xd + yd * yd);
}
double Euc2DDistance(const Coordinates2<double>& from,
const Coordinates2<double>& to) {
return std::floor(DoubleEuc2DDistance(from, to));
}
constexpr double kInfinity = std::numeric_limits<double>::infinity();
std::shared_ptr<zipfile::ZipArchive> OpenZipArchiveIfItExists(
const std::string& file_name) {
const absl::string_view archive_name = file::Dirname(file_name);
if (file::Extension(archive_name) == "zip") {
return zipfile::OpenZipArchive(archive_name);
} else {
return nullptr;
}
}
} // namespace
TspTWParser::TspTWParser()
: size_(0),
depot_(0),
total_service_time_(0),
distance_function_(nullptr),
time_function_(nullptr) {}
bool TspTWParser::LoadFile(const std::string& file_name) {
std::shared_ptr<zipfile::ZipArchive> zip_archive(
OpenZipArchiveIfItExists(file_name));
coords_.clear();
time_windows_.clear();
service_times_.clear();
distance_matrix_.clear();
size_ = 0;
depot_ = 0;
total_service_time_ = 0;
distance_function_ = nullptr;
time_function_ = nullptr;
return ParseLopezIbanezBlum(file_name) || ParseDaSilvaUrrutia(file_name);
}
bool TspTWParser::ParseLopezIbanezBlum(const std::string& file_name) {
int section = 0;
int entry_count = 0;
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
if (words.empty()) continue;
// Parsing comments.
if (words[0] == "#") {
if (absl::StrContains(line, "service times")) {
const double total_service_time =
strings::ParseLeadingDoubleValue(words.back(), kInfinity);
if (total_service_time != kInfinity) {
total_service_time_ = MathUtil::FastInt64Round(total_service_time);
}
}
continue;
}
switch (section) {
case 0: { // Parsing size.
if (words.size() != 1) return false;
size_ = strings::ParseLeadingInt32Value(words[0], -1);
if (size_ < 0) return false;
distance_matrix_.reserve(size_ * size_);
++section;
entry_count = 0;
break;
}
case 1: { // Parsing distances.
if (words.size() != size_) return false;
for (const std::string& word : words) {
const double distance =
strings::ParseLeadingDoubleValue(word, kInfinity);
if (distance == kInfinity) return false;
distance_matrix_.push_back(distance);
}
++entry_count;
if (entry_count == size_) {
++section;
entry_count = 0;
}
break;
}
case 2: { // Parsing time windows.
if (words.size() != 2) return false;
std::vector<double> values;
for (const std::string& word : words) {
const double value =
strings::ParseLeadingDoubleValue(word, kInfinity);
if (value == kInfinity) return false;
values.push_back(value);
}
time_windows_.push_back({values[0], values[1]});
service_times_.push_back(0);
++entry_count;
if (entry_count == size_) {
++section;
}
break;
}
default: {
return false;
}
}
}
distance_function_ = [this](int from, int to) {
return distance_matrix_[from * size_ + to];
};
time_function_ = distance_function_;
return entry_count == size_;
}
bool TspTWParser::ParseDaSilvaUrrutia(const std::string& file_name) {
for (const std::string& line :
FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) {
// Skip header.
if (absl::StartsWith(line, "CUST NO.")) continue;
const std::vector<std::string> words =
absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
// Skip comments and empty lines.
if (words.empty() || words[0] == "!!" || words[0][0] == '#') continue;
if (words.size() != 7) return false;
// Check that all field values are doubles, except first which must be a
// positive integer.
const int value = strings::ParseLeadingInt32Value(words[0], -1);
if (value < 0) return false;
// 999 represents the eof.
if (value == 999) continue;
std::vector<double> values;
for (int i = 1; i < words.size(); ++i) {
const double value = strings::ParseLeadingDoubleValue(
words[i], std::numeric_limits<double>::infinity());
if (value == std::numeric_limits<double>::infinity()) return false;
values.push_back(value);
}
coords_.push_back({values[0], values[1]});
time_windows_.push_back({values[3], values[4]});
service_times_.push_back(values[5]);
}
size_ = coords_.size();
// Enforce the triangular inequality (needed due to rounding).
distance_matrix_.reserve(size_ * size_);
for (int i = 0; i < size_; ++i) {
for (int j = 0; j < size_; ++j) {
distance_matrix_.push_back(Euc2DDistance(coords_[i], coords_[j]));
}
}
for (int i = 0; i < size_; i++) {
for (int j = 0; j < size_; j++) {
for (int k = 0; k < size_; k++) {
if (distance_matrix_[i * size_ + j] >
distance_matrix_[i * size_ + k] + distance_matrix_[k * size_ + j]) {
distance_matrix_[i * size_ + j] =
distance_matrix_[i * size_ + k] + distance_matrix_[k * size_ + j];
}
}
}
}
distance_function_ = [this](int from, int to) {
return distance_matrix_[from * size_ + to];
};
time_function_ = [this](int from, int to) {
return distance_matrix_[from * size_ + to] + service_times_[from];
};
return true;
}
} // namespace operations_research

View File

@@ -0,0 +1,88 @@
// Copyright 2010-2022 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.
// A TSPTW parser.
//
// Takes as input a data file, potentially gzipped. The data must
// follow the format described at
// http://lopez-ibanez.eu/tsptw-instances and
// https://homepages.dcc.ufmg.br/~rfsilva/tsptw.
#ifndef OR_TOOLS_ROUTING_TSPTW_PARSER_H_
#define OR_TOOLS_ROUTING_TSPTW_PARSER_H_
#include <functional>
#include <string>
#include <vector>
#include "ortools/base/integral_types.h"
#include "ortools/routing/simple_graph.h"
namespace operations_research {
class TspTWParser final {
public:
TspTWParser();
// Loads and parses a routing problem from a given file.
bool LoadFile(const std::string& file_name);
// Returns a function returning the distance between nodes. On some instances
// service times are already included in values returned by this function.
// The actual distance of a route can be obtained by removing
// total_service_time() from the sum of distances in that case.
const std::function<double(int, int)>& distance_function() const {
return distance_function_;
}
// Returns a function returning the time between nodes (equivalent to
// distance_function(i, j) + service_time(j)).
const std::function<double(int, int)>& time_function() const {
return time_function_;
}
// Returns the index of the depot.
int depot() const { return depot_; }
// Returns the number of nodes in the current routing problem.
int size() const { return size_; }
// Returns the total service time already included in distance_function.
double total_service_time() const { return total_service_time_; }
// Returns the coordinates of the nodes in the current routing problem.
const std::vector<Coordinates2<double>>& coordinates() const {
return coords_;
}
// Returns the time windows of the nodes in the current routing problem.
const std::vector<SimpleTimeWindow<double>>& time_windows() const {
return time_windows_;
}
// Returns the service times of the nodes in the current routing problem.
const std::vector<double>& service_times() const { return service_times_; }
private:
#ifndef SWIG
TspTWParser(const TspTWParser&) = delete;
void operator=(const TspTWParser&) = delete;
#endif
bool ParseLopezIbanezBlum(const std::string& file_name);
bool ParseDaSilvaUrrutia(const std::string& file_name);
int64_t size_;
int depot_;
double total_service_time_;
std::function<double(int, int)> distance_function_;
std::function<double(int, int)> time_function_;
std::vector<Coordinates2<double>> coords_;
std::vector<SimpleTimeWindow<double>> time_windows_;
std::vector<double> service_times_;
std::vector<double> distance_matrix_;
};
} // namespace operations_research
#endif // OR_TOOLS_ROUTING_TSPTW_PARSER_H_

View File

@@ -0,0 +1,72 @@
// Copyright 2010-2022 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/routing/tsptw_parser.h"
#include <string>
#include "absl/flags/flag.h"
#include "gtest/gtest.h"
#include "ortools/base/helpers.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/path.h"
ABSL_FLAG(std::string, test_srcdir, "", "REQUIRED: src dir");
namespace operations_research {
namespace {
TEST(TspTWParserTest, LoadDataSet) {
const int sizes[] = {26, 21, 21};
const double distances[] = {25166.316, 9538, 9006};
const double times[] = {25166.316, 9538, 9006};
const double starts[] = {9362, 2388, 2392};
const double ends[] = {13322, 3131, 3146};
const double service_times[] = {250, 0, 0};
const bool has_coordinates[] = {false, false, true};
int count = 0;
for (const std::string& data :
{"ortools/util/testdata/rc201.0", "ortools/util/testdata/n20w20.001.txt",
"ortools/util/testdata/n20w20.002.txt"}) {
TspTWParser parser;
EXPECT_TRUE(parser.LoadFile(
file::JoinPath(absl::GetFlag(FLAGS_test_srcdir), data)));
EXPECT_EQ(0, parser.depot());
const int size = sizes[count];
EXPECT_EQ(size, parser.size());
double total_distances = 0;
double total_times = 0;
for (int i = 0; i < size; ++i) {
for (int j = 0; j < size; ++j) {
total_distances += parser.distance_function()(i, j);
total_times += parser.time_function()(i, j);
}
}
EXPECT_NEAR(distances[count], total_distances, 1e-6);
EXPECT_NEAR(times[count], total_times, 1e-6);
EXPECT_EQ(service_times[count], parser.total_service_time());
EXPECT_EQ(has_coordinates[count], !parser.coordinates().empty());
double total_starts = 0;
double total_ends = 0;
for (int i = 0; i < size; ++i) {
EXPECT_EQ(0, parser.service_times()[i]);
total_starts += parser.time_windows()[i].start;
total_ends += parser.time_windows()[i].end;
}
EXPECT_EQ(starts[count], total_starts);
EXPECT_EQ(ends[count], total_ends);
++count;
}
}
} // namespace
} // namespace operations_research