diff --git a/examples/jssp/parse.rs b/examples/jssp/parse.rs index 4c92f68..bb69d02 100644 --- a/examples/jssp/parse.rs +++ b/examples/jssp/parse.rs @@ -64,9 +64,9 @@ impl TryFrom<&PathBuf> for JsspInstance { .for_each(|op_def| { jobs.last_mut().unwrap().push(Operation::new( op_id, - usize::MAX, op_def[1].parse().unwrap(), op_def[0].parse().unwrap(), + None, Vec::from_iter(first_job_in_batch..op_id), )); op_id += 1; diff --git a/examples/jssp/problem.rs b/examples/jssp/problem.rs index 3ace5e1..cd274f9 100644 --- a/examples/jssp/problem.rs +++ b/examples/jssp/problem.rs @@ -10,15 +10,21 @@ pub mod population; pub mod probe; pub mod replacement; +/// Describes relation between two operations #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum EdgeKind { + /// Operation that the edge points to is from the same job (ops are on different machines) JobSucc, + /// Operation that the edge points to is on the same machine (ops are from different jobs) MachineSucc, } +/// Models the edge in neighbourhood graph where operations are nodes #[derive(Debug, Clone, Copy)] pub struct Edge { + /// Unique id of the neighbour operation pub neigh_id: usize, + /// Describes the relation between the operations pub kind: EdgeKind, } @@ -28,27 +34,57 @@ impl Edge { } } +/// Models Operation that is a part of some job +/// +/// TODO: Cleanup this struct. +/// 1. Move all data non-intrinsic to the Operation model to separate structs +/// 2. `critical_distance` should be an Option #[derive(Debug, Clone)] pub struct Operation { + /// Unique id of this operation id: usize, - finish_time: usize, + /// Duration of the operation duration: usize, + /// Machine this operation is assigned to machine: usize, - + /// Finish time tick of this operation as determined by the solver. The value of this field + /// is modified during the algorithm run + finish_time: Option, + /// Ids of all ops that this op depends on. TODO: Was the order guaranteed? preds: Vec, + /// Edges describing relations to other ops in neighbourhood graph. It contains *at most* two elements + /// as each op might have at most two successors: next operation in the job or next operation on the same machine + /// this op is executed on. The value of this field is modified as the algorithm runs edges_out: Vec, + /// Operation id of direct machine predecessor of this op. This might be `None` in following scenarios: + /// 1. Op is the first op on particular machine TODO: I'm not sure now, whether I set op no. 0 as machine predecessor + /// of every first op on given machine or not, so please verify it before using this fact. + /// 2. This is op with id 0 + /// + /// The value of this field is modified as the algorithm runs. machine_pred: Option, + /// If this operation lies on critical path in neighbourhood graph (as defined in paper by Nowicki & Smutnicki) + /// this is the edge pointing to next op on critical path, if there is one - this might be the last operation + /// or simply not on the path. The value of this field is modified as the algorithm runs. critical_path_edge: Option, + /// If this operation lies on critical path this field is used by the local search algorithm to store + /// distance from this op to the sink node. The value of this field is modified as the algorithm runs. critical_distance: usize, } impl Operation { - pub fn new(id: usize, finish_time: usize, duration: usize, machine: usize, preds: Vec) -> Self { + pub fn new( + id: usize, + duration: usize, + machine: usize, + finish_time: Option, + preds: Vec, + ) -> Self { Self { id, - finish_time, duration, machine, + finish_time, preds, edges_out: Vec::new(), machine_pred: None, @@ -57,9 +93,12 @@ impl Operation { } } + /// Resets the state of the operation so that this object can be reused to find new solution pub fn reset(&mut self) { - self.finish_time = usize::MAX; + self.finish_time = None; self.machine_pred = None; + // Job edges are determined by the problem instance we consider, while machine edges + // are determined by the scheduling process if let Some(edge_to_rm) = self .edges_out .iter() @@ -77,14 +116,16 @@ impl Operation { } } +/// Models the machine -- when it is occupied #[derive(Debug, Clone)] #[allow(dead_code)] pub struct Machine { + /// Unique id of the machine id: usize, // For naive implementation // rmc: Vec, - + /// Remaining machine capacity. If a range is added -> this means that the machine is occupied in that range // For "possibly better implementation" rmc: Vec>, pub last_scheduled_op: Option, @@ -124,27 +165,42 @@ impl Machine { self.last_scheduled_op = Some(op); } + /// Removes all ranges from the machine state allowing instance of this type to be reused pub fn reset(&mut self) { self.rmc.clear(); self.last_scheduled_op = None; } } +/// Basic information (metadata) about the jssp instance. #[derive(Debug, Clone)] pub struct JsspConfig { + /// Total number of jobs. Note that the job/operation naming/meaning is not consistent. + /// TODO: Unify this so that job is a ordered set of operations. pub n_jobs: usize, + /// Total number of machines in this problem instance pub n_machines: usize, + /// Total number of operations. Note that the job/operation naming/meaning is not consistent across + /// the codebase (but also in article...) pub n_ops: usize, } #[derive(Debug, Clone)] pub struct JsspInstanceMetadata { + /// Name of the instance. In case the instance was loaded from the disk, + /// the `name` should be related to the data file name. pub name: String, } +/// Describes single JSSP problem instance. +/// Instance is modeled as a set of jobs. +/// Each job is modeled as a set of operations. +/// Operations have precedency relation estabilished +/// and each operation is assigned to a particular machine. #[derive(Debug, Clone)] pub struct JsspInstance { pub jobs: Vec>, pub cfg: JsspConfig, + // TODO: I should merge Instance metadata with config pub metadata: JsspInstanceMetadata, } diff --git a/examples/jssp/problem/individual.rs b/examples/jssp/problem/individual.rs index f35fb7b..c8da38c 100644 --- a/examples/jssp/problem/individual.rs +++ b/examples/jssp/problem/individual.rs @@ -6,13 +6,24 @@ use log::{debug, info, trace, warn}; use super::{Edge, EdgeKind, Machine, Operation}; +/// Models single solution to the JSSP problem instance #[derive(Debug, Clone)] pub struct JsspIndividual { + /// Encoding of the solution. This can be decoded to the proper solution pub chromosome: Vec, + /// Clone of all operations from the problem instance pub operations: Vec, + /// Clone of all machines from the problem instance pub machines: Vec, + /// If computed - fitness value of this solution. Check `is_fitness_valid` + /// property to determine whether this value is up to date + /// This is not an Option for some practical reasons + /// TODO: But this should be an Option or some enum with additional information pub fitness: usize, + /// If `true` the `fitness` field holds the value for the current `chromosome` + /// and does need to be recomputed. This must be kept in sync! pub is_fitness_valid: bool, + /// TODO: Determine what I've used it for is_dirty: bool, } @@ -247,7 +258,7 @@ impl JsspIndividual { scheduled.insert(0); finish_times[0] = 0; - self.operations[0].finish_time = 0; + self.operations[0].finish_time = Some(0); let mut g = 1; let mut t_g = 0; @@ -277,7 +288,7 @@ impl JsspIndividual { let op_j_machine = self.operations[j].machine; let op_j = &self.operations[j]; - // Calculate earliset finish time (in terms of precedence only) + // Calculate the earliest finish time (in terms of precedence only) let pred_j_finish = op_j .preds .iter() @@ -331,6 +342,7 @@ impl JsspIndividual { makespan } + /// Resets all machines & operations associated with this individual fn reset(&mut self) { self.machines.iter_mut().for_each(|machine| machine.reset()); self.operations.iter_mut().for_each(|op| op.reset()); @@ -361,22 +373,27 @@ impl IndividualTrait for JsspIndividual { type ChromosomeT = Vec; type FitnessValueT = usize; + #[inline] fn chromosome(&self) -> &Self::ChromosomeT { &self.chromosome } + #[inline] fn chromosome_mut(&mut self) -> &mut Self::ChromosomeT { &mut self.chromosome } + #[inline] fn fitness(&self) -> Self::FitnessValueT { self.fitness } + #[inline] fn fitness_mut(&mut self) -> &mut Self::FitnessValueT { &mut self.fitness } + #[inline] fn requires_evaluation(&self) -> bool { !self.is_fitness_valid } diff --git a/examples/jssp/problem/population.rs b/examples/jssp/problem/population.rs index 2278c44..45cc77e 100644 --- a/examples/jssp/problem/population.rs +++ b/examples/jssp/problem/population.rs @@ -15,12 +15,15 @@ pub struct JsspPopProvider { impl JsspPopProvider { pub fn new(mut instance: JsspInstance) -> Self { - // Finding dimension of the chromosome + // Finding dimension of the chromosome -- total number of operations (later multiplied) let dim: usize = instance.jobs.iter().map(|job| job.len()).sum(); + // Prepare mock operations + // TODO: Shouldn't the duration be set to 0? + let mut zero_op = Operation::new(0, usize::MAX, 0, None, Vec::new()); + let sink_op = Operation::new(dim + 1, usize::MAX, 0, None, Vec::from_iter(0..=dim)); + // Shift all ids by 1 && and job 0 & n + 1 - let mut zero_op = Operation::new(0, usize::MAX, 0, 0, Vec::new()); - let sink_op = Operation::new(dim + 1, usize::MAX, 0, 0, Vec::from_iter(0..=dim)); instance.jobs.iter_mut().for_each(|job| { job.iter_mut().for_each(|op| { op.id += 1;