diff --git a/src/main/java/jobshop/Main.java b/src/main/java/jobshop/Main.java index fbf684f..9714da8 100644 --- a/src/main/java/jobshop/Main.java +++ b/src/main/java/jobshop/Main.java @@ -118,7 +118,11 @@ public class Main { Instance instance = Instance.fromFile(path); // print some general statistics on the instance + output.println(); + output.println(); + output.println("\u001b[36m" + "------------------------------------------------------------------------" + "\u001b[0m"); output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown); + output.println(); // run all selected solvers on the instance and print the results for(int solverId = 0 ; solverId < solvers.size() ; solverId++) { diff --git a/src/main/java/jobshop/encodings/ResourceOrder.java b/src/main/java/jobshop/encodings/ResourceOrder.java index b0900cf..0dfccad 100644 --- a/src/main/java/jobshop/encodings/ResourceOrder.java +++ b/src/main/java/jobshop/encodings/ResourceOrder.java @@ -17,6 +17,10 @@ public final class ResourceOrder extends Encoding { // for each machine, indicate how many tasks have been initialized final int[] nextFreeSlot; + public Task[][] getTasksByMachine() { + return tasksByMachine; + } + /** Creates a new empty resource order. */ public ResourceOrder(Instance instance) { diff --git a/src/main/java/jobshop/solvers/DescentSolver.java b/src/main/java/jobshop/solvers/DescentSolver.java index a999ac5..df18175 100644 --- a/src/main/java/jobshop/solvers/DescentSolver.java +++ b/src/main/java/jobshop/solvers/DescentSolver.java @@ -1,9 +1,15 @@ package jobshop.solvers; import jobshop.Instance; +import jobshop.encodings.ResourceOrder; import jobshop.encodings.Schedule; +import jobshop.encodings.Task; import jobshop.solvers.neighborhood.Neighborhood; +import jobshop.solvers.neighborhood.Nowicki; +import java.time.format.ResolverStyle; +import java.util.Comparator; +import java.util.List; import java.util.Optional; /** An empty shell to implement a descent solver. */ @@ -24,7 +30,35 @@ public class DescentSolver implements Solver { @Override public Optional solve(Instance instance, long deadline, int randomness, int randomRunNumber) { - throw new UnsupportedOperationException(); + Schedule schedule = baseSolver.solve(instance, deadline,randomness,randomRunNumber).get(); + ResourceOrder order = new ResourceOrder(schedule); + ResourceOrder result = order; + Boolean bestFound = false; + Integer best = Integer.MAX_VALUE; + while(!bestFound) { + List neighbours = neighborhood.generateNeighbors(order); + try { + Integer finalBest = best; + order = neighbours.stream() + .filter(e -> e.toSchedule().get().isValid()) // removes invalid solutions + .filter(e -> e.toSchedule().get().makespan() < finalBest) // removes solutions that do not improve + .sorted(Comparator.comparing(e -> e.toSchedule().get().makespan())) // takes the best + .toArray(ResourceOrder[]::new)[0]; + result = order; + best = order.toSchedule().get().makespan(); + System.out.println("\u001b[32m" + "Current best makespam : " + best + "\u001b[0m, " + + "\u001b[33m" + "Number of neighbours : " + getNumberOfNeighbours(neighbours) + "\u001b[0m"); + } catch (Exception e) { // no solution found ==> stop + bestFound = true; + } + } + return result.toSchedule(); + } + // used to compute the number of neighbours seen + private int getNumberOfNeighbours(List neighbours) { + return neighbours.stream() + .filter(e -> e.toSchedule().get().isValid()) // removes invalid solutions + .toArray(ResourceOrder[]::new).length; } } diff --git a/src/main/java/jobshop/solvers/DescentSolverMultiStart.java b/src/main/java/jobshop/solvers/DescentSolverMultiStart.java new file mode 100644 index 0000000..199a19d --- /dev/null +++ b/src/main/java/jobshop/solvers/DescentSolverMultiStart.java @@ -0,0 +1,41 @@ +package jobshop.solvers; + +import jobshop.Instance; +import jobshop.encodings.Schedule; +import jobshop.solvers.neighborhood.Neighborhood; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class DescentSolverMultiStart implements Solver { + final Neighborhood neighborhood; + final List solvers = List.of(new GreedySolver(GreedySolver.Priority.SPT), + new GreedySolver(GreedySolver.Priority.LRPT), + new GreedySolver(GreedySolver.Priority.EST_SPT), + new GreedySolver(GreedySolver.Priority.EST_LRPT)); + + public DescentSolverMultiStart(Neighborhood neighborhood) { + this.neighborhood = neighborhood; + } + + @Override + public Optional solve(Instance instance, long deadline, int randomness, int randomRunNumber){ + Integer best = Integer.MAX_VALUE; + Schedule bestSchedule = null; + + // we try all greedy solvers for the starting solution + for (Solver s : solvers){ + System.out.println(); + System.out.println("\u001b[36m" + "Trying with solver : " + s.toString() + "\u001b[0m"); + DescentSolver descentSolver = new DescentSolver(neighborhood, s); + Schedule solution = descentSolver.solve(instance,deadline,randomness,randomRunNumber).get(); + if (solution.makespan() < best){ + best = solution.makespan(); + bestSchedule = solution; + } + } + return Optional.of(bestSchedule); + } +} diff --git a/src/main/java/jobshop/solvers/GreedySolver.java b/src/main/java/jobshop/solvers/GreedySolver.java index 118bc65..cbe5058 100644 --- a/src/main/java/jobshop/solvers/GreedySolver.java +++ b/src/main/java/jobshop/solvers/GreedySolver.java @@ -24,6 +24,13 @@ public class GreedySolver implements Solver { } + @Override + public String toString() { + return "GreedySolver{" + + "priority=" + priority + + '}'; + } + private Integer heuristiqueSPT(Task t, Instance instance){ return instance.duration(t); } diff --git a/src/main/java/jobshop/solvers/Solver.java b/src/main/java/jobshop/solvers/Solver.java index 1d05eef..66f63f1 100644 --- a/src/main/java/jobshop/solvers/Solver.java +++ b/src/main/java/jobshop/solvers/Solver.java @@ -2,6 +2,7 @@ package jobshop.solvers; import jobshop.Instance; import jobshop.encodings.Schedule; +import jobshop.solvers.neighborhood.Nowicki; import java.util.Optional; @@ -25,7 +26,9 @@ public interface Solver { case "lrpt": return new GreedySolver(GreedySolver.Priority.LRPT); case "est_spt": return new GreedySolver(GreedySolver.Priority.EST_SPT); case "est_lrpt": return new GreedySolver(GreedySolver.Priority.EST_LRPT); - // TODO: add new solvers + case "desc_est_spt": return new DescentSolver(new Nowicki(), new GreedySolver(GreedySolver.Priority.EST_SPT)); + case "desc_est_lrpt": return new DescentSolver(new Nowicki(), new GreedySolver(GreedySolver.Priority.EST_LRPT)); + case "desc_multi": return new DescentSolverMultiStart(new Nowicki()); default: throw new RuntimeException("Unknown solver: "+ name); } } diff --git a/src/main/java/jobshop/solvers/neighborhood/Nowicki.java b/src/main/java/jobshop/solvers/neighborhood/Nowicki.java index c4d74a2..5b5ed62 100644 --- a/src/main/java/jobshop/solvers/neighborhood/Nowicki.java +++ b/src/main/java/jobshop/solvers/neighborhood/Nowicki.java @@ -1,8 +1,12 @@ package jobshop.solvers.neighborhood; +import jobshop.Instance; import jobshop.encodings.ResourceOrder; +import jobshop.encodings.Schedule; +import jobshop.encodings.Task; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -87,7 +91,9 @@ public class Nowicki extends Neighborhood { * The original ResourceOrder MUST NOT be modified by this operation. */ public ResourceOrder generateFrom(ResourceOrder original) { - throw new UnsupportedOperationException(); + ResourceOrder result = original.copy(); + result.swapTasks(machine,t1,t2); + return result; } @Override @@ -126,12 +132,56 @@ public class Nowicki extends Neighborhood { /** Returns a list of all the blocks of the critical path. */ List blocksOfCriticalPath(ResourceOrder order) { - throw new UnsupportedOperationException(); + ArrayList blocks = new ArrayList<>(); + Schedule schedule = order.toSchedule().get(); + List lTask = schedule.criticalPath(); + Instance instance = order.instance; + + // order in which a machine does the tasks + Task[][] tasksByMachine = order.getTasksByMachine(); + int currentMachine = -1; + + Task firstTask = lTask.get(0); + Task lastTask = lTask.get(0); + + for (Task task: lTask){ + // while the machine is the same we update the last task + if (currentMachine == instance.machine(task)){ + lastTask = task; + } else { + // then we add a new block + if (!firstTask.equals(lastTask)){ + blocks.add(new Block(currentMachine, + Arrays.asList(tasksByMachine[currentMachine]).indexOf(firstTask), + Arrays.asList(tasksByMachine[currentMachine]).indexOf(lastTask) + )); + } + // we update the pointers to the first and last tasks + firstTask = task; + lastTask = task; + currentMachine = instance.machine(task); + } + } + return blocks; } /** For a given block, return the possible swaps for the Nowicki and Smutnicki neighborhood */ List neighbors(Block block) { - throw new UnsupportedOperationException(); + ArrayList swaps = new ArrayList<>(); + + // case 2 tasks : 1 neighbour + if (block.firstTask - block.lastTask == 1){ + swaps.add(new Swap(block.machine, block.firstTask, block.lastTask)); + } else { + // case >2 tasks : 2 neighbours + if (block.firstTask != block.lastTask) { + swaps.add(new Swap(block.machine, block.firstTask, block.firstTask + 1)); + swaps.add(new Swap(block.machine, block.lastTask -1, block.lastTask)); + } else { + throw new RuntimeException("block with only one task"); + } + } + return swaps; } }