Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
nearp_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 <iterator>
18#include <optional>
19#include <string>
20#include <string_view>
21#include <vector>
22
23#include "absl/strings/str_join.h"
24#include "absl/strings/str_split.h"
27
28namespace operations_research {
29
30NearpParser::NearpParser() { Initialize(); }
31
32void NearpParser::Initialize() {
33 name_.clear();
34 comment_.clear();
35 num_arcs_ = 0;
36 num_edges_ = 0;
37 num_nodes_ = 0;
38 num_arcs_with_servicing_ = 0;
39 num_edges_with_servicing_ = 0;
40 num_nodes_with_servicing_ = 0;
41 depot_ = 0;
42 arc_traversing_costs_.clear();
43 edge_traversing_costs_.clear();
44 arc_servicing_demands_.clear();
45 edge_servicing_demands_.clear();
46 node_servicing_demands_.clear();
47 num_vehicles_ = 0;
48 capacity_ = 0;
49 section_ = METADATA;
50}
51
52bool NearpParser::LoadFile(const std::string& file_name) {
53 Initialize();
54 return ParseFile(file_name);
55}
56
57bool NearpParser::ParseFile(const std::string& file_name) {
58 // Only put the first word as header, as the main check is just done on this
59 // first word (no ambiguity is possible for well-formed files; a more precise
60 // check is done for metadata).
61 static auto section_headers = std::array<const char*, 14>({
62 "Name",
63 "Optimal", // "value"
64 "#Vehicles",
65 "Capacity",
66 "Depot", // "Node"
67 "#Nodes",
68 "#Edges",
69 "#Arcs",
70 "#Required", // "N", "E", or "A"
71 "ReN.",
72 "ReE.",
73 "EDGE",
74 "ReA.",
75 "ARC",
76 });
77
78 for (const std::string& line :
80 const std::vector<std::string> words =
81 absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
82
83 if (words.empty()) continue;
84
85 if (absl::c_linear_search(section_headers, words[0])) {
86 // First, check if a new section has been met.
87 if (words[0] == "ReN.") {
88 node_servicing_demands_.reserve(NumberOfNodesWithServicing());
89 section_ = NODES_WITH_SERVICING;
90 } else if (words[0] == "ReE.") {
91 edge_traversing_costs_.reserve(NumberOfEdges());
92 edge_servicing_demands_.reserve(NumberOfEdgesWithServicing());
93 section_ = EDGES_WITH_SERVICING;
94 } else if (words[0] == "EDGE") {
95 edge_traversing_costs_.reserve(NumberOfEdges());
96 section_ = EDGES_WITHOUT_SERVICING;
97 } else if (words[0] == "ReA.") {
98 arc_traversing_costs_.reserve(NumberOfArcs());
99 arc_servicing_demands_.reserve(NumberOfArcsWithServicing());
100 section_ = ARCS_WITH_SERVICING;
101 } else if (words[0] == "ARC") {
102 arc_traversing_costs_.reserve(NumberOfArcs());
103 section_ = ARCS_WITHOUT_SERVICING;
104 } else {
105 if (!ParseMetadataLine(words)) {
106 LOG(ERROR) << "Error when parsing a metadata line: " << line;
107 return false;
108 }
109 }
110 } else {
111 // If no new section is detected, process according to the current state.
112
113 // Is there still data expected? Don't process the line if every element
114 // it should contain have been read: there might be some garbage at the
115 // end (like comments without delimiter).
116 switch (section_) {
117 case NODES_WITH_SERVICING:
118 if (node_servicing_demands_.size() == NumberOfNodesWithServicing())
119 continue;
120 break;
121 case EDGES_WITH_SERVICING:
122 if (edge_servicing_demands_.size() == NumberOfEdgesWithServicing())
123 continue;
124 break;
125 case EDGES_WITHOUT_SERVICING:
126 // edge_traversing_costs_ is filled with all edges, whether they need
127 // servicing or not. The section EDGES_WITHOUT_SERVICING must be after
128 // EDGES_WITH_SERVICING: once edge_traversing_costs_ has one value
129 // per edge (coming first from EDGES_WITH_SERVICING, then from
130 // EDGES_WITHOUT_SERVICING), no more value should come.
131 if (edge_traversing_costs_.size() == NumberOfEdges()) continue;
132 break;
133 case ARCS_WITH_SERVICING:
134 if (arc_servicing_demands_.size() == NumberOfArcsWithServicing())
135 continue;
136 break;
137 case ARCS_WITHOUT_SERVICING:
138 // arc_traversing_costs_ is filled with all arcs, do they need
139 // servicing or not. The section ARCS_WITHOUT_SERVICING must be after
140 // ARCS_WITH_SERVICING: once edge_traversing_costs_ has one value
141 // per arc (coming first from ARCS_WITH_SERVICING, then from
142 // ARCS_WITHOUT_SERVICING), no more value should come.
143 if (arc_traversing_costs_.size() == NumberOfArcs()) continue;
144 break;
145 default:
146 break;
147 }
148
149 // Data is still expected: parse the current line according to the state.
150 switch (section_) {
151 case NODES_WITH_SERVICING:
152 if (!ParseNode(line)) {
153 LOG(ERROR) << "Could not parse line in required nodes: " << line;
154 return false;
155 }
156 break;
157 case EDGES_WITH_SERVICING:
158 if (!ParseEdge(line, true)) {
159 LOG(ERROR) << "Could not parse line in required edges: " << line;
160 return false;
161 }
162 break;
163 case EDGES_WITHOUT_SERVICING:
164 if (!ParseEdge(line, false)) {
165 LOG(ERROR) << "Could not parse line in edges: " << line;
166 return false;
167 }
168 break;
169 case ARCS_WITH_SERVICING:
170 if (!ParseArc(line, true)) {
171 LOG(ERROR) << "Could not parse line in required arcs: " << line;
172 return false;
173 }
174 break;
175 case ARCS_WITHOUT_SERVICING:
176 if (!ParseArc(line, false)) {
177 LOG(ERROR) << "Could not parse line in arcs: " << line;
178 return false;
179 }
180 break;
181 default:
182 LOG(ERROR) << "Could not parse line outside node-edge-arc lists: "
183 << line;
184 return false;
185 }
186 }
187 }
188
189 return section_ != METADATA;
190}
191
192namespace {
193std::optional<int64_t> ParseNodeIndex(std::string_view text);
194
195struct ArcOrEdge {
196 const int64_t tail;
197 const int64_t head;
198 const int64_t traversing_cost;
199 const int64_t servicing_demand;
200 const int64_t servicing_cost;
201};
202
203std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
204 bool with_servicing);
205} // namespace
206
207bool NearpParser::ParseMetadataLine(const std::vector<std::string>& words) {
208 if (words[0] == "Name") {
209 name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
210 } else if (words[0] == "Optimal" && words[1] == "value") {
211 comment_ = absl::StrJoin(words.begin() + 2, words.end(), " ");
212 } else if (words[0] == "#Vehicles") {
213 num_vehicles_ = strings::ParseLeadingInt32Value(words[1], -1);
214 // -1 indicates that the number of vehicles is unknown.
215 if (num_vehicles_ < -1) {
216 LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
217 return false;
218 }
219 } else if (words[0] == "Capacity") {
220 capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
221 if (capacity_ <= 0) {
222 LOG(ERROR) << "Error when parsing the capacity: " << words[1];
223 return false;
224 }
225 } else if (words[0] == "Depot" && words[1] == "Node") {
226 const std::optional<int64_t> depot = ParseNodeIndex(words[2]);
227 if (!depot.has_value()) {
228 LOG(ERROR) << "Error when parsing the depot: " << words[1];
229 return false;
230 }
231 depot_ = depot.value();
232 } else if (words[0] == "#Nodes") {
233 num_nodes_ = strings::ParseLeadingInt32Value(words[1], -1);
234 if (num_nodes_ <= 0) {
235 LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
236 return false;
237 }
238 } else if (words[0] == "#Edges") {
239 num_edges_ = strings::ParseLeadingInt32Value(words[1], -1);
240 if (num_edges_ <= 0) {
241 LOG(ERROR) << "Error when parsing the number of edges: " << words[1];
242 return false;
243 }
244 } else if (words[0] == "#Arcs") {
245 num_arcs_ = strings::ParseLeadingInt32Value(words[1], -1);
246 if (num_arcs_ <= 0) {
247 LOG(ERROR) << "Error when parsing the number of arcs: " << words[1];
248 return false;
249 }
250 } else if (words[0] == "#Required" && words[1] == "N") {
251 num_nodes_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
252 if (num_nodes_with_servicing_ <= 0) {
253 LOG(ERROR) << "Error when parsing the number of nodes with servicing: "
254 << words[1];
255 return false;
256 }
257 } else if (words[0] == "#Required" && words[1] == "E") {
258 num_edges_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
259 if (num_edges_with_servicing_ <= 0) {
260 LOG(ERROR) << "Error when parsing the number of edges with servicing: "
261 << words[1];
262 return false;
263 }
264 } else if (words[0] == "#Required" && words[1] == "A") {
265 num_arcs_with_servicing_ = strings::ParseLeadingInt32Value(words[2], -1);
266 if (num_arcs_with_servicing_ <= 0) {
267 LOG(ERROR) << "Error when parsing the number of arcs with servicing: "
268 << words[1];
269 return false;
270 }
271 } else {
272 LOG(ERROR) << "Unrecognized metadata line: " << absl::StrJoin(words, " ");
273 return false;
274 }
275 return true;
276}
277
278bool NearpParser::ParseArc(std::string_view line, bool with_servicing) {
279 std::optional<ArcOrEdge> parsed_arc = ParseArcOrEdge(line, with_servicing);
280 if (!parsed_arc.has_value()) {
281 return false;
282 }
283
284 Arc arc{parsed_arc->tail, parsed_arc->head};
285 arc_traversing_costs_[arc] = parsed_arc->traversing_cost;
286
287 if (with_servicing) {
288 arc_servicing_demands_[arc] = parsed_arc->servicing_demand;
289 arc_servicing_costs_[arc] = parsed_arc->servicing_cost;
290 }
291
292 return true;
293}
294
295bool NearpParser::ParseEdge(std::string_view line, bool with_servicing) {
296 std::optional<ArcOrEdge> parsed_edge = ParseArcOrEdge(line, with_servicing);
297 if (!parsed_edge.has_value()) {
298 return false;
299 }
300
301 Edge edge{parsed_edge->tail, parsed_edge->head};
302 edge_traversing_costs_[edge] = parsed_edge->traversing_cost;
303
304 if (with_servicing) {
305 edge_servicing_demands_[edge] = parsed_edge->servicing_demand;
306 edge_servicing_costs_[edge] = parsed_edge->servicing_cost;
307 }
308
309 return true;
310}
311
312bool NearpParser::ParseNode(std::string_view line) {
313 const std::vector<std::string> words =
314 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
315
316 // Parse the name and ID.
317 const int64_t id = strings::ParseLeadingInt64Value(words[0].substr(1), 0) - 1;
318 if (id < 0) {
319 LOG(ERROR) << "Error when parsing the node name: " << words[0];
320 return false;
321 }
322
323 // Parse the servicing details if needed.
324 const int64_t servicing_demand =
326 if (servicing_demand < 0) {
327 LOG(ERROR) << "Error when parsing the node servicing demand: " << words[1];
328 return false;
329 }
330
331 const int64_t servicing_cost = strings::ParseLeadingInt64Value(words[2], -1);
332 if (servicing_cost < 0) {
333 LOG(ERROR) << "Error when parsing the node servicing cost: " << words[1];
334 return false;
335 }
336
337 // Once the values have been parsed successfully, save them.
338 node_servicing_demands_.insert({id, servicing_demand});
339 node_servicing_costs_.insert({id, servicing_cost});
340
341 return true;
342}
343
344namespace {
345std::optional<int64_t> ParseNodeIndex(std::string_view text) {
346 const int64_t node = strings::ParseLeadingInt64Value(text, -1);
347 if (node < 0) {
348 LOG(ERROR) << "Could not parse node index: " << text;
349 return std::nullopt;
350 }
351 return {node - 1};
352}
353
354std::optional<ArcOrEdge> ParseArcOrEdge(std::string_view line,
355 bool with_servicing) {
356 const std::vector<std::string> words =
357 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
358
359 // Parse the name.
360 const std::string name = words[0];
361
362 // Parse the tail and the head of the arc/edge.
363 std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
364 if (!opt_tail.has_value()) {
365 LOG(ERROR) << "Error when parsing the tail node: " << words[1];
366 return {};
367 }
368 const int64_t tail = opt_tail.value();
369
370 std::optional<int64_t> opt_head = ParseNodeIndex(words[2]);
371 if (!opt_head.has_value()) {
372 LOG(ERROR) << "Error when parsing the head node: " << words[2];
373 return {};
374 }
375 const int64_t head = opt_head.value();
376
377 if (tail == head) {
378 LOG(ERROR) << "The head and tail nodes are identical: " << line;
379 return {};
380 }
381
382 // Parse the traversing cost.
383 const int64_t traversing_cost = strings::ParseLeadingInt64Value(words[3], -1);
384 if (traversing_cost < 0) {
385 LOG(ERROR) << "Error when parsing the traversing cost: " << words[3];
386 return {};
387 }
388
389 // Ensure there are no extraneous elements.
390 const int64_t next_id = (with_servicing) ? 6 : 4;
391 if (words.size() > next_id) {
392 LOG(ERROR) << "Extraneous elements in line, starting with: "
393 << words[next_id];
394 return {};
395 }
396
397 // Parse the servicing details if needed and return the elements.
398 if (!with_servicing) {
399 return ArcOrEdge{tail, head, traversing_cost,
400 /*servicing_demand=*/-1,
401 /*servicing_cost=*/-1};
402 } else {
403 const int64_t servicing_demand =
405 if (servicing_demand < 0) {
406 LOG(ERROR) << "Error when parsing the servicing demand: " << words[4];
407 return {};
408 }
409
410 const int64_t servicing_cost =
412 if (servicing_cost < 0) {
413 LOG(ERROR) << "Error when parsing the servicing cost: " << words[5];
414 return {};
415 }
416
417 return ArcOrEdge{tail, head, traversing_cost, servicing_demand,
419 }
420}
421} // namespace
422
423std::string NearpParser::GetArcName(Arc arc) const {
424 if (arc_servicing_costs_.contains(arc)) {
425 int64_t arc_position = std::distance(arc_servicing_demands_.begin(),
426 arc_servicing_demands_.find(arc));
427 CHECK_LT(arc_position, arc_servicing_demands_.size());
428 return absl::StrCat("A", arc_position + 1);
429 } else {
430 int64_t arc_position = std::distance(arc_traversing_costs_.begin(),
431 arc_traversing_costs_.find(arc));
432 CHECK_LT(arc_position, arc_traversing_costs_.size());
433 return absl::StrCat("NrA", arc_position - num_arcs_with_servicing_ + 1);
434 }
435}
436
438 if (edge_servicing_costs_.contains(edge)) {
439 int64_t edge_position = std::distance(edge_servicing_demands_.begin(),
440 edge_servicing_demands_.find(edge));
441 CHECK_LT(edge_position, edge_servicing_demands_.size());
442 return absl::StrCat("E", edge_position + 1);
443 } else {
444 int64_t edge_position = std::distance(edge_traversing_costs_.begin(),
445 edge_traversing_costs_.find(edge));
446 CHECK_LT(edge_position, edge_traversing_costs_.size());
447 return absl::StrCat("NrE", edge_position - num_edges_with_servicing_ + 1);
448 }
449}
450} // namespace operations_research
std::pair< iterator, bool > insert(const value_type &v)
ABSL_ATTRIBUTE_REINITIALIZES void clear()
size_type size() const
Derive size_ from set_, as list::size might be O(N).
bool LoadFile(const std::string &file_name)
Loads instance from a file into this parser object.
std::string GetEdgeName(Edge edge) const
std::string GetArcName(Arc arc) const
int64_t depot() const
Returns the index of the depot.
const std::string name
A name for logging purposes.
Edge edge
int arc
In SWIG mode, we don't want anything besides these top-level includes.
int32_t ParseLeadingInt32Value(const char *str, int32_t deflt)
Definition numbers.cc:60
int64_t ParseLeadingInt64Value(const char *str, int64_t deflt)
Definition numbers.cc:167
const int64_t traversing_cost
const int64_t servicing_demand
const int64_t servicing_cost
int line
int head
int tail