Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
jobshop_scheduling_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 <cmath>
17#include <cstdint>
18#include <string>
19#include <vector>
20
21#include "absl/base/attributes.h"
22#include "absl/flags/flag.h"
23#include "absl/log/check.h"
24#include "absl/strings/match.h"
25#include "absl/strings/numbers.h"
26#include "absl/strings/str_cat.h"
27#include "absl/strings/str_split.h"
28#include "absl/strings/string_view.h"
29#include "google/protobuf/wrappers.pb.h"
31#include "ortools/base/path.h"
32#include "ortools/scheduling/jobshop_scheduling.pb.h"
34
35ABSL_FLAG(int64_t, jssp_scaling_up_factor, 100000L,
36 "Scaling factor for floating point penalties.");
37
38namespace operations_research {
39namespace scheduling {
40namespace jssp {
41
42void JsspParser::SetJobs(int job_count) {
43 CHECK_GT(job_count, 0);
44 declared_job_count_ = job_count;
45 problem_.clear_jobs();
46 for (int i = 0; i < job_count; ++i) {
47 problem_.add_jobs()->set_name(absl::StrCat("J", i));
48 }
49}
50
51void JsspParser::SetMachines(int machine_count) {
52 CHECK_GT(machine_count, 0);
53 declared_machine_count_ = machine_count;
54 problem_.clear_machines();
55 for (int i = 0; i < machine_count; ++i) {
56 problem_.add_machines()->set_name(absl::StrCat("M", i));
57 }
58}
59
60bool JsspParser::ParseFile(absl::string_view filename) {
61 problem_.Clear();
62 // Try to detect the type of the data file.
63 // - fjs suffix -> Flexible Jobshop
64 // - txt suffix -> Taillard or time dependent scheduling.
65
66 if (absl::EndsWith(filename, "fjs")) {
67 problem_type_ = FLEXIBLE;
68 } else if (absl::EndsWith(filename, ".txt")) {
69 problem_type_ = TAILLARD;
70 } else {
71 problem_type_ = JSSP;
72 }
73
74 // We use a temporary string as open source protobufs do not accept
75 // set(string_view).
76 const std::string problem_name(file::Stem(filename));
77 problem_.set_name(problem_name);
78
79 for (const std::string& line : FileLines(filename)) {
80 if (line.empty()) {
81 continue;
82 }
83 switch (problem_type_) {
84 case JSSP: {
85 ProcessJsspLine(line);
86 break;
87 }
88 case TAILLARD: {
89 ProcessTaillardLine(line);
90 break;
91 }
92 case FLEXIBLE: {
93 ProcessFlexibleLine(line);
94 break;
95 }
96 case SDST: {
97 ProcessSdstLine(line);
98 break;
99 }
100 case TARDINESS: {
101 ProcessTardinessLine(line);
102 break;
103 }
104 case PSS: {
105 ProcessPssLine(line);
106 break;
107 }
108 case EARLY_TARDY: {
109 ProcessEarlyTardyLine(line);
110 break;
111 }
112 default: {
113 LOG(FATAL) << "Should not be here.";
114 break;
115 }
116 }
117 }
118 return parser_state_ != PARSING_ERROR;
119}
120
121void JsspParser::ProcessJsspLine(const std::string& line) {
122 const std::vector<std::string> words =
123 absl::StrSplit(line, ' ', absl::SkipEmpty());
124 switch (parser_state_) {
125 case START: {
126 if (words.size() == 2 && words[0] == "instance") {
127 problem_.set_name(words[1]);
128 parser_state_ = NAME_READ;
129 current_job_index_ = 0;
130 } else if (words.size() == 1 && words[0] == "1") {
131 problem_type_ = PSS;
132 } else if (words.size() == 2) {
133 SetJobs(strtoint32(words[0]));
134 SetMachines(strtoint32(words[1]));
135 problem_type_ = EARLY_TARDY;
136 parser_state_ = JOB_COUNT_READ;
137 }
138 break;
139 }
140 case NAME_READ: {
141 if (words.size() == 2) {
142 SetJobs(strtoint32(words[0]));
143 SetMachines(strtoint32(words[1]));
144 problem_.set_makespan_cost_per_time_unit(1L);
145 parser_state_ = JOB_COUNT_READ;
146 }
147 break;
148 }
149 case JOB_COUNT_READ: {
150 CHECK_GE(words.size(), declared_machine_count_ * 2);
151 Job* const job = problem_.mutable_jobs(current_job_index_);
152 for (int i = 0; i < declared_machine_count_; ++i) {
153 const int machine_id = strtoint32(words[2 * i]);
154 const int64_t duration = strtoint64(words[2 * i + 1]);
155 Task* const task = job->add_tasks();
156 task->add_machine(machine_id);
157 task->add_duration(duration);
158 }
159 if (words.size() == declared_machine_count_ * 2 + 3) {
160 // Early Tardy problem in JET format.
161 const int due_date = strtoint32(words[declared_machine_count_ * 2]);
162 const int early_cost =
163 strtoint32(words[declared_machine_count_ * 2 + 1]);
164 const int late_cost =
165 strtoint32(words[declared_machine_count_ * 2 + 2]);
166 job->set_early_due_date(due_date);
167 job->set_late_due_date(due_date);
168 job->set_earliness_cost_per_time_unit(early_cost);
169 job->set_lateness_cost_per_time_unit(late_cost);
170 }
171 current_job_index_++;
172 if (current_job_index_ == declared_job_count_) {
173 parser_state_ = DONE;
174 }
175 break;
176 }
177 default: {
178 LOG(FATAL) << "Should not be here with state " << parser_state_;
179 }
180 }
181}
182
183void JsspParser::ProcessTaillardLine(const std::string& line) {
184 const std::vector<std::string> words =
185 absl::StrSplit(line, ' ', absl::SkipEmpty());
186
187 switch (parser_state_) {
188 case START: {
189 if (words.size() == 2) { // Switch to SDST parser.
190 problem_type_ = SDST;
191 ProcessSdstLine(line);
192 return;
193 } else if (words.size() == 3) { // Switch to TARDINESS parser.
194 problem_type_ = TARDINESS;
195 ProcessTardinessLine(line);
196 return;
197 }
198 if (words.size() == 1 && strtoint32(words[0]) > 0) {
199 parser_state_ = JOB_COUNT_READ;
200 SetJobs(strtoint32(words[0]));
201 }
202 break;
203 }
204 case JOB_COUNT_READ: {
205 CHECK_EQ(1, words.size());
206 SetMachines(strtoint32(words[0]));
207 problem_.set_makespan_cost_per_time_unit(1L);
208 parser_state_ = MACHINE_COUNT_READ;
209 break;
210 }
211 case MACHINE_COUNT_READ: {
212 CHECK_EQ(1, words.size());
213 const int seed = strtoint32(words[0]);
214 problem_.set_seed(seed);
215 parser_state_ = SEED_READ;
216 break;
217 }
218 case SEED_READ:
219 ABSL_FALLTHROUGH_INTENDED;
220 case JOB_READ: {
221 CHECK_EQ(1, words.size());
222 current_job_index_ = strtoint32(words[0]);
223 parser_state_ = JOB_ID_READ;
224 break;
225 }
226 case JOB_ID_READ: {
227 CHECK_EQ(1, words.size());
228 parser_state_ = JOB_LENGTH_READ;
229 break;
230 }
231 case JOB_LENGTH_READ: {
232 CHECK_EQ(declared_machine_count_, words.size());
233 Job* const job = problem_.mutable_jobs(current_job_index_);
234 for (int i = 0; i < declared_machine_count_; ++i) {
235 const int64_t duration = strtoint64(words[i]);
236 Task* const task = job->add_tasks();
237 task->add_machine(i);
238 task->add_duration(duration);
239 }
240 parser_state_ =
241 current_job_index_ == declared_job_count_ - 1 ? DONE : JOB_READ;
242 break;
243 }
244 default: {
245 LOG(FATAL) << "Should not be here with state " << parser_state_;
246 }
247 }
248}
249void JsspParser::ProcessFlexibleLine(const std::string& line) {
250 const std::vector<std::string> words =
251 absl::StrSplit(line, ' ', absl::SkipEmpty());
252 switch (parser_state_) {
253 case START: {
254 CHECK_GE(words.size(), 2);
255 SetJobs(strtoint32(words[0]));
256 SetMachines(strtoint32(words[1]));
257 problem_.set_makespan_cost_per_time_unit(1L);
258 parser_state_ = JOB_COUNT_READ;
259 break;
260 }
261 case JOB_COUNT_READ: {
262 const int operations_count = strtoint32(words[0]);
263 int index = 1;
264 Job* const job = problem_.mutable_jobs(current_job_index_);
265 for (int operation = 0; operation < operations_count; ++operation) {
266 const int alternatives_count = strtoint32(words[index++]);
267 Task* const task = job->add_tasks();
268 for (int alt = 0; alt < alternatives_count; alt++) {
269 // Machine id are 1 based.
270 const int machine_id = strtoint32(words[index++]) - 1;
271 const int64_t duration = strtoint64(words[index++]);
272 task->add_machine(machine_id);
273 task->add_duration(duration);
274 }
275 }
276 CHECK_LE(index, words.size()); // Ignore CR at the end of the line.
277 current_job_index_++;
278 if (current_job_index_ == declared_job_count_) {
279 parser_state_ = DONE;
280 }
281 break;
282 }
283 default: {
284 LOG(FATAL) << "Should not be here with state " << parser_state_;
285 }
286 }
287}
288void JsspParser::ProcessSdstLine(const std::string& line) {
289 const std::vector<std::string> words =
290 absl::StrSplit(line, ' ', absl::SkipEmpty());
291 switch (parser_state_) {
292 case START: {
293 if (words.size() == 2) {
294 SetJobs(strtoint32(words[0]));
295 SetMachines(strtoint32(words[1]));
296 problem_.set_makespan_cost_per_time_unit(1L);
297 parser_state_ = JOB_COUNT_READ;
298 current_machine_index_ = 0;
299 }
300 break;
301 }
302 case JOB_COUNT_READ: {
303 CHECK_EQ(words.size(), declared_machine_count_ * 2);
304 Job* const job = problem_.mutable_jobs(current_job_index_);
305 for (int i = 0; i < declared_machine_count_; ++i) {
306 const int machine_id = strtoint32(words[2 * i]);
307 const int64_t duration = strtoint64(words[2 * i + 1]);
308 Task* const task = job->add_tasks();
309 task->add_machine(machine_id);
310 task->add_duration(duration);
311 }
312 current_job_index_++;
313 if (current_job_index_ == declared_job_count_) {
314 parser_state_ = JOBS_READ;
315 }
316 break;
317 }
318 case JOBS_READ: {
319 CHECK_EQ(1, words.size());
320 CHECK_EQ("SSD", words[0]);
321 parser_state_ = SSD_READ;
322 break;
323 }
324 case SSD_READ: {
325 CHECK_EQ(1, words.size());
326 CHECK_EQ(words[0], absl::StrCat("M", current_machine_index_)) << line;
327 current_job_index_ = 0;
328 parser_state_ = MACHINE_READ;
329 break;
330 }
331 case MACHINE_READ: {
332 CHECK_EQ(declared_job_count_, words.size());
333 Machine* const machine =
334 problem_.mutable_machines(current_machine_index_);
335 for (const std::string& w : words) {
336 const int64_t t = strtoint64(w);
337 machine->mutable_transition_time_matrix()->add_transition_time(t);
338 }
339 if (++current_job_index_ == declared_job_count_) {
340 parser_state_ = ++current_machine_index_ == declared_machine_count_
341 ? DONE
342 : SSD_READ;
343 }
344 break;
345 }
346 default: {
347 LOG(FATAL) << "Should not be here with state " << parser_state_
348 << "with line " << line;
349 }
350 }
351}
352
353void JsspParser::ProcessTardinessLine(const std::string& line) {
354 const std::vector<std::string> words =
355 absl::StrSplit(line, ' ', absl::SkipEmpty());
356 switch (parser_state_) {
357 case START: {
358 CHECK_EQ(3, words.size());
359 SetJobs(strtoint32(words[0]));
360 SetMachines(strtoint32(words[1]));
361 parser_state_ = JOB_COUNT_READ;
362 current_job_index_ = 0;
363 break;
364 }
365 case JOB_COUNT_READ: {
366 CHECK_GE(words.size(), 6);
367 Job* const job = problem_.mutable_jobs(current_job_index_);
368 const int64_t est = strtoint64(words[0]);
369 if (est != 0L) {
370 job->mutable_earliest_start()->set_value(est);
371 }
372 job->set_late_due_date(strtoint64(words[1]));
373 const double weight = std::stod(words[2]);
374 const int64_t tardiness = static_cast<int64_t>(
375 round(weight * absl::GetFlag(FLAGS_jssp_scaling_up_factor)));
376 job->set_lateness_cost_per_time_unit(tardiness);
377 const int num_operations = strtoint32(words[3]);
378 for (int i = 0; i < num_operations; ++i) {
379 const int machine_id = strtoint32(words[4 + 2 * i]) - 1; // 1 based.
380 const int64_t duration = strtoint64(words[5 + 2 * i]);
381 Task* const task = job->add_tasks();
382 task->add_machine(machine_id);
383 task->add_duration(duration);
384 }
385 current_job_index_++;
386 if (current_job_index_ == declared_job_count_) {
387 // Fix tardiness weights if all integer from start.
388 bool all_integral = true;
389 for (const Job& job : problem_.jobs()) {
390 if (job.lateness_cost_per_time_unit() %
391 absl::GetFlag(FLAGS_jssp_scaling_up_factor) !=
392 0) {
393 all_integral = false;
394 break;
395 }
396 }
397 if (all_integral) {
398 for (Job& job : *problem_.mutable_jobs()) {
399 job.set_lateness_cost_per_time_unit(
400 job.lateness_cost_per_time_unit() /
401 absl::GetFlag(FLAGS_jssp_scaling_up_factor));
402 }
403 } else {
404 problem_.mutable_scaling_factor()->set_value(
405 1.0L / absl::GetFlag(FLAGS_jssp_scaling_up_factor));
406 }
407 parser_state_ = DONE;
408 }
409 break;
410 }
411 default: {
412 LOG(FATAL) << "Should not be here with state " << parser_state_
413 << "with line " << line;
414 }
415 }
416}
417
418void JsspParser::ProcessPssLine(const std::string& line) {
419 const std::vector<std::string> words =
420 absl::StrSplit(line, ' ', absl::SkipEmpty());
421 switch (parser_state_) {
422 case START: {
423 problem_.set_makespan_cost_per_time_unit(1L);
424 CHECK_EQ(1, words.size());
425 SetJobs(strtoint32(words[0]));
426 parser_state_ = JOB_COUNT_READ;
427 break;
428 }
429 case JOB_COUNT_READ: {
430 CHECK_EQ(1, words.size());
431 SetMachines(strtoint32(words[0]));
432 parser_state_ = MACHINE_COUNT_READ;
433 current_job_index_ = 0;
434 break;
435 }
436 case MACHINE_COUNT_READ: {
437 CHECK_EQ(1, words.size());
438 CHECK_EQ(declared_machine_count_, strtoint32(words[0]));
439 if (++current_job_index_ == declared_job_count_) {
440 parser_state_ = JOB_LENGTH_READ;
441 current_job_index_ = 0;
442 current_machine_index_ = 0;
443 }
444 break;
445 }
446 case JOB_LENGTH_READ: {
447 CHECK_EQ(4, words.size());
448 CHECK_EQ(0, strtoint32(words[2]));
449 CHECK_EQ(0, strtoint32(words[3]));
450 const int machine_id = strtoint32(words[0]) - 1;
451 const int duration = strtoint32(words[1]);
452 Job* const job = problem_.mutable_jobs(current_job_index_);
453 Task* const task = job->add_tasks();
454 task->add_machine(machine_id);
455 task->add_duration(duration);
456 if (++current_machine_index_ == declared_machine_count_) {
457 current_machine_index_ = 0;
458 if (++current_job_index_ == declared_job_count_) {
459 current_job_index_ = -1;
460 current_machine_index_ = 0;
461 parser_state_ = JOBS_READ;
462 transition_index_ = 0;
463 for (int m = 0; m < declared_machine_count_; ++m) {
464 Machine* const machine = problem_.mutable_machines(m);
465 for (int i = 0; i < declared_job_count_ * declared_job_count_;
466 ++i) {
467 machine->mutable_transition_time_matrix()->add_transition_time(0);
468 }
469 }
470 }
471 }
472 break;
473 }
474 case JOBS_READ: {
475 CHECK_EQ(1, words.size());
476 const int index = transition_index_++;
477 const int size = declared_job_count_ * declared_machine_count_ + 1;
478 const int t1 = index / size;
479 const int t2 = index % size;
480 if (t1 == 0 || t2 == 0) { // Dummy task.
481 break;
482 }
483 const int item1 = t1 - 1;
484 const int item2 = t2 - 1;
485 const int job1 = item1 / declared_machine_count_;
486 const int task1 = item1 % declared_machine_count_;
487 const int m1 = problem_.jobs(job1).tasks(task1).machine(0);
488 const int job2 = item2 / declared_machine_count_;
489 const int task2 = item2 % declared_machine_count_;
490 const int m2 = problem_.jobs(job2).tasks(task2).machine(0);
491 if (m1 != m2) { // We are only interested in same machine transitions.
492 break;
493 }
494 const int transition = strtoint32(words[0]);
495 Machine* const machine = problem_.mutable_machines(m1);
496 machine->mutable_transition_time_matrix()->set_transition_time(
497 job1 * declared_job_count_ + job2, transition);
498 if (transition_index_ == size * size) {
499 parser_state_ = DONE;
500 }
501 break;
502 }
503 default: {
504 LOG(FATAL) << "Should not be here with state " << parser_state_
505 << "with line " << line;
506 }
507 }
508}
509
510void JsspParser::ProcessEarlyTardyLine(const std::string& line) {
511 const std::vector<std::string> words =
512 absl::StrSplit(line, ' ', absl::SkipEmpty());
513 switch (parser_state_) {
514 case JOB_COUNT_READ: {
515 CHECK_EQ(words.size(), declared_machine_count_ * 2 + 3);
516 Job* const job = problem_.mutable_jobs(current_job_index_);
517 for (int i = 0; i < declared_machine_count_; ++i) {
518 const int machine_id = strtoint32(words[2 * i]);
519 const int64_t duration = strtoint64(words[2 * i + 1]);
520 Task* const task = job->add_tasks();
521 task->add_machine(machine_id);
522 task->add_duration(duration);
523 }
524 // Early Tardy problem in JET format.
525 const int due_date = strtoint32(words[declared_machine_count_ * 2]);
526 const int early_cost = strtoint32(words[declared_machine_count_ * 2 + 1]);
527 const int late_cost = strtoint32(words[declared_machine_count_ * 2 + 2]);
528 job->set_early_due_date(due_date);
529 job->set_late_due_date(due_date);
530 job->set_earliness_cost_per_time_unit(early_cost);
531 job->set_lateness_cost_per_time_unit(late_cost);
532 current_job_index_++;
533 if (current_job_index_ == declared_job_count_) {
534 parser_state_ = DONE;
535 }
536 break;
537 }
538 default: {
539 LOG(FATAL) << "Should not be here with state " << parser_state_;
540 }
541 }
542}
543
544int JsspParser::strtoint32(absl::string_view word) {
545 int result;
546 CHECK(absl::SimpleAtoi(word, &result));
547 return result;
548}
549
550int64_t JsspParser::strtoint64(absl::string_view word) {
551 int64_t result;
552 CHECK(absl::SimpleAtoi(word, &result));
553 return result;
554}
555
556} // namespace jssp
557} // namespace scheduling
558} // namespace operations_research
IntegerValue size
ABSL_FLAG(int64_t, jssp_scaling_up_factor, 100000L, "Scaling factor for floating point penalties.")
int index
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.
trees with all degrees equal w the current value of w
int64_t weight
Definition pack.cc:510
int line