diff --git a/src/main/java/jobshop/BestKnownResult.java b/src/main/java/jobshop/BestKnownResult.java index 55d5f9b..e0cf7ea 100644 --- a/src/main/java/jobshop/BestKnownResult.java +++ b/src/main/java/jobshop/BestKnownResult.java @@ -1,189 +1,180 @@ -package jobshop; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.stream.Collectors; - -public class BestKnownResult { - - public static boolean isKnown(String instanceName) { - return bests.containsKey(instanceName); - } - - public static List instancesMatching(String namePrefix) { - return Arrays.stream(instances) - .filter(i -> i.startsWith(namePrefix)) - .sorted() - .collect(Collectors.toList()); - } - - public static int of(String instanceName) { - if(!bests.containsKey(instanceName)) { - throw new RuntimeException("Unknown best result for "+instanceName); - } - return bests.get(instanceName); - } - - static private HashMap bests; - static String[] instances; - static { - bests = new HashMap<>(); - bests.put("aaa1", 11); - bests.put("abz5", 1234); - bests.put("abz6", 943); - bests.put("abz7", 656); - bests.put("abz8", 665); - bests.put("abz9", 679); - bests.put("ft06", 55); - bests.put("ft10", 930); - bests.put("ft20", 1165); - bests.put("la01", 666); - bests.put("la02", 655); - bests.put("la03", 597); - bests.put("la04", 590); - bests.put("la05", 593); - bests.put("la06", 926); - bests.put("la07", 890); - bests.put("la08", 863); - bests.put("la09", 951); - bests.put("la10", 958); - bests.put("la11", 1222); - bests.put("la12", 1039); - bests.put("la13", 1150); - bests.put("la14", 1292); - bests.put("la15", 1207); - bests.put("la16", 945); - bests.put("la17", 784); - bests.put("la18", 848); - bests.put("la19", 842); - bests.put("la20", 902); - bests.put("la21", 1046); - bests.put("la22", 927); - bests.put("la23", 1032); - bests.put("la24", 935); - bests.put("la25", 977); - bests.put("la26", 1218); - bests.put("la27", 1235); - bests.put("la28", 1216); - bests.put("la29", 1152); - bests.put("la30", 1355); - bests.put("la31", 1784); - bests.put("la32", 1850); - bests.put("la33", 1719); - bests.put("la34", 1721); - bests.put("la35", 1888); - bests.put("la36", 1268); - bests.put("la37", 1397); - bests.put("la38", 1196); - bests.put("la39", 1233); - bests.put("la40", 1222); - bests.put("orb01", 1059); - bests.put("orb02", 888); - bests.put("orb03", 1005); - bests.put("orb04", 1005); - bests.put("orb05", 887); - bests.put("orb06", 1010); - bests.put("orb07", 397); - bests.put("orb08", 899); - bests.put("orb09", 934); - bests.put("orb10", 944); - bests.put("swv01", 1407); - bests.put("swv02", 1475); - bests.put("swv03", 1398); - bests.put("swv04", 1474); - bests.put("swv05", 1424); - bests.put("swv06", 1678); - bests.put("swv07", 1600); - bests.put("swv08", 1763); - bests.put("swv09", 1661); - bests.put("swv10", 1767); - bests.put("swv11", 2991); - bests.put("swv12", 3003); - bests.put("swv13", 3104); - bests.put("swv14", 2968); - bests.put("swv15", 2904); - bests.put("swv16", 2924); - bests.put("swv17", 2794); - bests.put("swv18", 2852); - bests.put("swv19", 2843); - bests.put("swv20", 2823); - bests.put("yn1", 885); - bests.put("yn2", 909); - bests.put("yn3", 892); - bests.put("yn4", 968); - bests.put("ta01", 1231); - bests.put("ta02", 1244); - bests.put("ta03", 1218); - bests.put("ta04", 1175); - bests.put("ta05", 1224); - bests.put("ta06", 1238); - bests.put("ta07", 1227); - bests.put("ta08", 1217); - bests.put("ta09", 1274); - bests.put("ta10", 1241); - bests.put("ta11", 1361); - bests.put("ta12", 1367); - bests.put("ta13", 1342); - bests.put("ta14", 1345); - bests.put("ta15", 1340); - bests.put("ta16", 1360); - bests.put("ta17", 1462); - bests.put("ta18", 1396); - bests.put("ta19", 1335); - bests.put("ta20", 1351); - bests.put("ta21", 1644); - bests.put("ta22", 1600); - bests.put("ta23", 1557); - bests.put("ta24", 1647); - bests.put("ta25", 1595); - bests.put("ta26", 1645); - bests.put("ta27", 1680); - bests.put("ta28", 1614); - bests.put("ta29", 1635); - bests.put("ta30", 1584); - bests.put("ta31", 1764); - bests.put("ta32", 1796); - bests.put("ta33", 1793); - bests.put("ta34", 1829); - bests.put("ta35", 2007); - bests.put("ta36", 1819); - bests.put("ta37", 1778); - bests.put("ta38", 1673); - bests.put("ta39", 1795); - bests.put("ta40", 1674); - bests.put("ta41", 2018); - bests.put("ta42", 1956); - bests.put("ta43", 1859); - bests.put("ta44", 1984); - bests.put("ta45", 2000); - bests.put("ta46", 2021); - bests.put("ta47", 1903); - bests.put("ta48", 1952); - bests.put("ta49", 1968); - bests.put("ta50", 1926); - bests.put("ta51", 2760); - bests.put("ta52", 2756); - bests.put("ta53", 2717); - bests.put("ta54", 2839); - bests.put("ta55", 2679); - bests.put("ta56", 2781); - bests.put("ta57", 2943); - bests.put("ta58", 2885); - bests.put("ta59", 2655); - bests.put("ta60", 2723); - bests.put("ta61", 2868); - bests.put("ta62", 2869); - bests.put("ta63", 2755); - bests.put("ta64", 2702); - bests.put("ta65", 2725); - bests.put("ta66", 2845); - bests.put("ta67", 2825); - bests.put("ta68", 2784); - bests.put("ta69", 3071); - bests.put("ta70", 2995); - instances = bests.keySet().toArray(new String[0]); - Arrays.sort(instances); - } - -} +package jobshop; + +import java.util.Arrays; +import java.util.HashMap; + +public class BestKnownResult { + + public static boolean isKnown(String instanceName) { + return bests.containsKey(instanceName); + } + + public static int of(String instanceName) { + if(!bests.containsKey(instanceName)) { + throw new RuntimeException("Unknown best result for "+instanceName); + } + return bests.get(instanceName); + } + + static private HashMap bests; + static String[] instances; + static { + bests = new HashMap<>(); + bests.put("aaa1", 11); + bests.put("abz5", 1234); + bests.put("abz6", 943); + bests.put("abz7", 656); + bests.put("abz8", 665); + bests.put("abz9", 679); + bests.put("ft06", 55); + bests.put("ft10", 930); + bests.put("ft20", 1165); + bests.put("la01", 666); + bests.put("la02", 655); + bests.put("la03", 597); + bests.put("la04", 590); + bests.put("la05", 593); + bests.put("la06", 926); + bests.put("la07", 890); + bests.put("la08", 863); + bests.put("la09", 951); + bests.put("la10", 958); + bests.put("la11", 1222); + bests.put("la12", 1039); + bests.put("la13", 1150); + bests.put("la14", 1292); + bests.put("la15", 1207); + bests.put("la16", 945); + bests.put("la17", 784); + bests.put("la18", 848); + bests.put("la19", 842); + bests.put("la20", 902); + bests.put("la21", 1046); + bests.put("la22", 927); + bests.put("la23", 1032); + bests.put("la24", 935); + bests.put("la25", 977); + bests.put("la26", 1218); + bests.put("la27", 1235); + bests.put("la28", 1216); + bests.put("la29", 1152); + bests.put("la30", 1355); + bests.put("la31", 1784); + bests.put("la32", 1850); + bests.put("la33", 1719); + bests.put("la34", 1721); + bests.put("la35", 1888); + bests.put("la36", 1268); + bests.put("la37", 1397); + bests.put("la38", 1196); + bests.put("la39", 1233); + bests.put("la40", 1222); + bests.put("orb01", 1059); + bests.put("orb02", 888); + bests.put("orb03", 1005); + bests.put("orb04", 1005); + bests.put("orb05", 887); + bests.put("orb06", 1010); + bests.put("orb07", 397); + bests.put("orb08", 899); + bests.put("orb09", 934); + bests.put("orb10", 944); + bests.put("swv01", 1407); + bests.put("swv02", 1475); + bests.put("swv03", 1398); + bests.put("swv04", 1474); + bests.put("swv05", 1424); + bests.put("swv06", 1678); + bests.put("swv07", 1600); + bests.put("swv08", 1763); + bests.put("swv09", 1661); + bests.put("swv10", 1767); + bests.put("swv11", 2991); + bests.put("swv12", 3003); + bests.put("swv13", 3104); + bests.put("swv14", 2968); + bests.put("swv15", 2904); + bests.put("swv16", 2924); + bests.put("swv17", 2794); + bests.put("swv18", 2852); + bests.put("swv19", 2843); + bests.put("swv20", 2823); + bests.put("yn1", 885); + bests.put("yn2", 909); + bests.put("yn3", 892); + bests.put("yn4", 968); + bests.put("ta01", 1231); + bests.put("ta02", 1244); + bests.put("ta03", 1218); + bests.put("ta04", 1175); + bests.put("ta05", 1224); + bests.put("ta06", 1238); + bests.put("ta07", 1227); + bests.put("ta08", 1217); + bests.put("ta09", 1274); + bests.put("ta10", 1241); + bests.put("ta11", 1361); + bests.put("ta12", 1367); + bests.put("ta13", 1342); + bests.put("ta14", 1345); + bests.put("ta15", 1340); + bests.put("ta16", 1360); + bests.put("ta17", 1462); + bests.put("ta18", 1396); + bests.put("ta19", 1335); + bests.put("ta20", 1351); + bests.put("ta21", 1644); + bests.put("ta22", 1600); + bests.put("ta23", 1557); + bests.put("ta24", 1647); + bests.put("ta25", 1595); + bests.put("ta26", 1645); + bests.put("ta27", 1680); + bests.put("ta28", 1614); + bests.put("ta29", 1635); + bests.put("ta30", 1584); + bests.put("ta31", 1764); + bests.put("ta32", 1796); + bests.put("ta33", 1793); + bests.put("ta34", 1829); + bests.put("ta35", 2007); + bests.put("ta36", 1819); + bests.put("ta37", 1778); + bests.put("ta38", 1673); + bests.put("ta39", 1795); + bests.put("ta40", 1674); + bests.put("ta41", 2018); + bests.put("ta42", 1956); + bests.put("ta43", 1859); + bests.put("ta44", 1984); + bests.put("ta45", 2000); + bests.put("ta46", 2021); + bests.put("ta47", 1903); + bests.put("ta48", 1952); + bests.put("ta49", 1968); + bests.put("ta50", 1926); + bests.put("ta51", 2760); + bests.put("ta52", 2756); + bests.put("ta53", 2717); + bests.put("ta54", 2839); + bests.put("ta55", 2679); + bests.put("ta56", 2781); + bests.put("ta57", 2943); + bests.put("ta58", 2885); + bests.put("ta59", 2655); + bests.put("ta60", 2723); + bests.put("ta61", 2868); + bests.put("ta62", 2869); + bests.put("ta63", 2755); + bests.put("ta64", 2702); + bests.put("ta65", 2725); + bests.put("ta66", 2845); + bests.put("ta67", 2825); + bests.put("ta68", 2784); + bests.put("ta69", 3071); + bests.put("ta70", 2995); + instances = bests.keySet().toArray(new String[0]); + Arrays.sort(instances); + } + +} diff --git a/src/main/java/jobshop/DebuggingMain.java b/src/main/java/jobshop/DebuggingMain.java index 9261b73..7fa6b61 100644 --- a/src/main/java/jobshop/DebuggingMain.java +++ b/src/main/java/jobshop/DebuggingMain.java @@ -1,42 +1,114 @@ -package jobshop; - -import jobshop.encodings.JobNumbers; - -import java.io.IOException; -import java.nio.file.Paths; - -public class DebuggingMain { - - public static void main(String[] args) { - try { - // load the aaa1 instance - Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); - - // construit une solution dans la représentation par - // numéro de jobs : [0 1 1 0 0 1] - // Note : cette solution a aussi été vue dans les exercices (section 3.3) - // mais on commençait à compter à 1 ce qui donnait [1 2 2 1 1 2] - JobNumbers enc = new JobNumbers(instance); - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - - System.out.println("\nENCODING: " + enc); - - Schedule sched = enc.toSchedule(); - // TODO: make it print something meaningful - // by implementing the toString() method - System.out.println("SCHEDULE: " + sched); - System.out.println("VALID: " + sched.isValid()); - System.out.println("MAKESPAN: " + sched.makespan()); - - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - - } -} +package jobshop; + +import jobshop.encodings.JobNumbers; +import jobshop.encodings.ResourceOrder; +import jobshop.encodings.Task; +import jobshop.solvers.GreedySolver; +import jobshop.solvers.GreedySolver.PriorityESTRule; +import jobshop.solvers.GreedySolver.PriorityRule; + +import java.io.IOException; +import java.nio.file.Paths; + +public class DebuggingMain { + + public static void main(String[] args) { + try { + // load the aaa1 instance + Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); + + // construit une solution dans la représentation par + // numéro de jobs : [0 1 1 0 0 1] + // Note : cette solution a aussi été vue dans les exercices (section 3.3) + // mais on commençait à compter à 1 ce qui donnait [1 2 2 1 1 2] + JobNumbers enc = new JobNumbers(instance); + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + + System.out.println("\nJOB NUMBER ENCODING: " + enc + "\n"); + + Schedule sched = enc.toSchedule(); + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid() + "\n"); + System.out.println("MAKESPAN: " + sched.makespan() + "\n"); + + System.out.println("---------------------------------------------\n"); + + ResourceOrder ro = new ResourceOrder(instance); + ro.tasksByMachine[0][0] = new Task(0,0); + ro.tasksByMachine[0][1] = new Task(1,1); + ro.tasksByMachine[1][0] = new Task(1,0); + ro.tasksByMachine[1][1] = new Task(0,1); + ro.tasksByMachine[2][0] = new Task(0,2); + ro.tasksByMachine[2][1] = new Task(1,2); + + System.out.println("RESOURCE ORDER ENCODING:\n" + ro + "\n"); + + System.out.println("Default Solver\n"); + sched = ro.toSchedule(); + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid()); + System.out.println("MAKESPAN: " + sched.makespan()); + + System.out.println("---------------------------------------------\n"); + + JobNumbers jo = JobNumbers.fromSchedule(sched); + System.out.println("JOB NUMBER ENCODING (FROM_SCHEDULE): " + jo + "\n"); + + System.out.println("---------------------------------------------\n"); + System.out.println("Greedy Solver: STP"); + PriorityRule SPT = PriorityRule.SPT; + Solver solverSPT = new GreedySolver(SPT); + Result resultSPT = solverSPT.solve(instance, System.currentTimeMillis() + 10); + sched = resultSPT.schedule; + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid()); + System.out.println("MAKESPAN: " + sched.makespan()); + + System.out.println("---------------------------------------------\n"); + System.out.println("Greedy Solver: LRPT\n"); + PriorityRule LRPT = PriorityRule.LRPT; + Solver solverLRPT = new GreedySolver(LRPT); + Result resultLRPT = solverLRPT.solve(instance, System.currentTimeMillis() + 10); + sched = resultLRPT.schedule; + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid()); + System.out.println("MAKESPAN: " + sched.makespan()); + + System.out.println("---------------------------------------------\n"); + System.out.println("Greedy Solver: EST_SPT\n"); + PriorityESTRule EST_SPT = PriorityESTRule.EST_SPT; + Solver solverEST_SPT = new GreedySolver(EST_SPT); + Result resultEST_SPT = solverEST_SPT.solve(instance, System.currentTimeMillis() + 10); + sched = resultEST_SPT.schedule; + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid()); + System.out.println("MAKESPAN: " + sched.makespan()); + + System.out.println("---------------------------------------------\n"); + System.out.println("Greedy Solver: EST_SPT\n"); + PriorityESTRule EST_LRPT = PriorityESTRule.EST_LRPT; + Solver solverEST_LRPT = new GreedySolver(EST_SPT); + Result resultEST_LRPT = solverEST_LRPT.solve(instance, System.currentTimeMillis() + 10); + sched = resultEST_LRPT.schedule; + + System.out.println("SCHEDULE:\n" + sched); + System.out.println("VALID: " + sched.isValid()); + System.out.println("MAKESPAN: " + sched.makespan()); + + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + + } +} diff --git a/src/main/java/jobshop/Encoding.java b/src/main/java/jobshop/Encoding.java index d6f0245..026ad89 100644 --- a/src/main/java/jobshop/Encoding.java +++ b/src/main/java/jobshop/Encoding.java @@ -1,12 +1,12 @@ -package jobshop; - -public abstract class Encoding { - - public final Instance instance; - - public Encoding(Instance instance) { - this.instance = instance; - } - - public abstract Schedule toSchedule(); -} +package jobshop; + +public abstract class Encoding { + + public final Instance instance; + + public Encoding(Instance instance) { + this.instance = instance; + } + + public abstract Schedule toSchedule(); +} diff --git a/src/main/java/jobshop/Instance.java b/src/main/java/jobshop/Instance.java index 73bcce7..001faad 100644 --- a/src/main/java/jobshop/Instance.java +++ b/src/main/java/jobshop/Instance.java @@ -1,79 +1,73 @@ -package jobshop; - -import jobshop.encodings.Task; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Iterator; -import java.util.Scanner; -import java.util.stream.Collectors; - -public class Instance { - - /** Number of jobs in the instance */ - public final int numJobs; - - /** Number of tasks per job */ - public final int numTasks; - - /** Number of machines, assumed to be same as number of tasks. */ - public final int numMachines; - - final int[][] durations; - final int[][] machines; - - public int duration(int job, int task) { - return durations[job][task]; - } - public int duration(Task t) { - return duration(t.job, t.task); - } - public int machine(int job, int task) { - return machines[job][task]; - } - public int machine(Task t) { - return this.machine(t.job, t.task); - } - - /** among the tasks of the given job, returns the task index that uses the given machine. */ - public int task_with_machine(int job, int wanted_machine) { - for(int task = 0 ; task < numTasks ; task++) { - if(machine(job, task) == wanted_machine) - return task; - } - throw new RuntimeException("No task targeting machine "+wanted_machine+" on job "+job); - } - - Instance(int numJobs, int numTasks) { - this.numJobs = numJobs; - this.numTasks = numTasks; - this.numMachines = numTasks; - - durations = new int[numJobs][numTasks]; - machines = new int[numJobs][numTasks]; - } - - /** Parses a instance from a file. */ - public static Instance fromFile(Path path) throws IOException { - Iterator lines = Files.readAllLines(path).stream() - .filter(l -> !l.startsWith("#")) - .collect(Collectors.toList()) - .iterator(); - - Scanner header = new Scanner(lines.next()); - int num_jobs = header.nextInt(); - int num_tasks = header.nextInt(); - Instance pb = new Instance(num_jobs, num_tasks); - - for(int job = 0 ; job lines = Files.readAllLines(path).stream() + .filter(l -> !l.startsWith("#")) + .collect(Collectors.toList()) + .iterator(); + + Scanner header = new Scanner(lines.next()); + int num_jobs = header.nextInt(); + int num_tasks = header.nextInt(); + Instance pb = new Instance(num_jobs, num_tasks); + + for(int job = 0 ; job solvers; - static { - solvers = new HashMap<>(); - solvers.put("basic", new BasicSolver()); - solvers.put("random", new RandomSolver()); - // add new solvers here - } - - - public static void main(String[] args) { - ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build() - .defaultHelp(true) - .description("Solves jobshop problems."); - - parser.addArgument("-t", "--timeout") - .setDefault(1L) - .type(Long.class) - .help("Solver timeout in seconds for each instance"); - parser.addArgument("--solver") - .nargs("+") - .required(true) - .help("Solver(s) to use (space separated if more than one)"); - - parser.addArgument("--instance") - .nargs("+") - .required(true) - .help("Instance(s) to solve (space separated if more than one)"); - - Namespace ns = null; - try { - ns = parser.parseArgs(args); - } catch (ArgumentParserException e) { - parser.handleError(e); - System.exit(1); - } - - PrintStream output = System.out; - - long solveTimeMs = ns.getLong("timeout") * 1000; - - List solversToTest = ns.getList("solver"); - for(String solverName : solversToTest) { - if(!solvers.containsKey(solverName)) { - System.err.println("ERROR: Solver \"" + solverName + "\" is not avalaible."); - System.err.println(" Available solvers: " + solvers.keySet().toString()); - System.err.println(" You can provide your own solvers by adding them to the `Main.solvers` HashMap."); - System.exit(1); - } - } - List instancePrefixes = ns.getList("instance"); - List instances = new ArrayList<>(); - for(String instancePrefix : instancePrefixes) { - List matches = BestKnownResult.instancesMatching(instancePrefix); - if(matches.isEmpty()) { - System.err.println("ERROR: instance prefix \"" + instancePrefix + "\" does not match any instance."); - System.err.println(" available instances: " + Arrays.toString(BestKnownResult.instances)); - System.exit(1); - } - instances.addAll(matches); - } - - float[] runtimes = new float[solversToTest.size()]; - float[] distances = new float[solversToTest.size()]; - - try { - output.print( " "); - for(String s : solversToTest) - output.printf("%-30s", s); - output.println(); - output.print("instance size best "); - for(String s : solversToTest) { - output.print("runtime makespan ecart "); - } - output.println(); - - - for(String instanceName : instances) { - int bestKnown = BestKnownResult.of(instanceName); - - - Path path = Paths.get("instances/", instanceName); - Instance instance = Instance.fromFile(path); - - output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown); - - for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { - String solverName = solversToTest.get(solverId); - Solver solver = solvers.get(solverName); - long start = System.currentTimeMillis(); - long deadline = System.currentTimeMillis() + solveTimeMs; - Result result = solver.solve(instance, deadline); - long runtime = System.currentTimeMillis() - start; - - if(!result.schedule.isValid()) { - System.err.println("ERROR: solver returned an invalid schedule"); - System.exit(1); - } - - assert result.schedule.isValid(); - int makespan = result.schedule.makespan(); - float dist = 100f * (makespan - bestKnown) / (float) bestKnown; - runtimes[solverId] += (float) runtime / (float) instances.size(); - distances[solverId] += dist / (float) instances.size(); - - output.printf("%7d %8s %5.1f ", runtime, makespan, dist); - output.flush(); - } - output.println(); - - } - - - output.printf("%-8s %-5s %4s ", "AVG", "-", "-"); - for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { - output.printf("%7.1f %8s %5.1f ", runtimes[solverId], "-", distances[solverId]); - } - - - - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } -} +package jobshop; + +import java.io.PrintStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + + +import jobshop.solvers.BasicSolver; +import jobshop.solvers.RandomSolver; +import jobshop.solvers.GreedySolver.PriorityESTRule; +import jobshop.solvers.GreedySolver.PriorityRule; +import jobshop.solvers.GreedySolver; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import net.sourceforge.argparse4j.inf.Namespace; + + +public class Main { + + /** All solvers available in this program */ + private static HashMap solvers; + static { + solvers = new HashMap<>(); + solvers.put("basic", new BasicSolver()); + solvers.put("random", new RandomSolver()); + // add new solvers here + PriorityRule SPT = PriorityRule.SPT; + solvers.put("greedySPT", new GreedySolver(SPT)); + PriorityRule LRPT = PriorityRule.LRPT; + solvers.put("greedyLRPT", new GreedySolver(LRPT)); + PriorityESTRule EST_SPT = PriorityESTRule.EST_SPT; + solvers.put("greedyEST_SPT", new GreedySolver(EST_SPT)); + PriorityESTRule EST_LRPT = PriorityESTRule.EST_LRPT; + solvers.put("greedyEST_LRPT", new GreedySolver(EST_LRPT)); + } + + @SuppressWarnings("unused") + public static void main(String[] args) { + ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build() + .defaultHelp(true) + .description("Solves jobshop problems."); + + parser.addArgument("-t", "--timeout") + .setDefault(1L) + .type(Long.class) + .help("Solver timeout in seconds for each instance"); + parser.addArgument("--solver") + .nargs("+") + .required(true) + .help("Solver(s) to use (space separated if more than one)"); + + parser.addArgument("--instance") + .nargs("+") + .required(true) + .help("Instance(s) to solve (space separated if more than one)"); + + Namespace ns = null; + try { + ns = parser.parseArgs(args); + } catch (ArgumentParserException e) { + parser.handleError(e); + System.exit(1); + } + + PrintStream output = System.out; + + long solveTimeMs = ns.getLong("timeout") * 1000; + + List solversToTest = ns.getList("solver"); + for(String solverName : solversToTest) { + if(!solvers.containsKey(solverName)) { + System.err.println("ERROR: Solver \"" + solverName + "\" is not avalaible."); + System.err.println(" Available solvers: " + solvers.keySet().toString()); + System.err.println(" You can provide your own solvers by adding them to the `Main.solvers` HashMap."); + System.exit(1); + } + } + List instances = ns.getList("instance"); + for(String instanceName : instances) { + if(!BestKnownResult.isKnown(instanceName)) { + System.err.println("ERROR: instance \"" + instanceName + "\" is not avalaible."); + System.err.println(" available instances: " + Arrays.toString(BestKnownResult.instances)); + System.exit(1); + } + } + + float[] runtimes = new float[solversToTest.size()]; + float[] distances = new float[solversToTest.size()]; + + try { + output.print( " ");; + for(String s : solversToTest) + output.printf("%-30s", s); + output.println(); + output.print("instance size best "); + for(String s : solversToTest) { + output.print("runtime makespan ecart "); + } + output.println(); + + + for(String instanceName : instances) { + int bestKnown = BestKnownResult.of(instanceName); + + + Path path = Paths.get("instances/", instanceName); + Instance instance = Instance.fromFile(path); + + output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown); + + for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { + String solverName = solversToTest.get(solverId); + Solver solver = solvers.get(solverName); + long start = System.currentTimeMillis(); + long deadline = System.currentTimeMillis() + solveTimeMs; + Result result = solver.solve(instance, deadline); + long runtime = System.currentTimeMillis() - start; + + if(!result.schedule.isValid()) { + System.err.println("ERROR: solver returned an invalid schedule"); + System.exit(1); + } + + assert result.schedule.isValid(); + int makespan = result.schedule.makespan(); + float dist = 100f * (makespan - bestKnown) / (float) bestKnown; + runtimes[solverId] += (float) runtime / (float) instances.size(); + distances[solverId] += dist / (float) instances.size(); + + output.printf("%7d %8s %5.1f ", runtime, makespan, dist); + output.flush(); + } + output.println(); + + } + + + output.printf("%-8s %-5s %4s ", "AVG", "-", "-"); + for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { + output.printf("%7.1f %8s %5.1f ", runtimes[solverId], "-", distances[solverId]); + } + + + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/src/main/java/jobshop/Result.java b/src/main/java/jobshop/Result.java index fa9d4c3..52b1b8d 100644 --- a/src/main/java/jobshop/Result.java +++ b/src/main/java/jobshop/Result.java @@ -1,22 +1,23 @@ -package jobshop; - -import java.util.Optional; - -public class Result { - - public Result(Instance instance, Schedule schedule, ExitCause cause) { - this.instance = instance; - this.schedule = schedule; - this.cause = cause; - } - - public enum ExitCause { - Timeout, ProvedOptimal, Blocked - } - - public final Instance instance; - public final Schedule schedule; - public final ExitCause cause; - - -} +package jobshop; + +import java.util.Optional; + +@SuppressWarnings("unused") +public class Result { + + public Result(Instance instance, Schedule schedule, ExitCause cause) { + this.instance = instance; + this.schedule = schedule; + this.cause = cause; + } + + public enum ExitCause { + Timeout, ProvedOptimal, Blocked + } + + public final Instance instance; + public final Schedule schedule; + public final ExitCause cause; + + +} diff --git a/src/main/java/jobshop/Schedule.java b/src/main/java/jobshop/Schedule.java index b3bae5c..b29fbf5 100644 --- a/src/main/java/jobshop/Schedule.java +++ b/src/main/java/jobshop/Schedule.java @@ -1,135 +1,157 @@ -package jobshop; - - -import jobshop.encodings.Task; - -import java.util.*; -import java.util.stream.IntStream; - -public class Schedule { - public final Instance pb; - // start times of each job and task - // times[j][i] is the start time of task (j,i) : i^th task of the j^th job - final int[][] times; - - public Schedule(Instance pb, int[][] times) { - this.pb = pb; - this.times = new int[pb.numJobs][]; - for(int j = 0 ; j < pb.numJobs ; j++) { - this.times[j] = Arrays.copyOf(times[j], pb.numTasks); - } - } - - public int startTime(int job, int task) { - return times[job][task]; - } - - /** Returns true if this schedule is valid (no constraint is violated) */ - public boolean isValid() { - for(int j = 0 ; j startTime(j, t)) - return false; - } - for(int t = 0 ; t path) { - if(startTime(path.get(0)) != 0) { - return false; - } - if(endTime(path.get(path.size()-1)) != makespan()) { - return false; - } - for(int i=0 ; i criticalPath() { - // select task with greatest end time - Task ldd = IntStream.range(0, pb.numJobs) - .mapToObj(j -> new Task(j, pb.numTasks-1)) - .max(Comparator.comparing(this::endTime)) - .get(); - assert endTime(ldd) == makespan(); - - // list that will contain the critical path. - // we construct it from the end, starting with the - // task that finishes last - LinkedList path = new LinkedList<>(); - path.add(0,ldd); - - // keep adding tasks to the path until the first task in the path - // starts a time 0 - while(startTime(path.getFirst()) != 0) { - Task cur = path.getFirst(); - int machine = pb.machine(cur.job, cur.task); - - // will contain the task that was delaying the start - // of our current task - Optional latestPredecessor = Optional.empty(); - - if(cur.task > 0) { - // our current task has a predecessor on the job - Task predOnJob = new Task(cur.job, cur.task -1); - - // if it was the delaying task, save it to predecessor - if(endTime(predOnJob) == startTime(cur)) - latestPredecessor = Optional.of(predOnJob); - } - if(!latestPredecessor.isPresent()) { - // no latest predecessor found yet, look among tasks executing on the same machine - latestPredecessor = IntStream.range(0, pb.numJobs) - .mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) - .filter(t -> endTime(t) == startTime(cur)) - .findFirst(); - } - // at this point we should have identified a latest predecessor, either on the job or on the machine - assert latestPredecessor.isPresent() && endTime(latestPredecessor.get()) == startTime(cur); - // insert predecessor at the beginning of the path - path.add(0, latestPredecessor.get()); - } - assert isCriticalPath(path); - return path; - } -} +package jobshop; + + +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +import jobshop.encodings.Task; + +public class Schedule { + public final Instance pb; + // start times of each job and task + // times[j][i] is the start time of task (j,i) : i^th task of the j^th job + final int[][] times; + + public Schedule(Instance pb, int[][] times) { + this.pb = pb; + this.times = new int[pb.numJobs][]; + for(int j = 0 ; j < pb.numJobs ; j++) { + this.times[j] = Arrays.copyOf(times[j], pb.numTasks); + } + } + + public int startTime(int job, int task) { + return times[job][task]; + } + + /** Returns true if this schedule is valid (no constraint is violated) */ + public boolean isValid() { + for(int j = 0 ; j startTime(j, t)) + return false; + } + for(int t = 0 ; t path) { + if(startTime(path.get(0)) != 0) { + return false; + } + if(endTime(path.get(path.size()-1)) != makespan()) { + return false; + } + for(int i=0 ; i criticalPath() { + // select task with greatest end time + Task ldd = IntStream.range(0, pb.numJobs) + .mapToObj(j -> new Task(j, pb.numTasks-1)) + .max(Comparator.comparing(this::endTime)) + .get(); + assert endTime(ldd) == makespan(); + + // list that will contain the critical path. + // we construct it from the end, starting with the + // task that finishes last + LinkedList path = new LinkedList<>(); + path.add(0,ldd); + + // keep adding tasks to the path until the first task in the path + // starts a time 0 + while(startTime(path.getFirst()) != 0) { + Task cur = path.getFirst(); + int machine = pb.machine(cur.job, cur.task); + + // will contain the task that was delaying the start + // of our current task + Optional latestPredecessor = Optional.empty(); + + if(cur.task > 0) { + // our current task has a predecessor on the job + Task predOnJob = new Task(cur.job, cur.task -1); + + // if it was the delaying task, save it to predecessor + if(endTime(predOnJob) == startTime(cur)) + latestPredecessor = Optional.of(predOnJob); + } + if(!latestPredecessor.isPresent()) { + // no latest predecessor found yet, look among tasks executing on the same machine + latestPredecessor = IntStream.range(0, pb.numJobs) + .mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) + .filter(t -> endTime(t) == startTime(cur)) + .findFirst(); + } + // at this point we should have identified a latest predecessor, either on the job or on the machine + assert latestPredecessor.isPresent() && endTime(latestPredecessor.get()) == startTime(cur); + // insert predecessor at the beginning of the path + path.add(0, latestPredecessor.get()); + } + assert isCriticalPath(path); + return path; + } + + public Schedule copy() { + return new Schedule(this.pb, this.times); + } + + /****************************************************************/ + /* Implémentation de la méthode toString() de la classe Schedule*/ + /****************************************************************/ + public String toString() { + String res = ""; + for (int i = 0; i < this.times.length; i++) { + res += "Job " + Integer.toString(i + 1) + " starting times : \n"; + for (int j = 0; j < this.times[i].length; j++) { + res += "\tTask " + Integer.toString(j + 1) + " starts at time : " + Integer.toString(this.times[i][j]) + "\n"; + } + } + return res; + } +} diff --git a/src/main/java/jobshop/Solver.java b/src/main/java/jobshop/Solver.java index 20ab271..61db5ee 100644 --- a/src/main/java/jobshop/Solver.java +++ b/src/main/java/jobshop/Solver.java @@ -1,7 +1,7 @@ -package jobshop; - -public interface Solver { - - Result solve(Instance instance, long deadline); - -} +package jobshop; + +public interface Solver { + + Result solve(Instance instance, long deadline); + +} diff --git a/src/main/java/jobshop/encodings/JobNumbers.java b/src/main/java/jobshop/encodings/JobNumbers.java index b81f207..b94caab 100644 --- a/src/main/java/jobshop/encodings/JobNumbers.java +++ b/src/main/java/jobshop/encodings/JobNumbers.java @@ -1,84 +1,116 @@ -package jobshop.encodings; - -import jobshop.Encoding; -import jobshop.Instance; -import jobshop.Schedule; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.stream.IntStream; - -/** Représentation par numéro de job. */ -public class JobNumbers extends Encoding { - - /** A numJobs * numTasks array containing the representation by job numbers. */ - public final int[] jobs; - - /** In case the encoding is only partially filled, indicates the index of the first - * element of `jobs` that has not been set yet. */ - public int nextToSet = 0; - - public JobNumbers(Instance instance) { - super(instance); - - jobs = new int[instance.numJobs * instance.numMachines]; - Arrays.fill(jobs, -1); - } - - public JobNumbers(Schedule schedule) { - super(schedule.pb); - - this.jobs = new int[instance.numJobs * instance.numTasks]; - - // for each job indicates which is the next task to be scheduled - int[] nextOnJob = new int[instance.numJobs]; - - while(Arrays.stream(nextOnJob).anyMatch(t -> t < instance.numTasks)) { - Task next = IntStream - // for all jobs numbers - .range(0, instance.numJobs) - // build the next task for this job - .mapToObj(j -> new Task(j, nextOnJob[j])) - // only keep valid tasks (some jobs have no task left to be executed) - .filter(t -> t.task < instance.numTasks) - // select the task with the earliest execution time - .min(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) - .get(); - - this.jobs[nextToSet++] = next.job; - nextOnJob[next.job] += 1; - } - } - - @Override - public Schedule toSchedule() { - // time at which each machine is going to be freed - int[] nextFreeTimeResource = new int[instance.numMachines]; - - // for each job, the first task that has not yet been scheduled - int[] nextTask = new int[instance.numJobs]; - - // for each task, its start time - int[][] startTimes = new int[instance.numJobs][instance.numTasks]; - - // compute the earliest start time for every task of every job - for(int job : jobs) { - int task = nextTask[job]; - int machine = instance.machine(job, task); - // earliest start time for this task - int est = task == 0 ? 0 : startTimes[job][task-1] + instance.duration(job, task-1); - est = Math.max(est, nextFreeTimeResource[machine]); - - startTimes[job][task] = est; - nextFreeTimeResource[machine] = est + instance.duration(job, task); - nextTask[job] = task + 1; - } - - return new Schedule(instance, startTimes); - } - - @Override - public String toString() { - return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet)); - } -} +package jobshop.encodings; + +import jobshop.Encoding; +import jobshop.Instance; +import jobshop.Schedule; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.stream.IntStream; + +/** Représentation par numéro de job. */ +public class JobNumbers extends Encoding { + + /** A numJobs * numTasks array containing the representation by job numbers. */ + public final int[] jobs; + + /** In case the encoding is only partially filled, indicates the index of first + * element of `jobs` that has not been set yet. */ + public int nextToSet = 0; + + public JobNumbers(Instance instance) { + super(instance); + + jobs = new int[instance.numJobs * instance.numMachines]; + Arrays.fill(jobs, -1); + } + + public JobNumbers(Schedule schedule) { + super(schedule.pb); + + this.jobs = new int[instance.numJobs * instance.numTasks]; + + // for each job indicates which is the next task to be scheduled + int[] nextOnJob = new int[instance.numJobs]; + + while(Arrays.stream(nextOnJob).anyMatch(t -> t < instance.numTasks)) { + Task next = IntStream + // for all jobs numbers + .range(0, instance.numJobs) + // build the next task for this job + .mapToObj(j -> new Task(j, nextOnJob[j])) + // only keep valid tasks (some jobs have no task left to be executed) + .filter(t -> t.task < instance.numTasks) + // select the task with the earliest execution time + .min(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) + .get(); + + this.jobs[nextToSet++] = next.job; + nextOnJob[next.job] += 1; + } + } + + @Override + public Schedule toSchedule() { + // time at which each machine is going to be freed + int[] nextFreeTimeResource = new int[instance.numMachines]; + + // for each job, the first task that has not yet been scheduled + int[] nextTask = new int[instance.numJobs]; + + // for each task, its start time + int[][] startTimes = new int[instance.numJobs][instance.numTasks]; + + // compute the earliest start time for every task of every job + for(int job : jobs) { + int task = nextTask[job]; + int machine = instance.machine(job, task); + // earliest start time for this task + int est = task == 0 ? 0 : startTimes[job][task-1] + instance.duration(job, task-1); + est = Math.max(est, nextFreeTimeResource[machine]); + + startTimes[job][task] = est; + nextFreeTimeResource[machine] = est + instance.duration(job, task); + nextTask[job] = task + 1; + } + + return new Schedule(instance, startTimes); + } + + public static JobNumbers fromSchedule(Schedule sched) { + JobNumbers jo = new JobNumbers(sched.pb); + + int current_time = 0; + Task current_task = new Task(-1,-1); + Task [] done_tasks = new Task[sched.pb.numJobs*sched.pb.numTasks]; + Arrays.fill(done_tasks, current_task); + + int min; + + for (int i = 0; i < sched.pb.numJobs*sched.pb.numTasks; i++) { + // Il faut faire le code ci-dessous autant de fois que l'on a de taches + // On trouve le minimum parmis les restants + min = Integer.MAX_VALUE; + for (int job = 0; job < sched.pb.numJobs; job++) { + for (int task = 0; task < sched.pb.numTasks; task++) { + int task_start_time = sched.startTime(job, task); + Task this_task = new Task(job, task); + if (task_start_time < min && task_start_time >= current_time && !(Arrays.asList(done_tasks).contains(this_task))) { + min = task_start_time; + current_task = this_task; + } + } + } + // Une fois on a trouvé la suivante tache a realiser on introduit le numero du job dans jobs + jo.jobs[jo.nextToSet++] = current_task.job; + done_tasks[i] = current_task; + } + + return jo; + } + + @Override + public String toString() { + return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet)); + } +} diff --git a/src/main/java/jobshop/encodings/ResourceOrder.java b/src/main/java/jobshop/encodings/ResourceOrder.java index fdd44a7..b115c2d 100644 --- a/src/main/java/jobshop/encodings/ResourceOrder.java +++ b/src/main/java/jobshop/encodings/ResourceOrder.java @@ -1,131 +1,120 @@ -package jobshop.encodings; - -import jobshop.Encoding; -import jobshop.Instance; -import jobshop.Schedule; - -import java.util.Comparator; -import java.util.Optional; -import java.util.stream.IntStream; - -public class ResourceOrder extends Encoding { - - // for each machine m, taskByMachine[m] is an array of tasks to be - // executed on this machine in the same order - public final Task[][] tasksByMachine; - - // for each machine, indicate on many tasks have been initialized - public final int[] nextFreeSlot; - - /** Creates a new empty resource order. */ - public ResourceOrder(Instance instance) - { - super(instance); - - // matrix of null elements (null is the default value of objects) - tasksByMachine = new Task[instance.numMachines][instance.numJobs]; - - // no task scheduled on any machine (0 is the default value) - nextFreeSlot = new int[instance.numMachines]; - } - - /** Creates a resource order from a schedule. */ - public ResourceOrder(Schedule schedule) - { - super(schedule.pb); - Instance pb = schedule.pb; - - this.tasksByMachine = new Task[pb.numMachines][]; - this.nextFreeSlot = new int[instance.numMachines]; - - for(int m = 0 ; m new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job) - .sorted(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) // sorted by start time - .toArray(Task[]::new); // as new array and store in tasksByMachine - - // indicate that all tasks have been initialized for machine m - nextFreeSlot[m] = instance.numJobs; - } - } - - @Override - public Schedule toSchedule() { - // indicate for each task that have been scheduled, its start time - int [][] startTimes = new int [instance.numJobs][instance.numTasks]; - - // for each job, how many tasks have been scheduled (0 initially) - int[] nextToScheduleByJob = new int[instance.numJobs]; - - // for each machine, how many tasks have been scheduled (0 initially) - int[] nextToScheduleByMachine = new int[instance.numMachines]; - - // for each machine, earliest time at which the machine can be used - int[] releaseTimeOfMachine = new int[instance.numMachines]; - - - // loop while there remains a job that has unscheduled tasks - while(IntStream.range(0, instance.numJobs).anyMatch(m -> nextToScheduleByJob[m] < instance.numTasks)) { - - // selects a task that has noun scheduled predecessor on its job and machine : - // - it is the next to be schedule on a machine - // - it is the next to be scheduled on its job - // if there is no such task, we have cyclic dependency and the solution is invalid - Optional schedulable = - IntStream.range(0, instance.numMachines) // all machines ... - .filter(m -> nextToScheduleByMachine[m] < instance.numJobs) // ... with unscheduled jobs - .mapToObj(m -> this.tasksByMachine[m][nextToScheduleByMachine[m]]) // tasks that are next to schedule on a machine ... - .filter(task -> task.task == nextToScheduleByJob[task.job]) // ... and on their job - .findFirst(); // select the first one if any - - if(schedulable.isPresent()) { - // we found a schedulable task, lets call it t - Task t = schedulable.get(); - int machine = instance.machine(t.job, t.task); - - // compute the earliest start time (est) of the task - int est = t.task == 0 ? 0 : startTimes[t.job][t.task-1] + instance.duration(t.job, t.task-1); - est = Math.max(est, releaseTimeOfMachine[instance.machine(t)]); - startTimes[t.job][t.task] = est; - - // mark the task as scheduled - nextToScheduleByJob[t.job]++; - nextToScheduleByMachine[machine]++; - // increase the release time of the machine - releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task); - } else { - // no tasks are schedulable, there is no solution for this resource ordering - return null; - } - } - // we exited the loop : all tasks have been scheduled successfully - return new Schedule(instance, startTimes); - } - - /** Creates an exact copy of this resource order. */ - public ResourceOrder copy() { - return new ResourceOrder(this.toSchedule()); - } - - @Override - public String toString() - { - StringBuilder s = new StringBuilder(); - for(int m=0; m < instance.numMachines; m++) - { - s.append("Machine ").append(m).append(" : "); - for(int j=0; j new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job) + .sorted(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) // sorted by start time + .toArray(Task[]::new); // as new array and store in tasksByMachine + + // indicate that all tasks have been initialized for machine m + nextFreeSlot[m] = instance.numJobs; + } + } + + + @Override + public Schedule toSchedule() { + // indicate for each task that have been scheduled, its start time + int [][] startTimes = new int [instance.numJobs][instance.numTasks]; + + // for each job, how many tasks have been scheduled (0 initially) + int[] nextToScheduleByJob = new int[instance.numJobs]; + + // for each machine, how many tasks have been scheduled (0 initially) + int[] nextToScheduleByMachine = new int[instance.numMachines]; + + // for each machine, earliest time at which the machine can be used + int[] releaseTimeOfMachine = new int[instance.numMachines]; + + + // loop while there remains a job that has unscheduled tasks + while(IntStream.range(0, instance.numJobs).anyMatch(m -> nextToScheduleByJob[m] < instance.numTasks)) { + + // selects a task that has noun scheduled predecessor on its job and machine : + // - it is the next to be schedule on a machine + // - it is the next to be scheduled on its job + // if there is no such task, we have cyclic dependency and the solution is invalid + Optional schedulable = + IntStream.range(0, instance.numMachines) // all machines ... + .filter(m -> nextToScheduleByMachine[m] < instance.numJobs) // ... with unscheduled jobs + .mapToObj(m -> this.tasksByMachine[m][nextToScheduleByMachine[m]]) // tasks that are next to schedule on a machine ... + .filter(task -> task.task == nextToScheduleByJob[task.job]) // ... and on their job + .findFirst(); // select the first one if any + + if(schedulable.isPresent()) { + // we found a schedulable task, lets call it t + Task t = schedulable.get(); + int machine = instance.machine(t.job, t.task); + + // compute the earliest start time (est) of the task + int est = t.task == 0 ? 0 : startTimes[t.job][t.task-1] + instance.duration(t.job, t.task-1); + est = Math.max(est, releaseTimeOfMachine[instance.machine(t.job, t.task)]); + startTimes[t.job][t.task] = est; + + // mark the task as scheduled + nextToScheduleByJob[t.job]++; + nextToScheduleByMachine[machine]++; + // increase the release time of the machine + releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task); + } else { + // no tasks are schedulable, there is no solution for this resource ordering + return null; + } + } + // we exited the loop : all tasks have been scheduled successfully + return new Schedule(instance, startTimes); + } + + @Override + public String toString() { + String res = ""; + for (int i = 0; i < this.tasksByMachine.length; i++) { + res += "Machine number : " + Integer.toString(i+1) + "\n"; + for (int j = 0; j < this.tasksByMachine[i].length; j++) { + res += "\tUse number " + Integer.toString(j+1) + " : " + this.tasksByMachine[i][j].add_one() + "\n"; + } + } + return res; + } + +} diff --git a/src/main/java/jobshop/encodings/Task.java b/src/main/java/jobshop/encodings/Task.java index 95fecca..c2d1ed3 100644 --- a/src/main/java/jobshop/encodings/Task.java +++ b/src/main/java/jobshop/encodings/Task.java @@ -1,41 +1,45 @@ -package jobshop.encodings; - -import java.util.Objects; - -/** Represents a task (job,task) of an jobshop problem. - * - * Example : (2, 3) repesents the fourth task of the third job. (remeber that we tart counting at 0) - * */ -public final class Task { - - /** Identifier of the job */ - public final int job; - - /** Index of the task inside the job. */ - public final int task; - - - public Task(int job, int task) { - this.job = job; - this.task = task; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Task task1 = (Task) o; - return job == task1.job && - task == task1.task; - } - - @Override - public int hashCode() { - return Objects.hash(job, task); - } - - @Override - public String toString() { - return "(" + job +", " + task + '}'; - } -} +package jobshop.encodings; + +import java.util.Objects; + +/** Represents a task (job,task) of an jobshop problem. + * + * Example : (2, 3) repesents the fourth task of the third job. (remeber that we tart counting at 0) + * */ +public final class Task { + + /** Identifier of the job */ + public final int job; + + /** Index of the task inside the job. */ + public final int task; + + + public Task(int job, int task) { + this.job = job; + this.task = task; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Task task1 = (Task) o; + return job == task1.job && + task == task1.task; + } + + @Override + public int hashCode() { + return Objects.hash(job, task); + } + + @Override + public String toString() { + return "Job " + this.job + " Task " + this.task; + } + + public Task add_one() { + return new Task(this.job + 1, this.task + 1); + } +} diff --git a/src/main/java/jobshop/solvers/BasicSolver.java b/src/main/java/jobshop/solvers/BasicSolver.java index 8c1b26e..e48fb12 100644 --- a/src/main/java/jobshop/solvers/BasicSolver.java +++ b/src/main/java/jobshop/solvers/BasicSolver.java @@ -1,21 +1,21 @@ -package jobshop.solvers; - -import jobshop.Instance; -import jobshop.Result; -import jobshop.Solver; -import jobshop.encodings.JobNumbers; - -public class BasicSolver implements Solver { - @Override - public Result solve(Instance instance, long deadline) { - - JobNumbers sol = new JobNumbers(instance); - for(int t = 0 ; t blocksOfCriticalPath(ResourceOrder order) { - throw new UnsupportedOperationException(); - } - - /** For a given block, return the possible swaps for the Nowicki and Smutnicki neighborhood */ - List neighbors(Block block) { - throw new UnsupportedOperationException(); - } - -} diff --git a/src/main/java/jobshop/solvers/GreedySolver.java b/src/main/java/jobshop/solvers/GreedySolver.java new file mode 100644 index 0000000..8de403a --- /dev/null +++ b/src/main/java/jobshop/solvers/GreedySolver.java @@ -0,0 +1,194 @@ +package jobshop.solvers; + +import java.util.ArrayList; + +import jobshop.*; +import jobshop.encodings.ResourceOrder; +import jobshop.encodings.Task; +import jobshop.solvers.GreedySolver.PriorityESTRule; + +public class GreedySolver implements Solver { + + /*********************************************************************************************/ + /******************* Priority Rules and EST Priority Rules enumerations **********************/ + /*********************************************************************************************/ + public enum PriorityRule{ + SPT { + @Override + public Task getTaskByRule(ArrayList achievableTasks, Instance instance) { + // Return the task with the MINIMAL duration + Task TaskSPT = null; + int minDuration = Integer.MAX_VALUE; + + for(int i = 0; i < achievableTasks.size(); i++) { + Task current = achievableTasks.get(i); + int currentDuration = instance.duration(current.job, current.task); + + if(currentDuration < minDuration) { + minDuration = currentDuration; + TaskSPT = current; + } + } + return TaskSPT; + } + }, + LRPT { + @Override + public Task getTaskByRule(ArrayList achievableTasks, Instance instance) { + Task TaskLRPT = null; + Task current; + int maxDuration = -1; + + for(int i = 0; i < achievableTasks.size(); i++) { + current = achievableTasks.get(i); + int remainingTime = 0; + for(int j = current.task; j < instance.numTasks; j++) { + remainingTime += instance.duration(current.job, j); + } + if(remainingTime > maxDuration) { + maxDuration = remainingTime; + TaskLRPT = current; + } + } + return TaskLRPT; + } + }; + /** + * Returns the Task to work with depending on the rule chosen + * @param achievableTasks, instance + * @return currentTask + */ + public abstract Task getTaskByRule(ArrayList achievableTasks, Instance instance); + } + + public enum PriorityESTRule{ + EST_SPT { + @Override + public Task getTaskByESTRule(ArrayList achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines) { + + ArrayList priorityTasks = this.getESTPriorityTasks(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines); + // STP function: Return the task with the MINIMAL duration + PriorityRule SPT = PriorityRule.SPT; + Task TaskSPT = SPT.getTaskByRule(priorityTasks, instance); + + return TaskSPT; + } + }, + EST_LRPT { + @Override + public Task getTaskByESTRule(ArrayList achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines) { + ArrayList priorityTasks = this.getESTPriorityTasks(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines); + // STP function: Return the task with the MINIMAL duration + PriorityRule LRPT = PriorityRule.LRPT; + Task TaskLRPT = LRPT.getTaskByRule(priorityTasks, instance); + + return TaskLRPT; + } + }; + /** + * Returns the Task to work with depending on the rule chosen + * @param achievableTasks, instance + * @return currentTask + */ + public abstract Task getTaskByESTRule(ArrayList achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines); + /** + * Returns the array of tasks with the shortest start date + * @param achievableTasks, instance + * @return priorityTask + */ + protected ArrayList getESTPriorityTasks(ArrayList achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines){ + // Search for the date or dates which start sooner + ArrayList priorityTasks = new ArrayList<>(); + int minStartDate = Integer.MAX_VALUE; + + + for(int i = 0; i < achievableTasks.size(); i++) { + Task currentTask = achievableTasks.get(i); + int currentMachine = instance.machine(currentTask.job, currentTask.task); + int currentStartDate = Integer.max(nextStartDateJobs[currentTask.job], nextStartDateMachines[currentMachine]); + + if(currentStartDate < minStartDate) { + minStartDate = currentStartDate; + // When we find a smaller start date we "restart" the array + priorityTasks.clear(); + priorityTasks.add(currentTask); + } else if (currentStartDate == minStartDate) { + priorityTasks.add(currentTask); + } + } + return priorityTasks; + } + } + + /*********************************************************************************************/ + /********************** Greedy Solver: Constructors + Solve function *************************/ + /*********************************************************************************************/ + + public PriorityRule priorityRule; + public PriorityESTRule priorityESTRule; + + // 2 constructors: the default and one with the EST restriction + public GreedySolver(PriorityRule rule) { + this.priorityRule = rule; + this.priorityESTRule = null; + } + + public GreedySolver(PriorityESTRule ruleEST) { + this.priorityESTRule = ruleEST; + this.priorityRule = null; + } + + + + @Override + public Result solve(Instance instance, long deadline) { + + int currentMachine, currentDuration; + // We declare 2 arrays containing the updated moment the next task will start in a job and a machine respectively + int[] nextStartDateJobs = new int[instance.numJobs]; + int[] nextStartDateMachines = new int[instance.numMachines]; + + // We create a new ResourceOrder for putting all tasks in the schedule + ResourceOrder solution = new ResourceOrder(instance); + // Array list with all the achievable current tasks + ArrayList achievableTasks = new ArrayList<>(); + + // Initialize the array list with all the first task achievable + for(int i = 0 ; i < instance.numJobs ; i++) { + Task currentTask = new Task(i, 0); + achievableTasks.add(currentTask); + } + + while(!achievableTasks.isEmpty()) { + // We take the task we should do now in function of the priority rule used + Task currentTask = null; + if(priorityESTRule == null) { + currentTask = priorityRule.getTaskByRule(achievableTasks, instance); + } else if(priorityRule == null) { + currentTask = priorityESTRule.getTaskByESTRule(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines); + } else { + System.out.printf("Error priorityRule and priorityRuleEST are null. You must give a value to one of them."); + } + + // Updating starting dates + currentMachine = instance.machine(currentTask.job, currentTask.task); + currentDuration = instance.duration(currentTask.job, currentTask.task); + nextStartDateJobs[currentTask.job] += currentDuration; + nextStartDateMachines[currentMachine] += currentDuration; + + // We remove the current task from the achievable tasks list + achievableTasks.remove(currentTask); + + // If it's not the last task of the job, we update the array list with the new task + if (currentTask.task < (instance.numTasks - 1)) { + achievableTasks.add(new Task(currentTask.job, currentTask.task + 1)); + } + + // We add the current task to the solution + int nextFreeSlot = solution.nextFreeSlot[currentMachine]++; + solution.tasksByMachine[currentMachine][nextFreeSlot] = currentTask; + } + + return new Result(instance, solution.toSchedule(), Result.ExitCause.Blocked); + } +} diff --git a/src/main/java/jobshop/solvers/RandomSolver.java b/src/main/java/jobshop/solvers/RandomSolver.java index 17e2b87..bd649b2 100644 --- a/src/main/java/jobshop/solvers/RandomSolver.java +++ b/src/main/java/jobshop/solvers/RandomSolver.java @@ -1,52 +1,53 @@ -package jobshop.solvers; - -import jobshop.*; -import jobshop.encodings.JobNumbers; - -import java.util.Optional; -import java.util.Random; - -public class RandomSolver implements Solver { - - @Override - public Result solve(Instance instance, long deadline) { - Random generator = new Random(0); - - JobNumbers sol = new JobNumbers(instance); - - for(int j = 0 ; j 1) { - shuffleArray(sol.jobs, generator); - Schedule s = sol.toSchedule(); - if(s.makespan() < best.makespan()) { - best = s; - } - } - - - return new Result(instance, best, Result.ExitCause.Timeout); - } - - /** Simple Fisher–Yates array shuffling */ - private static void shuffleArray(int[] array, Random random) - { - int index; - for (int i = array.length - 1; i > 0; i--) - { - index = random.nextInt(i + 1); - if (index != i) - { - array[index] ^= array[i]; - array[i] ^= array[index]; - array[index] ^= array[i]; - } - } - } -} - - +package jobshop.solvers; + +import jobshop.*; +import jobshop.encodings.JobNumbers; + +import java.util.Optional; +import java.util.Random; + +@SuppressWarnings("unused") +public class RandomSolver implements Solver { + + @Override + public Result solve(Instance instance, long deadline) { + Random generator = new Random(0); + + JobNumbers sol = new JobNumbers(instance); + + for(int j = 0 ; j 1) { + shuffleArray(sol.jobs, generator); + Schedule s = sol.toSchedule(); + if(s.makespan() < best.makespan()) { + best = s; + } + } + + + return new Result(instance, best, Result.ExitCause.Timeout); + } + + /** Simple Fisher–Yates array shuffling */ + private static void shuffleArray(int[] array, Random random) + { + int index; + for (int i = array.length - 1; i > 0; i--) + { + index = random.nextInt(i + 1); + if (index != i) + { + array[index] ^= array[i]; + array[i] ^= array[index]; + array[index] ^= array[i]; + } + } + } +} + + diff --git a/src/test/java/jobshop/encodings/EncodingTests.java b/src/test/java/jobshop/encodings/EncodingTests.java index 029fc71..a04eeeb 100644 --- a/src/test/java/jobshop/encodings/EncodingTests.java +++ b/src/test/java/jobshop/encodings/EncodingTests.java @@ -1,75 +1,103 @@ -package jobshop.encodings; - -import jobshop.Instance; -import jobshop.Result; -import jobshop.Schedule; -import jobshop.Solver; -import jobshop.solvers.BasicSolver; -import org.junit.Test; - -import java.io.IOException; -import java.nio.file.Paths; - -public class EncodingTests { - - @Test - public void testJobNumbers() throws IOException { - Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); - - // numéro de jobs : 1 2 2 1 1 2 (cf exercices) - JobNumbers enc = new JobNumbers(instance); - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - - Schedule sched = enc.toSchedule(); - // TODO: make it print something meaningful - // by implementing the toString() method - System.out.println(sched); - assert sched.isValid(); - assert sched.makespan() == 12; - - - - // numéro de jobs : 1 1 2 2 1 2 - enc = new JobNumbers(instance); - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - - sched = enc.toSchedule(); - assert sched.isValid(); - assert sched.makespan() == 14; - } - - @Test - public void testBasicSolver() throws IOException { - Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); - - // build a solution that should be equal to the result of BasicSolver - JobNumbers enc = new JobNumbers(instance); - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - enc.jobs[enc.nextToSet++] = 0; - enc.jobs[enc.nextToSet++] = 1; - - Schedule sched = enc.toSchedule(); - assert sched.isValid(); - assert sched.makespan() == 12; - - Solver solver = new BasicSolver(); - Result result = solver.solve(instance, System.currentTimeMillis() + 10); - - assert result.schedule.isValid(); - assert result.schedule.makespan() == sched.makespan(); // should have the same makespan - } - -} +package jobshop.encodings; + +import jobshop.Instance; +import jobshop.Result; +import jobshop.Schedule; +import jobshop.Solver; +import jobshop.solvers.BasicSolver; +import jobshop.solvers.GreedySolver; +import jobshop.solvers.GreedySolver.PriorityRule; + +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Paths; + +public class EncodingTests { + + @Test + public void testJobNumbers() throws IOException { + Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); + + // numéro de jobs : 1 2 2 1 1 2 (cf exercices) + JobNumbers enc = new JobNumbers(instance); + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + + Schedule sched = enc.toSchedule(); + // TODO: make it print something meaningful + // by implementing the toString() method + System.out.println(sched); + assert sched.isValid(); + assert sched.makespan() == 12; + + + + // numéro de jobs : 1 1 2 2 1 2 + enc = new JobNumbers(instance); + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + + sched = enc.toSchedule(); + assert sched.isValid(); + assert sched.makespan() == 14; + } + + @Test + public void testBasicSolver() throws IOException { + Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); + + // build a solution that should be equal to the result of BasicSolver + JobNumbers enc = new JobNumbers(instance); + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + + Schedule sched = enc.toSchedule(); + assert sched.isValid(); + assert sched.makespan() == 12; + + Solver solver = new BasicSolver(); + Result result = solver.solve(instance, System.currentTimeMillis() + 10); + + assert result.schedule.isValid(); + assert result.schedule.makespan() == sched.makespan(); // should have the same makespan + } + + @Test + public void testGreedySolver() throws IOException { + Instance instance = Instance.fromFile(Paths.get("instances/aaa1")); + + // build a solution that should be equal to the result of BasicSolver + JobNumbers enc = new JobNumbers(instance); + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + enc.jobs[enc.nextToSet++] = 0; + enc.jobs[enc.nextToSet++] = 1; + + Schedule sched = enc.toSchedule(); + assert sched.isValid(); + assert sched.makespan() == 12; + + PriorityRule priorityRule = PriorityRule.SPT; + Solver solver = new GreedySolver(priorityRule); + Result result = solver.solve(instance, System.currentTimeMillis() + 10); + + assert result.schedule.isValid(); + assert result.schedule.makespan() == sched.makespan(); // should have the same makespan + } + +}