Make Encoding.toSchedule() return an optional to handle the case where the solution is not valid.
This commit is contained in:
		
							parent
							
								
									fd9fa99913
								
							
						
					
					
						commit
						1957a255ba
					
				
					 10 changed files with 56 additions and 35 deletions
				
			
		|  | @ -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) | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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()); | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -238,7 +238,7 @@ public class Schedule extends Encoding { | |||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public Schedule toSchedule() { | ||||
|         return this; | ||||
|     public Optional<Schedule> toSchedule() { | ||||
|         return Optional.of(this); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue