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