OR-Tools  9.0
rcpsp_parser.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 <cstdint>
17 
18 #include "absl/strings/match.h"
19 #include "absl/strings/numbers.h"
20 #include "absl/strings/str_split.h"
22 #include "ortools/data/rcpsp.pb.h"
23 
24 namespace operations_research {
25 namespace data {
26 namespace rcpsp {
27 
29  : seed_(-1),
30  load_status_(NOT_STARTED),
31  num_declared_tasks_(-1),
32  current_task_(-1),
33  unreads_(0) {
34  rcpsp_.set_deadline(-1);
35  rcpsp_.set_horizon(-1);
36 }
37 
38 bool RcpspParser::ParseFile(const std::string& file_name) {
39  if (load_status_ != NOT_STARTED) {
40  return false;
41  }
42 
43  const bool is_rcpsp_max =
44  absl::EndsWith(file_name, ".sch") || absl::EndsWith(file_name, ".SCH");
45  const bool is_patterson = absl::EndsWith(file_name, ".rcp");
46  load_status_ = HEADER_SECTION;
47 
48  for (const std::string& line : FileLines(file_name)) {
49  if (is_rcpsp_max) {
50  ProcessRcpspMaxLine(line);
51  } else if (is_patterson) {
52  ProcessPattersonLine(line);
53  } else {
54  ProcessRcpspLine(line);
55  }
56  if (load_status_ == ERROR_FOUND) {
57  LOG(INFO) << rcpsp_.DebugString();
58  return false;
59  }
60  }
61  VLOG(1) << "Read file: " << file_name << ", max = " << is_rcpsp_max
62  << ", patterson = " << is_patterson << ", with "
63  << rcpsp_.tasks_size() << " tasks, and " << rcpsp_.resources_size()
64  << " resources.";
65  // Count the extra start and end tasks.
66  return num_declared_tasks_ + 2 == rcpsp_.tasks_size() &&
67  load_status_ == PARSING_FINISHED;
68 }
69 
70 void RcpspParser::ReportError(const std::string& line) {
71  LOG(ERROR) << "Error: status = " << load_status_ << ", line = " << line;
72  load_status_ = ERROR_FOUND;
73 }
74 
75 void RcpspParser::SetNumDeclaredTasks(int t) {
76  num_declared_tasks_ = t;
77  recipe_sizes_.resize(t + 2, 0); // The data format adds 2 sentinels.
78 }
79 
80 void RcpspParser::ProcessRcpspLine(const std::string& line) {
81  if (absl::StartsWith(line, "***")) return;
82  if (absl::StartsWith(line, "---")) return;
83 
84  const std::vector<std::string> words =
85  absl::StrSplit(line, absl::ByAnyChar(" :\t\r"), absl::SkipEmpty());
86 
87  if (words.empty()) return;
88 
89  switch (load_status_) {
90  case NOT_STARTED: {
91  ReportError(line);
92  break;
93  }
94  case HEADER_SECTION: {
95  if (words[0] == "file") {
96  rcpsp_.set_basedata(words[3]);
97  } else if (words[0] == "initial") {
98  rcpsp_.set_seed(strtoint64(words[4]));
99  load_status_ = PROJECT_SECTION;
100  } else if (words[0] == "jobs") {
101  // Workaround for the mmlib files which has less headers.
102  SetNumDeclaredTasks(strtoint32(words[4]) - 2);
103  load_status_ = PROJECT_SECTION;
104  } else {
105  ReportError(line);
106  }
107  break;
108  }
109  case PROJECT_SECTION: {
110  if (words[0] == "projects") {
111  // Nothing to do.
112  } else if (words[0] == "jobs") {
113  // This declaration counts the 2 sentinels.
114  SetNumDeclaredTasks(strtoint32(words[4]) - 2);
115  } else if (words[0] == "horizon") {
116  rcpsp_.set_horizon(strtoint32(words[1]));
117  } else if (words[0] == "RESOURCES") {
118  // Nothing to do.
119  } else if (words.size() > 1 && words[1] == "renewable") {
120  for (int i = 0; i < strtoint32(words[2]); ++i) {
121  Resource* const res = rcpsp_.add_resources();
122  res->set_max_capacity(-1);
123  res->set_renewable(true);
124  res->set_unit_cost(0);
125  }
126  } else if (words.size() > 1 && words[1] == "nonrenewable") {
127  for (int i = 0; i < strtoint32(words[2]); ++i) {
128  Resource* const res = rcpsp_.add_resources();
129  res->set_max_capacity(-1);
130  res->set_min_capacity(-1);
131  res->set_renewable(false);
132  res->set_unit_cost(0);
133  }
134  } else if (words.size() > 1 && words[1] == "doubly") {
135  // Nothing to do.
136  } else if (words.size() == 2 && words[0] == "PROJECT") {
137  load_status_ = INFO_SECTION;
138  } else if (words.size() == 2 && words[0] == "PRECEDENCE") {
139  // mmlib files have no info section.
140  load_status_ = PRECEDENCE_SECTION;
141  } else {
142  ReportError(line);
143  }
144  break;
145  }
146  case INFO_SECTION: {
147  if (words[0] == "pronr.") {
148  // Nothing to do.
149  } else if (words.size() == 6) {
150  SetNumDeclaredTasks(strtoint32(words[1]));
151  rcpsp_.set_release_date(strtoint32(words[2]));
152  rcpsp_.set_due_date(strtoint32(words[3]));
153  rcpsp_.set_tardiness_cost(strtoint32(words[4]));
154  rcpsp_.set_mpm_time(strtoint32(words[5]));
155  } else if (words.size() == 2 && words[0] == "PRECEDENCE") {
156  load_status_ = PRECEDENCE_SECTION;
157  } else {
158  ReportError(line);
159  }
160  break;
161  }
162  case PRECEDENCE_SECTION: {
163  if (words[0] == "jobnr.") {
164  // Nothing to do.
165  } else if (words.size() >= 3) {
166  const int task_index = strtoint32(words[0]) - 1;
167  CHECK_EQ(task_index, rcpsp_.tasks_size());
168  recipe_sizes_[task_index] = strtoint32(words[1]);
169  const int num_successors = strtoint32(words[2]);
170  if (words.size() != 3 + num_successors) {
171  ReportError(line);
172  break;
173  }
174  Task* const task = rcpsp_.add_tasks();
175  for (int i = 0; i < num_successors; ++i) {
176  // The array of tasks is 0-based for us.
177  task->add_successors(strtoint32(words[3 + i]) - 1);
178  }
179  } else if (words[0] == "REQUESTS/DURATIONS") {
180  load_status_ = REQUEST_SECTION;
181  } else {
182  ReportError(line);
183  }
184  break;
185  }
186  case REQUEST_SECTION: {
187  if (words[0] == "jobnr.") {
188  // Nothing to do.
189  } else if (words.size() == 3 + rcpsp_.resources_size()) {
190  // Start of a new task (index is 0-based for us).
191  current_task_ = strtoint32(words[0]) - 1;
192  const int current_recipe = strtoint32(words[1]) - 1;
193  CHECK_EQ(current_recipe, rcpsp_.tasks(current_task_).recipes_size());
194  if (current_recipe != 0) {
195  ReportError(line);
196  break;
197  }
198  Recipe* const recipe =
199  rcpsp_.mutable_tasks(current_task_)->add_recipes();
200  recipe->set_duration(strtoint32(words[2]));
201  for (int i = 0; i < rcpsp_.resources_size(); ++i) {
202  const int demand = strtoint32(words[3 + i]);
203  if (demand != 0) {
204  recipe->add_demands(demand);
205  recipe->add_resources(i);
206  }
207  }
208  } else if (words.size() == 2 + rcpsp_.resources_size()) {
209  // New recipe for a current task.
210  const int current_recipe = strtoint32(words[0]) - 1;
211  CHECK_EQ(current_recipe, rcpsp_.tasks(current_task_).recipes_size());
212  Recipe* const recipe =
213  rcpsp_.mutable_tasks(current_task_)->add_recipes();
214  recipe->set_duration(strtoint32(words[1]));
215  for (int i = 0; i < rcpsp_.resources_size(); ++i) {
216  const int demand = strtoint32(words[2 + i]);
217  if (demand != 0) {
218  recipe->add_demands(demand);
219  recipe->add_resources(i);
220  }
221  }
222  } else if (words[0] == "RESOURCEAVAILABILITIES" ||
223  (words[0] == "RESOURCE" && words[1] == "AVAILABILITIES")) {
224  load_status_ = RESOURCE_SECTION;
225  } else {
226  ReportError(line);
227  }
228  break;
229  }
230  case RESOURCE_SECTION: {
231  if (words.size() == 2 * rcpsp_.resources_size()) {
232  // Nothing to do.
233  } else if (words.size() == rcpsp_.resources_size()) {
234  for (int i = 0; i < words.size(); ++i) {
235  rcpsp_.mutable_resources(i)->set_max_capacity(strtoint32(words[i]));
236  }
237  load_status_ = PARSING_FINISHED;
238  } else {
239  ReportError(line);
240  }
241  break;
242  }
243  case RESOURCE_MIN_SECTION: {
244  LOG(FATAL) << "Should not be here";
245  break;
246  }
247  case PARSING_FINISHED: {
248  break;
249  }
250  case ERROR_FOUND: {
251  break;
252  }
253  }
254 }
255 
256 void RcpspParser::ProcessRcpspMaxLine(const std::string& line) {
257  const std::vector<std::string> words =
258  absl::StrSplit(line, absl::ByAnyChar(" :\t[]\r"), absl::SkipEmpty());
259 
260  switch (load_status_) {
261  case NOT_STARTED: {
262  ReportError(line);
263  break;
264  }
265  case HEADER_SECTION: {
266  rcpsp_.set_is_rcpsp_max(true);
267  if (words.size() == 2) {
268  rcpsp_.set_is_consumer_producer(true);
269  } else if (words.size() < 4 || strtoint32(words[3]) != 0) {
270  ReportError(line);
271  break;
272  }
273 
274  if (words.size() == 5) {
275  rcpsp_.set_deadline(strtoint32(words[4]));
276  rcpsp_.set_is_resource_investment(true);
277  }
278 
279  SetNumDeclaredTasks(strtoint32(words[0]));
280  temp_delays_.resize(num_declared_tasks_ + 2);
281 
282  // Creates resources.
283  if (rcpsp_.is_consumer_producer()) {
284  const int num_nonrenewable_resources = strtoint32(words[1]);
285  for (int i = 0; i < num_nonrenewable_resources; ++i) {
286  Resource* const res = rcpsp_.add_resources();
287  res->set_max_capacity(-1);
288  res->set_min_capacity(-1);
289  res->set_renewable(false);
290  res->set_unit_cost(0);
291  }
292  } else {
293  const int num_renewable_resources = strtoint32(words[1]);
294  const int num_nonrenewable_resources = strtoint32(words[2]);
295  for (int i = 0; i < num_renewable_resources; ++i) {
296  Resource* const res = rcpsp_.add_resources();
297  res->set_max_capacity(-1);
298  res->set_renewable(true);
299  res->set_unit_cost(0);
300  }
301  for (int i = 0; i < num_nonrenewable_resources; ++i) {
302  Resource* const res = rcpsp_.add_resources();
303  res->set_max_capacity(-1);
304  res->set_min_capacity(-1);
305  res->set_renewable(false);
306  res->set_unit_cost(0);
307  }
308  }
309 
310  // Set up for the next section.
311  load_status_ = PRECEDENCE_SECTION;
312  current_task_ = 0;
313  break;
314  }
315  case PROJECT_SECTION: {
316  LOG(FATAL) << "Should not be here";
317  break;
318  }
319  case INFO_SECTION: {
320  LOG(FATAL) << "Should not be here";
321  break;
322  }
323  case PRECEDENCE_SECTION: {
324  if (words.size() < 3) {
325  ReportError(line);
326  break;
327  }
328 
329  const int task_id = strtoint32(words[0]);
330  if (task_id != current_task_) {
331  ReportError(line);
332  break;
333  } else {
334  current_task_++;
335  }
336 
337  const int num_recipes = strtoint32(words[1]);
338  recipe_sizes_[task_id] = num_recipes;
339  const int num_successors = strtoint32(words[2]);
340 
341  Task* const task = rcpsp_.add_tasks();
342 
343  // Read successors.
344  for (int i = 0; i < num_successors; ++i) {
345  task->add_successors(strtoint32(words[3 + i]));
346  }
347 
348  // Read flattened delays into temp_delays_.
349  for (int i = 3 + num_successors; i < words.size(); ++i) {
350  temp_delays_[task_id].push_back(strtoint32(words[i]));
351  }
352 
353  if (task_id == num_declared_tasks_ + 1) {
354  // Convert the flattened delays into structured delays (1 vector per
355  // successor) in the task_size.
356  for (int t = 1; t <= num_declared_tasks_; ++t) {
357  const int num_recipes = recipe_sizes_[t];
358  const int num_successors = rcpsp_.tasks(t).successors_size();
359  int count = 0;
360  for (int s = 0; s < num_successors; ++s) {
361  PerSuccessorDelays* const succ_delays =
362  rcpsp_.mutable_tasks(t)->add_successor_delays();
363  for (int r1 = 0; r1 < num_recipes; ++r1) {
364  PerRecipeDelays* const recipe_delays =
365  succ_delays->add_recipe_delays();
366  const int other = rcpsp_.tasks(t).successors(s);
367  const int num_other_recipes = recipe_sizes_[other];
368  for (int r2 = 0; r2 < num_other_recipes; ++r2) {
369  recipe_delays->add_min_delays(temp_delays_[t][count++]);
370  }
371  }
372  }
373  CHECK_EQ(count, temp_delays_[t].size());
374  }
375 
376  // Setup for next section.
377  current_task_ = 0;
378  load_status_ = REQUEST_SECTION;
379  }
380  break;
381  }
382  case REQUEST_SECTION: {
383  if (words.size() == 3 + rcpsp_.resources_size()) {
384  // Start of a new task.
385  current_task_ = strtoint32(words[0]);
386 
387  // 0 based indices for the recipe.
388  const int current_recipe = strtoint32(words[1]) - 1;
389  CHECK_EQ(current_recipe, rcpsp_.tasks(current_task_).recipes_size());
390  if (current_recipe != 0) {
391  ReportError(line);
392  break;
393  }
394  Recipe* const recipe =
395  rcpsp_.mutable_tasks(current_task_)->add_recipes();
396  recipe->set_duration(strtoint32(words[2]));
397  for (int i = 0; i < rcpsp_.resources_size(); ++i) {
398  const int demand = strtoint32(words[3 + i]);
399  if (demand != 0) {
400  recipe->add_demands(demand);
401  recipe->add_resources(i);
402  }
403  }
404  } else if (words.size() == 2 + rcpsp_.resources_size() &&
405  rcpsp_.is_consumer_producer()) {
406  // Start of a new task.
407  current_task_ = strtoint32(words[0]);
408 
409  // 0 based indices for the recipe.
410  const int current_recipe = strtoint32(words[1]) - 1;
411  CHECK_EQ(current_recipe, rcpsp_.tasks(current_task_).recipes_size());
412  if (current_recipe != 0) {
413  ReportError(line);
414  break;
415  }
416  Recipe* const recipe =
417  rcpsp_.mutable_tasks(current_task_)->add_recipes();
418  recipe->set_duration(0);
419  for (int i = 0; i < rcpsp_.resources_size(); ++i) {
420  const int demand = strtoint32(words[2 + i]);
421  if (demand != 0) {
422  recipe->add_demands(demand);
423  recipe->add_resources(i);
424  }
425  }
426  } else if (words.size() == 2 + rcpsp_.resources_size()) {
427  // New recipe for a current task.
428  const int current_recipe = strtoint32(words[0]) - 1;
429  CHECK_EQ(current_recipe, rcpsp_.tasks(current_task_).recipes_size());
430  Recipe* const recipe =
431  rcpsp_.mutable_tasks(current_task_)->add_recipes();
432  recipe->set_duration(strtoint32(words[1]));
433  for (int i = 0; i < rcpsp_.resources_size(); ++i) {
434  const int demand = strtoint32(words[2 + i]);
435  if (demand != 0) {
436  recipe->add_demands(demand);
437  recipe->add_resources(i);
438  }
439  }
440  }
441  if (current_task_ == num_declared_tasks_ + 1) {
442  if (rcpsp_.is_consumer_producer()) {
443  load_status_ = RESOURCE_MIN_SECTION;
444  } else {
445  load_status_ = RESOURCE_SECTION;
446  }
447  }
448  break;
449  }
450  case RESOURCE_SECTION: {
451  if (words.size() == rcpsp_.resources_size()) {
452  for (int i = 0; i < words.size(); ++i) {
453  if (rcpsp_.is_resource_investment()) {
454  rcpsp_.mutable_resources(i)->set_unit_cost(strtoint32(words[i]));
455  } else {
456  rcpsp_.mutable_resources(i)->set_max_capacity(strtoint32(words[i]));
457  }
458  }
459  load_status_ = PARSING_FINISHED;
460  } else {
461  ReportError(line);
462  }
463  break;
464  }
465  case RESOURCE_MIN_SECTION: {
466  if (words.size() == rcpsp_.resources_size()) {
467  for (int i = 0; i < words.size(); ++i) {
468  rcpsp_.mutable_resources(i)->set_min_capacity(strtoint32(words[i]));
469  }
470  load_status_ = RESOURCE_SECTION;
471  } else {
472  ReportError(line);
473  }
474  break;
475  }
476  case PARSING_FINISHED: {
477  break;
478  }
479  case ERROR_FOUND: {
480  break;
481  }
482  }
483 }
484 
485 void RcpspParser::ProcessPattersonLine(const std::string& line) {
486  const std::vector<std::string> words =
487  absl::StrSplit(line, absl::ByAnyChar(" :\t[]\r"), absl::SkipEmpty());
488 
489  if (words.empty()) return;
490 
491  switch (load_status_) {
492  case NOT_STARTED: {
493  ReportError(line);
494  break;
495  }
496  case HEADER_SECTION: {
497  if (words.size() != 2) {
498  ReportError(line);
499  break;
500  }
501  SetNumDeclaredTasks(strtoint32(words[0]) - 2); // Remove the 2 sentinels.
502 
503  // Creates resources.
504  const int num_renewable_resources = strtoint32(words[1]);
505  for (int i = 0; i < num_renewable_resources; ++i) {
506  Resource* const res = rcpsp_.add_resources();
507  res->set_max_capacity(-1);
508  res->set_min_capacity(-1);
509  res->set_renewable(true);
510  res->set_unit_cost(0);
511  }
512 
513  // Set up for the next section.
514  load_status_ = RESOURCE_SECTION;
515  break;
516  }
517  case PROJECT_SECTION: {
518  LOG(FATAL) << "Should not be here";
519  break;
520  }
521  case INFO_SECTION: {
522  LOG(FATAL) << "Should not be here";
523  break;
524  }
525  case PRECEDENCE_SECTION: {
526  if (unreads_ > 0) {
527  for (int i = 0; i < words.size(); ++i) {
528  rcpsp_.mutable_tasks(current_task_)
529  ->add_successors(strtoint32(words[i]) - 1);
530  unreads_--;
531  CHECK_GE(unreads_, 0);
532  }
533  } else {
534  if (words.size() < 2 + rcpsp_.resources_size()) {
535  ReportError(line);
536  break;
537  }
538  CHECK_EQ(current_task_, rcpsp_.tasks_size());
539  Task* const task = rcpsp_.add_tasks();
540  Recipe* const recipe = task->add_recipes();
541  recipe->set_duration(strtoint32(words[0]));
542 
543  const int num_resources = rcpsp_.resources_size();
544  for (int i = 1; i <= num_resources; ++i) {
545  const int demand = strtoint32(words[i]);
546  if (demand != 0) {
547  recipe->add_demands(demand);
548  recipe->add_resources(i - 1);
549  }
550  }
551 
552  unreads_ = strtoint32(words[1 + num_resources]);
553  for (int i = 2 + num_resources; i < words.size(); ++i) {
554  // Successors are 1 based in the data file.
555  task->add_successors(strtoint32(words[i]) - 1);
556  unreads_--;
557  CHECK_GE(unreads_, 0);
558  }
559  }
560 
561  if (unreads_ == 0 && ++current_task_ == num_declared_tasks_ + 2) {
562  load_status_ = PARSING_FINISHED;
563  }
564  break;
565  }
566  case REQUEST_SECTION: {
567  LOG(FATAL) << "Should not be here";
568  break;
569  }
570  case RESOURCE_SECTION: {
571  if (words.size() == rcpsp_.resources_size()) {
572  for (int i = 0; i < words.size(); ++i) {
573  rcpsp_.mutable_resources(i)->set_max_capacity(strtoint32(words[i]));
574  }
575  load_status_ = PRECEDENCE_SECTION;
576  current_task_ = 0;
577  } else {
578  ReportError(line);
579  }
580  break;
581  }
582  case RESOURCE_MIN_SECTION: {
583  LOG(FATAL) << "Should not be here";
584  break;
585  }
586  case PARSING_FINISHED: {
587  break;
588  }
589  case ERROR_FOUND: {
590  break;
591  }
592  }
593 }
594 
595 int RcpspParser::strtoint32(const std::string& word) {
596  int result;
597  CHECK(absl::SimpleAtoi(word, &result));
598  return result;
599 }
600 
601 int64_t RcpspParser::strtoint64(const std::string& word) {
602  int64_t result;
603  CHECK(absl::SimpleAtoi(word, &result));
604  return result;
605 }
606 
607 } // namespace rcpsp
608 } // namespace data
609 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GE(val1, val2)
Definition: base/logging.h:709
#define LOG(severity)
Definition: base/logging.h:423
#define VLOG(verboselevel)
Definition: base/logging.h:986
bool ParseFile(const std::string &file_name)
Definition: rcpsp_parser.cc:38
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
const int FATAL
Definition: log_severity.h:32
Collection of objects used to extend the Constraint Solver library.
int64_t demand
Definition: resource.cc:125