Doc improvements + rename DebuggingMain to MainTest
This commit is contained in:
		
							parent
							
								
									741eeb02f3
								
							
						
					
					
						commit
						0b551e3b4e
					
				
					 8 changed files with 108 additions and 63 deletions
				
			
		|  | @ -17,14 +17,10 @@ import net.sourceforge.argparse4j.inf.ArgumentParserException; | |||
| import net.sourceforge.argparse4j.inf.Namespace; | ||||
| 
 | ||||
| /** | ||||
|  * This class is the main entry point for testing solver on instances. | ||||
|  * It provides | ||||
|  * This class is the main entry point for doing comparative performance tests of solvers. | ||||
|  */ | ||||
| public class Main { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         // configure the argument parser | ||||
|         ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build() | ||||
|  |  | |||
|  | @ -1,13 +1,15 @@ | |||
| package jobshop; | ||||
| 
 | ||||
| import jobshop.encodings.JobNumbers; | ||||
| import jobshop.encodings.ResourceOrder; | ||||
| import jobshop.encodings.Schedule; | ||||
| import jobshop.encodings.Task; | ||||
| import jobshop.solvers.GreedySolver; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Paths; | ||||
| 
 | ||||
| public class DebuggingMain { | ||||
| public class MainTest { | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         try { | ||||
|  | @ -25,12 +27,18 @@ public class DebuggingMain { | |||
| 
 | ||||
|             System.out.println("\nENCODING: " + enc); | ||||
| 
 | ||||
|             // convert to a schedule and display | ||||
|             Schedule schedule = enc.toSchedule(); | ||||
|             System.out.println("VALID: " + schedule.isValid()); | ||||
|             System.out.println("MAKESPAN: " + schedule.makespan()); | ||||
|             System.out.println("SCHEDULE: " + schedule.toString()); | ||||
|             System.out.println("GANTT: " + schedule.asciiGantt()); | ||||
| 
 | ||||
|             Schedule manualSchedule = new Schedule(instance); | ||||
|             // TODO: encode the same solution | ||||
| 
 | ||||
|             ResourceOrder manualRO = new ResourceOrder(instance); | ||||
|             // TODO: encode the same solution | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             System.exit(1); | ||||
|  | @ -2,6 +2,7 @@ package jobshop; | |||
| 
 | ||||
| import jobshop.encodings.Schedule; | ||||
| 
 | ||||
| /** Class representing the result of a solver. */ | ||||
| public class Result { | ||||
| 
 | ||||
|     public Result(Instance instance, Schedule schedule, ExitCause cause) { | ||||
|  | @ -10,12 +11,23 @@ public class Result { | |||
|         this.cause = cause; | ||||
|     } | ||||
| 
 | ||||
|     /** Documents the reason why a solver returned the solution. */ | ||||
|     public enum ExitCause { | ||||
|         Timeout, ProvedOptimal, Blocked | ||||
|         /** The solver ran out of time and had to exit. */ | ||||
|         Timeout, | ||||
|         /** The solution has been proved optimal and thus can no longer be improved. */ | ||||
|         ProvedOptimal, | ||||
|         /** The solver was not able to further improve the solution (e.g. blocked in a local minima. */ | ||||
|         Blocked | ||||
|     } | ||||
| 
 | ||||
|     /** Instance that was solved. */ | ||||
|     public final Instance instance; | ||||
| 
 | ||||
|     /** A schedule of the solution or null if no solution was found. */ | ||||
|     public final Schedule schedule; | ||||
| 
 | ||||
|     /** Reason why the solver exited with this solution. */ | ||||
|     public final ExitCause cause; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,13 +2,19 @@ package jobshop.encodings; | |||
| 
 | ||||
| import jobshop.Instance; | ||||
| 
 | ||||
| /** Common class for all encodings. | ||||
|  * | ||||
|  * The only requirement for this class is to provide a conversion from the encoding into a Schedule. | ||||
|  */ | ||||
| public abstract class Encoding { | ||||
| 
 | ||||
|     /** Problem instance of which this is the solution. */ | ||||
|     public final Instance instance; | ||||
| 
 | ||||
|     public Encoding(Instance instance) { | ||||
|         this.instance = instance; | ||||
|     } | ||||
| 
 | ||||
|     /** Convert into a schedule. */ | ||||
|     public abstract Schedule toSchedule(); | ||||
| } | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ public class JobNumbers extends Encoding { | |||
|     } | ||||
| 
 | ||||
|     public JobNumbers(Schedule schedule) { | ||||
|         super(schedule.pb); | ||||
|         super(schedule.instance); | ||||
| 
 | ||||
|         this.jobs = new int[instance.numJobs * instance.numTasks]; | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,13 +30,13 @@ public class ResourceOrder extends Encoding { | |||
|     /** Creates a resource order from a schedule. */ | ||||
|     public ResourceOrder(Schedule schedule) | ||||
|     { | ||||
|         super(schedule.pb); | ||||
|         Instance pb = schedule.pb; | ||||
|         super(schedule.instance); | ||||
|         Instance pb = schedule.instance; | ||||
| 
 | ||||
|         this.tasksByMachine = new Task[pb.numMachines][]; | ||||
|         this.nextFreeSlot = new int[instance.numMachines]; | ||||
| 
 | ||||
|         for(int m = 0 ; m<schedule.pb.numMachines ; m++) { | ||||
|         for(int m = 0; m<schedule.instance.numMachines ; m++) { | ||||
|             final int machine = m; | ||||
| 
 | ||||
|             // for this machine, find all tasks that are executed on it and sort them by their start time | ||||
|  | @ -51,6 +51,10 @@ public class ResourceOrder extends Encoding { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void addToMachine(int machine, int jobNumber) { | ||||
|         addTaskToMachine(machine, new Task(jobNumber, instance.task_with_machine(jobNumber, machine))); | ||||
|     } | ||||
| 
 | ||||
|     public void addTaskToMachine(int machine, Task task) { | ||||
|         tasksByMachine[machine][nextFreeSlot[machine]] = task; | ||||
|         nextFreeSlot[machine] += 1; | ||||
|  |  | |||
|  | @ -5,21 +5,46 @@ import jobshop.Instance; | |||
| import java.util.*; | ||||
| import java.util.stream.IntStream; | ||||
| 
 | ||||
| public class Schedule { | ||||
|     public final Instance pb; | ||||
| /** Direct encoding of the solution to JobShop problem. | ||||
|  * | ||||
|  * Associates every task to its start time. | ||||
|  */ | ||||
| public class Schedule extends Encoding { | ||||
| 
 | ||||
|     // 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; | ||||
| 
 | ||||
|     /** Creates a new schedule for the given instance where all start times are uninitialized. */ | ||||
|     public Schedule(Instance pb) { | ||||
|         this.pb = pb; | ||||
|         this.times = new int[pb.numJobs][]; | ||||
|         for(int j = 0 ; j < pb.numJobs ; j++) { | ||||
|             this.times[j] = new int[pb.numTasks]; | ||||
|     public Schedule(Instance instance) { | ||||
|         super(instance); | ||||
|         this.times = new int[instance.numJobs][]; | ||||
|         for(int j = 0; j < instance.numJobs ; j++) { | ||||
|             this.times[j] = new int[instance.numTasks]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** Start time of the given task. */ | ||||
|     public int startTime(int job, int task) { | ||||
|         return times[job][task]; | ||||
|     } | ||||
| 
 | ||||
|     /** Start time of the given task. */ | ||||
|     public int startTime(Task task) { | ||||
|         return startTime(task.job, task.task); | ||||
|     } | ||||
| 
 | ||||
|     /** End time of the given task. */ | ||||
|     public int endTime(int job, int task) { | ||||
|         return startTime(job, task) + instance.duration(job, task); | ||||
|     } | ||||
| 
 | ||||
|     /** End time of the given task. */ | ||||
|     public int endTime(Task task) { | ||||
|         return endTime(task.job, task.task); | ||||
|     } | ||||
| 
 | ||||
|     /** Sets the start time of the given task. */ | ||||
|     public void setStartTime(int job, int task, int startTime) { | ||||
|         times[job][task] = startTime; | ||||
|  | @ -27,25 +52,25 @@ public class Schedule { | |||
| 
 | ||||
|     /** Returns true if this schedule is valid (no constraint is violated) */ | ||||
|     public boolean isValid() { | ||||
|         for(int j = 0 ; j<pb.numJobs ; j++) { | ||||
|             for(int t = 1 ; t<pb.numTasks ; t++) { | ||||
|                 if(startTime(j, t-1) + pb.duration(j, t-1) > startTime(j, t)) | ||||
|         for(int j = 0; j<instance.numJobs ; j++) { | ||||
|             for(int t = 1; t< instance.numTasks ; t++) { | ||||
|                 if(startTime(j, t-1) + instance.duration(j, t-1) > startTime(j, t)) | ||||
|                     return false; | ||||
|             } | ||||
|             for(int t = 0 ; t<pb.numTasks ; t++) { | ||||
|             for(int t = 0; t< instance.numTasks ; t++) { | ||||
|                 if(startTime(j, t) < 0) | ||||
|                     return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (int machine = 0 ; machine < pb.numMachines ; machine++) { | ||||
|             for(int j1=0 ; j1<pb.numJobs ; j1++) { | ||||
|                 int t1 = pb.task_with_machine(j1, machine); | ||||
|                 for(int j2=j1+1 ; j2<pb.numJobs ; j2++) { | ||||
|                     int t2 = pb.task_with_machine(j2, machine); | ||||
|         for (int machine = 0; machine < instance.numMachines ; machine++) { | ||||
|             for(int j1 = 0; j1< instance.numJobs ; j1++) { | ||||
|                 int t1 = instance.task_with_machine(j1, machine); | ||||
|                 for(int j2 = j1+1; j2< instance.numJobs ; j2++) { | ||||
|                     int t2 = instance.task_with_machine(j2, machine); | ||||
| 
 | ||||
|                     boolean t1_first = startTime(j1, t1) + pb.duration(j1, t1) <= startTime(j2, t2); | ||||
|                     boolean t2_first = startTime(j2, t2) + pb.duration(j2, t2) <= startTime(j1, t1); | ||||
|                     boolean t1_first = startTime(j1, t1) + instance.duration(j1, t1) <= startTime(j2, t2); | ||||
|                     boolean t2_first = startTime(j2, t2) + instance.duration(j2, t2) <= startTime(j1, t1); | ||||
| 
 | ||||
|                     if(!t1_first && !t2_first) | ||||
|                         return false; | ||||
|  | @ -61,32 +86,12 @@ public class Schedule { | |||
|      */ | ||||
|     public int makespan() { | ||||
|         int max = -1; | ||||
|         for(int j = 0 ; j<pb.numJobs ; j++) { | ||||
|             max = Math.max(max, endTime(j, pb.numTasks-1)); | ||||
|         for(int j = 0; j< instance.numJobs ; j++) { | ||||
|             max = Math.max(max, endTime(j, instance.numTasks-1)); | ||||
|         } | ||||
|         return max; | ||||
|     } | ||||
| 
 | ||||
|     /** Start time of the given task. */ | ||||
|     public int startTime(int job, int task) { | ||||
|         return times[job][task]; | ||||
|     } | ||||
| 
 | ||||
|     /** Start time of the given task. */ | ||||
|     public int startTime(Task task) { | ||||
|         return startTime(task.job, task.task); | ||||
|     } | ||||
| 
 | ||||
|     /** End time of the given task. */ | ||||
|     public int endTime(int job, int task) { | ||||
|         return startTime(job, task) + pb.duration(job, task); | ||||
|     } | ||||
| 
 | ||||
|     /** End time of the given task. */ | ||||
|     public int endTime(Task task) { | ||||
|         return endTime(task.job, task.task); | ||||
|     } | ||||
| 
 | ||||
|     /** Returns true if the given sequence of task is a critical path of the schedule. */ | ||||
|     public boolean isCriticalPath(List<Task> path) { | ||||
|         if(startTime(path.get(0)) != 0) { | ||||
|  | @ -108,8 +113,8 @@ public class Schedule { | |||
|      */ | ||||
|     public List<Task> criticalPath() { | ||||
|         // select task with greatest end time | ||||
|         Task ldd = IntStream.range(0, pb.numJobs) | ||||
|                 .mapToObj(j -> new Task(j, pb.numTasks-1)) | ||||
|         Task ldd = IntStream.range(0, instance.numJobs) | ||||
|                 .mapToObj(j -> new Task(j, instance.numTasks-1)) | ||||
|                 .max(Comparator.comparing(this::endTime)) | ||||
|                 .get(); | ||||
|         assert endTime(ldd) == makespan(); | ||||
|  | @ -124,7 +129,7 @@ public class Schedule { | |||
|         // starts a time 0 | ||||
|         while(startTime(path.getFirst()) != 0) { | ||||
|             Task cur = path.getFirst(); | ||||
|             int machine = pb.machine(cur.job, cur.task); | ||||
|             int machine = instance.machine(cur.job, cur.task); | ||||
| 
 | ||||
|             // will contain the task that was delaying the start | ||||
|             // of our current task | ||||
|  | @ -140,8 +145,8 @@ public class Schedule { | |||
|             } | ||||
|             if(latestPredecessor.isEmpty()) { | ||||
|                 // 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))) | ||||
|                 latestPredecessor = IntStream.range(0, instance.numJobs) | ||||
|                         .mapToObj(j -> new Task(j, instance.task_with_machine(j, machine))) | ||||
|                         .filter(t -> endTime(t) == startTime(cur)) | ||||
|                         .findFirst(); | ||||
|             } | ||||
|  | @ -158,11 +163,11 @@ public class Schedule { | |||
|     public String toString() { | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         sb.append("\nStart times of all tasks:\n"); | ||||
|         for(int job=0; job<pb.numJobs; job++) { | ||||
|         for(int job = 0; job< instance.numJobs; job++) { | ||||
|             sb.append("Job "); | ||||
|             sb.append(job); | ||||
|             sb.append(": "); | ||||
|             for(int task=0; task<pb.numTasks; task++) { | ||||
|             for(int task = 0; task< instance.numTasks; task++) { | ||||
|                 sb.append(String.format("%5d",  startTime(job, task))); | ||||
| 
 | ||||
|             } | ||||
|  | @ -182,15 +187,15 @@ public class Schedule { | |||
|      */ | ||||
|     public String asciiGantt() { | ||||
|         var criticalPath = this.criticalPath(); | ||||
|         int minTaskDur = IntStream.range(0, pb.numJobs).flatMap(job -> IntStream.range(0, pb.numTasks).map(task -> pb.duration(job, task))).min().getAsInt(); | ||||
|         int minTaskDur = IntStream.range(0, instance.numJobs).flatMap(job -> IntStream.range(0, instance.numTasks).map(task -> instance.duration(job, task))).min().getAsInt(); | ||||
|         // time units by character | ||||
|         int charsPerTimeUnit = minTaskDur >= 5 ? 1 : (5 / minTaskDur) +1; | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         sb.append("\nGantt Chart\n"); | ||||
|         for(int job=0; job<pb.numJobs; job++) { | ||||
|         for(int job = 0; job< instance.numJobs; job++) { | ||||
|             sb.append(String.format("Job %2d: ", job)); | ||||
|             int cursor = 0; | ||||
|             for(int task=0; task<pb.numTasks; task++) { | ||||
|             for(int task = 0; task< instance.numTasks; task++) { | ||||
|                 Task t = new Task(job, task); | ||||
|                 var st = startTime(job, task); | ||||
|                 // add spaces until the start of our task | ||||
|  | @ -214,8 +219,8 @@ public class Schedule { | |||
|     String formatTask(Task t, int charPerTimeUnit, boolean isCritical) { | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         String fill = isCritical ? "*" : "-"; | ||||
|         int dur = pb.duration(t); | ||||
|         int machine = pb.machine(t); | ||||
|         int dur = instance.duration(t); | ||||
|         int machine = instance.machine(t); | ||||
|         int stringLength = dur * charPerTimeUnit; | ||||
|         int charsForMachine = machine < 10 ? 1 : 2; | ||||
|         int numSpaces = stringLength - 2 - charsForMachine; // we use 2 chars for '[' and '[' + 1 or 2 for the machine number | ||||
|  | @ -230,4 +235,10 @@ public class Schedule { | |||
|         sb.append("]"); | ||||
|         return sb.toString(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public Schedule toSchedule() { | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,16 @@ package jobshop.solvers; | |||
| import jobshop.Instance; | ||||
| import jobshop.Result; | ||||
| 
 | ||||
| /** Common interface that must implemented by all solvers. */ | ||||
| public interface Solver { | ||||
| 
 | ||||
|     /** Look for a solution until blocked or a deadline has been met. | ||||
|      * | ||||
|      * @param instance Jobshop instance that should be solved. | ||||
|      * @param deadline Absolute time at which the solver should have returned a solution. | ||||
|      *                 This time is in milliseconds and can be compared with System.currentTimeMilliseconds() | ||||
|      * @return A Result containing the solution found and an explanation of why the solver exited. | ||||
|      */ | ||||
|     Result solve(Instance instance, long deadline); | ||||
| 
 | ||||
|     /** Static factory method to create a new solver based on its name. */ | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue