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