Greedy Solver completed

This commit is contained in:
AlexJavor 2020-04-27 16:12:01 +02:00
parent 3b67200387
commit 6f78309d10
16 changed files with 1372 additions and 1126 deletions

View file

@ -1,189 +1,180 @@
package jobshop;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
public class BestKnownResult {
public static boolean isKnown(String instanceName) {
return bests.containsKey(instanceName);
}
public static List<String> instancesMatching(String namePrefix) {
return Arrays.stream(instances)
.filter(i -> i.startsWith(namePrefix))
.sorted()
.collect(Collectors.toList());
}
public static int of(String instanceName) {
if(!bests.containsKey(instanceName)) {
throw new RuntimeException("Unknown best result for "+instanceName);
}
return bests.get(instanceName);
}
static private HashMap<String, Integer> bests;
static String[] instances;
static {
bests = new HashMap<>();
bests.put("aaa1", 11);
bests.put("abz5", 1234);
bests.put("abz6", 943);
bests.put("abz7", 656);
bests.put("abz8", 665);
bests.put("abz9", 679);
bests.put("ft06", 55);
bests.put("ft10", 930);
bests.put("ft20", 1165);
bests.put("la01", 666);
bests.put("la02", 655);
bests.put("la03", 597);
bests.put("la04", 590);
bests.put("la05", 593);
bests.put("la06", 926);
bests.put("la07", 890);
bests.put("la08", 863);
bests.put("la09", 951);
bests.put("la10", 958);
bests.put("la11", 1222);
bests.put("la12", 1039);
bests.put("la13", 1150);
bests.put("la14", 1292);
bests.put("la15", 1207);
bests.put("la16", 945);
bests.put("la17", 784);
bests.put("la18", 848);
bests.put("la19", 842);
bests.put("la20", 902);
bests.put("la21", 1046);
bests.put("la22", 927);
bests.put("la23", 1032);
bests.put("la24", 935);
bests.put("la25", 977);
bests.put("la26", 1218);
bests.put("la27", 1235);
bests.put("la28", 1216);
bests.put("la29", 1152);
bests.put("la30", 1355);
bests.put("la31", 1784);
bests.put("la32", 1850);
bests.put("la33", 1719);
bests.put("la34", 1721);
bests.put("la35", 1888);
bests.put("la36", 1268);
bests.put("la37", 1397);
bests.put("la38", 1196);
bests.put("la39", 1233);
bests.put("la40", 1222);
bests.put("orb01", 1059);
bests.put("orb02", 888);
bests.put("orb03", 1005);
bests.put("orb04", 1005);
bests.put("orb05", 887);
bests.put("orb06", 1010);
bests.put("orb07", 397);
bests.put("orb08", 899);
bests.put("orb09", 934);
bests.put("orb10", 944);
bests.put("swv01", 1407);
bests.put("swv02", 1475);
bests.put("swv03", 1398);
bests.put("swv04", 1474);
bests.put("swv05", 1424);
bests.put("swv06", 1678);
bests.put("swv07", 1600);
bests.put("swv08", 1763);
bests.put("swv09", 1661);
bests.put("swv10", 1767);
bests.put("swv11", 2991);
bests.put("swv12", 3003);
bests.put("swv13", 3104);
bests.put("swv14", 2968);
bests.put("swv15", 2904);
bests.put("swv16", 2924);
bests.put("swv17", 2794);
bests.put("swv18", 2852);
bests.put("swv19", 2843);
bests.put("swv20", 2823);
bests.put("yn1", 885);
bests.put("yn2", 909);
bests.put("yn3", 892);
bests.put("yn4", 968);
bests.put("ta01", 1231);
bests.put("ta02", 1244);
bests.put("ta03", 1218);
bests.put("ta04", 1175);
bests.put("ta05", 1224);
bests.put("ta06", 1238);
bests.put("ta07", 1227);
bests.put("ta08", 1217);
bests.put("ta09", 1274);
bests.put("ta10", 1241);
bests.put("ta11", 1361);
bests.put("ta12", 1367);
bests.put("ta13", 1342);
bests.put("ta14", 1345);
bests.put("ta15", 1340);
bests.put("ta16", 1360);
bests.put("ta17", 1462);
bests.put("ta18", 1396);
bests.put("ta19", 1335);
bests.put("ta20", 1351);
bests.put("ta21", 1644);
bests.put("ta22", 1600);
bests.put("ta23", 1557);
bests.put("ta24", 1647);
bests.put("ta25", 1595);
bests.put("ta26", 1645);
bests.put("ta27", 1680);
bests.put("ta28", 1614);
bests.put("ta29", 1635);
bests.put("ta30", 1584);
bests.put("ta31", 1764);
bests.put("ta32", 1796);
bests.put("ta33", 1793);
bests.put("ta34", 1829);
bests.put("ta35", 2007);
bests.put("ta36", 1819);
bests.put("ta37", 1778);
bests.put("ta38", 1673);
bests.put("ta39", 1795);
bests.put("ta40", 1674);
bests.put("ta41", 2018);
bests.put("ta42", 1956);
bests.put("ta43", 1859);
bests.put("ta44", 1984);
bests.put("ta45", 2000);
bests.put("ta46", 2021);
bests.put("ta47", 1903);
bests.put("ta48", 1952);
bests.put("ta49", 1968);
bests.put("ta50", 1926);
bests.put("ta51", 2760);
bests.put("ta52", 2756);
bests.put("ta53", 2717);
bests.put("ta54", 2839);
bests.put("ta55", 2679);
bests.put("ta56", 2781);
bests.put("ta57", 2943);
bests.put("ta58", 2885);
bests.put("ta59", 2655);
bests.put("ta60", 2723);
bests.put("ta61", 2868);
bests.put("ta62", 2869);
bests.put("ta63", 2755);
bests.put("ta64", 2702);
bests.put("ta65", 2725);
bests.put("ta66", 2845);
bests.put("ta67", 2825);
bests.put("ta68", 2784);
bests.put("ta69", 3071);
bests.put("ta70", 2995);
instances = bests.keySet().toArray(new String[0]);
Arrays.sort(instances);
}
}
package jobshop;
import java.util.Arrays;
import java.util.HashMap;
public class BestKnownResult {
public static boolean isKnown(String instanceName) {
return bests.containsKey(instanceName);
}
public static int of(String instanceName) {
if(!bests.containsKey(instanceName)) {
throw new RuntimeException("Unknown best result for "+instanceName);
}
return bests.get(instanceName);
}
static private HashMap<String, Integer> bests;
static String[] instances;
static {
bests = new HashMap<>();
bests.put("aaa1", 11);
bests.put("abz5", 1234);
bests.put("abz6", 943);
bests.put("abz7", 656);
bests.put("abz8", 665);
bests.put("abz9", 679);
bests.put("ft06", 55);
bests.put("ft10", 930);
bests.put("ft20", 1165);
bests.put("la01", 666);
bests.put("la02", 655);
bests.put("la03", 597);
bests.put("la04", 590);
bests.put("la05", 593);
bests.put("la06", 926);
bests.put("la07", 890);
bests.put("la08", 863);
bests.put("la09", 951);
bests.put("la10", 958);
bests.put("la11", 1222);
bests.put("la12", 1039);
bests.put("la13", 1150);
bests.put("la14", 1292);
bests.put("la15", 1207);
bests.put("la16", 945);
bests.put("la17", 784);
bests.put("la18", 848);
bests.put("la19", 842);
bests.put("la20", 902);
bests.put("la21", 1046);
bests.put("la22", 927);
bests.put("la23", 1032);
bests.put("la24", 935);
bests.put("la25", 977);
bests.put("la26", 1218);
bests.put("la27", 1235);
bests.put("la28", 1216);
bests.put("la29", 1152);
bests.put("la30", 1355);
bests.put("la31", 1784);
bests.put("la32", 1850);
bests.put("la33", 1719);
bests.put("la34", 1721);
bests.put("la35", 1888);
bests.put("la36", 1268);
bests.put("la37", 1397);
bests.put("la38", 1196);
bests.put("la39", 1233);
bests.put("la40", 1222);
bests.put("orb01", 1059);
bests.put("orb02", 888);
bests.put("orb03", 1005);
bests.put("orb04", 1005);
bests.put("orb05", 887);
bests.put("orb06", 1010);
bests.put("orb07", 397);
bests.put("orb08", 899);
bests.put("orb09", 934);
bests.put("orb10", 944);
bests.put("swv01", 1407);
bests.put("swv02", 1475);
bests.put("swv03", 1398);
bests.put("swv04", 1474);
bests.put("swv05", 1424);
bests.put("swv06", 1678);
bests.put("swv07", 1600);
bests.put("swv08", 1763);
bests.put("swv09", 1661);
bests.put("swv10", 1767);
bests.put("swv11", 2991);
bests.put("swv12", 3003);
bests.put("swv13", 3104);
bests.put("swv14", 2968);
bests.put("swv15", 2904);
bests.put("swv16", 2924);
bests.put("swv17", 2794);
bests.put("swv18", 2852);
bests.put("swv19", 2843);
bests.put("swv20", 2823);
bests.put("yn1", 885);
bests.put("yn2", 909);
bests.put("yn3", 892);
bests.put("yn4", 968);
bests.put("ta01", 1231);
bests.put("ta02", 1244);
bests.put("ta03", 1218);
bests.put("ta04", 1175);
bests.put("ta05", 1224);
bests.put("ta06", 1238);
bests.put("ta07", 1227);
bests.put("ta08", 1217);
bests.put("ta09", 1274);
bests.put("ta10", 1241);
bests.put("ta11", 1361);
bests.put("ta12", 1367);
bests.put("ta13", 1342);
bests.put("ta14", 1345);
bests.put("ta15", 1340);
bests.put("ta16", 1360);
bests.put("ta17", 1462);
bests.put("ta18", 1396);
bests.put("ta19", 1335);
bests.put("ta20", 1351);
bests.put("ta21", 1644);
bests.put("ta22", 1600);
bests.put("ta23", 1557);
bests.put("ta24", 1647);
bests.put("ta25", 1595);
bests.put("ta26", 1645);
bests.put("ta27", 1680);
bests.put("ta28", 1614);
bests.put("ta29", 1635);
bests.put("ta30", 1584);
bests.put("ta31", 1764);
bests.put("ta32", 1796);
bests.put("ta33", 1793);
bests.put("ta34", 1829);
bests.put("ta35", 2007);
bests.put("ta36", 1819);
bests.put("ta37", 1778);
bests.put("ta38", 1673);
bests.put("ta39", 1795);
bests.put("ta40", 1674);
bests.put("ta41", 2018);
bests.put("ta42", 1956);
bests.put("ta43", 1859);
bests.put("ta44", 1984);
bests.put("ta45", 2000);
bests.put("ta46", 2021);
bests.put("ta47", 1903);
bests.put("ta48", 1952);
bests.put("ta49", 1968);
bests.put("ta50", 1926);
bests.put("ta51", 2760);
bests.put("ta52", 2756);
bests.put("ta53", 2717);
bests.put("ta54", 2839);
bests.put("ta55", 2679);
bests.put("ta56", 2781);
bests.put("ta57", 2943);
bests.put("ta58", 2885);
bests.put("ta59", 2655);
bests.put("ta60", 2723);
bests.put("ta61", 2868);
bests.put("ta62", 2869);
bests.put("ta63", 2755);
bests.put("ta64", 2702);
bests.put("ta65", 2725);
bests.put("ta66", 2845);
bests.put("ta67", 2825);
bests.put("ta68", 2784);
bests.put("ta69", 3071);
bests.put("ta70", 2995);
instances = bests.keySet().toArray(new String[0]);
Arrays.sort(instances);
}
}

View file

@ -1,42 +1,114 @@
package jobshop;
import jobshop.encodings.JobNumbers;
import java.io.IOException;
import java.nio.file.Paths;
public class DebuggingMain {
public static void main(String[] args) {
try {
// load the aaa1 instance
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// construit une solution dans la représentation par
// 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);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
System.out.println("\nENCODING: " + enc);
Schedule sched = enc.toSchedule();
// TODO: make it print something meaningful
// by implementing the toString() method
System.out.println("SCHEDULE: " + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
package jobshop;
import jobshop.encodings.JobNumbers;
import jobshop.encodings.ResourceOrder;
import jobshop.encodings.Task;
import jobshop.solvers.GreedySolver;
import jobshop.solvers.GreedySolver.PriorityESTRule;
import jobshop.solvers.GreedySolver.PriorityRule;
import java.io.IOException;
import java.nio.file.Paths;
public class DebuggingMain {
public static void main(String[] args) {
try {
// load the aaa1 instance
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// construit une solution dans la représentation par
// 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);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
System.out.println("\nJOB NUMBER ENCODING: " + enc + "\n");
Schedule sched = enc.toSchedule();
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid() + "\n");
System.out.println("MAKESPAN: " + sched.makespan() + "\n");
System.out.println("---------------------------------------------\n");
ResourceOrder ro = new ResourceOrder(instance);
ro.tasksByMachine[0][0] = new Task(0,0);
ro.tasksByMachine[0][1] = new Task(1,1);
ro.tasksByMachine[1][0] = new Task(1,0);
ro.tasksByMachine[1][1] = new Task(0,1);
ro.tasksByMachine[2][0] = new Task(0,2);
ro.tasksByMachine[2][1] = new Task(1,2);
System.out.println("RESOURCE ORDER ENCODING:\n" + ro + "\n");
System.out.println("Default Solver\n");
sched = ro.toSchedule();
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
System.out.println("---------------------------------------------\n");
JobNumbers jo = JobNumbers.fromSchedule(sched);
System.out.println("JOB NUMBER ENCODING (FROM_SCHEDULE): " + jo + "\n");
System.out.println("---------------------------------------------\n");
System.out.println("Greedy Solver: STP");
PriorityRule SPT = PriorityRule.SPT;
Solver solverSPT = new GreedySolver(SPT);
Result resultSPT = solverSPT.solve(instance, System.currentTimeMillis() + 10);
sched = resultSPT.schedule;
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
System.out.println("---------------------------------------------\n");
System.out.println("Greedy Solver: LRPT\n");
PriorityRule LRPT = PriorityRule.LRPT;
Solver solverLRPT = new GreedySolver(LRPT);
Result resultLRPT = solverLRPT.solve(instance, System.currentTimeMillis() + 10);
sched = resultLRPT.schedule;
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
System.out.println("---------------------------------------------\n");
System.out.println("Greedy Solver: EST_SPT\n");
PriorityESTRule EST_SPT = PriorityESTRule.EST_SPT;
Solver solverEST_SPT = new GreedySolver(EST_SPT);
Result resultEST_SPT = solverEST_SPT.solve(instance, System.currentTimeMillis() + 10);
sched = resultEST_SPT.schedule;
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
System.out.println("---------------------------------------------\n");
System.out.println("Greedy Solver: EST_SPT\n");
PriorityESTRule EST_LRPT = PriorityESTRule.EST_LRPT;
Solver solverEST_LRPT = new GreedySolver(EST_SPT);
Result resultEST_LRPT = solverEST_LRPT.solve(instance, System.currentTimeMillis() + 10);
sched = resultEST_LRPT.schedule;
System.out.println("SCHEDULE:\n" + sched);
System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan());
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -1,12 +1,12 @@
package jobshop;
public abstract class Encoding {
public final Instance instance;
public Encoding(Instance instance) {
this.instance = instance;
}
public abstract Schedule toSchedule();
}
package jobshop;
public abstract class Encoding {
public final Instance instance;
public Encoding(Instance instance) {
this.instance = instance;
}
public abstract Schedule toSchedule();
}

View file

@ -1,79 +1,73 @@
package jobshop;
import jobshop.encodings.Task;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Instance {
/** Number of jobs in the instance */
public final int numJobs;
/** Number of tasks per job */
public final int numTasks;
/** Number of machines, assumed to be same as number of tasks. */
public final int numMachines;
final int[][] durations;
final int[][] machines;
public int duration(int job, int task) {
return durations[job][task];
}
public int duration(Task t) {
return duration(t.job, t.task);
}
public int machine(int job, int task) {
return machines[job][task];
}
public int machine(Task t) {
return this.machine(t.job, t.task);
}
/** among the tasks of the given job, returns the task index that uses the given machine. */
public int task_with_machine(int job, int wanted_machine) {
for(int task = 0 ; task < numTasks ; task++) {
if(machine(job, task) == wanted_machine)
return task;
}
throw new RuntimeException("No task targeting machine "+wanted_machine+" on job "+job);
}
Instance(int numJobs, int numTasks) {
this.numJobs = numJobs;
this.numTasks = numTasks;
this.numMachines = numTasks;
durations = new int[numJobs][numTasks];
machines = new int[numJobs][numTasks];
}
/** Parses a instance from a file. */
public static Instance fromFile(Path path) throws IOException {
Iterator<String> lines = Files.readAllLines(path).stream()
.filter(l -> !l.startsWith("#"))
.collect(Collectors.toList())
.iterator();
Scanner header = new Scanner(lines.next());
int num_jobs = header.nextInt();
int num_tasks = header.nextInt();
Instance pb = new Instance(num_jobs, num_tasks);
for(int job = 0 ; job<num_jobs ; job++) {
Scanner line = new Scanner(lines.next());
for(int task = 0 ; task < num_tasks ; task++) {
pb.machines[job][task] = line.nextInt();
pb.durations[job][task] = line.nextInt();
}
}
return pb;
}
}
package jobshop;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Instance {
/** Number of jobs in the instance */
public final int numJobs;
/** Number of tasks per job */
public final int numTasks;
/** Number of machines, assumed to be same as number of tasks. */
public final int numMachines;
final int[][] durations;
final int[][] machines;
public int duration(int job, int task) {
return durations[job][task];
}
public int machine(int job, int task) {
return machines[job][task];
}
/** among the tasks of the given job, returns the task index that uses the given machine. */
public int task_with_machine(int job, int wanted_machine) {
for(int task = 0 ; task < numTasks ; task++) {
if(machine(job, task) == wanted_machine)
return task;
}
throw new RuntimeException("No task targeting machine "+wanted_machine+" on job "+job);
}
Instance(int numJobs, int numTasks) {
this.numJobs = numJobs;
this.numTasks = numTasks;
this.numMachines = numTasks;
durations = new int[numJobs][numTasks];
machines = new int[numJobs][numTasks];
}
public static Instance fromFile(Path path) throws IOException {
Iterator<String> lines = Files.readAllLines(path).stream()
.filter(l -> !l.startsWith("#"))
.collect(Collectors.toList())
.iterator();
Scanner header = new Scanner(lines.next());
int num_jobs = header.nextInt();
int num_tasks = header.nextInt();
Instance pb = new Instance(num_jobs, num_tasks);
for(int job = 0 ; job<num_jobs ; job++) {
Scanner line = new Scanner(lines.next());
for(int task = 0 ; task < num_tasks ; task++) {
pb.machines[job][task] = line.nextInt();
pb.durations[job][task] = line.nextInt();
}
line.close();
}
header.close();
return pb;
}
}

View file

@ -1,146 +1,154 @@
package jobshop;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import jobshop.solvers.*;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;
public class Main {
/** All solvers available in this program */
private static HashMap<String, Solver> solvers;
static {
solvers = new HashMap<>();
solvers.put("basic", new BasicSolver());
solvers.put("random", new RandomSolver());
// add new solvers here
}
public static void main(String[] args) {
ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build()
.defaultHelp(true)
.description("Solves jobshop problems.");
parser.addArgument("-t", "--timeout")
.setDefault(1L)
.type(Long.class)
.help("Solver timeout in seconds for each instance");
parser.addArgument("--solver")
.nargs("+")
.required(true)
.help("Solver(s) to use (space separated if more than one)");
parser.addArgument("--instance")
.nargs("+")
.required(true)
.help("Instance(s) to solve (space separated if more than one)");
Namespace ns = null;
try {
ns = parser.parseArgs(args);
} catch (ArgumentParserException e) {
parser.handleError(e);
System.exit(1);
}
PrintStream output = System.out;
long solveTimeMs = ns.getLong("timeout") * 1000;
List<String> solversToTest = ns.getList("solver");
for(String solverName : solversToTest) {
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(1);
}
}
List<String> instancePrefixes = ns.getList("instance");
List<String> instances = new ArrayList<>();
for(String instancePrefix : instancePrefixes) {
List<String> matches = BestKnownResult.instancesMatching(instancePrefix);
if(matches.isEmpty()) {
System.err.println("ERROR: instance prefix \"" + instancePrefix + "\" does not match any instance.");
System.err.println(" available instances: " + Arrays.toString(BestKnownResult.instances));
System.exit(1);
}
instances.addAll(matches);
}
float[] runtimes = new float[solversToTest.size()];
float[] distances = new float[solversToTest.size()];
try {
output.print( " ");
for(String s : solversToTest)
output.printf("%-30s", s);
output.println();
output.print("instance size best ");
for(String s : solversToTest) {
output.print("runtime makespan ecart ");
}
output.println();
for(String instanceName : instances) {
int bestKnown = BestKnownResult.of(instanceName);
Path path = Paths.get("instances/", instanceName);
Instance instance = Instance.fromFile(path);
output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
String solverName = solversToTest.get(solverId);
Solver solver = solvers.get(solverName);
long start = System.currentTimeMillis();
long deadline = System.currentTimeMillis() + solveTimeMs;
Result result = solver.solve(instance, deadline);
long runtime = System.currentTimeMillis() - start;
if(!result.schedule.isValid()) {
System.err.println("ERROR: solver returned an invalid schedule");
System.exit(1);
}
assert result.schedule.isValid();
int makespan = result.schedule.makespan();
float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
runtimes[solverId] += (float) runtime / (float) instances.size();
distances[solverId] += dist / (float) instances.size();
output.printf("%7d %8s %5.1f ", runtime, makespan, dist);
output.flush();
}
output.println();
}
output.printf("%-8s %-5s %4s ", "AVG", "-", "-");
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
output.printf("%7.1f %8s %5.1f ", runtimes[solverId], "-", distances[solverId]);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
package jobshop;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import jobshop.solvers.BasicSolver;
import jobshop.solvers.RandomSolver;
import jobshop.solvers.GreedySolver.PriorityESTRule;
import jobshop.solvers.GreedySolver.PriorityRule;
import jobshop.solvers.GreedySolver;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;
public class Main {
/** All solvers available in this program */
private static HashMap<String, Solver> solvers;
static {
solvers = new HashMap<>();
solvers.put("basic", new BasicSolver());
solvers.put("random", new RandomSolver());
// add new solvers here
PriorityRule SPT = PriorityRule.SPT;
solvers.put("greedySPT", new GreedySolver(SPT));
PriorityRule LRPT = PriorityRule.LRPT;
solvers.put("greedyLRPT", new GreedySolver(LRPT));
PriorityESTRule EST_SPT = PriorityESTRule.EST_SPT;
solvers.put("greedyEST_SPT", new GreedySolver(EST_SPT));
PriorityESTRule EST_LRPT = PriorityESTRule.EST_LRPT;
solvers.put("greedyEST_LRPT", new GreedySolver(EST_LRPT));
}
@SuppressWarnings("unused")
public static void main(String[] args) {
ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build()
.defaultHelp(true)
.description("Solves jobshop problems.");
parser.addArgument("-t", "--timeout")
.setDefault(1L)
.type(Long.class)
.help("Solver timeout in seconds for each instance");
parser.addArgument("--solver")
.nargs("+")
.required(true)
.help("Solver(s) to use (space separated if more than one)");
parser.addArgument("--instance")
.nargs("+")
.required(true)
.help("Instance(s) to solve (space separated if more than one)");
Namespace ns = null;
try {
ns = parser.parseArgs(args);
} catch (ArgumentParserException e) {
parser.handleError(e);
System.exit(1);
}
PrintStream output = System.out;
long solveTimeMs = ns.getLong("timeout") * 1000;
List<String> solversToTest = ns.getList("solver");
for(String solverName : solversToTest) {
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(1);
}
}
List<String> instances = ns.<String>getList("instance");
for(String instanceName : instances) {
if(!BestKnownResult.isKnown(instanceName)) {
System.err.println("ERROR: instance \"" + instanceName + "\" is not avalaible.");
System.err.println(" available instances: " + Arrays.toString(BestKnownResult.instances));
System.exit(1);
}
}
float[] runtimes = new float[solversToTest.size()];
float[] distances = new float[solversToTest.size()];
try {
output.print( " ");;
for(String s : solversToTest)
output.printf("%-30s", s);
output.println();
output.print("instance size best ");
for(String s : solversToTest) {
output.print("runtime makespan ecart ");
}
output.println();
for(String instanceName : instances) {
int bestKnown = BestKnownResult.of(instanceName);
Path path = Paths.get("instances/", instanceName);
Instance instance = Instance.fromFile(path);
output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
String solverName = solversToTest.get(solverId);
Solver solver = solvers.get(solverName);
long start = System.currentTimeMillis();
long deadline = System.currentTimeMillis() + solveTimeMs;
Result result = solver.solve(instance, deadline);
long runtime = System.currentTimeMillis() - start;
if(!result.schedule.isValid()) {
System.err.println("ERROR: solver returned an invalid schedule");
System.exit(1);
}
assert result.schedule.isValid();
int makespan = result.schedule.makespan();
float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
runtimes[solverId] += (float) runtime / (float) instances.size();
distances[solverId] += dist / (float) instances.size();
output.printf("%7d %8s %5.1f ", runtime, makespan, dist);
output.flush();
}
output.println();
}
output.printf("%-8s %-5s %4s ", "AVG", "-", "-");
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
output.printf("%7.1f %8s %5.1f ", runtimes[solverId], "-", distances[solverId]);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -1,22 +1,23 @@
package jobshop;
import java.util.Optional;
public class Result {
public Result(Instance instance, Schedule schedule, ExitCause cause) {
this.instance = instance;
this.schedule = schedule;
this.cause = cause;
}
public enum ExitCause {
Timeout, ProvedOptimal, Blocked
}
public final Instance instance;
public final Schedule schedule;
public final ExitCause cause;
}
package jobshop;
import java.util.Optional;
@SuppressWarnings("unused")
public class Result {
public Result(Instance instance, Schedule schedule, ExitCause cause) {
this.instance = instance;
this.schedule = schedule;
this.cause = cause;
}
public enum ExitCause {
Timeout, ProvedOptimal, Blocked
}
public final Instance instance;
public final Schedule schedule;
public final ExitCause cause;
}

View file

@ -1,135 +1,157 @@
package jobshop;
import jobshop.encodings.Task;
import java.util.*;
import java.util.stream.IntStream;
public class Schedule {
public final Instance pb;
// 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;
public Schedule(Instance pb, int[][] times) {
this.pb = pb;
this.times = new int[pb.numJobs][];
for(int j = 0 ; j < pb.numJobs ; j++) {
this.times[j] = Arrays.copyOf(times[j], pb.numTasks);
}
}
public int startTime(int job, int task) {
return times[job][task];
}
/** 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))
return false;
}
for(int t = 0 ; t<pb.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);
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);
if(!t1_first && !t2_first)
return false;
}
}
}
return true;
}
public int makespan() {
int max = -1;
for(int j = 0 ; j<pb.numJobs ; j++) {
max = Math.max(max, startTime(j, pb.numTasks-1) + pb.duration(j, pb.numTasks -1));
}
return max;
}
public int startTime(Task task) {
return startTime(task.job, task.task);
}
public int endTime(Task task) {
return startTime(task) + pb.duration(task.job, task.task);
}
public boolean isCriticalPath(List<Task> path) {
if(startTime(path.get(0)) != 0) {
return false;
}
if(endTime(path.get(path.size()-1)) != makespan()) {
return false;
}
for(int i=0 ; i<path.size()-1 ; i++) {
if(endTime(path.get(i)) != startTime(path.get(i+1)))
return false;
}
return true;
}
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))
.max(Comparator.comparing(this::endTime))
.get();
assert endTime(ldd) == makespan();
// list that will contain the critical path.
// we construct it from the end, starting with the
// task that finishes last
LinkedList<Task> path = new LinkedList<>();
path.add(0,ldd);
// keep adding tasks to the path until the first task in the path
// starts a time 0
while(startTime(path.getFirst()) != 0) {
Task cur = path.getFirst();
int machine = pb.machine(cur.job, cur.task);
// will contain the task that was delaying the start
// of our current task
Optional<Task> latestPredecessor = Optional.empty();
if(cur.task > 0) {
// our current task has a predecessor on the job
Task predOnJob = new Task(cur.job, cur.task -1);
// if it was the delaying task, save it to predecessor
if(endTime(predOnJob) == startTime(cur))
latestPredecessor = Optional.of(predOnJob);
}
if(!latestPredecessor.isPresent()) {
// 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)))
.filter(t -> endTime(t) == startTime(cur))
.findFirst();
}
// at this point we should have identified a latest predecessor, either on the job or on the machine
assert latestPredecessor.isPresent() && endTime(latestPredecessor.get()) == startTime(cur);
// insert predecessor at the beginning of the path
path.add(0, latestPredecessor.get());
}
assert isCriticalPath(path);
return path;
}
}
package jobshop;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import jobshop.encodings.Task;
public class Schedule {
public final Instance pb;
// 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;
public Schedule(Instance pb, int[][] times) {
this.pb = pb;
this.times = new int[pb.numJobs][];
for(int j = 0 ; j < pb.numJobs ; j++) {
this.times[j] = Arrays.copyOf(times[j], pb.numTasks);
}
}
public int startTime(int job, int task) {
return times[job][task];
}
/** 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))
return false;
}
for(int t = 0 ; t<pb.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);
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);
if(!t1_first && !t2_first)
return false;
}
}
}
return true;
}
public int makespan() {
int max = -1;
for(int j = 0 ; j<pb.numJobs ; j++) {
max = Math.max(max, startTime(j, pb.numTasks-1) + pb.duration(j, pb.numTasks -1));
}
return max;
}
public int startTime(Task task) {
return startTime(task.job, task.task);
}
public int endTime(Task task) {
return startTime(task) + pb.duration(task.job, task.task);
}
public boolean isCriticalPath(List<Task> path) {
if(startTime(path.get(0)) != 0) {
return false;
}
if(endTime(path.get(path.size()-1)) != makespan()) {
return false;
}
for(int i=0 ; i<path.size()-1 ; i++) {
if(endTime(path.get(i)) != startTime(path.get(i+1)))
return false;
}
return true;
}
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))
.max(Comparator.comparing(this::endTime))
.get();
assert endTime(ldd) == makespan();
// list that will contain the critical path.
// we construct it from the end, starting with the
// task that finishes last
LinkedList<Task> path = new LinkedList<>();
path.add(0,ldd);
// keep adding tasks to the path until the first task in the path
// starts a time 0
while(startTime(path.getFirst()) != 0) {
Task cur = path.getFirst();
int machine = pb.machine(cur.job, cur.task);
// will contain the task that was delaying the start
// of our current task
Optional<Task> latestPredecessor = Optional.empty();
if(cur.task > 0) {
// our current task has a predecessor on the job
Task predOnJob = new Task(cur.job, cur.task -1);
// if it was the delaying task, save it to predecessor
if(endTime(predOnJob) == startTime(cur))
latestPredecessor = Optional.of(predOnJob);
}
if(!latestPredecessor.isPresent()) {
// 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)))
.filter(t -> endTime(t) == startTime(cur))
.findFirst();
}
// at this point we should have identified a latest predecessor, either on the job or on the machine
assert latestPredecessor.isPresent() && endTime(latestPredecessor.get()) == startTime(cur);
// insert predecessor at the beginning of the path
path.add(0, latestPredecessor.get());
}
assert isCriticalPath(path);
return path;
}
public Schedule copy() {
return new Schedule(this.pb, this.times);
}
/****************************************************************/
/* Implémentation de la méthode toString() de la classe Schedule*/
/****************************************************************/
public String toString() {
String res = "";
for (int i = 0; i < this.times.length; i++) {
res += "Job " + Integer.toString(i + 1) + " starting times : \n";
for (int j = 0; j < this.times[i].length; j++) {
res += "\tTask " + Integer.toString(j + 1) + " starts at time : " + Integer.toString(this.times[i][j]) + "\n";
}
}
return res;
}
}

View file

@ -1,7 +1,7 @@
package jobshop;
public interface Solver {
Result solve(Instance instance, long deadline);
}
package jobshop;
public interface Solver {
Result solve(Instance instance, long deadline);
}

View file

@ -1,84 +1,116 @@
package jobshop.encodings;
import jobshop.Encoding;
import jobshop.Instance;
import jobshop.Schedule;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.IntStream;
/** Représentation par numéro de job. */
public class JobNumbers extends Encoding {
/** A numJobs * numTasks array containing the representation by job numbers. */
public final int[] jobs;
/** In case the encoding is only partially filled, indicates the index of the first
* element of `jobs` that has not been set yet. */
public int nextToSet = 0;
public JobNumbers(Instance instance) {
super(instance);
jobs = new int[instance.numJobs * instance.numMachines];
Arrays.fill(jobs, -1);
}
public JobNumbers(Schedule schedule) {
super(schedule.pb);
this.jobs = new int[instance.numJobs * instance.numTasks];
// for each job indicates which is the next task to be scheduled
int[] nextOnJob = new int[instance.numJobs];
while(Arrays.stream(nextOnJob).anyMatch(t -> t < instance.numTasks)) {
Task next = IntStream
// for all jobs numbers
.range(0, instance.numJobs)
// build the next task for this job
.mapToObj(j -> new Task(j, nextOnJob[j]))
// only keep valid tasks (some jobs have no task left to be executed)
.filter(t -> t.task < instance.numTasks)
// select the task with the earliest execution time
.min(Comparator.comparing(t -> schedule.startTime(t.job, t.task)))
.get();
this.jobs[nextToSet++] = next.job;
nextOnJob[next.job] += 1;
}
}
@Override
public Schedule toSchedule() {
// time at which each machine is going to be freed
int[] nextFreeTimeResource = new int[instance.numMachines];
// for each job, the first task that has not yet been scheduled
int[] nextTask = new int[instance.numJobs];
// for each task, its start time
int[][] startTimes = new int[instance.numJobs][instance.numTasks];
// compute the earliest start time for every task of every job
for(int job : jobs) {
int task = nextTask[job];
int machine = instance.machine(job, task);
// earliest start time for this task
int est = task == 0 ? 0 : startTimes[job][task-1] + instance.duration(job, task-1);
est = Math.max(est, nextFreeTimeResource[machine]);
startTimes[job][task] = est;
nextFreeTimeResource[machine] = est + instance.duration(job, task);
nextTask[job] = task + 1;
}
return new Schedule(instance, startTimes);
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet));
}
}
package jobshop.encodings;
import jobshop.Encoding;
import jobshop.Instance;
import jobshop.Schedule;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.IntStream;
/** Représentation par numéro de job. */
public class JobNumbers extends Encoding {
/** A numJobs * numTasks array containing the representation by job numbers. */
public final int[] jobs;
/** In case the encoding is only partially filled, indicates the index of first
* element of `jobs` that has not been set yet. */
public int nextToSet = 0;
public JobNumbers(Instance instance) {
super(instance);
jobs = new int[instance.numJobs * instance.numMachines];
Arrays.fill(jobs, -1);
}
public JobNumbers(Schedule schedule) {
super(schedule.pb);
this.jobs = new int[instance.numJobs * instance.numTasks];
// for each job indicates which is the next task to be scheduled
int[] nextOnJob = new int[instance.numJobs];
while(Arrays.stream(nextOnJob).anyMatch(t -> t < instance.numTasks)) {
Task next = IntStream
// for all jobs numbers
.range(0, instance.numJobs)
// build the next task for this job
.mapToObj(j -> new Task(j, nextOnJob[j]))
// only keep valid tasks (some jobs have no task left to be executed)
.filter(t -> t.task < instance.numTasks)
// select the task with the earliest execution time
.min(Comparator.comparing(t -> schedule.startTime(t.job, t.task)))
.get();
this.jobs[nextToSet++] = next.job;
nextOnJob[next.job] += 1;
}
}
@Override
public Schedule toSchedule() {
// time at which each machine is going to be freed
int[] nextFreeTimeResource = new int[instance.numMachines];
// for each job, the first task that has not yet been scheduled
int[] nextTask = new int[instance.numJobs];
// for each task, its start time
int[][] startTimes = new int[instance.numJobs][instance.numTasks];
// compute the earliest start time for every task of every job
for(int job : jobs) {
int task = nextTask[job];
int machine = instance.machine(job, task);
// earliest start time for this task
int est = task == 0 ? 0 : startTimes[job][task-1] + instance.duration(job, task-1);
est = Math.max(est, nextFreeTimeResource[machine]);
startTimes[job][task] = est;
nextFreeTimeResource[machine] = est + instance.duration(job, task);
nextTask[job] = task + 1;
}
return new Schedule(instance, startTimes);
}
public static JobNumbers fromSchedule(Schedule sched) {
JobNumbers jo = new JobNumbers(sched.pb);
int current_time = 0;
Task current_task = new Task(-1,-1);
Task [] done_tasks = new Task[sched.pb.numJobs*sched.pb.numTasks];
Arrays.fill(done_tasks, current_task);
int min;
for (int i = 0; i < sched.pb.numJobs*sched.pb.numTasks; i++) {
// Il faut faire le code ci-dessous autant de fois que l'on a de taches
// On trouve le minimum parmis les restants
min = Integer.MAX_VALUE;
for (int job = 0; job < sched.pb.numJobs; job++) {
for (int task = 0; task < sched.pb.numTasks; task++) {
int task_start_time = sched.startTime(job, task);
Task this_task = new Task(job, task);
if (task_start_time < min && task_start_time >= current_time && !(Arrays.asList(done_tasks).contains(this_task))) {
min = task_start_time;
current_task = this_task;
}
}
}
// Une fois on a trouvé la suivante tache a realiser on introduit le numero du job dans jobs
jo.jobs[jo.nextToSet++] = current_task.job;
done_tasks[i] = current_task;
}
return jo;
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet));
}
}

View file

@ -1,131 +1,120 @@
package jobshop.encodings;
import jobshop.Encoding;
import jobshop.Instance;
import jobshop.Schedule;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.IntStream;
public class ResourceOrder extends Encoding {
// for each machine m, taskByMachine[m] is an array of tasks to be
// executed on this machine in the same order
public final Task[][] tasksByMachine;
// for each machine, indicate on many tasks have been initialized
public final int[] nextFreeSlot;
/** Creates a new empty resource order. */
public ResourceOrder(Instance instance)
{
super(instance);
// matrix of null elements (null is the default value of objects)
tasksByMachine = new Task[instance.numMachines][instance.numJobs];
// no task scheduled on any machine (0 is the default value)
nextFreeSlot = new int[instance.numMachines];
}
/** Creates a resource order from a schedule. */
public ResourceOrder(Schedule schedule)
{
super(schedule.pb);
Instance pb = schedule.pb;
this.tasksByMachine = new Task[pb.numMachines][];
this.nextFreeSlot = new int[instance.numMachines];
for(int m = 0 ; m<schedule.pb.numMachines ; m++) {
final int machine = m;
// for thi machine, find all tasks that are executed on it and sort them by their start time
tasksByMachine[m] =
IntStream.range(0, pb.numJobs) // all job numbers
.mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job)
.sorted(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) // sorted by start time
.toArray(Task[]::new); // as new array and store in tasksByMachine
// indicate that all tasks have been initialized for machine m
nextFreeSlot[m] = instance.numJobs;
}
}
@Override
public Schedule toSchedule() {
// indicate for each task that have been scheduled, its start time
int [][] startTimes = new int [instance.numJobs][instance.numTasks];
// for each job, how many tasks have been scheduled (0 initially)
int[] nextToScheduleByJob = new int[instance.numJobs];
// for each machine, how many tasks have been scheduled (0 initially)
int[] nextToScheduleByMachine = new int[instance.numMachines];
// for each machine, earliest time at which the machine can be used
int[] releaseTimeOfMachine = new int[instance.numMachines];
// loop while there remains a job that has unscheduled tasks
while(IntStream.range(0, instance.numJobs).anyMatch(m -> nextToScheduleByJob[m] < instance.numTasks)) {
// selects a task that has noun scheduled predecessor on its job and machine :
// - it is the next to be schedule on a machine
// - it is the next to be scheduled on its job
// if there is no such task, we have cyclic dependency and the solution is invalid
Optional<Task> schedulable =
IntStream.range(0, instance.numMachines) // all machines ...
.filter(m -> nextToScheduleByMachine[m] < instance.numJobs) // ... with unscheduled jobs
.mapToObj(m -> this.tasksByMachine[m][nextToScheduleByMachine[m]]) // tasks that are next to schedule on a machine ...
.filter(task -> task.task == nextToScheduleByJob[task.job]) // ... and on their job
.findFirst(); // select the first one if any
if(schedulable.isPresent()) {
// we found a schedulable task, lets call it t
Task t = schedulable.get();
int machine = instance.machine(t.job, t.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);
est = Math.max(est, releaseTimeOfMachine[instance.machine(t)]);
startTimes[t.job][t.task] = est;
// mark the task as scheduled
nextToScheduleByJob[t.job]++;
nextToScheduleByMachine[machine]++;
// increase the release time of the machine
releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task);
} else {
// no tasks are schedulable, there is no solution for this resource ordering
return null;
}
}
// we exited the loop : all tasks have been scheduled successfully
return new Schedule(instance, startTimes);
}
/** Creates an exact copy of this resource order. */
public ResourceOrder copy() {
return new ResourceOrder(this.toSchedule());
}
@Override
public String toString()
{
StringBuilder s = new StringBuilder();
for(int m=0; m < instance.numMachines; m++)
{
s.append("Machine ").append(m).append(" : ");
for(int j=0; j<instance.numJobs; j++)
{
s.append(tasksByMachine[m][j]).append(" ; ");
}
s.append("\n");
}
return s.toString();
}
}
package jobshop.encodings;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.IntStream;
import jobshop.Encoding;
import jobshop.Instance;
import jobshop.Schedule;
public class ResourceOrder extends Encoding {
public final Task[][] tasksByMachine;
public final int[] nextFreeSlot;
public ResourceOrder(Instance instance) {
super(instance);
this.tasksByMachine = new Task[instance.numMachines][instance.numJobs];
for (int i = 0; i < instance.numMachines; i++) {
for (int j = 0; j < instance.numJobs; j++) {
this.tasksByMachine[i][j] = new Task(-1,-1);
}
}
// no task scheduled on any machine (0 is the default value)
nextFreeSlot = new int[instance.numMachines];
}
public ResourceOrder(Schedule schedule) {
super(schedule.pb);
Instance pb = schedule.pb;
this.tasksByMachine = new Task[pb.numMachines][];
this.nextFreeSlot = new int[instance.numMachines];
for(int m = 0 ; m<schedule.pb.numMachines ; m++) {
final int machine = m;
// for thi machine, find all tasks that are executed on it and sort them by their start time
tasksByMachine[m] =
IntStream.range(0, pb.numJobs) // all job numbers
.mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job)
.sorted(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) // sorted by start time
.toArray(Task[]::new); // as new array and store in tasksByMachine
// indicate that all tasks have been initialized for machine m
nextFreeSlot[m] = instance.numJobs;
}
}
@Override
public Schedule toSchedule() {
// indicate for each task that have been scheduled, its start time
int [][] startTimes = new int [instance.numJobs][instance.numTasks];
// for each job, how many tasks have been scheduled (0 initially)
int[] nextToScheduleByJob = new int[instance.numJobs];
// for each machine, how many tasks have been scheduled (0 initially)
int[] nextToScheduleByMachine = new int[instance.numMachines];
// for each machine, earliest time at which the machine can be used
int[] releaseTimeOfMachine = new int[instance.numMachines];
// loop while there remains a job that has unscheduled tasks
while(IntStream.range(0, instance.numJobs).anyMatch(m -> nextToScheduleByJob[m] < instance.numTasks)) {
// selects a task that has noun scheduled predecessor on its job and machine :
// - it is the next to be schedule on a machine
// - it is the next to be scheduled on its job
// if there is no such task, we have cyclic dependency and the solution is invalid
Optional<Task> schedulable =
IntStream.range(0, instance.numMachines) // all machines ...
.filter(m -> nextToScheduleByMachine[m] < instance.numJobs) // ... with unscheduled jobs
.mapToObj(m -> this.tasksByMachine[m][nextToScheduleByMachine[m]]) // tasks that are next to schedule on a machine ...
.filter(task -> task.task == nextToScheduleByJob[task.job]) // ... and on their job
.findFirst(); // select the first one if any
if(schedulable.isPresent()) {
// we found a schedulable task, lets call it t
Task t = schedulable.get();
int machine = instance.machine(t.job, t.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);
est = Math.max(est, releaseTimeOfMachine[instance.machine(t.job, t.task)]);
startTimes[t.job][t.task] = est;
// mark the task as scheduled
nextToScheduleByJob[t.job]++;
nextToScheduleByMachine[machine]++;
// increase the release time of the machine
releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task);
} else {
// no tasks are schedulable, there is no solution for this resource ordering
return null;
}
}
// we exited the loop : all tasks have been scheduled successfully
return new Schedule(instance, startTimes);
}
@Override
public String toString() {
String res = "";
for (int i = 0; i < this.tasksByMachine.length; i++) {
res += "Machine number : " + Integer.toString(i+1) + "\n";
for (int j = 0; j < this.tasksByMachine[i].length; j++) {
res += "\tUse number " + Integer.toString(j+1) + " : " + this.tasksByMachine[i][j].add_one() + "\n";
}
}
return res;
}
}

View file

@ -1,41 +1,45 @@
package jobshop.encodings;
import java.util.Objects;
/** Represents a task (job,task) of an jobshop problem.
*
* Example : (2, 3) repesents the fourth task of the third job. (remeber that we tart counting at 0)
* */
public final class Task {
/** Identifier of the job */
public final int job;
/** Index of the task inside the job. */
public final int task;
public Task(int job, int task) {
this.job = job;
this.task = task;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task1 = (Task) o;
return job == task1.job &&
task == task1.task;
}
@Override
public int hashCode() {
return Objects.hash(job, task);
}
@Override
public String toString() {
return "(" + job +", " + task + '}';
}
}
package jobshop.encodings;
import java.util.Objects;
/** Represents a task (job,task) of an jobshop problem.
*
* Example : (2, 3) repesents the fourth task of the third job. (remeber that we tart counting at 0)
* */
public final class Task {
/** Identifier of the job */
public final int job;
/** Index of the task inside the job. */
public final int task;
public Task(int job, int task) {
this.job = job;
this.task = task;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task1 = (Task) o;
return job == task1.job &&
task == task1.task;
}
@Override
public int hashCode() {
return Objects.hash(job, task);
}
@Override
public String toString() {
return "Job " + this.job + " Task " + this.task;
}
public Task add_one() {
return new Task(this.job + 1, this.task + 1);
}
}

View file

@ -1,21 +1,21 @@
package jobshop.solvers;
import jobshop.Instance;
import jobshop.Result;
import jobshop.Solver;
import jobshop.encodings.JobNumbers;
public class BasicSolver implements Solver {
@Override
public Result solve(Instance instance, long deadline) {
JobNumbers sol = new JobNumbers(instance);
for(int t = 0 ; t<instance.numTasks ; t++) {
for(int j = 0 ; j<instance.numJobs ; j++) {
sol.jobs[sol.nextToSet++] = j;
}
}
return new Result(instance, sol.toSchedule(), Result.ExitCause.Blocked);
}
}
package jobshop.solvers;
import jobshop.Instance;
import jobshop.Result;
import jobshop.Solver;
import jobshop.encodings.JobNumbers;
public class BasicSolver implements Solver {
@Override
public Result solve(Instance instance, long deadline) {
JobNumbers sol = new JobNumbers(instance);
for(int t = 0 ; t<instance.numTasks ; t++) {
for(int j = 0 ; j<instance.numJobs ; j++) {
sol.jobs[sol.nextToSet++] = j;
}
}
return new Result(instance, sol.toSchedule(), Result.ExitCause.Blocked);
}
}

View file

@ -1,90 +0,0 @@
package jobshop.solvers;
import jobshop.Instance;
import jobshop.Result;
import jobshop.Solver;
import jobshop.encodings.ResourceOrder;
import java.util.List;
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.
* 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 swam 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
public Result solve(Instance instance, long deadline) {
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();
}
}

View file

@ -0,0 +1,194 @@
package jobshop.solvers;
import java.util.ArrayList;
import jobshop.*;
import jobshop.encodings.ResourceOrder;
import jobshop.encodings.Task;
import jobshop.solvers.GreedySolver.PriorityESTRule;
public class GreedySolver implements Solver {
/*********************************************************************************************/
/******************* Priority Rules and EST Priority Rules enumerations **********************/
/*********************************************************************************************/
public enum PriorityRule{
SPT {
@Override
public Task getTaskByRule(ArrayList<Task> achievableTasks, Instance instance) {
// Return the task with the MINIMAL duration
Task TaskSPT = null;
int minDuration = Integer.MAX_VALUE;
for(int i = 0; i < achievableTasks.size(); i++) {
Task current = achievableTasks.get(i);
int currentDuration = instance.duration(current.job, current.task);
if(currentDuration < minDuration) {
minDuration = currentDuration;
TaskSPT = current;
}
}
return TaskSPT;
}
},
LRPT {
@Override
public Task getTaskByRule(ArrayList<Task> achievableTasks, Instance instance) {
Task TaskLRPT = null;
Task current;
int maxDuration = -1;
for(int i = 0; i < achievableTasks.size(); i++) {
current = achievableTasks.get(i);
int remainingTime = 0;
for(int j = current.task; j < instance.numTasks; j++) {
remainingTime += instance.duration(current.job, j);
}
if(remainingTime > maxDuration) {
maxDuration = remainingTime;
TaskLRPT = current;
}
}
return TaskLRPT;
}
};
/**
* Returns the Task to work with depending on the rule chosen
* @param achievableTasks, instance
* @return currentTask
*/
public abstract Task getTaskByRule(ArrayList<Task> achievableTasks, Instance instance);
}
public enum PriorityESTRule{
EST_SPT {
@Override
public Task getTaskByESTRule(ArrayList<Task> achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines) {
ArrayList<Task> priorityTasks = this.getESTPriorityTasks(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines);
// STP function: Return the task with the MINIMAL duration
PriorityRule SPT = PriorityRule.SPT;
Task TaskSPT = SPT.getTaskByRule(priorityTasks, instance);
return TaskSPT;
}
},
EST_LRPT {
@Override
public Task getTaskByESTRule(ArrayList<Task> achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines) {
ArrayList<Task> priorityTasks = this.getESTPriorityTasks(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines);
// STP function: Return the task with the MINIMAL duration
PriorityRule LRPT = PriorityRule.LRPT;
Task TaskLRPT = LRPT.getTaskByRule(priorityTasks, instance);
return TaskLRPT;
}
};
/**
* Returns the Task to work with depending on the rule chosen
* @param achievableTasks, instance
* @return currentTask
*/
public abstract Task getTaskByESTRule(ArrayList<Task> achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines);
/**
* Returns the array of tasks with the shortest start date
* @param achievableTasks, instance
* @return priorityTask
*/
protected ArrayList<Task> getESTPriorityTasks(ArrayList<Task> achievableTasks, Instance instance, int[] nextStartDateJobs, int[] nextStartDateMachines){
// Search for the date or dates which start sooner
ArrayList<Task> priorityTasks = new ArrayList<>();
int minStartDate = Integer.MAX_VALUE;
for(int i = 0; i < achievableTasks.size(); i++) {
Task currentTask = achievableTasks.get(i);
int currentMachine = instance.machine(currentTask.job, currentTask.task);
int currentStartDate = Integer.max(nextStartDateJobs[currentTask.job], nextStartDateMachines[currentMachine]);
if(currentStartDate < minStartDate) {
minStartDate = currentStartDate;
// When we find a smaller start date we "restart" the array
priorityTasks.clear();
priorityTasks.add(currentTask);
} else if (currentStartDate == minStartDate) {
priorityTasks.add(currentTask);
}
}
return priorityTasks;
}
}
/*********************************************************************************************/
/********************** Greedy Solver: Constructors + Solve function *************************/
/*********************************************************************************************/
public PriorityRule priorityRule;
public PriorityESTRule priorityESTRule;
// 2 constructors: the default and one with the EST restriction
public GreedySolver(PriorityRule rule) {
this.priorityRule = rule;
this.priorityESTRule = null;
}
public GreedySolver(PriorityESTRule ruleEST) {
this.priorityESTRule = ruleEST;
this.priorityRule = null;
}
@Override
public Result solve(Instance instance, long deadline) {
int currentMachine, currentDuration;
// We declare 2 arrays containing the updated moment the next task will start in a job and a machine respectively
int[] nextStartDateJobs = new int[instance.numJobs];
int[] nextStartDateMachines = new int[instance.numMachines];
// We create a new ResourceOrder for putting all tasks in the schedule
ResourceOrder solution = new ResourceOrder(instance);
// Array list with all the achievable current tasks
ArrayList<Task> achievableTasks = new ArrayList<>();
// Initialize the array list with all the first task achievable
for(int i = 0 ; i < instance.numJobs ; i++) {
Task currentTask = new Task(i, 0);
achievableTasks.add(currentTask);
}
while(!achievableTasks.isEmpty()) {
// We take the task we should do now in function of the priority rule used
Task currentTask = null;
if(priorityESTRule == null) {
currentTask = priorityRule.getTaskByRule(achievableTasks, instance);
} else if(priorityRule == null) {
currentTask = priorityESTRule.getTaskByESTRule(achievableTasks, instance, nextStartDateJobs, nextStartDateMachines);
} else {
System.out.printf("Error priorityRule and priorityRuleEST are null. You must give a value to one of them.");
}
// Updating starting dates
currentMachine = instance.machine(currentTask.job, currentTask.task);
currentDuration = instance.duration(currentTask.job, currentTask.task);
nextStartDateJobs[currentTask.job] += currentDuration;
nextStartDateMachines[currentMachine] += currentDuration;
// We remove the current task from the achievable tasks list
achievableTasks.remove(currentTask);
// If it's not the last task of the job, we update the array list with the new task
if (currentTask.task < (instance.numTasks - 1)) {
achievableTasks.add(new Task(currentTask.job, currentTask.task + 1));
}
// We add the current task to the solution
int nextFreeSlot = solution.nextFreeSlot[currentMachine]++;
solution.tasksByMachine[currentMachine][nextFreeSlot] = currentTask;
}
return new Result(instance, solution.toSchedule(), Result.ExitCause.Blocked);
}
}

