Complete documentation + equals/hashCode for encodings

This commit is contained in:
Arthur Bit-Monnot 2021-04-10 20:00:56 +02:00
parent 1957a255ba
commit c98accd114
14 changed files with 92 additions and 31 deletions

View file

@ -2,10 +2,11 @@
Several encoding and associated utilities are provided in the `jobshop.encodings` package. Several encoding and associated utilities are provided in the `jobshop.encodings` package.
The abstract class `Encoding` provides a common interface for all encodings. The abstract class `Encoding` provides a common interface for all encodings.
The only requirement for an encoding is to transform it self into a `Schedule`.
## Schedule ## Schedule
The `Schedule` has direct encoding of a solution: it associates every task in the jobshop to a start time. The `Schedule` is direct encoding of a solution: it associates every task in the jobshop instance to a start time.
It plays a particular role as it is the standard way of representing a solution. As a consequence, all other encodings must provide a way to produce a schedule. It plays a particular role as it is the standard way of representing a solution. As a consequence, all other encodings must provide a way to produce a schedule.
@ -19,7 +20,7 @@ Convenience methods:
## NumJobs ## NumJobs
The `NumJobs` encoding consists of a sequence of job numbers. To produce a schedule, one should iterate on the job numbers and tries to schedule *as early as possible* the next task of the job. The `NumJobs` encoding consists of a sequence of job numbers. To produce a schedule, one should iterate on the job numbers and try to schedule *as early as possible* the next task of the job.
For instance the encoding `[0 0 1 1 0 1]` will produce a schedule by trying to place as early as possible the following tasks (in this order): For instance the encoding `[0 0 1 1 0 1]` will produce a schedule by trying to place as early as possible the following tasks (in this order):
@ -35,7 +36,7 @@ Convenience methods:
The resource order encoding specifies the order in which each machine will process its tasks. The resource order encoding specifies the order in which each machine will process its tasks.
Each machine is associated with an array of task that specifies the order on which the task must be scheduled on the machine. Each machine is associated with an array of tasks that specifies the order on which the tasks must be scheduled on the machine.
For instance, the encoding: For instance, the encoding:
@ -46,3 +47,5 @@ For instance, the encoding:
``` ```
Specifies that the first machine (machine 0) will first process the second task of the first job `(0, 1)` and only when it is finished can start processing the first task of the second job `(1, 0)`. Specifies that the first machine (machine 0) will first process the second task of the first job `(0, 1)` and only when it is finished can start processing the first task of the second job `(1, 0)`.
Unlike `JobNumbers`, the `ResourceOrder` encoding might represent invalid solutions. In this case, its `toSchedule()` method will return an empty result.

View file

@ -4,14 +4,19 @@
`jobshop.solvers.Solver` provides a common interface for all solvers. `jobshop.solvers.Solver` provides a common interface for all solvers.
Implementing the `Solver` interface requires implementing a method `solve(Instance instance, long deadline)` where:
## Basic solver - `instance` is the jobshop instance that should be solved.
- `deadline` is the absolute time by which the solver should have exited. This deadline is in milliseconds and can be compared with the result of `System.currentTimeMillis()`.
The `solve()` method should return a `Result` object, that provides the found solution as a `Schedule` and the cause for exiting.
## `BasicSolver`
A very simple solver that tries to schedule all first tasks, then all second tasks, then all third tasks, ... A very simple solver that tries to schedule all first tasks, then all second tasks, then all third tasks, ...
It does so using the `JobNumbers` encoding.
It does so using the `JobNumbers` encoding ## `RandomSolver`
## Random solver
Another very simple solver based on the `JobNumbers` encoding. Another very simple solver based on the `JobNumbers` encoding.
At each iteration, the solver generates a new random solution keeps it if it the best one found so far. At each iteration, the solver generates a new random solution keeps it if it the best one found so far.
@ -19,14 +24,11 @@ At each iteration, the solver generates a new random solution keeps it if it the
It repeats this process until the deadline to produce a result is met and finally returns the best solution found. It repeats this process until the deadline to produce a result is met and finally returns the best solution found.
## Greedy solver ## `GreedySolver`
The greedy solver is not implemented yet. The greedy solver is not implemented yet.
Its constructor accepts a parameter that specifies the priority that should be used to produce solutions. Its constructor accepts a parameter that specifies the priority that should be used to produce solutions.
## Descent Solver ## `DescentSolver`
Not implemented yet. It should use the *Nowicki and Smutnicki* neighborhood for which some initial code is provided in the `jobshop.solver.neighborhood` package.
### Neighborhoods
TODO

