20 #include "absl/strings/match.h"
21 #include "absl/strings/numbers.h"
22 #include "absl/strings/str_split.h"
24 #include "ortools/scheduling/rcpsp.pb.h"
28 namespace scheduling {
33 load_status_(NOT_STARTED),
34 num_declared_tasks_(-1),
37 rcpsp_.set_deadline(-1);
38 rcpsp_.set_horizon(-1);
42 if (load_status_ != NOT_STARTED) {
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;
53 ProcessRcpspMaxLine(
line);
54 }
else if (is_patterson) {
55 ProcessPattersonLine(
line);
57 ProcessRcpspLine(
line);
59 if (load_status_ == ERROR_FOUND) {
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()
71 std::string problem_name(
file::Stem(file_name));
72 rcpsp_.set_name(problem_name);
75 return num_declared_tasks_ + 2 == rcpsp_.tasks_size() &&
76 load_status_ == PARSING_FINISHED;
79 void RcpspParser::ReportError(
const std::string&
line) {
80 LOG(ERROR) <<
"Error: status = " << load_status_ <<
", line = " <<
line;
81 load_status_ = ERROR_FOUND;
84 void RcpspParser::SetNumDeclaredTasks(
int t) {
85 num_declared_tasks_ = t;
86 recipe_sizes_.resize(t + 2, 0);
89 void RcpspParser::ProcessRcpspLine(
const std::string&
line) {
90 if (absl::StartsWith(
line,
"***"))
return;
91 if (absl::StartsWith(
line,
"---"))
return;
93 const std::vector<std::string> words =
94 absl::StrSplit(
line, absl::ByAnyChar(
" :\t\r"), absl::SkipEmpty());
96 if (words.empty())
return;
98 switch (load_status_) {
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") {
111 SetNumDeclaredTasks(strtoint32(words[4]) - 2);
112 load_status_ = PROJECT_SECTION;
118 case PROJECT_SECTION: {
119 if (words[0] ==
"projects") {
121 }
else if (words[0] ==
"jobs") {
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") {
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);
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);
143 }
else if (words.size() > 1 && words[1] ==
"doubly") {
145 }
else if (words.size() == 2 && words[0] ==
"PROJECT") {
146 load_status_ = INFO_SECTION;
147 }
else if (words.size() == 2 && words[0] ==
"PRECEDENCE") {
149 load_status_ = PRECEDENCE_SECTION;
156 if (words[0] ==
"pronr.") {
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;
171 case PRECEDENCE_SECTION: {
172 if (words[0] ==
"jobnr.") {
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) {
183 Task*
const task = rcpsp_.add_tasks();
184 for (
int i = 0; i < num_successors; ++i) {
186 task->add_successors(strtoint32(words[3 + i]) - 1);
188 }
else if (words[0] ==
"REQUESTS/DURATIONS") {
189 load_status_ = REQUEST_SECTION;
195 case REQUEST_SECTION: {
196 if (words[0] ==
"jobnr.") {
198 }
else if (words.size() == 3 + rcpsp_.resources_size()) {
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) {
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]);
213 recipe->add_demands(
demand);
214 recipe->add_resources(i);
217 }
else if (words.size() == 2 + rcpsp_.resources_size()) {
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]);
227 recipe->add_demands(
demand);
228 recipe->add_resources(i);
231 }
else if (words[0] ==
"RESOURCEAVAILABILITIES" ||
232 (words[0] ==
"RESOURCE" && words[1] ==
"AVAILABILITIES")) {
233 load_status_ = RESOURCE_SECTION;
239 case RESOURCE_SECTION: {
240 if (words.size() == 2 * rcpsp_.resources_size()) {
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]));
246 load_status_ = PARSING_FINISHED;
252 case RESOURCE_MIN_SECTION: {
253 LOG(FATAL) <<
"Should not be here";
256 case PARSING_FINISHED: {
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());
269 switch (load_status_) {
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) {
283 if (words.size() == 5) {
284 rcpsp_.set_deadline(strtoint32(words[4]));
285 rcpsp_.set_is_resource_investment(
true);
288 SetNumDeclaredTasks(strtoint32(words[0]));
289 temp_delays_.resize(num_declared_tasks_ + 2);
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);
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);
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);
320 load_status_ = PRECEDENCE_SECTION;
324 case PROJECT_SECTION: {
325 LOG(FATAL) <<
"Should not be here";
329 LOG(FATAL) <<
"Should not be here";
332 case PRECEDENCE_SECTION: {
333 if (words.size() < 3) {
338 const int task_id = strtoint32(words[0]);
339 if (task_id != current_task_) {
346 const int num_recipes = strtoint32(words[1]);
347 recipe_sizes_[task_id] = num_recipes;
348 const int num_successors = strtoint32(words[2]);
350 Task*
const task = rcpsp_.add_tasks();
353 for (
int i = 0; i < num_successors; ++i) {
354 task->add_successors(strtoint32(words[3 + i]));
358 for (
int i = 3 + num_successors; i < words.size(); ++i) {
359 temp_delays_[task_id].push_back(strtoint32(words[i]));
362 if (task_id == num_declared_tasks_ + 1) {
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();
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++]);
382 CHECK_EQ(count, temp_delays_[t].size());
387 load_status_ = REQUEST_SECTION;
391 case REQUEST_SECTION: {
392 if (words.size() == 3 + rcpsp_.resources_size()) {
394 current_task_ = strtoint32(words[0]);
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) {
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]);
409 recipe->add_demands(
demand);
410 recipe->add_resources(i);
413 }
else if (words.size() == 2 + rcpsp_.resources_size() &&
414 rcpsp_.is_consumer_producer()) {
416 current_task_ = strtoint32(words[0]);
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) {
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]);
431 recipe->add_demands(
demand);
432 recipe->add_resources(i);
435 }
else if (words.size() == 2 + rcpsp_.resources_size()) {
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]);
445 recipe->add_demands(
demand);
446 recipe->add_resources(i);
450 if (current_task_ == num_declared_tasks_ + 1) {
451 if (rcpsp_.is_consumer_producer()) {
452 load_status_ = RESOURCE_MIN_SECTION;
454 load_status_ = RESOURCE_SECTION;
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]));
465 rcpsp_.mutable_resources(i)->set_max_capacity(strtoint32(words[i]));
468 load_status_ = PARSING_FINISHED;
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]));
479 load_status_ = RESOURCE_SECTION;
485 case PARSING_FINISHED: {
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());
498 if (words.empty())
return;
500 switch (load_status_) {
505 case HEADER_SECTION: {
506 if (words.size() != 2) {
510 SetNumDeclaredTasks(strtoint32(words[0]) - 2);
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);
523 load_status_ = RESOURCE_SECTION;
526 case PROJECT_SECTION: {
527 LOG(FATAL) <<
"Should not be here";
531 LOG(FATAL) <<
"Should not be here";
534 case PRECEDENCE_SECTION: {
536 for (
int i = 0; i < words.size(); ++i) {
537 rcpsp_.mutable_tasks(current_task_)
538 ->add_successors(strtoint32(words[i]) - 1);
540 CHECK_GE(unreads_, 0);
543 if (words.size() < 2 + rcpsp_.resources_size()) {
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]));
552 const int num_resources = rcpsp_.resources_size();
553 for (
int i = 1; i <= num_resources; ++i) {
554 const int demand = strtoint32(words[i]);
556 recipe->add_demands(
demand);
557 recipe->add_resources(i - 1);
561 unreads_ = strtoint32(words[1 + num_resources]);
562 for (
int i = 2 + num_resources; i < words.size(); ++i) {
564 task->add_successors(strtoint32(words[i]) - 1);
566 CHECK_GE(unreads_, 0);
570 if (unreads_ == 0 && ++current_task_ == num_declared_tasks_ + 2) {
571 load_status_ = PARSING_FINISHED;
575 case REQUEST_SECTION: {
576 LOG(FATAL) <<
"Should not be here";
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]));
584 load_status_ = PRECEDENCE_SECTION;
591 case RESOURCE_MIN_SECTION: {
592 LOG(FATAL) <<
"Should not be here";
595 case PARSING_FINISHED: {
604 int RcpspParser::strtoint32(
const std::string& word) {
606 CHECK(absl::SimpleAtoi(word, &result));
610 int64_t RcpspParser::strtoint64(
const std::string& word) {
612 CHECK(absl::SimpleAtoi(word, &result));
bool ParseFile(const std::string &file_name)
absl::string_view Stem(absl::string_view path)
Collection of objects used to extend the Constraint Solver library.
#define VLOG(verboselevel)