Make Encoding.toSchedule() return an optional to handle the case where the solution is not valid.

This commit is contained in:
Arthur Bit-Monnot 2021-04-10 16:55:55 +02:00
förälder fd9fa99913
incheckning 1957a255ba
10 ändrade filer med 56 tillägg och 35 borttagningar

Visa fil

@ -49,7 +49,7 @@ public class Instance {
return this.machine(t.job, t.task);
}
/** among the tasks of the given job, returns the task index that uses the given machine. */
/** Among the tasks of the given job, returns the task number of the one that uses the given machine. */
public int task_with_machine(int job, int wanted_machine) {
for(int task = 0 ; task < numTasks ; task++) {
if(machine(job, task) == wanted_machine)

Visa fil

@ -8,6 +8,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import jobshop.encodings.Schedule;
import jobshop.solvers.*;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.inf.ArgumentParser;
@ -122,13 +123,15 @@ public class Main {
long runtime = System.currentTimeMillis() - start;
// check that the solver returned a valid solution
if(!result.schedule.isValid()) {
if(result.schedule.isEmpty() || !result.schedule.get().isValid()) {
System.err.println("ERROR: solver returned an invalid schedule");
System.exit(1); // bug in implementation, bail out
}
// we have a valid schedule
Schedule schedule = result.schedule.get();
// compute some statistics on the solution and print them.
int makespan = result.schedule.makespan();
int makespan = schedule.makespan();
float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
avg_runtimes[solverId] += (float) runtime / (float) instances.size();
avg_distances[solverId] += dist / (float) instances.size();

Visa fil

@ -28,7 +28,7 @@ public class MainTest {
System.out.println("\nENCODING: " + enc);
// convert to a schedule and display
Schedule schedule = enc.toSchedule();
Schedule schedule = enc.toSchedule().get();
System.out.println("VALID: " + schedule.isValid());
System.out.println("MAKESPAN: " + schedule.makespan());
System.out.println("SCHEDULE: " + schedule.toString());

Visa fil

@ -2,10 +2,22 @@ package jobshop;
import jobshop.encodings.Schedule;
import java.util.Optional;
/** Class representing the result of a solver. */
public class Result {
public Result(Instance instance, Schedule schedule, ExitCause cause) {
/** Instance that was solved. */
public final Instance instance;
/** A schedule of the solution or Optional.empty() if no solution was found. */
public final Optional<Schedule> schedule;
/** Reason why the solver exited with this solution. */
public final ExitCause cause;
/** Creates a new Result object with the corresponding fields. */
public Result(Instance instance, Optional<Schedule> schedule, ExitCause cause) {
this.instance = instance;
this.schedule = schedule;
this.cause = cause;
@ -20,15 +32,4 @@ public class Result {
/** The solver was not able to further improve the solution (e.g. blocked in a local minima. */
Blocked
}
/** Instance that was solved. */
public final Instance instance;
/** A schedule of the solution or null if no solution was found. */
public final Schedule schedule;
/** Reason why the solver exited with this solution. */
public final ExitCause cause;
}

Visa fil

@ -2,6 +2,8 @@ package jobshop.encodings;
import jobshop.Instance;
import java.util.Optional;
/** Common class for all encodings.
*
* The only requirement for this class is to provide a conversion from the encoding into a Schedule.
@ -11,10 +13,15 @@ public abstract class Encoding {
/** Problem instance of which this is the solution. */
public final Instance instance;
/** Constructor, that initializes the instance field. */
public Encoding(Instance instance) {
this.instance = instance;
}
/** Convert into a schedule. */
public abstract Schedule toSchedule();
/** Attempts to convert this solution into a schedule.
*
* @return A empty optional if the solution is not valid. Otherwise the optional will contain a valid schedule of
* the solution.
*/
public abstract Optional<Schedule> toSchedule();
}

Visa fil

@ -4,6 +4,7 @@ import jobshop.Instance;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.IntStream;
/** Encoding of the solution of a jobshop problem by job numbers. */
@ -56,7 +57,7 @@ public class JobNumbers extends Encoding {
}
@Override
public Schedule toSchedule() {
public Optional<Schedule> toSchedule() {
// time at which each machine is going to be freed
int[] nextFreeTimeResource = new int[instance.numMachines];
@ -79,7 +80,7 @@ public class JobNumbers extends Encoding {
nextTask[job] = task + 1;
}
return schedule;
return Optional.of(schedule);
}
@Override

Visa fil

@ -2,6 +2,7 @@ package jobshop.encodings;
import jobshop.Instance;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.IntStream;
@ -85,7 +86,7 @@ public class ResourceOrder extends Encoding {
}
@Override
public Schedule toSchedule() {
public Optional<Schedule> toSchedule() {
// indicate for each task that have been scheduled, its start time
Schedule schedule = new Schedule(instance);
@ -130,16 +131,21 @@ public class ResourceOrder extends Encoding {
releaseTimeOfMachine[machine] = est + instance.duration(t.job, t.task);
} else {
// no tasks are schedulable, there is no solution for this resource ordering
return null;
return Optional.empty();
}
}
// we exited the loop : all tasks have been scheduled successfully
return schedule;
return Optional.of(schedule);
}
/** Creates an exact copy of this resource order. */
public ResourceOrder copy() {
return new ResourceOrder(this.toSchedule());
var schedule = this.toSchedule();
if (schedule.isEmpty()) {
throw new RuntimeException("Cannot copy an invalid ResourceOrder");
} else {
return new ResourceOrder(schedule.get());
}
}
@Override

Visa fil

@ -238,7 +238,7 @@ public class Schedule extends Encoding {
@Override
public Schedule toSchedule() {
return this;
public Optional<Schedule> toSchedule() {
return Optional.of(this);
}
}

Visa fil

@ -4,6 +4,7 @@ import jobshop.*;
import jobshop.encodings.JobNumbers;
import jobshop.encodings.Schedule;
import java.util.Optional;
import java.util.Random;
/** A solver that generates random solutions until a deadline is met.
@ -22,12 +23,14 @@ public class RandomSolver implements Solver {
sol.jobs[sol.nextToSet++] = j;
}
}
Schedule best = sol.toSchedule();
Optional<Schedule> best = sol.toSchedule();
while(deadline - System.currentTimeMillis() > 1) {
shuffleArray(sol.jobs, generator);
Schedule s = sol.toSchedule();
if(s.makespan() < best.makespan()) {
best = s;
Optional<Schedule> candidate = sol.toSchedule();
if(candidate.isPresent()) {
if (best.isEmpty() || candidate.get().makespan() < best.get().makespan()) {
best = candidate;
}
}
}

Visa fil

@ -24,7 +24,7 @@ public class EncodingTests {
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
Schedule sched = enc.toSchedule().get();
// TODO: make it print something meaningful
// by implementing the toString() method
System.out.println(sched);
@ -42,7 +42,7 @@ public class EncodingTests {
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
sched = enc.toSchedule();
sched = enc.toSchedule().get();
assert sched.isValid();
assert sched.makespan() == 14;
}
@ -60,15 +60,15 @@ public class EncodingTests {
enc.jobs[enc.nextToSet++] = 0;
enc.jobs[enc.nextToSet++] = 1;
Schedule sched = enc.toSchedule();
Schedule sched = enc.toSchedule().get();
assert sched.isValid();
assert sched.makespan() == 12;
Solver solver = new BasicSolver();
Result result = solver.solve(instance, System.currentTimeMillis() + 10);
assert result.schedule.isValid();
assert result.schedule.makespan() == sched.makespan(); // should have the same makespan
assert result.schedule.get().isValid();
assert result.schedule.get().makespan() == sched.makespan(); // should have the same makespan
}
}