Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
carp_parser.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <array>
17#include <optional>
18#include <string>
19#include <string_view>
20#include <vector>
21
22#include "absl/strings/str_join.h"
23#include "absl/strings/str_split.h"
24#include "absl/types/span.h"
27
28namespace operations_research {
29
30CarpParser::CarpParser() { Initialize(); }
31
32void CarpParser::Initialize() {
33 name_.clear();
34 comment_.clear();
35 number_of_nodes_ = 0;
36 number_of_edges_with_servicing_ = 0;
37 number_of_edges_without_servicing_ = 0;
38 total_servicing_cost_ = 0;
39 depot_ = 0;
40 traversing_costs_.clear();
41 servicing_demands_.clear();
42 n_vehicles_ = 0;
43 capacity_ = 0;
44 section_ = METADATA;
45}
46
47bool CarpParser::LoadFile(const std::string& file_name) {
48 Initialize();
49 return ParseFile(file_name);
50}
51
52bool CarpParser::ParseFile(const std::string& file_name) {
53 static auto section_headers = std::array<const char*, 12>({
54 "NOMBRE",
55 "COMENTARIO",
56 "VERTICES",
57 "ARISTAS_REQ",
58 "ARISTAS_NOREQ",
59 "VEHICULOS",
60 "CAPACIDAD",
61 "TIPO_COSTES_ARISTAS",
62 "COSTE_TOTAL_REQ",
63 "LISTA_ARISTAS_REQ",
64 "LISTA_ARISTAS_NOREQ",
65 "DEPOSITO",
66 });
67
68 for (const std::string& line :
70 const std::vector<std::string> words =
71 absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
72
73 if (absl::c_linear_search(section_headers, words[0])) {
74 // First, check if a new section has been met.
75 if (words[0] == "LISTA_ARISTAS_REQ") {
76 traversing_costs_.reserve(NumberOfEdges());
77 servicing_demands_.reserve(NumberOfEdgesWithServicing());
78 section_ = ARCS_WITH_SERVICING;
79 } else if (words[0] == "LISTA_ARISTAS_NOREQ") {
80 traversing_costs_.reserve(NumberOfEdges());
81 section_ = ARCS_WITHOUT_SERVICING;
82 } else {
83 if (!ParseMetadataLine(words)) {
84 LOG(ERROR) << "Error when parsing the following metadata line: "
85 << line;
86 return false;
87 }
88 }
89 } else {
90 // If no new section is detected, process according to the current state.
91 switch (section_) {
92 case ARCS_WITH_SERVICING:
93 if (!ParseEdge(line, true)) {
94 LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_REQ: " << line;
95 return false;
96 }
97 break;
98 case ARCS_WITHOUT_SERVICING:
99 if (!ParseEdge(line, false)) {
100 LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_NOREQ: "
101 << line;
102 return false;
103 }
104 break;
105 default:
106 LOG(ERROR) << "Could not parse line outside edge lists: " << line;
107 return false;
108 }
109 }
110 }
111
112 return !servicing_demands_.empty();
113}
114
115namespace {
116std::optional<int64_t> ParseNodeIndex(std::string_view text);
117} // namespace
118
119bool CarpParser::ParseMetadataLine(absl::Span<const std::string> words) {
120 if (words[0] == "NOMBRE") {
121 name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
122 } else if (words[0] == "COMENTARIO") {
123 comment_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
124 } else if (words[0] == "VERTICES") {
125 number_of_nodes_ = strings::ParseLeadingInt64Value(words[1], -1);
126 if (number_of_nodes_ <= 0) {
127 LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
128 return false;
129 }
130 } else if (words[0] == "ARISTAS_REQ") {
131 number_of_edges_with_servicing_ =
133 if (number_of_edges_with_servicing_ <= 0) {
134 LOG(ERROR) << "Error when parsing the number of edges with servicing: "
135 << words[1];
136 return false;
137 }
138 } else if (words[0] == "ARISTAS_NOREQ") {
139 number_of_edges_without_servicing_ =
141 if (number_of_edges_without_servicing_ < 0) {
142 // It is possible to have a valid instance with zero edges that have no
143 // servicing (i.e. number_of_edges_without_servicing_ == 0).
144 LOG(ERROR) << "Error when parsing the number of edges without servicing: "
145 << words[1];
146 return false;
147 }
148 } else if (words[0] == "VEHICULOS") {
149 n_vehicles_ = strings::ParseLeadingInt64Value(words[1], -1);
150 if (n_vehicles_ <= 0) {
151 LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
152 return false;
153 }
154 } else if (words[0] == "CAPACIDAD") {
155 capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
156 if (capacity_ <= 0) {
157 LOG(ERROR) << "Error when parsing the capacity: " << words[1];
158 return false;
159 }
160 } else if (words[0] == "TIPO_COSTES_ARISTAS") {
161 if (words[1] != "EXPLICITOS") {
162 // Actually, this is the only defined value for this file format.
163 LOG(ERROR) << "Value of TIPO_COSTES_ARISTAS is unexpected, only "
164 "EXPLICITOS is supported, but "
165 << words[1] << " was found";
166 return false;
167 }
168 } else if (words[0] == "COSTE_TOTAL_REQ") {
169 total_servicing_cost_ = strings::ParseLeadingInt64Value(words[1], -1);
170 if (total_servicing_cost_ == -1) {
171 LOG(ERROR) << "Error when parsing the total servicing cost: " << words[1];
172 return false;
173 }
174 } else if (words[0] == "DEPOSITO") {
175 // Supposed to be the last value of the file.
176 const std::optional<int64_t> depot = ParseNodeIndex(words[1]);
177 if (!depot.has_value()) {
178 LOG(ERROR) << "Error when parsing the depot: " << words[1];
179 return false;
180 }
181 depot_ = depot.value();
182 }
183 return true;
184}
185
186bool CarpParser::ParseEdge(std::string_view line, bool with_servicing) {
187 const std::vector<std::string> words =
188 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
189
190 // Parse the edge.
191 std::optional<int64_t> opt_head = ParseNodeIndex(words[0]);
192 if (!opt_head.has_value()) {
193 LOG(ERROR) << "Error when parsing the head node: " << words[0];
194 return false;
195 }
196 const int64_t head = opt_head.value();
197
198 std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
199 if (!opt_tail.has_value()) {
200 LOG(ERROR) << "Error when parsing the tail node: " << words[1];
201 return false;
202 }
203 const int64_t tail = opt_tail.value();
204
205 if (head == tail) {
206 LOG(ERROR) << "The head and tail nodes are identical: " << line;
207 return false;
208 }
209
210 // Parse the cost.
211 if (words[2] != "coste") {
212 LOG(ERROR) << "Unexpected keyword: " << words[2];
213 return false;
214 }
215 const int64_t cost = strings::ParseLeadingInt64Value(words[3], -1);
216 traversing_costs_[{tail, head}] = cost;
217
218 // Parse the servicing if needed.
219 if (with_servicing) {
220 if (words[4] != "demanda") {
221 LOG(ERROR) << "Unexpected keyword: " << words[2];
222 return false;
223 }
224 const int64_t servicing = strings::ParseLeadingInt64Value(words[5], -1);
225 servicing_demands_[{tail, head}] = servicing;
226 }
227
228 // Ensure there are no extraneous elements.
229 const int64_t next_id = (with_servicing) ? 6 : 4;
230 if (words.size() > next_id) {
231 LOG(ERROR) << "Extraneous elements in line, starting with: "
232 << words[next_id];
233 return false;
234 }
235
236 return true;
237}
238
239namespace {
240std::optional<int64_t> ParseNodeIndex(std::string_view text) {
241 const int64_t node = strings::ParseLeadingInt64Value(text, -1);
242 if (node < 0) {
243 LOG(ERROR) << "Could not parse node index: " << text;
244 return std::nullopt;
245 }
246 return {node - 1};
247}
248} // namespace
249} // namespace operations_research
bool LoadFile(const std::string &file_name)
Loads instance from a file into this parser object.
int64_t NumberOfEdgesWithServicing() const
Definition carp_parser.h:96
int64_t depot() const
Returns the index of the depot.
Definition carp_parser.h:86
In SWIG mode, we don't want anything besides these top-level includes.
int64_t ParseLeadingInt64Value(const char *str, int64_t deflt)
Definition numbers.cc:167
int line
int head
int tail