Provide ResourceOrder implementation + minor additions.

This commit is contained in:
Arthur Bit-Monnot 2020-04-10 17:03:16 +02:00
parent 8cc959a766
commit ec04f02ed3
6 changed files with 307 additions and 38 deletions

View file

@ -2,6 +2,8 @@ package jobshop;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
public class BestKnownResult {
@ -9,6 +11,13 @@ public class BestKnownResult {
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);

View file

@ -1,5 +1,7 @@
package jobshop;
import jobshop.encodings.Task;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -24,9 +26,15 @@ public class Instance {
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) {
@ -46,6 +54,7 @@ public class Instance {
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("#"))

View file

@ -3,13 +3,13 @@ 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.BasicSolver;
import jobshop.solvers.RandomSolver;
import jobshop.solvers.*;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
@ -68,20 +68,23 @@ public class Main {
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.");
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( " ");;
output.print( " ");
for(String s : solversToTest)
output.printf("%-30s", s);
output.println();
@ -92,46 +95,46 @@ public class Main {
output.println();
for(String instanceName : instances) {
int bestKnown = BestKnownResult.of(instanceName);
for(String instanceName : instances) {
int bestKnown = BestKnownResult.of(instanceName);
Path path = Paths.get("instances/", instanceName);
Instance instance = Instance.fromFile(path);
Path path = Paths.get("instances/", instanceName);
Instance instance = Instance.fromFile(path);
output.printf("%-8s %-5s %4d ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
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;
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);
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();
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]);
}
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]);
}

View file

@ -5,6 +5,8 @@ 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 {
@ -12,7 +14,7 @@ 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
/** 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;
@ -23,6 +25,31 @@ public class JobNumbers extends Encoding {
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

View file

@ -0,0 +1,131 @@
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();
}
}

View file

@ -0,0 +1,90 @@
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();
}
}