Provide ResourceOrder implementation + minor additions.
This commit is contained in:
parent
8cc959a766
commit
ec04f02ed3
6 changed files with 307 additions and 38 deletions
|
@ -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);
|
||||
|
|
|
@ -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("#"))
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
131
src/main/java/jobshop/encodings/ResourceOrder.java
Normal file
131
src/main/java/jobshop/encodings/ResourceOrder.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
90
src/main/java/jobshop/solvers/DescentSolver.java
Normal file
90
src/main/java/jobshop/solvers/DescentSolver.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue