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