Browse Source

Provide ResourceOrder implementation + minor additions.

Arthur Bit-Monnot 1 year ago
parent
commit
ec04f02ed3

+ 9
- 0
src/main/java/jobshop/BestKnownResult.java View File

@@ -2,6 +2,8 @@ package jobshop;
2 2
 
3 3
 import java.util.Arrays;
4 4
 import java.util.HashMap;
5
+import java.util.List;
6
+import java.util.stream.Collectors;
5 7
 
6 8
 public class BestKnownResult {
7 9
 
@@ -9,6 +11,13 @@ public class BestKnownResult {
9 11
         return bests.containsKey(instanceName);
10 12
     }
11 13
 
14
+    public static List<String> instancesMatching(String namePrefix) {
15
+        return Arrays.stream(instances)
16
+                .filter(i -> i.startsWith(namePrefix))
17
+                .sorted()
18
+                .collect(Collectors.toList());
19
+    }
20
+
12 21
     public static int of(String instanceName) {
13 22
         if(!bests.containsKey(instanceName)) {
14 23
             throw new RuntimeException("Unknown best result for "+instanceName);

+ 9
- 0
src/main/java/jobshop/Instance.java View File

@@ -1,5 +1,7 @@
1 1
 package jobshop;
2 2
 
3
+import jobshop.encodings.Task;
4
+
3 5
 import java.io.IOException;
4 6
 import java.nio.file.Files;
5 7
 import java.nio.file.Path;
@@ -24,9 +26,15 @@ public class Instance {
24 26
     public int duration(int job, int task) {
25 27
         return durations[job][task];
26 28
     }
29
+    public int duration(Task t) {
30
+        return duration(t.job, t.task);
31
+    }
27 32
     public int machine(int job, int task) {
28 33
         return machines[job][task];
29 34
     }
35
+    public int machine(Task t) {
36
+        return this.machine(t.job, t.task);
37
+    }
30 38
 
31 39
     /** among the tasks of the given job, returns the task index that uses the given machine. */
32 40
     public int task_with_machine(int job, int wanted_machine) {
@@ -46,6 +54,7 @@ public class Instance {
46 54
         machines = new int[numJobs][numTasks];
47 55
     }
48 56
 
57
+    /** Parses a instance from a file. */
49 58
     public static Instance fromFile(Path path) throws IOException {
50 59
         Iterator<String> lines = Files.readAllLines(path).stream()
51 60
                 .filter(l -> !l.startsWith("#"))

+ 41
- 38
src/main/java/jobshop/Main.java View File

@@ -3,13 +3,13 @@ package jobshop;
3 3
 import java.io.PrintStream;
4 4
 import java.nio.file.Path;
5 5
 import java.nio.file.Paths;
6
+import java.util.ArrayList;
6 7
 import java.util.Arrays;
7 8
 import java.util.HashMap;
8 9
 import java.util.List;
9 10
 
10 11
 
11
-import jobshop.solvers.BasicSolver;
12
-import jobshop.solvers.RandomSolver;
12
+import jobshop.solvers.*;
13 13
 import net.sourceforge.argparse4j.ArgumentParsers;
14 14
 import net.sourceforge.argparse4j.inf.ArgumentParser;
15 15
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
@@ -68,20 +68,23 @@ public class Main {
68 68
                 System.exit(1);
69 69
             }
70 70
         }
71
-        List<String> instances = ns.<String>getList("instance");
72
-        for(String instanceName : instances) {
73
-            if(!BestKnownResult.isKnown(instanceName)) {
74
-                System.err.println("ERROR: instance \"" + instanceName + "\" is not avalaible.");
71
+        List<String> instancePrefixes = ns.getList("instance");
72
+        List<String> instances = new ArrayList<>();
73
+        for(String instancePrefix : instancePrefixes) {
74
+            List<String> matches = BestKnownResult.instancesMatching(instancePrefix);
75
+            if(matches.isEmpty()) {
76
+                System.err.println("ERROR: instance prefix \"" + instancePrefix + "\" does not match any instance.");
75 77
                 System.err.println("       available instances: " + Arrays.toString(BestKnownResult.instances));
76 78
                 System.exit(1);
77 79
             }
80
+            instances.addAll(matches);
78 81
         }
79 82
 
80 83
         float[] runtimes = new float[solversToTest.size()];
81 84
         float[] distances = new float[solversToTest.size()];
82 85
 
83 86
         try {
84
-            output.print(  "                         ");;
87
+            output.print(  "                         ");
85 88
             for(String s : solversToTest)
86 89
                 output.printf("%-30s", s);
87 90
             output.println();
@@ -92,46 +95,46 @@ public class Main {
92 95
             output.println();
93 96
 
94 97
 
95
-        for(String instanceName : instances) {
96
-            int bestKnown = BestKnownResult.of(instanceName);
98
+            for(String instanceName : instances) {
99
+                int bestKnown = BestKnownResult.of(instanceName);
97 100
 
98 101
 
99
-            Path path = Paths.get("instances/", instanceName);
100
-            Instance instance = Instance.fromFile(path);
102
+                Path path = Paths.get("instances/", instanceName);
103
+                Instance instance = Instance.fromFile(path);
101 104
 
102
-            output.printf("%-8s %-5s %4d      ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
105
+                output.printf("%-8s %-5s %4d      ",instanceName, instance.numJobs +"x"+instance.numTasks, bestKnown);
103 106
 
104
-            for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
105
-                String solverName = solversToTest.get(solverId);
106
-                Solver solver = solvers.get(solverName);
107
-                long start = System.currentTimeMillis();
108
-                long deadline = System.currentTimeMillis() + solveTimeMs;
109
-                Result result = solver.solve(instance, deadline);
110
-                long runtime = System.currentTimeMillis() - start;
111
-
112
-                if(!result.schedule.isValid()) {
113
-                    System.err.println("ERROR: solver returned an invalid schedule");
114
-                    System.exit(1);
115
-                }
107
+                for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
108
+                    String solverName = solversToTest.get(solverId);
109
+                    Solver solver = solvers.get(solverName);
110
+                    long start = System.currentTimeMillis();
111
+                    long deadline = System.currentTimeMillis() + solveTimeMs;
112
+                    Result result = solver.solve(instance, deadline);
113
+                    long runtime = System.currentTimeMillis() - start;
116 114
 
117
-                assert result.schedule.isValid();
118
-                int makespan = result.schedule.makespan();
119
-                float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
120
-                runtimes[solverId] += (float) runtime / (float) instances.size();
121
-                distances[solverId] += dist / (float) instances.size();
115
+                    if(!result.schedule.isValid()) {
116
+                        System.err.println("ERROR: solver returned an invalid schedule");
117
+                        System.exit(1);
118
+                    }
122 119
 
123
-                output.printf("%7d %8s %5.1f        ", runtime, makespan, dist);
124
-                output.flush();
125
-            }
126
-            output.println();
120
+                    assert result.schedule.isValid();
121
+                    int makespan = result.schedule.makespan();
122
+                    float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
123
+                    runtimes[solverId] += (float) runtime / (float) instances.size();
124
+                    distances[solverId] += dist / (float) instances.size();
127 125
 
128
-        }
126
+                    output.printf("%7d %8s %5.1f        ", runtime, makespan, dist);
127
+                    output.flush();
128
+                }
129
+                output.println();
129 130
 
131
+            }
130 132
 
131
-        output.printf("%-8s %-5s %4s      ", "AVG", "-", "-");
132
-        for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
133
-            output.printf("%7.1f %8s %5.1f        ", runtimes[solverId], "-", distances[solverId]);
134
-        }
133
+
134
+            output.printf("%-8s %-5s %4s      ", "AVG", "-", "-");
135
+            for(int solverId = 0 ; solverId < solversToTest.size() ; solverId++) {
136
+                output.printf("%7.1f %8s %5.1f        ", runtimes[solverId], "-", distances[solverId]);
137
+            }
135 138
 
136 139
 
137 140
 

+ 28
- 1
src/main/java/jobshop/encodings/JobNumbers.java View File

@@ -5,6 +5,8 @@ import jobshop.Instance;
5 5
 import jobshop.Schedule;
6 6
 
7 7
 import java.util.Arrays;
8
+import java.util.Comparator;
9
+import java.util.stream.IntStream;
8 10
 
9 11
 /** Représentation par numéro de job. */
10 12
 public class JobNumbers extends Encoding {
@@ -12,7 +14,7 @@ public class JobNumbers extends Encoding {
12 14
     /** A numJobs * numTasks array containing the representation by job numbers. */
13 15
     public final int[] jobs;
14 16
 
15
-    /** In case the encoding is only partially filled, indicates the index of first
17
+    /** In case the encoding is only partially filled, indicates the index of the first
16 18
      * element of `jobs` that has not been set yet. */
17 19
     public int nextToSet = 0;
18 20
 
@@ -23,6 +25,31 @@ public class JobNumbers extends Encoding {
23 25
         Arrays.fill(jobs, -1);
24 26
     }
25 27
 
28
+    public JobNumbers(Schedule schedule) {
29
+        super(schedule.pb);
30
+
31
+        this.jobs = new int[instance.numJobs * instance.numTasks];
32
+
33
+        // for each job indicates which is the next task to be scheduled
34
+        int[] nextOnJob = new int[instance.numJobs];
35
+
36
+        while(Arrays.stream(nextOnJob).anyMatch(t -> t < instance.numTasks)) {
37
+            Task next = IntStream
38
+                    // for all jobs numbers
39
+                    .range(0, instance.numJobs)
40
+                    // build the next task for this job
41
+                    .mapToObj(j -> new Task(j, nextOnJob[j]))
42
+                    // only keep valid tasks (some jobs have no task left to be executed)
43
+                    .filter(t -> t.task < instance.numTasks)
44
+                    // select the task with the earliest execution time
45
+                    .min(Comparator.comparing(t -> schedule.startTime(t.job, t.task)))
46
+                    .get();
47
+
48
+            this.jobs[nextToSet++] = next.job;
49
+            nextOnJob[next.job] += 1;
50
+        }
51
+    }
52
+
26 53
     @Override
27 54
     public Schedule toSchedule() {
28 55
         // time at which each machine is going to be freed

+ 131
- 0
src/main/java/jobshop/encodings/ResourceOrder.java View File

@@ -0,0 +1,131 @@
1
+package jobshop.encodings;
2
+
3
+import jobshop.Encoding;
4
+import jobshop.Instance;
5
+import jobshop.Schedule;
6
+
7
+import java.util.Comparator;
8
+import java.util.Optional;
9
+import java.util.stream.IntStream;
10
+
11
+public class ResourceOrder extends Encoding {
12
+
13
+    // for each machine m, taskByMachine[m] is an array of tasks to be
14
+    // executed on this machine in the same order
15
+    public final Task[][] tasksByMachine;
16
+
17
+    // for each machine, indicate on many tasks have been initialized
18
+    public final int[] nextFreeSlot;
19
+
20
+    /** Creates a new empty resource order. */
21
+    public ResourceOrder(Instance instance)
22
+    {
23
+        super(instance);
24
+
25
+        // matrix of null elements (null is the default value of objects)
26
+        tasksByMachine = new Task[instance.numMachines][instance.numJobs];
27
+
28
+        // no task scheduled on any machine (0 is the default value)
29
+        nextFreeSlot = new int[instance.numMachines];
30
+    }
31
+
32
+    /** Creates a resource order from a schedule. */
33
+    public ResourceOrder(Schedule schedule)
34
+    {
35
+        super(schedule.pb);
36
+        Instance pb = schedule.pb;
37
+
38
+        this.tasksByMachine = new Task[pb.numMachines][];
39
+        this.nextFreeSlot = new int[instance.numMachines];
40
+
41
+        for(int m = 0 ; m<schedule.pb.numMachines ; m++) {
42
+            final int machine = m;
43
+
44
+            // for thi machine, find all tasks that are executed on it and sort them by their start time
45
+            tasksByMachine[m] =
46
+                    IntStream.range(0, pb.numJobs) // all job numbers
47
+                            .mapToObj(j -> new Task(j, pb.task_with_machine(j, machine))) // all tasks on this machine (one per job)
48
+                            .sorted(Comparator.comparing(t -> schedule.startTime(t.job, t.task))) // sorted by start time
49
+                            .toArray(Task[]::new); // as new array and store in tasksByMachine
50
+
51
+            // indicate that all tasks have been initialized for machine m
52
+            nextFreeSlot[m] = instance.numJobs;
53
+        }
54
+    }
55
+
56
+    @Override
57
+    public Schedule toSchedule() {
58
+        // indicate for each task that have been scheduled, its start time
59
+        int [][] startTimes = new int [instance.numJobs][instance.numTasks];
60
+
61
+        // for each job, how many tasks have been scheduled (0 initially)
62
+        int[] nextToScheduleByJob = new int[instance.numJobs];
63
+
64
+        // for each machine, how many tasks have been scheduled (0 initially)
65
+        int[] nextToScheduleByMachine = new int[instance.numMachines];
66
+
67
+        // for each machine, earliest time at which the machine can be used
68
+        int[] releaseTimeOfMachine = new int[instance.numMachines];
69
+
70
+
71
+        // loop while there remains a job that has unscheduled tasks
72
+        while(IntStream.range(0, instance.numJobs).anyMatch(m -> nextToScheduleByJob[m] < instance.numTasks)) {
73
+
74
+            // selects a task that has noun scheduled predecessor on its job and machine :
75
+            //  - it is the next to be schedule on a machine
76
+            //  - it is the next to be scheduled on its job
77
+            // if there is no such task, we have cyclic dependency and the solution is invalid
78
+            Optional<Task> schedulable =
79
+                    IntStream.range(0, instance.numMachines) // all machines ...
80
+                    .filter(m -> nextToScheduleByMachine[m] < instance.numJobs) // ... with unscheduled jobs
81
+                    .mapToObj(m -> this.tasksByMachine[m][nextToScheduleByMachine[m]]) // tasks that are next to schedule on a machine ...
82
+                    .filter(task -> task.task == nextToScheduleByJob[task.job])  // ... and on their job
83
+                    .findFirst(); // select the first one if any
84
+
85
+            if(schedulable.isPresent()) {
86
+                // we found a schedulable task, lets call it t
87
+                Task t = schedulable.get();
88
+                int machine = instance.machine(t.job, t.task);
89
+
90
+                // compute the earliest start time (est) of the task
91
+                int est = t.task == 0 ? 0 : startTimes[t.job][t.task-1] + instance.duration(t.job, t.task-1);
92
+                est = Math.max(est, releaseTimeOfMachine[instance.machine(t)]);
93
+                startTimes[t.job][t.task] = est;
94
+
95
+                // mark the task as scheduled
96
+                nextToScheduleByJob[t.job]++;
97
+                nextToScheduleByMachine[machine]++;
98
+                // increase the release time of the machine
99
+                releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task);
100
+            } else {
101
+                // no tasks are schedulable, there is no solution for this resource ordering
102
+                return null;
103
+            }
104
+        }
105
+        // we exited the loop : all tasks have been scheduled successfully
106
+        return new Schedule(instance, startTimes);
107
+    }
108
+
109
+    /** Creates an exact copy of this resource order. */
110
+    public ResourceOrder copy() {
111
+        return new ResourceOrder(this.toSchedule());
112
+    }
113
+
114
+    @Override
115
+    public String toString()
116
+    {
117
+        StringBuilder s = new StringBuilder();
118
+        for(int m=0; m < instance.numMachines; m++)
119
+        {
120
+            s.append("Machine ").append(m).append(" : ");
121
+            for(int j=0; j<instance.numJobs; j++)
122
+            {
123
+                s.append(tasksByMachine[m][j]).append(" ; ");
124
+            }
125
+            s.append("\n");
126
+        }
127
+
128
+        return s.toString();
129
+    }
130
+
131
+}

+ 90
- 0
src/main/java/jobshop/solvers/DescentSolver.java View File

@@ -0,0 +1,90 @@
1
+package jobshop.solvers;
2
+
3
+import jobshop.Instance;
4
+import jobshop.Result;
5
+import jobshop.Solver;
6
+import jobshop.encodings.ResourceOrder;
7
+
8
+import java.util.List;
9
+
10
+public class DescentSolver implements Solver {
11
+
12
+    /** A block represents a subsequence of the critical path such that all tasks in it execute on the same machine.
13
+     * This class identifies a block in a ResourceOrder representation.
14
+     *
15
+     * Consider the solution in ResourceOrder representation
16
+     * machine 0 : (0,1) (1,2) (2,2)
17
+     * machine 1 : (0,2) (2,1) (1,1)
18
+     * machine 2 : ...
19
+     *
20
+     * The block with : machine = 1, firstTask= 0 and lastTask = 1
21
+     * Represent the task sequence : [(0,2) (2,1)]
22
+     *
23
+     * */
24
+    static class Block {
25
+        /** machine on which the block is identified */
26
+        final int machine;
27
+        /** index of the first task of the block */
28
+        final int firstTask;
29
+        /** index of the last task of the block */
30
+        final int lastTask;
31
+
32
+        Block(int machine, int firstTask, int lastTask) {
33
+            this.machine = machine;
34
+            this.firstTask = firstTask;
35
+            this.lastTask = lastTask;
36
+        }
37
+    }
38
+
39
+    /**
40
+     * Represents a swap of two tasks on the same machine in a ResourceOrder encoding.
41
+     *
42
+     * Consider the solution in ResourceOrder representation
43
+     * machine 0 : (0,1) (1,2) (2,2)
44
+     * machine 1 : (0,2) (2,1) (1,1)
45
+     * machine 2 : ...
46
+     *
47
+     * The swam with : machine = 1, t1= 0 and t2 = 1
48
+     * Represent inversion of the two tasks : (0,2) and (2,1)
49
+     * Applying this swap on the above resource order should result in the following one :
50
+     * machine 0 : (0,1) (1,2) (2,2)
51
+     * machine 1 : (2,1) (0,2) (1,1)
52
+     * machine 2 : ...
53
+     */
54
+    static class Swap {
55
+        // machine on which to perform the swap
56
+        final int machine;
57
+        // index of one task to be swapped
58
+        final int t1;
59
+        // index of the other task to be swapped
60
+        final int t2;
61
+
62
+        Swap(int machine, int t1, int t2) {
63
+            this.machine = machine;
64
+            this.t1 = t1;
65
+            this.t2 = t2;
66
+        }
67
+
68
+        /** Apply this swap on the given resource order, transforming it into a new solution. */
69
+        public void applyOn(ResourceOrder order) {
70
+            throw new UnsupportedOperationException();
71
+        }
72
+    }
73
+
74
+
75
+    @Override
76
+    public Result solve(Instance instance, long deadline) {
77
+        throw new UnsupportedOperationException();
78
+    }
79
+
80
+    /** Returns a list of all blocks of the critical path. */
81
+    List<Block> blocksOfCriticalPath(ResourceOrder order) {
82
+        throw new UnsupportedOperationException();
83
+    }
84
+
85
+    /** For a given block, return the possible swaps for the Nowicki and Smutnicki neighborhood */
86
+    List<Swap> neighbors(Block block) {
87
+        throw new UnsupportedOperationException();
88
+    }
89
+
90
+}

Loading…
Cancel
Save