Improvments for year 2020-2021
This commit is contained in:
parent
27a970026f
commit
1d4883388b
22 changed files with 362 additions and 147 deletions
|
@ -7,7 +7,7 @@ plugins {
|
||||||
group 'jobshop'
|
group 'jobshop'
|
||||||
//version '0.1'
|
//version '0.1'
|
||||||
|
|
||||||
sourceCompatibility = 8
|
sourceCompatibility = 11
|
||||||
|
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|
11
instances/aaa2
Normal file
11
instances/aaa2
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Example 2
|
||||||
|
# This is the example used as a support for exercises during classes (year 2020-2021)
|
||||||
|
4 3 # num-jobs num-tasks
|
||||||
|
0 1 1 3 2 2
|
||||||
|
1 8 0 5 2 10
|
||||||
|
0 5 2 4 1 8
|
||||||
|
2 4 0 10 1 6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
20
instances/aaa3
Normal file
20
instances/aaa3
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Example 3
|
||||||
|
# This problem has deterministic results for all greedy solver variants.
|
||||||
|
# Makespan for each variant:
|
||||||
|
# - SPT: 53
|
||||||
|
# - LPT: 92
|
||||||
|
# - SRPT: 78
|
||||||
|
# - LRPT: 54
|
||||||
|
# - EST_SPT: 48
|
||||||
|
# - EST_LPT: 56
|
||||||
|
# - EST_SRPT: 53
|
||||||
|
# - EST_LRPT: 56
|
||||||
|
4 3 # num-jobs num-tasks
|
||||||
|
0 1 1 2 2 15
|
||||||
|
1 4 0 5 2 11
|
||||||
|
0 7 2 8 1 13
|
||||||
|
2 3 0 18 1 6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@ public class BestKnownResults {
|
||||||
static {
|
static {
|
||||||
bests = new HashMap<>();
|
bests = new HashMap<>();
|
||||||
bests.put("aaa1", 11);
|
bests.put("aaa1", 11);
|
||||||
|
bests.put("aaa2", 29);
|
||||||
|
bests.put("aaa3", 41);
|
||||||
bests.put("abz5", 1234);
|
bests.put("abz5", 1234);
|
||||||
bests.put("abz6", 943);
|
bests.put("abz6", 943);
|
||||||
bests.put("abz7", 656);
|
bests.put("abz7", 656);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package jobshop;
|
package jobshop;
|
||||||
|
|
||||||
import jobshop.encodings.JobNumbers;
|
import jobshop.encodings.JobNumbers;
|
||||||
|
import jobshop.encodings.Schedule;
|
||||||
|
import jobshop.solvers.GreedySolver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -12,25 +14,22 @@ public class DebuggingMain {
|
||||||
// load the aaa1 instance
|
// load the aaa1 instance
|
||||||
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
|
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
|
||||||
|
|
||||||
// construit une solution dans la représentation par
|
// builds a solution in the job-numbers encoding [0 1 1 0 0 1]
|
||||||
// numéro de jobs : [0 1 1 0 0 1]
|
|
||||||
// Note : cette solution a aussi été vue dans les exercices (section 3.3)
|
|
||||||
// mais on commençait à compter à 1 ce qui donnait [1 2 2 1 1 2]
|
|
||||||
JobNumbers enc = new JobNumbers(instance);
|
JobNumbers enc = new JobNumbers(instance);
|
||||||
enc.jobs[enc.nextToSet++] = 0;
|
enc.addTask(0);
|
||||||
enc.jobs[enc.nextToSet++] = 1;
|
enc.addTask(1);
|
||||||
enc.jobs[enc.nextToSet++] = 1;
|
enc.addTask(1);
|
||||||
enc.jobs[enc.nextToSet++] = 0;
|
enc.addTask(0);
|
||||||
enc.jobs[enc.nextToSet++] = 0;
|
enc.addTask(0);
|
||||||
enc.jobs[enc.nextToSet++] = 1;
|
enc.addTask(1);
|
||||||
|
|
||||||
System.out.println("\nENCODING: " + enc);
|
System.out.println("\nENCODING: " + enc);
|
||||||
|
|
||||||
Schedule sched = enc.toSchedule();
|
Schedule schedule = enc.toSchedule();
|
||||||
// TODO: make it print something meaningful by implementing the Schedule.toString() method
|
System.out.println("VALID: " + schedule.isValid());
|
||||||
System.out.println("SCHEDULE: " + sched);
|
System.out.println("MAKESPAN: " + schedule.makespan());
|
||||||
System.out.println("VALID: " + sched.isValid());
|
System.out.println("SCHEDULE: " + schedule.toString());
|
||||||
System.out.println("MAKESPAN: " + sched.makespan());
|
System.out.println("GANTT: " + schedule.asciiGantt());
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
@ -7,8 +7,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jobshop.solvers.*;
|
import jobshop.solvers.*;
|
||||||
|
import jobshop.solvers.neighborhood.Nowicki;
|
||||||
import net.sourceforge.argparse4j.ArgumentParsers;
|
import net.sourceforge.argparse4j.ArgumentParsers;
|
||||||
import net.sourceforge.argparse4j.inf.ArgumentParser;
|
import net.sourceforge.argparse4j.inf.ArgumentParser;
|
||||||
import net.sourceforge.argparse4j.inf.ArgumentParserException;
|
import net.sourceforge.argparse4j.inf.ArgumentParserException;
|
||||||
|
@ -20,14 +22,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
/** All solvers available in this program */
|
|
||||||
private static final HashMap<String, Solver> solvers;
|
|
||||||
static {
|
|
||||||
solvers = new HashMap<>();
|
|
||||||
solvers.put("basic", new BasicSolver());
|
|
||||||
solvers.put("random", new RandomSolver());
|
|
||||||
// TODO: add new solvers here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -72,14 +67,7 @@ public class Main {
|
||||||
// Get the list of solvers that we should benchmark.
|
// Get the list of solvers that we should benchmark.
|
||||||
// We also check that we have a solver available for the given name and print an error message otherwise.
|
// We also check that we have a solver available for the given name and print an error message otherwise.
|
||||||
List<String> solversToTest = ns.getList("solver");
|
List<String> solversToTest = ns.getList("solver");
|
||||||
for(String solverName : solversToTest) {
|
List<Solver> solvers = solversToTest.stream().map(Solver::getSolver).collect(Collectors.toList());
|
||||||
if(!solvers.containsKey(solverName)) {
|
|
||||||
System.err.println("ERROR: Solver \"" + solverName + "\" is not avalaible.");
|
|
||||||
System.err.println(" Available solvers: " + solvers.keySet().toString());
|
|
||||||
System.err.println(" You can provide your own solvers by adding them to the `Main.solvers` HashMap.");
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve all instances on which we should run the solvers.
|
// retrieve all instances on which we should run the solvers.
|
||||||
List<String> instances = new ArrayList<>();
|
List<String> instances = new ArrayList<>();
|
||||||
|
@ -126,11 +114,10 @@ public class Main {
|
||||||
output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
|
output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
|
||||||
|
|
||||||
// run all selected solvers on the instance and print the results
|
// run all selected solvers on the instance and print the results
|
||||||
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
|
for(int solverId = 0 ; solverId < solvers.size() ; solverId++) {
|
||||||
// Select the next solver to run. Given the solver name passed on the command line,
|
// Select the next solver to run. Given the solver name passed on the command line,
|
||||||
// we lookup the `Main.solvers` hash map to get the solver object with the given name.
|
// we lookup the `Main.solvers` hash map to get the solver object with the given name.
|
||||||
String solverName = solversToTest.get(solverId);
|
Solver solver = solvers.get(solverId);
|
||||||
Solver solver = solvers.get(solverName);
|
|
||||||
|
|
||||||
// start chronometer and compute deadline for the solver to provide a result.
|
// start chronometer and compute deadline for the solver to provide a result.
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
@ -168,7 +155,7 @@ public class Main {
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// there was uncought exception, print the stack trace and exit with error.
|
// there was uncaught exception, print the stack trace and exit with error.
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package jobshop;
|
package jobshop;
|
||||||
|
|
||||||
import java.util.Optional;
|
import jobshop.encodings.Schedule;
|
||||||
|
|
||||||
public class Result {
|
public class Result {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package jobshop;
|
|
||||||
|
|
||||||
public interface Solver {
|
|
||||||
|
|
||||||
Result solve(Instance instance, long deadline);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,6 @@
|
||||||
package jobshop;
|
package jobshop.encodings;
|
||||||
|
|
||||||
|
import jobshop.Instance;
|
||||||
|
|
||||||
public abstract class Encoding {
|
public abstract class Encoding {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package jobshop.encodings;
|
package jobshop.encodings;
|
||||||
|
|
||||||
import jobshop.Encoding;
|
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Schedule;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -45,11 +43,15 @@ public class JobNumbers extends Encoding {
|
||||||
.min(Comparator.comparing(t -> schedule.startTime(t.job, t.task)))
|
.min(Comparator.comparing(t -> schedule.startTime(t.job, t.task)))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
this.jobs[nextToSet++] = next.job;
|
this.addTask(next.job);
|
||||||
nextOnJob[next.job] += 1;
|
nextOnJob[next.job] += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTask(int jobNumber) {
|
||||||
|
this.jobs[nextToSet++] = jobNumber;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Schedule toSchedule() {
|
public Schedule toSchedule() {
|
||||||
// time at which each machine is going to be freed
|
// time at which each machine is going to be freed
|
||||||
|
@ -59,22 +61,22 @@ public class JobNumbers extends Encoding {
|
||||||
int[] nextTask = new int[instance.numJobs];
|
int[] nextTask = new int[instance.numJobs];
|
||||||
|
|
||||||
// for each task, its start time
|
// for each task, its start time
|
||||||
int[][] startTimes = new int[instance.numJobs][instance.numTasks];
|
Schedule schedule = new Schedule(instance);
|
||||||
|
|
||||||
// compute the earliest start time for every task of every job
|
// compute the earliest start time for every task of every job
|
||||||
for(int job : jobs) {
|
for(int job : jobs) {
|
||||||
int task = nextTask[job];
|
int task = nextTask[job];
|
||||||
int machine = instance.machine(job, task);
|
int machine = instance.machine(job, task);
|
||||||
// earliest start time for this task
|
// earliest start time for this task
|
||||||
int est = task == 0 ? 0 : startTimes[job][task-1] + instance.duration(job, task-1);
|
int est = task == 0 ? 0 : schedule.endTime(job, task-1);
|
||||||
est = Math.max(est, nextFreeTimeResource[machine]);
|
est = Math.max(est, nextFreeTimeResource[machine]);
|
||||||
|
|
||||||
startTimes[job][task] = est;
|
schedule.setStartTime(job, task, est);
|
||||||
nextFreeTimeResource[machine] = est + instance.duration(job, task);
|
nextFreeTimeResource[machine] = est + instance.duration(job, task);
|
||||||
nextTask[job] = task + 1;
|
nextTask[job] = task + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Schedule(instance, startTimes);
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package jobshop.encodings;
|
package jobshop.encodings;
|
||||||
|
|
||||||
import jobshop.Encoding;
|
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Schedule;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -53,10 +51,21 @@ public class ResourceOrder extends Encoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTaskToMachine(int machine, Task task) {
|
||||||
|
tasksByMachine[machine][nextFreeSlot[machine]] = task;
|
||||||
|
nextFreeSlot[machine] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapTasks(int machine, int indexFirstTask, int indexSecondTask) {
|
||||||
|
Task tmp = tasksByMachine[machine][indexFirstTask];
|
||||||
|
tasksByMachine[machine][indexFirstTask] = tasksByMachine[machine][indexSecondTask];
|
||||||
|
tasksByMachine[machine][indexSecondTask] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Schedule toSchedule() {
|
public Schedule toSchedule() {
|
||||||
// indicate for each task that have been scheduled, its start time
|
// indicate for each task that have been scheduled, its start time
|
||||||
int [][] startTimes = new int [instance.numJobs][instance.numTasks];
|
Schedule schedule = new Schedule(instance);
|
||||||
|
|
||||||
// for each job, how many tasks have been scheduled (0 initially)
|
// for each job, how many tasks have been scheduled (0 initially)
|
||||||
int[] nextToScheduleByJob = new int[instance.numJobs];
|
int[] nextToScheduleByJob = new int[instance.numJobs];
|
||||||
|
@ -88,9 +97,9 @@ public class ResourceOrder extends Encoding {
|
||||||
int machine = instance.machine(t.job, t.task);
|
int machine = instance.machine(t.job, t.task);
|
||||||
|
|
||||||
// compute the earliest start time (est) of the 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);
|
int est = t.task == 0 ? 0 : schedule.endTime(t.job, t.task-1);
|
||||||
est = Math.max(est, releaseTimeOfMachine[instance.machine(t)]);
|
est = Math.max(est, releaseTimeOfMachine[instance.machine(t)]);
|
||||||
startTimes[t.job][t.task] = est;
|
schedule.setStartTime(t.job, t.task, est);
|
||||||
|
|
||||||
// mark the task as scheduled
|
// mark the task as scheduled
|
||||||
nextToScheduleByJob[t.job]++;
|
nextToScheduleByJob[t.job]++;
|
||||||
|
@ -103,7 +112,7 @@ public class ResourceOrder extends Encoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we exited the loop : all tasks have been scheduled successfully
|
// we exited the loop : all tasks have been scheduled successfully
|
||||||
return new Schedule(instance, startTimes);
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates an exact copy of this resource order. */
|
/** Creates an exact copy of this resource order. */
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jobshop;
|
package jobshop.encodings;
|
||||||
|
|
||||||
|
import jobshop.Instance;
|
||||||
import jobshop.encodings.Task;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
@ -12,16 +11,18 @@ public class Schedule {
|
||||||
// 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;
|
||||||
|
|
||||||
public Schedule(Instance pb, int[][] times) {
|
/** Creates a new schedule for the given instance where all start times are uninitialized. */
|
||||||
|
public Schedule(Instance pb) {
|
||||||
this.pb = pb;
|
this.pb = pb;
|
||||||
this.times = new int[pb.numJobs][];
|
this.times = new int[pb.numJobs][];
|
||||||
for(int j = 0 ; j < pb.numJobs ; j++) {
|
for(int j = 0 ; j < pb.numJobs ; j++) {
|
||||||
this.times[j] = Arrays.copyOf(times[j], pb.numTasks);
|
this.times[j] = new int[pb.numTasks];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int startTime(int job, int task) {
|
/** Sets the start time of the given task. */
|
||||||
return times[job][task];
|
public void setStartTime(int job, int task, int startTime) {
|
||||||
|
times[job][task] = startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if this schedule is valid (no constraint is violated) */
|
/** Returns true if this schedule is valid (no constraint is violated) */
|
||||||
|
@ -55,22 +56,38 @@ public class Schedule {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Makespan of the solution.
|
||||||
|
* The makespan is the end time of the latest finishing task.
|
||||||
|
*/
|
||||||
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<pb.numJobs ; j++) {
|
||||||
max = Math.max(max, startTime(j, pb.numTasks-1) + pb.duration(j, pb.numTasks -1));
|
max = Math.max(max, endTime(j, pb.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) {
|
public int startTime(Task task) {
|
||||||
return startTime(task.job, task.task);
|
return startTime(task.job, task.task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int endTime(Task task) {
|
/** End time of the given task. */
|
||||||
return startTime(task) + pb.duration(task.job, task.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) {
|
public boolean isCriticalPath(List<Task> path) {
|
||||||
if(startTime(path.get(0)) != 0) {
|
if(startTime(path.get(0)) != 0) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -85,6 +102,10 @@ public class Schedule {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Computes a critical path of the schedule.
|
||||||
|
*
|
||||||
|
* @return A sequence of task along a critical path.
|
||||||
|
*/
|
||||||
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, pb.numJobs)
|
||||||
|
@ -117,7 +138,7 @@ public class Schedule {
|
||||||
if(endTime(predOnJob) == startTime(cur))
|
if(endTime(predOnJob) == startTime(cur))
|
||||||
latestPredecessor = Optional.of(predOnJob);
|
latestPredecessor = Optional.of(predOnJob);
|
||||||
}
|
}
|
||||||
if(!latestPredecessor.isPresent()) {
|
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, pb.numJobs)
|
||||||
.mapToObj(j -> new Task(j, pb.task_with_machine(j, machine)))
|
.mapToObj(j -> new Task(j, pb.task_with_machine(j, machine)))
|
||||||
|
@ -132,4 +153,81 @@ public class Schedule {
|
||||||
assert isCriticalPath(path);
|
assert isCriticalPath(path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("\nStart times of all tasks:\n");
|
||||||
|
for(int job=0; job<pb.numJobs; job++) {
|
||||||
|
sb.append("Job ");
|
||||||
|
sb.append(job);
|
||||||
|
sb.append(": ");
|
||||||
|
for(int task=0; task<pb.numTasks; task++) {
|
||||||
|
sb.append(String.format("%5d", startTime(job, task)));
|
||||||
|
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string containing the Gantt chart of the given schedule, in ASCII art.
|
||||||
|
*
|
||||||
|
* Each line of the Gantt chart, contains the tasks of a particular job. Each character in the output represents a
|
||||||
|
* fixed number of time units
|
||||||
|
* For each task, we indicate :
|
||||||
|
* - the machine on which the task must be executed
|
||||||
|
* - whether this task is on the critical path (task on the critical path are filled in with stars).
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
// 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++) {
|
||||||
|
sb.append(String.format("Job %2d: ", job));
|
||||||
|
int cursor = 0;
|
||||||
|
for(int task=0; task<pb.numTasks; task++) {
|
||||||
|
Task t = new Task(job, task);
|
||||||
|
var st = startTime(job, task);
|
||||||
|
// add spaces until the start of our task
|
||||||
|
sb.append(" ".repeat(charsPerTimeUnit * (st - cursor )));
|
||||||
|
sb.append(formatTask(t, charsPerTimeUnit, criticalPath.contains(t)));
|
||||||
|
|
||||||
|
cursor = endTime(job, task);
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Utility function to display a set of characters representing a task in a gantt chart.
|
||||||
|
*
|
||||||
|
* @param t Task to display
|
||||||
|
* @param charPerTimeUnit How many characters to represent a time unit.
|
||||||
|
* @param isCritical Is the task on the critical path.
|
||||||
|
* @return Ascii representation of the task. Length is the duration * charPerTimeUnit.
|
||||||
|
*/
|
||||||
|
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 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
|
||||||
|
int startSpaces = numSpaces / 2;
|
||||||
|
int endSpaces = numSpaces - startSpaces;
|
||||||
|
sb.append("[");
|
||||||
|
sb.append(fill.repeat(startSpaces - 1));
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(machine);
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(fill.repeat(endSpaces - 1));
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,6 @@ public final class Task {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "(" + job +", " + task + '}';
|
return "(" + job +", " + task + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package jobshop.solvers;
|
||||||
|
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Result;
|
import jobshop.Result;
|
||||||
import jobshop.Solver;
|
|
||||||
import jobshop.encodings.JobNumbers;
|
import jobshop.encodings.JobNumbers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,89 +2,22 @@ package jobshop.solvers;
|
||||||
|
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Result;
|
import jobshop.Result;
|
||||||
import jobshop.Solver;
|
|
||||||
import jobshop.encodings.ResourceOrder;
|
import jobshop.encodings.ResourceOrder;
|
||||||
|
import jobshop.solvers.neighborhood.Neighborhood;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DescentSolver implements Solver {
|
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.
|
final Neighborhood<ResourceOrder> neighborhood;
|
||||||
* This class identifies a block in a ResourceOrder representation.
|
final Solver baseSolver;
|
||||||
*
|
|
||||||
* 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) {
|
public DescentSolver(Neighborhood<ResourceOrder> neighborhood, Solver baseSolver) {
|
||||||
this.machine = machine;
|
this.neighborhood = neighborhood;
|
||||||
this.firstTask = firstTask;
|
this.baseSolver = baseSolver;
|
||||||
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 swap 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
|
@Override
|
||||||
public Result solve(Instance instance, long deadline) {
|
public Result solve(Instance instance, long deadline) {
|
||||||
throw new UnsupportedOperationException();
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
21
src/main/java/jobshop/solvers/GreedySolver.java
Normal file
21
src/main/java/jobshop/solvers/GreedySolver.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package jobshop.solvers;
|
||||||
|
|
||||||
|
import jobshop.Instance;
|
||||||
|
import jobshop.Result;
|
||||||
|
|
||||||
|
public class GreedySolver implements Solver {
|
||||||
|
|
||||||
|
public enum Priority {
|
||||||
|
SPT, LPT, SRPT, LRPT, EST_SPT, EST_LPT, EST_SRPT, EST_LRPT
|
||||||
|
}
|
||||||
|
|
||||||
|
final Priority priority;
|
||||||
|
public GreedySolver(Priority p) {
|
||||||
|
this.priority = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result solve(Instance instance, long deadline) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@ package jobshop.solvers;
|
||||||
|
|
||||||
import jobshop.*;
|
import jobshop.*;
|
||||||
import jobshop.encodings.JobNumbers;
|
import jobshop.encodings.JobNumbers;
|
||||||
|
import jobshop.encodings.Schedule;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class RandomSolver implements Solver {
|
public class RandomSolver implements Solver {
|
||||||
|
|
21
src/main/java/jobshop/solvers/Solver.java
Normal file
21
src/main/java/jobshop/solvers/Solver.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package jobshop.solvers;
|
||||||
|
|
||||||
|
import jobshop.Instance;
|
||||||
|
import jobshop.Result;
|
||||||
|
|
||||||
|
public interface Solver {
|
||||||
|
|
||||||
|
Result solve(Instance instance, long deadline);
|
||||||
|
|
||||||
|
/** Static factory method to create a new solver based on its name. */
|
||||||
|
static Solver getSolver(String name) {
|
||||||
|
switch (name) {
|
||||||
|
case "basic": return new BasicSolver();
|
||||||
|
case "random": return new RandomSolver();
|
||||||
|
case "spt": return new GreedySolver(GreedySolver.Priority.SPT);
|
||||||
|
// TODO: add new solvers
|
||||||
|
default: throw new RuntimeException("Unknown solver: "+ name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
src/main/java/jobshop/solvers/neighborhood/Neighbor.java
Normal file
8
src/main/java/jobshop/solvers/neighborhood/Neighbor.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package jobshop.solvers.neighborhood;
|
||||||
|
|
||||||
|
public abstract class Neighbor<Encoding> {
|
||||||
|
|
||||||
|
public abstract void applyOn(Encoding current);
|
||||||
|
public abstract void undoApplyOn(Encoding current);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package jobshop.solvers.neighborhood;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class Neighborhood<Encoding> {
|
||||||
|
|
||||||
|
public abstract List<Neighbor<Encoding>> generateNeighbors(Encoding current);
|
||||||
|
|
||||||
|
}
|
102
src/main/java/jobshop/solvers/neighborhood/Nowicki.java
Normal file
102
src/main/java/jobshop/solvers/neighborhood/Nowicki.java
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package jobshop.solvers.neighborhood;
|
||||||
|
|
||||||
|
import jobshop.encodings.ResourceOrder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Nowicki extends Neighborhood<ResourceOrder> {
|
||||||
|
|
||||||
|
/** 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 swap 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 extends Neighbor<ResourceOrder> {
|
||||||
|
// 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. */
|
||||||
|
@Override
|
||||||
|
public void applyOn(ResourceOrder current) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undoApplyOn(ResourceOrder current) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Neighbor<ResourceOrder>> generateNeighbors(ResourceOrder current) {
|
||||||
|
List<Neighbor<ResourceOrder>> neighbors = new ArrayList<>();
|
||||||
|
// iterate over all blocks of the critical
|
||||||
|
for(var block : blocksOfCriticalPath(current)) {
|
||||||
|
// for this block, compute all neighbors and add them to the list of neighbors
|
||||||
|
neighbors.addAll(neighbors(block));
|
||||||
|
}
|
||||||
|
return neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a list of all the 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,8 +2,7 @@ package jobshop.encodings;
|
||||||
|
|
||||||
import jobshop.Instance;
|
import jobshop.Instance;
|
||||||
import jobshop.Result;
|
import jobshop.Result;
|
||||||
import jobshop.Schedule;
|
import jobshop.solvers.Solver;
|
||||||
import jobshop.Solver;
|
|
||||||
import jobshop.solvers.BasicSolver;
|
import jobshop.solvers.BasicSolver;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue