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