View file

@ -10,7 +10,7 @@ import java.util.stream.Collectors;
* Note that the best known result might not have been proven to be the optimal solution * Note that the best known result might not have been proven to be the optimal solution
* for the instance. * for the instance.
*/ */
public class BestKnownResults { public final class BestKnownResults {
/** /**
* Checks whether we have data available for the provided instance. * Checks whether we have data available for the provided instance.
@ -35,7 +35,7 @@ public class BestKnownResults {
} }
/** /**
* Returns the best known result for the given instance name. * Returns the best known result for the given instance.
* @param instanceName Instance of which we want to retrieve the best result. * @param instanceName Instance of which we want to retrieve the best result.
* @return Best makespan that has ever been found for this instance. * @return Best makespan that has ever been found for this instance.
*/ */

View file

@ -9,7 +9,8 @@ import java.util.Iterator;
import java.util.Scanner; import java.util.Scanner;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Instance { /** Represents an instance of a JobShop problem. */
public final class Instance {
/** Name of the instance. Same as the filename from which the instance is loaded. */ /** Name of the instance. Same as the filename from which the instance is loaded. */
public final String name; public final String name;

View file

@ -9,6 +9,7 @@ import jobshop.solvers.GreedySolver;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
/** A java main classes for testing purposes. */
public class MainTest { public class MainTest {
public static void main(String[] args) { public static void main(String[] args) {

View file

@ -4,11 +4,12 @@ import jobshop.Instance;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.IntStream; import java.util.stream.IntStream;
/** Encoding of the solution of a jobshop problem by job numbers. */ /** Encoding of the solution of a jobshop problem by job numbers. */
public class JobNumbers extends Encoding { public final class JobNumbers extends Encoding {
/** A numJobs * numTasks array containing the representation by job numbers. */ /** A numJobs * numTasks array containing the representation by job numbers. */
public final int[] jobs; public final int[] jobs;
@ -25,7 +26,7 @@ public class JobNumbers extends Encoding {
Arrays.fill(jobs, -1); Arrays.fill(jobs, -1);
} }
/** Cerates a new encoding based on the given schedule. */ /** Creates a new encoding based on the given schedule. */
public JobNumbers(Schedule schedule) { public JobNumbers(Schedule schedule) {
super(schedule.instance); super(schedule.instance);
@ -87,4 +88,19 @@ public class JobNumbers extends Encoding {
public String toString() { public String toString() {
return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet)); return Arrays.toString(Arrays.copyOfRange(jobs,0, nextToSet));
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JobNumbers that = (JobNumbers) o;
return nextToSet == that.nextToSet && Arrays.equals(jobs, that.jobs);
}
@Override
public int hashCode() {
int result = Objects.hash(nextToSet);
result = 31 * result + Arrays.hashCode(jobs);
return result;
}
} }

View file

@ -7,7 +7,8 @@ import java.util.Comparator;
import java.util.Optional; import java.util.Optional;
import java.util.stream.IntStream; import java.util.stream.IntStream;
public class ResourceOrder extends Encoding { /** Encoding of a solution by the ordering of tasks on each machine. */
public final class ResourceOrder extends Encoding {
// for each machine m, taskByMachine[m] is an array of tasks to be // for each machine m, taskByMachine[m] is an array of tasks to be
// executed on this machine in the same order // executed on this machine in the same order
@ -55,9 +56,11 @@ public class ResourceOrder extends Encoding {
/** Enqueues a task for the given job on the machine. We automatically, find the task /** Enqueues a task for the given job on the machine. We automatically, find the task
* that must be executed on this particular machine. */ * that must be executed on this particular machine. */
public void addToMachine(int machine, int jobNumber) { public void addToMachine(int machine, int jobNumber) {
addTaskToMachine(machine, new Task(jobNumber, instance.task_with_machine(jobNumber, machine))); Task taskToEnqueue = new Task(jobNumber, instance.task_with_machine(jobNumber, machine));
addTaskToMachine(machine, taskToEnqueue);
} }
/** Adds the given task to the queue of the given machine. */
public void addTaskToMachine(int machine, Task task) { public void addTaskToMachine(int machine, Task task) {
tasksByMachine[machine][nextFreeSlot[machine]] = task; tasksByMachine[machine][nextFreeSlot[machine]] = task;
nextFreeSlot[machine] += 1; nextFreeSlot[machine] += 1;
@ -76,8 +79,8 @@ public class ResourceOrder extends Encoding {
/** Exchange the order of two tasks that are scheduled on a given machine. /** Exchange the order of two tasks that are scheduled on a given machine.
* *
* @param machine Machine on which the two tasks appear (line on which to perform the exchange) * @param machine Machine on which the two tasks appear (line on which to perform the exchange)
* @param indexTask1 Position of the first task on the machine (column of the first element) * @param indexTask1 Position of the first task in the machine's queue
* @param indexTask2 Position of the second task on the machine (column of the second element) * @param indexTask2 Position of the second task in the machine's queue
*/ */
public void swapTasks(int machine, int indexTask1, int indexTask2) { public void swapTasks(int machine, int indexTask1, int indexTask2) {
Task tmp = tasksByMachine[machine][indexTask1]; Task tmp = tasksByMachine[machine][indexTask1];
@ -138,7 +141,10 @@ public class ResourceOrder extends Encoding {
return Optional.of(schedule); return Optional.of(schedule);
} }
/** Creates an exact copy of this resource order. */ /** Creates an exact copy of this resource order.
*
* May fail if the resource order does not represent a valid solution.
*/
public ResourceOrder copy() { public ResourceOrder copy() {
var schedule = this.toSchedule(); var schedule = this.toSchedule();
if (schedule.isEmpty()) { if (schedule.isEmpty()) {
@ -165,4 +171,18 @@ public class ResourceOrder extends Encoding {
return s.toString(); return s.toString();
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResourceOrder that = (ResourceOrder) o;
return Arrays.deepEquals(tasksByMachine, that.tasksByMachine) && Arrays.equals(nextFreeSlot, that.nextFreeSlot);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(tasksByMachine);
result = 31 * result + Arrays.hashCode(nextFreeSlot);
return result;
}
} }

View file

@ -14,7 +14,7 @@ public final class Task {
/** Index of the task inside the job. */ /** Index of the task inside the job. */
public final int task; public final int task;
/** Creates a new Task object (job, task). */
public Task(int job, int task) { public Task(int job, int task) {
this.job = job; this.job = job;
this.task = task; this.task = task;

View file

@ -14,7 +14,7 @@ public class BasicSolver implements Solver {
JobNumbers sol = new JobNumbers(instance); JobNumbers sol = new JobNumbers(instance);
for(int t = 0 ; t<instance.numTasks ; t++) { for(int t = 0 ; t<instance.numTasks ; t++) {
for(int j = 0 ; j<instance.numJobs ; j++) { for(int j = 0 ; j<instance.numJobs ; j++) {
sol.jobs[sol.nextToSet++] = j; sol.addTask(j);
} }
} }

View file

@ -5,11 +5,17 @@ import jobshop.Result;
import jobshop.encodings.ResourceOrder; import jobshop.encodings.ResourceOrder;
import jobshop.solvers.neighborhood.Neighborhood; import jobshop.solvers.neighborhood.Neighborhood;
/** An empty shell to implement a descent solver. */
public class DescentSolver implements Solver { public class DescentSolver implements Solver {
final Neighborhood<ResourceOrder> neighborhood; final Neighborhood<ResourceOrder> neighborhood;
final Solver baseSolver; final Solver baseSolver;
/** Creates a new descent solver with a given neighborhood and a solver for the initial solution.
*
* @param neighborhood Neighborhood object that should be used to generates neighbor solutions to the current candidate.
* @param baseSolver A solver to provide the initial solution.
*/
public DescentSolver(Neighborhood<ResourceOrder> neighborhood, Solver baseSolver) { public DescentSolver(Neighborhood<ResourceOrder> neighborhood, Solver baseSolver) {
this.neighborhood = neighborhood; this.neighborhood = neighborhood;
this.baseSolver = baseSolver; this.baseSolver = baseSolver;

View file

@ -3,13 +3,18 @@ package jobshop.solvers;
import jobshop.Instance; import jobshop.Instance;
import jobshop.Result; import jobshop.Result;
/** An empty shell to implement a greedy solver. */
public class GreedySolver implements Solver { public class GreedySolver implements Solver {
/** All possible priorities for the greedy solver. */
public enum Priority { public enum Priority {
SPT, LPT, SRPT, LRPT, EST_SPT, EST_LPT, EST_SRPT, EST_LRPT SPT, LPT, SRPT, LRPT, EST_SPT, EST_LPT, EST_SRPT, EST_LRPT
} }
/** Priority that the solver should use. */
final Priority priority; final Priority priority;
/** Creates a new greedy solver that will use the given priority. */
public GreedySolver(Priority p) { public GreedySolver(Priority p) {
this.priority = p; this.priority = p;
} }

View file

@ -18,12 +18,16 @@ public class RandomSolver implements Solver {
JobNumbers sol = new JobNumbers(instance); JobNumbers sol = new JobNumbers(instance);
// initialize a first solution to the problem.
for(int j = 0 ; j<instance.numJobs ; j++) { for(int j = 0 ; j<instance.numJobs ; j++) {
for(int t = 0 ; t<instance.numTasks ; t++) { for(int t = 0 ; t<instance.numTasks ; t++) {
sol.jobs[sol.nextToSet++] = j; sol.addTask(j);
} }
} }
// best solution is currently the initial one
Optional<Schedule> best = sol.toSchedule(); Optional<Schedule> best = sol.toSchedule();
// while we have some time left, generate new solutions by shuffling the current one
while(deadline - System.currentTimeMillis() > 1) { while(deadline - System.currentTimeMillis() > 1) {
shuffleArray(sol.jobs, generator); shuffleArray(sol.jobs, generator);
Optional<Schedule> candidate = sol.toSchedule(); Optional<Schedule> candidate = sol.toSchedule();
@ -39,12 +43,12 @@ public class RandomSolver implements Solver {
} }
/** Simple FisherYates array shuffling */ /** Simple FisherYates array shuffling */
private static void shuffleArray(int[] array, Random random) private static void shuffleArray(int[] array, Random randomNumberGenerator)
{ {
int index; int index;
for (int i = array.length - 1; i > 0; i--) for (int i = array.length - 1; i > 0; i--)
{ {
index = random.nextInt(i + 1); index = randomNumberGenerator.nextInt(i + 1);
if (index != i) if (index != i)
{ {
array[index] ^= array[i]; array[index] ^= array[i];

View file

@ -3,7 +3,10 @@ package jobshop.solvers.neighborhood;
import jobshop.encodings.Encoding; import jobshop.encodings.Encoding;
/** This class provides a representation of neighbor by allowing to transform /** This class provides a representation of neighbor by allowing to transform
* a solution in a particular encoding Enc into the neighbor and back.*/ * a solution in a particular encoding Enc into the neighbor and back.
*
* @param <Enc> A subclass of Encoding for that can be transformed into a neighbor and back.
* */
public abstract class Neighbor<Enc extends Encoding> { public abstract class Neighbor<Enc extends Encoding> {
/** Transform the given solution into the neighbor. */ /** Transform the given solution into the neighbor. */

View file

@ -7,7 +7,7 @@ import java.util.List;
/** For a particular encoding Enc, a neighborhood allow the generation of the neighbors of /** For a particular encoding Enc, a neighborhood allow the generation of the neighbors of
* a particular solution. * a particular solution.
* *
* @param <Enc> A subcless of Encoding for which this encoding can generate neighbors. * @param <Enc> A subclass of Encoding for which this encoding can generate neighbors.
*/ */
public abstract class Neighborhood<Enc extends Encoding> { public abstract class Neighborhood<Enc extends Encoding> {