View file

@ -1,52 +1,53 @@
package jobshop.solvers;
import jobshop.*;
import jobshop.encodings.JobNumbers;
import java.util.Optional;
import java.util.Random;
public class RandomSolver implements Solver {
@Override
public Result solve(Instance instance, long deadline) {
Random generator = new Random(0);
JobNumbers sol = new JobNumbers(instance);
for(int j = 0 ; j<instance.numJobs ; j++) {
for(int t = 0 ; t<instance.numTasks ; t++) {
sol.jobs[sol.nextToSet++] = j;
}
}
Schedule best = sol.toSchedule();
while(deadline - System.currentTimeMillis() > 1) {
shuffleArray(sol.jobs, generator);
Schedule s = sol.toSchedule();
if(s.makespan() < best.makespan()) {
best = s;
}
}
return new Result(instance, best, Result.ExitCause.Timeout);
}
/** Simple FisherYates array shuffling */
private static void shuffleArray(int[] array, Random random)
{
int index;
for (int i = array.length - 1; i > 0; i--)
{
index = random.nextInt(i + 1);
if (index != i)
{
array[index] ^= array[i];
array[i] ^= array[index];
array[index] ^= array[i];
}
}
}
}
package jobshop.solvers;
import jobshop.*;
import jobshop.encodings.JobNumbers;
import java.util.Optional;
import java.util.Random;
@SuppressWarnings("unused")
public class RandomSolver implements Solver {
@Override
public Result solve(Instance instance, long deadline) {
Random generator = new Random(0);
JobNumbers sol = new JobNumbers(instance);
for(int j = 0 ; j<instance.numJobs ; j++) {
for(int t = 0 ; t<instance.numTasks ; t++) {
sol.jobs[sol.nextToSet++] = j;
}
}
Schedule best = sol.toSchedule();
while(deadline - System.currentTimeMillis() > 1) {
shuffleArray(sol.jobs, generator);
Schedule s = sol.toSchedule();
if(s.makespan() < best.makespan()) {
best = s;
}
}
return new Result(instance, best, Result.ExitCause.Timeout);
}
/** Simple FisherYates array shuffling */
private static void shuffleArray(int[] array, Random random)
{
int index;
for (int i = array.length - 1; i > 0; i--)
{
index = random.nextInt(i + 1);
if (index != i)
{
array[index] ^= array[i];
array[i] ^= array[index];
array[index] ^= array[i];
}
}
}
}

View file

@ -1,75 +1,103 @@
package jobshop.encodings;
import jobshop.Instance;
import jobshop.Result;
import jobshop.Schedule;
import jobshop.Solver;
import jobshop.solvers.BasicSolver;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
public class EncodingTests {
@Test
public void testJobNumbers() throws IOException {
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// numéro de jobs : 1 2 2 1 1 2 (cf exercices)
JobNumbers enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
// TODO: make it print something meaningful
// by implementing the toString() method
System.out.println(sched);
assert sched.isValid();
assert sched.makespan() == 12;
// numéro de jobs : 1 1 2 2 1 2
enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
sched = enc.toSchedule();
assert sched.isValid();
assert sched.makespan() == 14;
}
@Test
public void testBasicSolver() throws IOException {
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// build a solution that should be equal to the result of BasicSolver
JobNumbers enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
assert sched.isValid();
assert sched.makespan() == 12;
Solver solver = new BasicSolver();
Result result = solver.solve(instance, System.currentTimeMillis() + 10);
assert result.schedule.isValid();
assert result.schedule.makespan() == sched.makespan(); // should have the same makespan
}
}
package jobshop.encodings;
import jobshop.Instance;
import jobshop.Result;
import jobshop.Schedule;
import jobshop.Solver;
import jobshop.solvers.BasicSolver;
import jobshop.solvers.GreedySolver;
import jobshop.solvers.GreedySolver.PriorityRule;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
public class EncodingTests {
@Test
public void testJobNumbers() throws IOException {
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// numéro de jobs : 1 2 2 1 1 2 (cf exercices)
JobNumbers enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
// TODO: make it print something meaningful
// by implementing the toString() method
System.out.println(sched);
assert sched.isValid();
assert sched.makespan() == 12;
// numéro de jobs : 1 1 2 2 1 2
enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
sched = enc.toSchedule();
assert sched.isValid();
assert sched.makespan() == 14;
}
@Test
public void testBasicSolver() throws IOException {
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// build a solution that should be equal to the result of BasicSolver
JobNumbers enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
assert sched.isValid();
assert sched.makespan() == 12;
Solver solver = new BasicSolver();
Result result = solver.solve(instance, System.currentTimeMillis() + 10);
assert result.schedule.isValid();
assert result.schedule.makespan() == sched.makespan(); // should have the same makespan
}
@Test
public void testGreedySolver() throws IOException {
Instance instance = Instance.fromFile(Paths.get("instances/aaa1"));
// build a solution that should be equal to the result of BasicSolver
JobNumbers enc = new JobNumbers(instance);
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
assert sched.isValid();
assert sched.makespan() == 12;
PriorityRule priorityRule = PriorityRule.SPT;
Solver solver = new GreedySolver(priorityRule);
Result result = solver.solve(instance, System.currentTimeMillis() + 10);
assert result.schedule.isValid();
assert result.schedule.makespan() == sched.makespan(); // should have the same makespan
}
}