From ec04f02ed3a0b948ed3deb5b6b526c51f578eeef Mon Sep 17 00:00:00 2001 From: Arthur Bit-Monnot Date: Fri, 10 Apr 2020 17:03:16 +0200 Subject: [PATCH] Provide ResourceOrder implementation + minor additions. --- src/main/java/jobshop/BestKnownResult.java | 9 ++ src/main/java/jobshop/Instance.java | 9 ++ src/main/java/jobshop/Main.java | 77 +++++----- .../java/jobshop/encodings/JobNumbers.java | 29 +++- .../java/jobshop/encodings/ResourceOrder.java | 131 ++++++++++++++++++ .../java/jobshop/solvers/DescentSolver.java | 90 ++++++++++++ 6 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 src/main/java/jobshop/encodings/ResourceOrder.java create mode 100644 src/main/java/jobshop/solvers/DescentSolver.java diff --git a/src/main/java/jobshop/BestKnownResult.java b/src/main/java/jobshop/BestKnownResult.java index 6c08b58..55d5f9b 100644 --- a/src/main/java/jobshop/BestKnownResult.java +++ b/src/main/java/jobshop/BestKnownResult.java @@ -2,6 +2,8 @@ package jobshop; import java.util.Arrays; import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; public class BestKnownResult { @@ -9,6 +11,13 @@ public class BestKnownResult { 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); diff --git a/src/main/java/jobshop/Instance.java b/src/main/java/jobshop/Instance.java index 838e750..73bcce7 100644 --- a/src/main/java/jobshop/Instance.java +++ b/src/main/java/jobshop/Instance.java @@ -1,5 +1,7 @@ package jobshop; +import jobshop.encodings.Task; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -24,9 +26,15 @@ public class Instance { 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) { @@ -46,6 +54,7 @@ public class Instance { 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("#")) diff --git a/src/main/java/jobshop/Main.java b/src/main/java/jobshop/Main.java index 1a48319..7382124 100644 --- a/src/main/java/jobshop/Main.java +++ b/src/main/java/jobshop/Main.java @@ -3,13 +3,13 @@ package jobshop; import java.io.PrintStream; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import jobshop.solvers.BasicSolver; -import jobshop.solvers.RandomSolver; +import jobshop.solvers.*; import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.inf.ArgumentParser; import net.sourceforge.argparse4j.inf.ArgumentParserException; @@ -68,20 +68,23 @@ public class Main { 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."); + 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( " ");; + output.print( " "); for(String s : solversToTest) output.printf("%-30s", s); output.println(); @@ -92,46 +95,46 @@ public class Main { output.println(); - for(String instanceName : instances) { - int bestKnown = BestKnownResult.of(instanceName); + for(String instanceName : instances) { + int bestKnown = BestKnownResult.of(instanceName); - Path path = Paths.get("instances/", instanceName); - Instance instance = Instance.fromFile(path); + Path path = Paths.get("instances/", instanceName); + Instance instance = Instance.fromFile(path); - output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown); + 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; + 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); + 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(); - 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]); - } + 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]); + } diff --git a/src/main/java/jobshop/encodings/JobNumbers.java b/src/main/java/jobshop/encodings/JobNumbers.java index b83ed0a..b81f207 100644 --- a/src/main/java/jobshop/encodings/JobNumbers.java +++ b/src/main/java/jobshop/encodings/JobNumbers.java @@ -5,6 +5,8 @@ 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 { @@ -12,7 +14,7 @@ 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 + /** 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; @@ -23,6 +25,31 @@ public class JobNumbers extends Encoding { 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 diff --git a/src/main/java/jobshop/encodings/ResourceOrder.java b/src/main/java/jobshop/encodings/ResourceOrder.java new file mode 100644 index 0000000..fdd44a7 --- /dev/null +++ b/src/main/java/jobshop/encodings/ResourceOrder.java @@ -0,0 +1,131 @@ +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 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(); + } + +}