OR-Tools  9.2
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"
23
24namespace operations_research {
25namespace scheduling {
26namespace 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
38bool 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
70void RcpspParser::ReportError(const std::string& line) {
71 LOG(ERROR) << "Error: status = " << load_status_ << ", line = " << line;
72 load_status_ = ERROR_FOUND;
73}
74
75void RcpspParser::SetNumDeclaredTasks(int t) {
76 num_declared_tasks_ = t;
77 recipe_sizes_.resize(t + 2, 0); // The data format adds 2 sentinels.
78}
79
80void 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
256void 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 =
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
485void 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
595int RcpspParser::strtoint32(const std::string& word) {
596 int result;
597 CHECK(absl::SimpleAtoi(word, &result));
598 return result;
599}
600
601int64_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 scheduling
609} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:492
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:699
#define CHECK_GE(val1, val2)
Definition: base/logging.h:703
#define LOG(severity)
Definition: base/logging.h:417
#define VLOG(verboselevel)
Definition: base/logging.h:980
::operations_research::scheduling::rcpsp::PerRecipeDelays * add_recipe_delays()
Definition: rcpsp.pb.h:1592
bool ParseFile(const std::string &file_name)
Definition: rcpsp_parser.cc:38
::operations_research::scheduling::rcpsp::Task * mutable_tasks(int index)
Definition: rcpsp.pb.h:1788
::operations_research::scheduling::rcpsp::Task * add_tasks()
Definition: rcpsp.pb.h:1807
void set_basedata(ArgT0 &&arg0, ArgT... args)
::operations_research::scheduling::rcpsp::Resource * mutable_resources(int index)
Definition: rcpsp.pb.h:1748
const ::operations_research::scheduling::rcpsp::Task & tasks(int index) const
Definition: rcpsp.pb.h:1800
::operations_research::scheduling::rcpsp::Resource * add_resources()
Definition: rcpsp.pb.h:1767
::operations_research::scheduling::rcpsp::Recipe * add_recipes()
Definition: rcpsp.pb.h:1683
::operations_research::scheduling::rcpsp::PerSuccessorDelays * add_successor_delays()
Definition: rcpsp.pb.h:1723
const int INFO
Definition: log_severity.h:31
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