Provide ResourceOrder implementation + minor additions.
This commit is contained in:
		
							parent
							
								
									8cc959a766
								
							
						
					
					
						commit
						ec04f02ed3
					
				
					 6 changed files with 307 additions and 38 deletions
				
			
		|  | @ -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<String> 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); | ||||
|  |  | |||
|  | @ -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<String> lines = Files.readAllLines(path).stream() | ||||
|                 .filter(l -> !l.startsWith("#")) | ||||
|  |  | |||
|  | @ -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<String> instances = ns.<String>getList("instance"); | ||||
|         for(String instanceName : instances) { | ||||
|             if(!BestKnownResult.isKnown(instanceName)) { | ||||
|                 System.err.println("ERROR: instance \"" + instanceName + "\" is not avalaible."); | ||||
|         List<String> instancePrefixes = ns.getList("instance"); | ||||
|         List<String> instances = new ArrayList<>(); | ||||
|         for(String instancePrefix : instancePrefixes) { | ||||
|             List<String> 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]); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										131
									
								
								src/main/java/jobshop/encodings/ResourceOrder.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/main/java/jobshop/encodings/ResourceOrder.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<schedule.pb.numMachines ; m++) { | ||||
|             final int machine = m; | ||||
| 
 | ||||
|             // for thi machine, find all tasks that are executed on it and sort them by their start time | ||||
|             tasksByMachine[m] = | ||||
|                     IntStream.range(0, pb.numJobs) // all job numbers | ||||
|                             .mapToObj(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<Task> 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<instance.numJobs; j++) | ||||
|             { | ||||
|                 s.append(tasksByMachine[m][j]).append(" ; "); | ||||
|             } | ||||
|             s.append("\n"); | ||||
|         } | ||||
| 
 | ||||
|         return s.toString(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										90
									
								
								src/main/java/jobshop/solvers/DescentSolver.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/main/java/jobshop/solvers/DescentSolver.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | |||
| package jobshop.solvers; | ||||
| 
 | ||||
| import jobshop.Instance; | ||||
| import jobshop.Result; | ||||
| import jobshop.Solver; | ||||
| import jobshop.encodings.ResourceOrder; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class DescentSolver implements Solver { | ||||
| 
 | ||||
|     /** A block represents a subsequence of the critical path such that all tasks in it execute on the same machine. | ||||
|      * This class identifies a block in a ResourceOrder representation. | ||||
|      * | ||||
|      * Consider the solution in ResourceOrder representation | ||||
|      * machine 0 : (0,1) (1,2) (2,2) | ||||
|      * machine 1 : (0,2) (2,1) (1,1) | ||||
|      * machine 2 : ... | ||||
|      * | ||||
|      * The block with : machine = 1, firstTask= 0 and lastTask = 1 | ||||
|      * Represent the task sequence : [(0,2) (2,1)] | ||||
|      * | ||||
|      * */ | ||||
|     static class Block { | ||||
|         /** machine on which the block is identified */ | ||||
|         final int machine; | ||||
|         /** index of the first task of the block */ | ||||
|         final int firstTask; | ||||
|         /** index of the last task of the block */ | ||||
|         final int lastTask; | ||||
| 
 | ||||
|         Block(int machine, int firstTask, int lastTask) { | ||||
|             this.machine = machine; | ||||
|             this.firstTask = firstTask; | ||||
|             this.lastTask = lastTask; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Represents a swap of two tasks on the same machine in a ResourceOrder encoding. | ||||
|      * | ||||
|      * Consider the solution in ResourceOrder representation | ||||
|      * machine 0 : (0,1) (1,2) (2,2) | ||||
|      * machine 1 : (0,2) (2,1) (1,1) | ||||
|      * machine 2 : ... | ||||
|      * | ||||
|      * The swam with : machine = 1, t1= 0 and t2 = 1 | ||||
|      * Represent inversion of the two tasks : (0,2) and (2,1) | ||||
|      * Applying this swap on the above resource order should result in the following one : | ||||
|      * machine 0 : (0,1) (1,2) (2,2) | ||||
|      * machine 1 : (2,1) (0,2) (1,1) | ||||
|      * machine 2 : ... | ||||
|      */ | ||||
|     static class Swap { | ||||
|         // machine on which to perform the swap | ||||
|         final int machine; | ||||
|         // index of one task to be swapped | ||||
|         final int t1; | ||||
|         // index of the other task to be swapped | ||||
|         final int t2; | ||||
| 
 | ||||
|         Swap(int machine, int t1, int t2) { | ||||
|             this.machine = machine; | ||||
|             this.t1 = t1; | ||||
|             this.t2 = t2; | ||||
|         } | ||||
| 
 | ||||
|         /** Apply this swap on the given resource order, transforming it into a new solution. */ | ||||
|         public void applyOn(ResourceOrder order) { | ||||
|             throw new UnsupportedOperationException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public Result solve(Instance instance, long deadline) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     /** Returns a list of all blocks of the critical path. */ | ||||
|     List<Block> blocksOfCriticalPath(ResourceOrder order) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     /** For a given block, return the possible swaps for the Nowicki and Smutnicki neighborhood */ | ||||
|     List<Swap> neighbors(Block block) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in a new issue