No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Schedule.java 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. package jobshop.encodings;
  2. import jobshop.Instance;
  3. import java.util.*;
  4. import java.util.stream.IntStream;
  5. public class Schedule {
  6. public final Instance pb;
  7. // start times of each job and task
  8. // times[j][i] is the start time of task (j,i) : i^th task of the j^th job
  9. final int[][] times;
  10. /** Creates a new schedule for the given instance where all start times are uninitialized. */
  11. public Schedule(Instance pb) {
  12. this.pb = pb;
  13. this.times = new int[pb.numJobs][];
  14. for(int j = 0 ; j < pb.numJobs ; j++) {
  15. this.times[j] = new int[pb.numTasks];
  16. }
  17. }
  18. /** Sets the start time of the given task. */
  19. public void setStartTime(int job, int task, int startTime) {
  20. times[job][task] = startTime;
  21. }
  22. /** Returns true if this schedule is valid (no constraint is violated) */
  23. public boolean isValid() {
  24. for(int j = 0 ; j<pb.numJobs ; j++) {
  25. for(int t = 1 ; t<pb.numTasks ; t++) {
  26. if(startTime(j, t-1) + pb.duration(j, t-1) > startTime(j, t))
  27. return false;
  28. }
  29. for(int t = 0 ; t<pb.numTasks ; t++) {
  30. if(startTime(j, t) < 0)
  31. return false;
  32. }
  33. }
  34. for (int machine = 0 ; machine < pb.numMachines ; machine++) {
  35. for(int j1=0 ; j1<pb.numJobs ; j1++) {
  36. int t1 = pb.task_with_machine(j1, machine);
  37. for(int j2=j1+1 ; j2<pb.numJobs ; j2++) {
  38. int t2 = pb.task_with_machine(j2, machine);
  39. boolean t1_first = startTime(j1, t1) + pb.duration(j1, t1) <= startTime(j2, t2);
  40. boolean t2_first = startTime(j2, t2) + pb.duration(j2, t2) <= startTime(j1, t1);
  41. if(!t1_first && !t2_first)
  42. return false;
  43. }
  44. }
  45. }
  46. return true;
  47. }
  48. /** Makespan of the solution.
  49. * The makespan is the end time of the latest finishing task.
  50. */
  51. public int makespan() {
  52. int max = -1;
  53. for(int j = 0 ; j<pb.numJobs ; j++) {
  54. max = Math.max(max, endTime(j, pb.numTasks-1));
  55. }
  56. return max;
  57. }
  58. /** Start time of the given task. */
  59. public int startTime(int job, int task) {
  60. return times[job][task];
  61. }
  62. /** Start time of the given task. */
  63. public int startTime(Task task) {
  64. return startTime(task.job, task.task);
  65. }
  66. /** End time of the given task. */
  67. public int endTime(int job, int task) {
  68. return startTime(job, task) + pb.duration(job, task);
  69. }
  70. /** End time of the given task. */
  71. public int endTime(Task task) {
  72. return endTime(task.job, task.task);
  73. }
  74. /** Returns true if the given sequence of task is a critical path of the schedule. */
  75. public boolean isCriticalPath(List<Task> path) {
  76. if(startTime(path.get(0)) != 0) {
  77. return false;
  78. }
  79. if(endTime(path.get(path.size()-1)) != makespan()) {
  80. return false;
  81. }
  82. for(int i=0 ; i<path.size()-1 ; i++) {
  83. if(endTime(path.get(i)) != startTime(path.get(i+1)))
  84. return false;
  85. }
  86. return true;
  87. }
  88. /** Computes a critical path of the schedule.
  89. *
  90. * @return A sequence of task along a critical path.
  91. */
  92. public List<Task> criticalPath() {
  93. // select task with greatest end time
  94. Task ldd = IntStream.range(0, pb.numJobs)
  95. .mapToObj(j -> new Task(j, pb.numTasks-1))
  96. .max(Comparator.comparing(this::endTime))
  97. .get();
  98. assert endTime(ldd) == makespan();
  99. // list that will contain the critical path.
  100. // we construct it from the end, starting with the
  101. // task that finishes last
  102. LinkedList<Task> path = new LinkedList<>();
  103. path.add(0,ldd);
  104. // keep adding tasks to the path until the first task in the path
  105. // starts a time 0
  106. while(startTime(path.getFirst()) != 0) {
  107. Task cur = path.getFirst();
  108. int machine = pb.machine(cur.job, cur.task);
  109. // will contain the task that was delaying the start
  110. // of our current task
  111. Optional<Task> latestPredecessor = Optional.empty();
  112. if(cur.task > 0) {
  113. // our current task has a predecessor on the job
  114. Task predOnJob = new Task(cur.job, cur.task -1);
  115. // if it was the delaying task, save it to predecessor
  116. if(endTime(predOnJob) == startTime(cur))
  117. latestPredecessor = Optional.of(predOnJob);
  118. }
  119. if(latestPredecessor.isEmpty()) {
  120. // no latest predecessor found yet, look among tasks executing on the same machine
  121. latestPredecessor = IntStream.range(0, pb.numJobs)
  122. .mapToObj(j -> new Task(j, pb.task_with_machine(j, machine)))
  123. .filter(t -> endTime(t) == startTime(cur))
  124. .findFirst();
  125. }
  126. // at this point we should have identified a latest predecessor, either on the job or on the machine
  127. assert latestPredecessor.isPresent() && endTime(latestPredecessor.get()) == startTime(cur);
  128. // insert predecessor at the beginning of the path
  129. path.add(0, latestPredecessor.get());
  130. }
  131. assert isCriticalPath(path);
  132. return path;
  133. }
  134. @Override
  135. public String toString() {
  136. StringBuilder sb = new StringBuilder();
  137. sb.append("\nStart times of all tasks:\n");
  138. for(int job=0; job<pb.numJobs; job++) {
  139. sb.append("Job ");
  140. sb.append(job);
  141. sb.append(": ");
  142. for(int task=0; task<pb.numTasks; task++) {
  143. sb.append(String.format("%5d", startTime(job, task)));
  144. }
  145. sb.append("\n");
  146. }
  147. return sb.toString();
  148. }
  149. /**
  150. * Returns a string containing the Gantt chart of the given schedule, in ASCII art.
  151. *
  152. * Each line of the Gantt chart, contains the tasks of a particular job. Each character in the output represents a
  153. * fixed number of time units
  154. * For each task, we indicate :
  155. * - the machine on which the task must be executed
  156. * - whether this task is on the critical path (task on the critical path are filled in with stars).
  157. */
  158. public String asciiGantt() {
  159. var criticalPath = this.criticalPath();
  160. int minTaskDur = IntStream.range(0, pb.numJobs).flatMap(job -> IntStream.range(0, pb.numTasks).map(task -> pb.duration(job, task))).min().getAsInt();
  161. // time units by character
  162. int charsPerTimeUnit = minTaskDur >= 5 ? 1 : (5 / minTaskDur) +1;
  163. StringBuilder sb = new StringBuilder();
  164. sb.append("\nGantt Chart\n");
  165. for(int job=0; job<pb.numJobs; job++) {
  166. sb.append(String.format("Job %2d: ", job));
  167. int cursor = 0;
  168. for(int task=0; task<pb.numTasks; task++) {
  169. Task t = new Task(job, task);
  170. var st = startTime(job, task);
  171. // add spaces until the start of our task
  172. sb.append(" ".repeat(charsPerTimeUnit * (st - cursor )));
  173. sb.append(formatTask(t, charsPerTimeUnit, criticalPath.contains(t)));
  174. cursor = endTime(job, task);
  175. }
  176. sb.append("\n");
  177. }
  178. return sb.toString();
  179. }
  180. /** Utility function to display a set of characters representing a task in a gantt chart.
  181. *
  182. * @param t Task to display
  183. * @param charPerTimeUnit How many characters to represent a time unit.
  184. * @param isCritical Is the task on the critical path.
  185. * @return Ascii representation of the task. Length is the duration * charPerTimeUnit.
  186. */
  187. String formatTask(Task t, int charPerTimeUnit, boolean isCritical) {
  188. StringBuilder sb = new StringBuilder();
  189. String fill = isCritical ? "*" : "-";
  190. int dur = pb.duration(t);
  191. int machine = pb.machine(t);
  192. int stringLength = dur * charPerTimeUnit;
  193. int charsForMachine = machine < 10 ? 1 : 2;
  194. int numSpaces = stringLength - 2 - charsForMachine; // we use 2 chars for '[' and '[' + 1 or 2 for the machine number
  195. int startSpaces = numSpaces / 2;
  196. int endSpaces = numSpaces - startSpaces;
  197. sb.append("[");
  198. sb.append(fill.repeat(startSpaces - 1));
  199. sb.append(" ");
  200. sb.append(machine);
  201. sb.append(" ");
  202. sb.append(fill.repeat(endSpaces - 1));
  203. sb.append("]");
  204. return sb.toString();
  205. }
  206. }