Google OR-Tools v9.15
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-2025 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 <cstdint>
18#include <optional>
19#include <string>
20#include <string_view>
21#include <vector>
22
23#include "absl/algorithm/container.h"
24#include "absl/log/log.h"
25#include "absl/strings/str_join.h"
26#include "absl/strings/str_split.h"
27#include "absl/strings/string_view.h"
28#include "absl/types/span.h"
31
33
34CarpParser::CarpParser() { Initialize(); }
35
36void CarpParser::Initialize() {
37 name_.clear();
38 comment_.clear();
39 number_of_nodes_ = 0;
40 number_of_edges_with_servicing_ = 0;
41 number_of_edges_without_servicing_ = 0;
42 total_servicing_cost_ = 0;
43 depot_ = 0;
44 traversing_costs_.clear();
45 servicing_demands_.clear();
46 n_vehicles_ = 0;
47 capacity_ = 0;
48 section_ = METADATA;
49}
50
51bool CarpParser::LoadFile(absl::string_view file_name) {
52 Initialize();
53 return ParseFile(file_name);
54}
55
56bool CarpParser::ParseFile(absl::string_view file_name) {
57 static auto section_headers = std::array<const char*, 12>({
58 "NOMBRE",
59 "COMENTARIO",
60 "VERTICES",
61 "ARISTAS_REQ",
62 "ARISTAS_NOREQ",
63 "VEHICULOS",
64 "CAPACIDAD",
65 "TIPO_COSTES_ARISTAS",
66 "COSTE_TOTAL_REQ",
67 "LISTA_ARISTAS_REQ",
68 "LISTA_ARISTAS_NOREQ",
69 "DEPOSITO",
70 });
71
72 for (const std::string& line :
74 const std::vector<std::string> words =
75 absl::StrSplit(line, absl::ByAnyChar(" :\t"), absl::SkipEmpty());
76
77 if (absl::c_linear_search(section_headers, words[0])) {
78 // First, check if a new section has been met.
79 if (words[0] == "LISTA_ARISTAS_REQ") {
80 traversing_costs_.reserve(NumberOfEdges());
81 servicing_demands_.reserve(NumberOfEdgesWithServicing());
82 section_ = ARCS_WITH_SERVICING;
83 } else if (words[0] == "LISTA_ARISTAS_NOREQ") {
84 traversing_costs_.reserve(NumberOfEdges());
85 section_ = ARCS_WITHOUT_SERVICING;
86 } else {
87 if (!ParseMetadataLine(words)) {
88 LOG(ERROR) << "Error when parsing the following metadata line: "
89 << line;
90 return false;
91 }
92 }
93 } else {
94 // If no new section is detected, process according to the current state.
95 switch (section_) {
96 case ARCS_WITH_SERVICING:
97 if (!ParseEdge(line, true)) {
98 LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_REQ: " << line;
99 return false;
100 }
101 break;
102 case ARCS_WITHOUT_SERVICING:
103 if (!ParseEdge(line, false)) {
104 LOG(ERROR) << "Could not parse line in LISTA_ARISTAS_NOREQ: "
105 << line;
106 return false;
107 }
108 break;
109 default:
110 LOG(ERROR) << "Could not parse line outside edge lists: " << line;
111 return false;
112 }
113 }
114 }
115
116 return !servicing_demands_.empty();
117}
118
119namespace {
120std::optional<int64_t> ParseNodeIndex(std::string_view text);
121} // namespace
122
123bool CarpParser::ParseMetadataLine(absl::Span<const std::string> words) {
124 if (words[0] == "NOMBRE") {
125 name_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
126 } else if (words[0] == "COMENTARIO") {
127 comment_ = absl::StrJoin(words.begin() + 1, words.end(), " ");
128 } else if (words[0] == "VERTICES") {
129 number_of_nodes_ = strings::ParseLeadingInt64Value(words[1], -1);
130 if (number_of_nodes_ <= 0) {
131 LOG(ERROR) << "Error when parsing the number of nodes: " << words[1];
132 return false;
133 }
134 } else if (words[0] == "ARISTAS_REQ") {
135 number_of_edges_with_servicing_ =
137 if (number_of_edges_with_servicing_ <= 0) {
138 LOG(ERROR) << "Error when parsing the number of edges with servicing: "
139 << words[1];
140 return false;
141 }
142 } else if (words[0] == "ARISTAS_NOREQ") {
143 number_of_edges_without_servicing_ =
145 if (number_of_edges_without_servicing_ < 0) {
146 // It is possible to have a valid instance with zero edges that have no
147 // servicing (i.e. number_of_edges_without_servicing_ == 0).
148 LOG(ERROR) << "Error when parsing the number of edges without servicing: "
149 << words[1];
150 return false;
151 }
152 } else if (words[0] == "VEHICULOS") {
153 n_vehicles_ = strings::ParseLeadingInt64Value(words[1], -1);
154 if (n_vehicles_ <= 0) {
155 LOG(ERROR) << "Error when parsing the number of vehicles: " << words[1];
156 return false;
157 }
158 } else if (words[0] == "CAPACIDAD") {
159 capacity_ = strings::ParseLeadingInt64Value(words[1], -1);
160 if (capacity_ <= 0) {
161 LOG(ERROR) << "Error when parsing the capacity: " << words[1];
162 return false;
163 }
164 } else if (words[0] == "TIPO_COSTES_ARISTAS") {
165 if (words[1] != "EXPLICITOS") {
166 // Actually, this is the only defined value for this file format.
167 LOG(ERROR) << "Value of TIPO_COSTES_ARISTAS is unexpected, only "
168 "EXPLICITOS is supported, but "
169 << words[1] << " was found";
170 return false;
171 }
172 } else if (words[0] == "COSTE_TOTAL_REQ") {
173 total_servicing_cost_ = strings::ParseLeadingInt64Value(words[1], -1);
174 if (total_servicing_cost_ == -1) {
175 LOG(ERROR) << "Error when parsing the total servicing cost: " << words[1];
176 return false;
177 }
178 } else if (words[0] == "DEPOSITO") {
179 // Supposed to be the last value of the file.
180 const std::optional<int64_t> depot = ParseNodeIndex(words[1]);
181 if (!depot.has_value()) {
182 LOG(ERROR) << "Error when parsing the depot: " << words[1];
183 return false;
184 }
185 depot_ = depot.value();
186 }
187 return true;
188}
189
190bool CarpParser::ParseEdge(std::string_view line, bool with_servicing) {
191 const std::vector<std::string> words =
192 absl::StrSplit(line, absl::ByAnyChar(" :\t(),"), absl::SkipEmpty());
193
194 // Parse the edge.
195 std::optional<int64_t> opt_head = ParseNodeIndex(words[0]);
196 if (!opt_head.has_value()) {
197 LOG(ERROR) << "Error when parsing the head node: " << words[0];
198 return false;
199 }
200 const int64_t head = opt_head.value();
201
202 std::optional<int64_t> opt_tail = ParseNodeIndex(words[1]);
203 if (!opt_tail.has_value()) {
204 LOG(ERROR) << "Error when parsing the tail node: " << words[1];
205 return false;
206 }
207 const int64_t tail = opt_tail.value();
208
209 if (head == tail) {
210 LOG(ERROR) << "The head and tail nodes are identical: " << line;
211 return false;
212 }
213
214 // Parse the cost.
215 if (words[2] != "coste") {
216 LOG(ERROR) << "Unexpected keyword: " << words[2];
217 return false;
218 }
219 const int64_t cost = strings::ParseLeadingInt64Value(words[3], -1);
220 traversing_costs_[{tail, head}] = cost;
221
222 // Parse the servicing if needed.
223 if (with_servicing) {
224 if (words[4] != "demanda") {
225 LOG(ERROR) << "Unexpected keyword: " << words[2];
226 return false;
227 }
228 const int64_t servicing = strings::ParseLeadingInt64Value(words[5], -1);
229 servicing_demands_[{tail, head}] = servicing;
230 }
231
232 // Ensure there are no extraneous elements.
233 const int64_t next_id = (with_servicing) ? 6 : 4;
234 if (words.size() > next_id) {
235 LOG(ERROR) << "Extraneous elements in line, starting with: "
236 << words[next_id];
237 return false;
238 }
239
240 return true;
241}
242
243namespace {
244std::optional<int64_t> ParseNodeIndex(std::string_view text) {
245 const int64_t node = strings::ParseLeadingInt64Value(text, -1);
246 if (node < 0) {
247 LOG(ERROR) << "Could not parse node index: " << text;
248 return std::nullopt;
249 }
250 return {node - 1};
251}
252} // namespace
253} // namespace operations_research::routing
bool LoadFile(absl::string_view file_name)
int64_t ParseLeadingInt64Value(const char *str, int64_t deflt)
Definition numbers.cc:162