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