Improve documentation

This commit is contained in:
Arthur Bit-Monnot 2021-04-06 12:09:56 +02:00
parent 9802bd39fd
commit dce9b786e9
7 changed files with 140 additions and 54 deletions

View file

@ -2,21 +2,48 @@
This repository contains the starter code for the assignment. This repository contains the starter code for the assignment.
## Working in IntelliJ
## Compile For working on this project, we recommend using the IntelliJ-IDEA development environment. It is available in INSA's
classrooms as well as on `montp.insa-toulouse.fr`.
Compilation instructions are given for Linux. On windows you can use the `gradlew.bat` script. To import the project in IntelliJ (once IntelliJ is running):
- Open a new project : `Open project` or `File > Open`
- Select the `gradle.build` file in the cloned repository.
- Select `Open as project`.
To run the program in IntelliJ, you can
- Right click on the `src/main/java/Jobshop/Main` class in the project view.
- Select `Run Main.main()`. It should complain that some arguments are missing.
- Give it the expected command line arguments : `Run > Edit Configuration`, then fill in the `Program arguments` text box.
## Working on the command line (Gradle)
Compilation instructions are given for Linux. On Windows you can use the `gradlew.bat` script (but you are on your own).
``` ```
./gradlew build # Compiles the project ./gradlew build # Compiles the project
./gradlew jar # Creates a fat-jar in build/libs/JSP.jar
``` ```
The compiled jar is now `build/libs/JSP.jar` can be executed like so : The project can be executed directly with `gradle` by specifying the arguments like so :
``` ```
./gradlew run --args="--solver basic random --instance aaa1 ft"
```
You can also build an executable jar file, and run it with the java command.
This is especially useful if you want to run it on another machine.
```
# Create a jar file with all dependencies in build/libs/JSP.jar
./gradlew jar
# Run the jar file. Only requires a Java Runtime Environment (JRE)
java -jar build/libs/JSP.jar --solver basic --instance ft06 java -jar build/libs/JSP.jar --solver basic --instance ft06
``` ```
The command line above indicates that we want to solve the instance named`ft06` with the `basic` solver. It should give an output like the following : The command line above indicates that we want to solve the instance named`ft06` with the `basic` solver. It should give an output like the following :
``` ```
basic basic
@ -48,36 +75,26 @@ AVG - - 0.3 - 31.5 999.0 - 20.4
Here the last line give the average `runtime` and `ecart` for each solver. Here the last line give the average `runtime` and `ecart` for each solver.
``` ```
usage: jsp-solver [-h] [-t TIMEOUT] --solver SOLVER [SOLVER ...] sage: jsp-solver [-h] [-t TIMEOUT] --solver SOLVER [SOLVER ...]
--instance INSTANCE [INSTANCE ...] --instance INSTANCE [INSTANCE ...]
Solves jobshop problems. Solves jobshop problems.
named arguments: named arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-t TIMEOUT, --timeout TIMEOUT
Solver timeout in seconds for each instance.
(default: 1)
--solver SOLVER [SOLVER ...] --solver SOLVER [SOLVER ...]
Solver(s) to use (space separated if more than Solver(s) to use (space separated if more than
one) one)
-t TIMEOUT, --timeout TIMEOUT
Solver timeout in seconds for each instance
(default: 1)
--instance INSTANCE [INSTANCE ...] --instance INSTANCE [INSTANCE ...]
Instance(s) to solve (space separated if more Instance(s) to solve (space separated if more
than one) than one). All instances starting with the given
String will be selected. (e.g. "ft" will select
the instances ft06, ft10 and ft20.
``` ```
### Running directly from Gradle
The project can be executed directly with `gradle` by specifying the arguments like so :
```
./gradlew run --args="--solver basic random --instance aaa1 ft06 ft10 ft20"
```
This notably ensures that sources have been recompiled whenever necessary.
## IDE Support ## IDE Support

View file

@ -5,12 +5,28 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BestKnownResult { /**
* This class contains the best known results for common jobshop instances.
* Note that the best known result might not have been proven to be the optimal solution
* for the instance.
*/
public class BestKnownResults {
/**
* Checks whether we have data available for the provided instance.
* @param instanceName Name of the instance.
* @return True if the isntance is known, false otherwise.
*/
public static boolean isKnown(String instanceName) { public static boolean isKnown(String instanceName) {
return bests.containsKey(instanceName); return bests.containsKey(instanceName);
} }
/**
* Returns all instances that start with the given prefix.
* For example, the prefix "ft" should match the instances "ft06", "ft10" and "ft20"
* @param namePrefix Prefix that should be looked-up.
* @return All instances that start with the given prefix, in alphabetical order.
*/
public static List<String> instancesMatching(String namePrefix) { public static List<String> instancesMatching(String namePrefix) {
return Arrays.stream(instances) return Arrays.stream(instances)
.filter(i -> i.startsWith(namePrefix)) .filter(i -> i.startsWith(namePrefix))
@ -18,6 +34,11 @@ public class BestKnownResult {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* Returns the best known result for the given instance name.
* @param instanceName Instance of which we want to retrieve the best result.
* @return Best makespan that has ever been found for this instance.
*/
public static int of(String instanceName) { public static int of(String instanceName) {
if(!bests.containsKey(instanceName)) { if(!bests.containsKey(instanceName)) {
throw new RuntimeException("Unknown best result for "+instanceName); throw new RuntimeException("Unknown best result for "+instanceName);
@ -25,8 +46,12 @@ public class BestKnownResult {
return bests.get(instanceName); return bests.get(instanceName);
} }
static private HashMap<String, Integer> bests; // all best results.
static final private HashMap<String, Integer> bests;
// a sorted array of instance names
static String[] instances; static String[] instances;
// initialize the internal data structures.
static { static {
bests = new HashMap<>(); bests = new HashMap<>();
bests.put("aaa1", 11); bests.put("aaa1", 11);

View file

@ -27,8 +27,7 @@ public class DebuggingMain {
System.out.println("\nENCODING: " + enc); System.out.println("\nENCODING: " + enc);
Schedule sched = enc.toSchedule(); Schedule sched = enc.toSchedule();
// TODO: make it print something meaningful // TODO: make it print something meaningful by implementing the Schedule.toString() method
// by implementing the toString() method
System.out.println("SCHEDULE: " + sched); System.out.println("SCHEDULE: " + sched);
System.out.println("VALID: " + sched.isValid()); System.out.println("VALID: " + sched.isValid());
System.out.println("MAKESPAN: " + sched.makespan()); System.out.println("MAKESPAN: " + sched.makespan());

View file

@ -20,18 +20,28 @@ public class Instance {
/** Number of machines, assumed to be same as number of tasks. */ /** Number of machines, assumed to be same as number of tasks. */
public final int numMachines; public final int numMachines;
/** Matrix containing the duration of all tasks. */
final int[][] durations; final int[][] durations;
/** Matrix containing the machine on which each task must be scheduled. */
final int[][] machines; final int[][] machines;
/** Duration of the given task. */
public int duration(int job, int task) { public int duration(int job, int task) {
return durations[job][task]; return durations[job][task];
} }
/** Duration of the given task. */
public int duration(Task t) { public int duration(Task t) {
return duration(t.job, t.task); return duration(t.job, t.task);
} }
/** Machine on which the given task must be scheduled. */
public int machine(int job, int task) { public int machine(int job, int task) {
return machines[job][task]; return machines[job][task];
} }
/** Machine on which the given task must be scheduled. */
public int machine(Task t) { public int machine(Task t) {
return this.machine(t.job, t.task); return this.machine(t.job, t.task);
} }
@ -45,6 +55,11 @@ public class Instance {
throw new RuntimeException("No task targeting machine "+wanted_machine+" on job "+job); throw new RuntimeException("No task targeting machine "+wanted_machine+" on job "+job);
} }
/**
* Creates a new instance, with uninitialized durations and machines.
* This should no be called directly. Instead, Instance objects should be created with the
* <code>Instance.fromFile()</code> static method.
*/
Instance(int numJobs, int numTasks) { Instance(int numJobs, int numTasks) {
this.numJobs = numJobs; this.numJobs = numJobs;
this.numTasks = numTasks; this.numTasks = numTasks;

View file

@ -8,82 +8,101 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import jobshop.solvers.*; import jobshop.solvers.*;
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;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
/**
* This class is the main entry point for testing solver on instances.
* It provides
*/
public class Main { public class Main {
/** All solvers available in this program */ /** All solvers available in this program */
private static HashMap<String, Solver> solvers; private static final HashMap<String, Solver> solvers;
static { static {
solvers = new HashMap<>(); solvers = new HashMap<>();
solvers.put("basic", new BasicSolver()); solvers.put("basic", new BasicSolver());
solvers.put("random", new RandomSolver()); solvers.put("random", new RandomSolver());
// add new solvers here // TODO: add new solvers here
} }
public static void main(String[] args) { public static void main(String[] args) {
// configure the argument parser
ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build() ArgumentParser parser = ArgumentParsers.newFor("jsp-solver").build()
.defaultHelp(true) .defaultHelp(true)
.description("Solves jobshop problems."); .description("Solves jobshop problems.");
parser.addArgument("-t", "--timeout") parser.addArgument("-t", "--timeout")
.setDefault(1L) .setDefault(1L)
.type(Long.class) .type(Long.class)
.help("Solver timeout in seconds for each instance"); .help("Solver timeout in seconds for each instance. Default is 1 second.");
parser.addArgument("--solver") parser.addArgument("--solver")
.nargs("+") .nargs("+")
.required(true) .required(true)
.help("Solver(s) to use (space separated if more than one)"); .help("Solver(s) to use (space separated if more than one)");
parser.addArgument("--instance") parser.addArgument("--instance")
.nargs("+") .nargs("+")
.required(true) .required(true)
.help("Instance(s) to solve (space separated if more than one)"); .help("Instance(s) to solve (space separated if more than one). All instances starting with the given " +
"string will be selected. (e.g. \"ft\" will select the instances ft06, ft10 and ft20.");
// parse command line arguments
Namespace ns = null; Namespace ns = null;
try { try {
ns = parser.parseArgs(args); ns = parser.parseArgs(args);
} catch (ArgumentParserException e) { } catch (ArgumentParserException e) {
// error while parsing arguments, provide helpful error message and exit.
System.err.println("Invalid arguments provided to the program.\n");
System.err.println("In IntelliJ, you can provide arguments to the program by opening the dialog,");
System.err.println("\"Run > Edit Configurations\" and filling in the \"program arguments\" box.");
System.err.println("See the README for a documentation of the expected arguments.");
System.err.println();
parser.handleError(e); parser.handleError(e);
System.exit(1); System.exit(0);
} }
PrintStream output = System.out; PrintStream output = System.out;
// convert the timeout from seconds to milliseconds.
long solveTimeMs = ns.getLong("timeout") * 1000; long solveTimeMs = ns.getLong("timeout") * 1000;
// 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.
List<String> solversToTest = ns.getList("solver"); List<String> solversToTest = ns.getList("solver");
for(String solverName : solversToTest) { for(String solverName : solversToTest) {
if(!solvers.containsKey(solverName)) { if(!solvers.containsKey(solverName)) {
System.err.println("ERROR: Solver \"" + solverName + "\" is not avalaible."); System.err.println("ERROR: Solver \"" + solverName + "\" is not avalaible.");
System.err.println(" Available solvers: " + solvers.keySet().toString()); 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.err.println(" You can provide your own solvers by adding them to the `Main.solvers` HashMap.");
System.exit(1); System.exit(0);
} }
} }
List<String> instancePrefixes = ns.getList("instance");
// retrieve all instances on which we should run the solvers.
List<String> instances = new ArrayList<>(); List<String> instances = new ArrayList<>();
List<String> instancePrefixes = ns.getList("instance");
for(String instancePrefix : instancePrefixes) { for(String instancePrefix : instancePrefixes) {
List<String> matches = BestKnownResult.instancesMatching(instancePrefix); List<String> matches = BestKnownResults.instancesMatching(instancePrefix);
if(matches.isEmpty()) { if(matches.isEmpty()) {
System.err.println("ERROR: instance prefix \"" + instancePrefix + "\" does not match any instance."); System.err.println("ERROR: instance prefix \"" + instancePrefix + "\" does not match any instance.");
System.err.println(" available instances: " + Arrays.toString(BestKnownResult.instances)); System.err.println(" available instances: " + Arrays.toString(BestKnownResults.instances));
System.exit(1); System.exit(1);
} }
instances.addAll(matches); instances.addAll(matches);
} }
float[] runtimes = new float[solversToTest.size()]; // average runtime of each solver
float[] distances = new float[solversToTest.size()]; float[] avg_runtimes = new float[solversToTest.size()];
// average distance to best known result for each solver
float[] avg_distances = new float[solversToTest.size()];
try { try {
// header of the result table :
// - solver names (first line)
// - name of each column (second line)
output.print( " "); output.print( " ");
for(String s : solversToTest) for(String s : solversToTest)
output.printf("%-30s", s); output.printf("%-30s", s);
@ -94,51 +113,62 @@ public class Main {
} }
output.println(); output.println();
// for all instances, load it from f
for(String instanceName : instances) { for(String instanceName : instances) {
int bestKnown = BestKnownResult.of(instanceName); // get the best known result for this instance
int bestKnown = BestKnownResults.of(instanceName);
// load instance from file.
Path path = Paths.get("instances/", instanceName); Path path = Paths.get("instances/", instanceName);
Instance instance = Instance.fromFile(path); Instance instance = Instance.fromFile(path);
// print some general statistics on the instance
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
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
// 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.
String solverName = solversToTest.get(solverId); String solverName = solversToTest.get(solverId);
Solver solver = solvers.get(solverName); Solver solver = solvers.get(solverName);
// start chronometer and compute deadline for the solver to provide a result.
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
long deadline = System.currentTimeMillis() + solveTimeMs; long deadline = System.currentTimeMillis() + solveTimeMs;
// run the solver on the current instance
Result result = solver.solve(instance, deadline); Result result = solver.solve(instance, deadline);
// measure elapsed time (in milliseconds)
long runtime = System.currentTimeMillis() - start; long runtime = System.currentTimeMillis() - start;
// check that the solver returned a valid solution
if(!result.schedule.isValid()) { if(!result.schedule.isValid()) {
System.err.println("ERROR: solver returned an invalid schedule"); System.err.println("ERROR: solver returned an invalid schedule");
System.exit(1); System.exit(1); // bug in implementation, bail out
} }
assert result.schedule.isValid(); // compute some statistics on the solution and print them.
int makespan = result.schedule.makespan(); int makespan = result.schedule.makespan();
float dist = 100f * (makespan - bestKnown) / (float) bestKnown; float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
runtimes[solverId] += (float) runtime / (float) instances.size(); avg_runtimes[solverId] += (float) runtime / (float) instances.size();
distances[solverId] += dist / (float) instances.size(); avg_distances[solverId] += dist / (float) instances.size();
output.printf("%7d %8s %5.1f ", runtime, makespan, dist); output.printf("%7d %8s %5.1f ", runtime, makespan, dist);
output.flush(); output.flush();
} }
output.println(); output.println();
} }
// we have finished all benchmarks, compute the average solve time and distance of each solver.
output.printf("%-8s %-5s %4s ", "AVG", "-", "-"); output.printf("%-8s %-5s %4s ", "AVG", "-", "-");
for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) { for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
output.printf("%7.1f %8s %5.1f ", runtimes[solverId], "-", distances[solverId]); output.printf("%7.1f %8s %5.1f ", avg_runtimes[solverId], "-", avg_distances[solverId]);
} }
} catch (Exception e) { } catch (Exception e) {
// there was uncought exception, print the stack trace and exit with error.
e.printStackTrace(); e.printStackTrace();
System.exit(1); System.exit(1);
} }

View file

@ -41,7 +41,7 @@ public class ResourceOrder extends Encoding {
for(int m = 0 ; m<schedule.pb.numMachines ; m++) { for(int m = 0 ; m<schedule.pb.numMachines ; m++) {
final int machine = m; final int machine = m;
// for thi machine, find all tasks that are executed on it and sort them by their start time // for this machine, find all tasks that are executed on it and sort them by their start time
tasksByMachine[m] = tasksByMachine[m] =
IntStream.range(0, pb.numJobs) // all job numbers 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) .mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job)

View file

@ -2,9 +2,9 @@ package jobshop.encodings;
import java.util.Objects; import java.util.Objects;
/** Represents a task (job,task) of an jobshop problem. /** Represents a task (job,task) of a jobshop problem.
* *
* Example : (2, 3) repesents the fourth task of the third job. (remeber that we tart counting at 0) * Example : (2, 3) represents the fourth task of the third job. (remember that we start counting at 0)
**/ **/
public final class Task { public final class Task {