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;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the main entry point for testing solver on instances.
|
* This class is the main entry point for doing comparative performance tests of solvers.
|
||||||
* It provides
|
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// configure the argument parser
|
// configure the argument parser
|
||||||
ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build()
|
ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build()
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package jobshop;
|
package jobshop;
|
||||||
|
|
||||||
import jobshop.encodings.JobNumbers;
|
import jobshop.encodings.JobNumbers;
|
||||||
|
import jobshop.encodings.ResourceOrder;
|
||||||
import jobshop.encodings.Schedule;
|
import jobshop.encodings.Schedule;
|
||||||
|
import jobshop.encodings.Task;
|
||||||
import jobshop.solvers.GreedySolver;
|
import jobshop.solvers.GreedySolver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
public class DebuggingMain {
|
public class MainTest {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
|
@ -25,12 +27,18 @@ public class DebuggingMain {
|
||||||
|
|
||||||
System.out.println("\nENCODING: " + enc);
|
System.out.println("\nENCODING: " + enc);
|
||||||
|
|
||||||
|
// convert to a schedule and display
|
||||||
Schedule schedule = enc.toSchedule();
|
Schedule schedule = enc.toSchedule();
|
||||||
System.out.println("VALID: " + schedule.isValid());
|
System.out.println("VALID: " + schedule.isValid());
|
||||||
System.out.println("MAKESPAN: " + schedule.makespan());
|
System.out.println("MAKESPAN: " + schedule.makespan());
|
||||||
System.out.println("SCHEDULE: " + schedule.toString());
|
System.out.println("SCHEDULE: " + schedule.toString());
|
||||||
System.out.println("GANTT: " + schedule.asciiGantt());
|
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) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
|
@ -2,6 +2,7 @@ package jobshop;
|
||||||
|
|
||||||
import jobshop.encodings.Schedule;
|
import jobshop.encodings.Schedule;
|
||||||
|
|
||||||
|
/** Class representing the result of a solver. */
|
||||||
public class Result {
|
public class Result {
|
||||||
|
|
||||||
public Result(Instance instance, Schedule schedule, ExitCause cause) {
|
public Result(Instance instance, Schedule schedule, ExitCause cause) {
|
||||||
|
@ -10,12 +11,23 @@ public class Result {
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Documents the reason why a solver returned the solution. */
|
||||||
public enum ExitCause {
|
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;
|
public final Instance instance;
|
||||||
|
|
||||||
|
/** A schedule of the solution or null if no solution was found. */
|
||||||
public final Schedule schedule;
|
public final Schedule schedule;
|
||||||
|
|
||||||
|
/** Reason why the solver exited with this solution. */
|
||||||
public final ExitCause cause;
|
public final ExitCause cause;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,19 @@ package jobshop.encodings;
|
||||||
|
|
||||||
import jobshop.Instance;
|
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 {
|
public abstract class Encoding {
|
||||||
|
|
||||||
|
/** Problem instance of which this is the solution. */
|
||||||
public final Instance instance;
|
public final Instance instance;
|
||||||
|
|
||||||
public Encoding(Instance instance) {
|
public Encoding(Instance instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convert into a schedule. */
|
||||||
public abstract Schedule toSchedule();
|
public abstract Schedule toSchedule();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class JobNumbers extends Encoding {
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobNumbers(Schedule schedule) {
|
public JobNumbers(Schedule schedule) {
|
||||||
super(schedule.pb);
|
super(schedule.instance);
|
||||||
|
|
||||||
this.jobs = new int[instance.numJobs * instance.numTasks];
|
this.jobs = new int[instance.numJobs * instance.numTasks];
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,13 @@ public class ResourceOrder extends Encoding {
|
||||||
/** Creates a resource order from a schedule. */
|
/** Creates a resource order from a schedule. */
|
||||||
public ResourceOrder(Schedule schedule)
|
public ResourceOrder(Schedule schedule)
|
||||||
{
|
{
|
||||||
super(schedule.pb);
|
super(schedule.instance);
|
||||||
Instance pb = schedule.pb;
|
Instance pb = schedule.instance;
|
||||||
|
|
||||||
this.tasksByMachine = new Task[pb.numMachines][];
|
this.tasksByMachine = new Task[pb.numMachines][];
|
||||||
this.nextFreeSlot = new int[instance.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;
|
final int machine = m;
|
||||||
|
|
||||||
// for this machine, find all tasks that are executed on it and sort them by their start time
|
// 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) {
|
public void addTaskToMachine(int machine, Task task) {
|
||||||
tasksByMachine[machine][nextFreeSlot[machine]] = task;
|
tasksByMachine[machine][nextFreeSlot[machine]] = task;
|
||||||
nextFreeSlot[machine] += 1;
|
nextFreeSlot[machine] += 1;
|
||||||
|
|
|
@ -5,21 +5,46 @@ import jobshop.Instance;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class Schedule {
|
/** Direct encoding of the solution to JobShop problem.
|
||||||
public final Instance pb;
|
*
|
||||||
|
* Associates every task to its start time.
|
||||||
|
*/
|
||||||
|
public class Schedule extends Encoding {
|
||||||
|
|
||||||
// start times of each job and task
|
// 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
|
// times[j][i] is the start time of task (j,i) : i^th task of the j^th job
|
||||||
final int[][] times;
|
final int[][] times;
|
||||||
|
|
||||||
/** Creates a new schedule for the given instance where all start times are uninitialized. */
|
/** Creates a new schedule for the given instance where all start times are uninitialized. */
|
||||||
public Schedule(Instance pb) {
|
public Schedule(Instance instance) {
|
||||||
this.pb = pb;
|
super(instance);
|
||||||
this.times = new int[pb.numJobs][];
|
this.times = new int[instance.numJobs][];
|
||||||
for(int j = 0 ; j < pb.numJobs ; j++) {
|
for(int j = 0; j < instance.numJobs ; j++) {
|
||||||
this.times[j] = new int[pb.numTasks];
|
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. */
|
/** Sets the start time of the given task. */
|
||||||
public void setStartTime(int job, int task, int startTime) {
|
public void setStartTime(int job, int task, int startTime) {
|
||||||
times[job][task] = startTime;
|
times[job][task] = startTime;
|
||||||
|
@ -27,25 +52,25 @@ public class Schedule {
|
||||||
|
|
||||||
/** Returns true if this schedule is valid (no constraint is violated) */
|
/** Returns true if this schedule is valid (no constraint is violated) */
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
for(int j = 0 ; j<pb.numJobs ; j++) {
|
for(int j = 0; j<instance.numJobs ; j++) {
|
||||||
for(int t = 1 ; t<pb.numTasks ; t++) {
|
for(int t = 1; t< instance.numTasks ; t++) {
|
||||||
if(startTime(j, t-1) + pb.duration(j, t-1) > startTime(j, t))
|
if(startTime(j, t-1) + instance.duration(j, t-1) > startTime(j, t))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for(int t = 0 ; t<pb.numTasks ; t++) {
|
for(int t = 0; t< instance.numTasks ; t++) {
|
||||||
if(startTime(j, t) < 0)
|
if(startTime(j, t) < 0)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int machine = 0 ; machine < pb.numMachines ; machine++) {
|
for (int machine = 0; machine < instance.numMachines ; machine++) {
|
||||||
for(int j1=0 ; j1<pb.numJobs ; j1++) {
|
for(int j1 = 0; j1< instance.numJobs ; j1++) {
|
||||||
int t1 = pb.task_with_machine(j1, machine);
|
int t1 = instance.task_with_machine(j1, machine);
|
||||||
for(int j2=j1+1 ; j2<pb.numJobs ; j2++) {
|
for(int j2 = j1+1; j2< instance.numJobs ; j2++) {
|
||||||
int t2 = pb.task_with_machine(j2, machine);
|
int t2 = instance.task_with_machine(j2, machine);
|
||||||
|
|
||||||
boolean t1_first = startTime(j1, t1) + pb.duration(j1, t1) <= startTime(j2, t2);
|
boolean t1_first = startTime(j1, t1) + instance.duration(j1, t1) <= startTime(j2, t2);
|
||||||
boolean t2_first = startTime(j2, t2) + pb.duration(j2, t2) <= startTime(j1, t1);
|
boolean t2_first = startTime(j2, t2) + instance.duration(j2, t2) <= startTime(j1, t1);
|
||||||
|
|
||||||
if(!t1_first && !t2_first)
|
if(!t1_first && !t2_first)
|
||||||
return false;
|
return false;
|
||||||
|
@ -61,32 +86,12 @@ public class Schedule {
|
||||||
*/
|
*/
|
||||||
public int makespan() {
|
public int makespan() {
|
||||||
int max = -1;
|
int max = -1;
|
||||||
for(int j = 0 ; j<pb.numJobs ; j++) {
|
for(int j = 0; j< instance.numJobs ; j++) {
|
||||||
max = Math.max(max, endTime(j, pb.numTasks-1));
|
max = Math.max(max, endTime(j, instance.numTasks-1));
|
||||||
}
|
}
|
||||||
return max;
|
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. */
|
/** Returns true if the given sequence of task is a critical path of the schedule. */
|
||||||
public boolean isCriticalPath(List<Task> path) {
|
public boolean isCriticalPath(List<Task> path) {
|
||||||
if(startTime(path.get(0)) != 0) {
|
if(startTime(path.get(0)) != 0) {
|
||||||
|
@ -108,8 +113,8 @@ public class Schedule {
|
||||||
*/
|
*/
|
||||||
public List<Task> criticalPath() {
|
public List<Task> criticalPath() {
|
||||||
// select task with greatest end time
|
// select task with greatest end time
|
||||||
Task ldd = IntStream.range(0, pb.numJobs)
|
Task ldd = IntStream.range(0, instance.numJobs)
|
||||||
.mapToObj(j -> new Task(j, pb.numTasks-1))
|
.mapToObj(j -> new Task(j, instance.numTasks-1))
|
||||||
.max(Comparator.comparing(this::endTime))
|
.max(Comparator.comparing(this::endTime))
|
||||||
.get();
|
.get();
|
||||||
assert endTime(ldd) == makespan();
|
assert endTime(ldd) == makespan();
|
||||||
|
@ -124,7 +129,7 @@ public class Schedule {
|
||||||
// starts a time 0
|
// starts a time 0
|
||||||
while(startTime(path.getFirst()) != 0) {
|
while(startTime(path.getFirst()) != 0) {
|
||||||
Task cur = path.getFirst();
|
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
|
// will contain the task that was delaying the start
|
||||||
// of our current task
|
// of our current task
|
||||||
|
@ -140,8 +145,8 @@ public class Schedule {
|
||||||
}
|
}
|
||||||
if(latestPredecessor.isEmpty()) {
|
if(latestPredecessor.isEmpty()) {
|
||||||
// no latest predecessor found yet, look among tasks executing on the same machine
|
// no latest predecessor found yet, look among tasks executing on the same machine
|
||||||
latestPredecessor = IntStream.range(0, pb.numJobs)
|
latestPredecessor = IntStream.range(0, instance.numJobs)
|
||||||
.mapToObj(j -> new Task(j, pb.task_with_machine(j, machine)))
|
.mapToObj(j -> new Task(j, instance.task_with_machine(j, machine)))
|
||||||
.filter(t -> endTime(t) == startTime(cur))
|
.filter(t -> endTime(t) == startTime(cur))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
@ -158,11 +163,11 @@ public class Schedule {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("\nStart times of all tasks:\n");
|
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(job);
|
sb.append(job);
|
||||||
sb.append(": ");
|
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)));
|
sb.append(String.format("%5d", startTime(job, task)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -182,15 +187,15 @@ public class Schedule {
|
||||||
*/
|
*/
|
||||||
public String asciiGantt() {
|
public String asciiGantt() {
|
||||||
var criticalPath = this.criticalPath();
|
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
|
// time units by character
|
||||||
int charsPerTimeUnit = minTaskDur >= 5 ? 1 : (5 / minTaskDur) +1;
|
int charsPerTimeUnit = minTaskDur >= 5 ? 1 : (5 / minTaskDur) +1;
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("\nGantt Chart\n");
|
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));
|
sb.append(String.format("Job %2d: ", job));
|
||||||
int cursor = 0;
|
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);
|
Task t = new Task(job, task);
|
||||||
var st = startTime(job, task);
|
var st = startTime(job, task);
|
||||||
// add spaces until the start of our task
|
// add spaces until the start of our task
|
||||||
|
@ -214,8 +219,8 @@ public class Schedule {
|
||||||
String formatTask(Task t, int charPerTimeUnit, boolean isCritical) {
|
String formatTask(Task t, int charPerTimeUnit, boolean isCritical) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
String fill = isCritical ? "*" : "-";
|
String fill = isCritical ? "*" : "-";
|
||||||
int dur = pb.duration(t);
|
int dur = instance.duration(t);
|
||||||
int machine = pb.machine(t);
|
int machine = instance.machine(t);
|
||||||
int stringLength = dur * charPerTimeUnit;
|
int stringLength = dur * charPerTimeUnit;
|
||||||
int charsForMachine = machine < 10 ? 1 : 2;
|
int charsForMachine = machine < 10 ? 1 : 2;
|
||||||
int numSpaces = stringLength - 2 - charsForMachine; // we use 2 chars for '[' and '[' + 1 or 2 for the machine number
|
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("]");
|
sb.append("]");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Schedule toSchedule() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,16 @@ package jobshop.solvers;
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Result;
|
import jobshop.Result;
|
||||||
|
|
||||||
|
/** Common interface that must implemented by all solvers. */
|
||||||
public interface Solver {
|
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);
|
Result solve(Instance instance, long deadline);
|
||||||
|
|
||||||
/** Static factory method to create a new solver based on its name. */
|
/** Static factory method to create a new solver based on its name. */
|
||||||
|
|
Loading…
Reference in a new issue