Doc improvements + rename DebuggingMain to MainTest

This commit is contained in:
Arthur Bit-Monnot 2021-04-08 22:24:22 +02:00
parent 741eeb02f3
commit 0b551e3b4e
8 changed files with 108 additions and 63 deletions

View file

@ -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()

View file

@ -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);

View file

@ -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;

View file

@ -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();
}

View file

@ -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];

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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. */