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.
## 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 jar # Creates a fat-jar in build/libs/JSP.jar
./gradlew build # Compiles the project
```
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
```
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
@ -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.
```
usage: jsp-solver [-h] [-t TIMEOUT] --solver SOLVER [SOLVER ...]
sage: jsp-solver [-h] [-t TIMEOUT] --solver SOLVER [SOLVER ...]
--instance INSTANCE [INSTANCE ...]
Solves jobshop problems.
named arguments:
-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(s) to use (space separated if more than
one)
-t TIMEOUT, --timeout TIMEOUT
Solver timeout in seconds for each instance
(default: 1)
--instance INSTANCE [INSTANCE ...]
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

View file

@ -5,12 +5,28 @@ import java.util.HashMap;
import java.util.List;
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) {
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) {
return Arrays.stream(instances)
.filter(i -> i.startsWith(namePrefix))
@ -18,6 +34,11 @@ public class BestKnownResult {
.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) {
if(!bests.containsKey(instanceName)) {
throw new RuntimeException("Unknown best result for "+instanceName);
@ -25,8 +46,12 @@ public class BestKnownResult {
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;
// initialize the internal data structures.
static {
bests = new HashMap<>();
bests.put("aaa1", 11);

View file

@ -27,8 +27,7 @@ public class DebuggingMain {
System.out.println("\nENCODING: " + enc);
Schedule sched = enc.toSchedule();
// TODO: make it print something meaningful
// by implementing the toString() method
// TODO: make it print something meaningful by implementing the Schedule.toString() method
System.out.println("SCHEDULE: " + sched);
System.out.println("VALID: " + sched.isValid());
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. */
public final int numMachines;
/** Matrix containing the duration of all tasks. */
final int[][] durations;
/** Matrix containing the machine on which each task must be scheduled. */
final int[][] machines;
/** Duration of the given task. */
public int duration(int job, int task) {
return durations[job][task];
}
/** Duration of the given task. */
public int duration(Task t) {
return duration(t.job, t.task);
}
/** Machine on which the given task must be scheduled. */
public int machine(int job, int task) {
return machines[job][task];
}
/** Machine on which the given task must be scheduled. */
public int machine(Task t) {
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);
}
/**
* 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) {
this.numJobs = numJobs;
this.numTasks = numTasks;

View file

@ -8,82 +8,101 @@ 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;
/**
* This class is the main entry point for testing solver on instances.
* It provides
*/
public class Main {
/** All solvers available in this program */
private static HashMap<String, Solver> solvers;
private static final HashMap<String, Solver> solvers;
static {
solvers = new HashMap<>();
solvers.put("basic", new BasicSolver());
solvers.put("random", new RandomSolver());
// add new solvers here
// TODO: add new solvers here
}
public static void main(String[] args) {
// configure the argument parser
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");
.help("Solver timeout in seconds for each instance. Default is 1 second.");
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)");
.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;
try {
ns = parser.parseArgs(args);
} 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);
System.exit(1);
System.exit(0);
}
PrintStream output = System.out;
// convert the timeout from seconds to milliseconds.
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");
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);
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> instancePrefixes = ns.getList("instance");
for(String instancePrefix : instancePrefixes) {
List<String> matches = BestKnownResult.instancesMatching(instancePrefix);
List<String> matches = BestKnownResults.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.err.println(" available instances: " + Arrays.toString(BestKnownResults.instances));
System.exit(1);
}
instances.addAll(matches);
}
float[] runtimes = new float[solversToTest.size()];
float[] distances = new float[solversToTest.size()];
// average runtime of each solver
float[] avg_runtimes = new float[solversToTest.size()];
// average distance to best known result for each solver
float[] avg_distances = new float[solversToTest.size()];
try {
// header of the result table :
// - solver names (first line)
// - name of each column (second line)
output.print( " ");
for(String s : solversToTest)
output.printf("%-30s", s);
@ -94,51 +113,62 @@ public class Main {
}
output.println();
// for all instances, load it from f
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);
Instance instance = Instance.fromFile(path);
// print some general statistics on the instance
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++) {
// 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);
Solver solver = solvers.get(solverName);
// start chronometer and compute deadline for the solver to provide a result.
long start = System.currentTimeMillis();
long deadline = System.currentTimeMillis() + solveTimeMs;
// run the solver on the current instance
Result result = solver.solve(instance, deadline);
// measure elapsed time (in milliseconds)
long runtime = System.currentTimeMillis() - start;
// check that the solver returned a valid solution
if(!result.schedule.isValid()) {
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();
float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
runtimes[solverId] += (float) runtime / (float) instances.size();
distances[solverId] += dist / (float) instances.size();
avg_runtimes[solverId] += (float) runtime / (float) instances.size();
avg_distances[solverId] += dist / (float) instances.size();
output.printf("%7d %8s %5.1f ", runtime, makespan, dist);
output.flush();
}
output.println();
}
// we have finished all benchmarks, compute the average solve time and distance of each solver.
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]);
output.printf("%7.1f %8s %5.1f ", avg_runtimes[solverId], "-", avg_distances[solverId]);
}
} catch (Exception e) {
// there was uncought exception, print the stack trace and exit with error.
e.printStackTrace();
System.exit(1);
}

View file

@ -41,7 +41,7 @@ public class ResourceOrder extends Encoding {
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
// for this 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)

View file

@ -2,10 +2,10 @@ package jobshop.encodings;
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 {
/** Identifier of the job */