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
parent fd9fa99913
commit 1957a255ba
10 changed files with 56 additions and 35 deletions

View file

@ -49,7 +49,7 @@ public class Instance {
return this.machine(t.job, t.task); 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) { public int task_with_machine(int job, int wanted_machine) {
for(int task = 0 ; task < numTasks ; task++) { for(int task = 0 ; task < numTasks ; task++) {
if(machine(job, task) == wanted_machine) if(machine(job, task) == wanted_machine)

View file

@ -8,6 +8,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jobshop.encodings.Schedule;
import jobshop.solvers.*; import jobshop.solvers.*;
import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.inf.ArgumentParser; import net.sourceforge.argparse4j.inf.ArgumentParser;
@ -122,13 +123,15 @@ public class Main {
long runtime = System.currentTimeMillis() - start; long runtime = System.currentTimeMillis() - start;
// check that the solver returned a valid solution // 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.err.println("ERROR: solver returned an invalid schedule");
System.exit(1); // bug in implementation, bail out 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. // 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; float dist = 100f * (makespan - bestKnown) / (float) bestKnown;
avg_runtimes[solverId] += (float) runtime / (float) instances.size(); avg_runtimes[solverId] += (float) runtime / (float) instances.size();
avg_distances[solverId] += dist / (float) instances.size(); avg_distances[solverId] += dist / (float) instances.size();

View file

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

View file

@ -2,10 +2,22 @@ package jobshop;
import jobshop.encodings.Schedule; import jobshop.encodings.Schedule;
import java.util.Optional;
/** Class representing the result of a solver. */ /** Class representing the result of a solver. */
public class Result { 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.instance = instance;
this.schedule = schedule; this.schedule = schedule;
this.cause = cause; 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. */ /** The solver was not able to further improve the solution (e.g. blocked in a local minima. */
Blocked 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;
} }

View file

@ -2,6 +2,8 @@ package jobshop.encodings;
import jobshop.Instance; import jobshop.Instance;
import java.util.Optional;
/** Common class for all encodings. /** Common class for all encodings.
* *
* The only requirement for this class is to provide a conversion from the encoding into a Schedule. * 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. */ /** Problem instance of which this is the solution. */
public final Instance instance; public final Instance instance;
/** Constructor, that initializes the instance field. */
public Encoding(Instance instance) { public Encoding(Instance instance) {
this.instance = instance; this.instance = instance;
} }
/** Convert into a schedule. */ /** Attempts to convert this solution into a schedule.
public abstract Schedule toSchedule(); *
* @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();
} }

View file

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

View file

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

View file

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

View file

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

View file

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