use serde::{Deserialize, Serialize};
use converge_pack::{ExecutionIdentity, FactPayload};
use crate::domain_types::{JobId, MachineId, ProcessingTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum JobShopSolveStatus {
Optimal,
Feasible,
Infeasible,
Error,
Invalid,
}
impl JobShopSolveStatus {
pub fn is_successful(self) -> bool {
matches!(self, Self::Optimal | Self::Feasible)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Operation {
pub machine_id: MachineId,
pub duration: ProcessingTime,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Job {
pub id: JobId,
pub name: String,
pub operations: Vec<Operation>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct JobShopRequest {
pub id: String,
pub jobs: Vec<Job>,
pub num_machines: usize,
#[serde(default = "default_time_limit")]
pub time_limit_seconds: f64,
}
impl FactPayload for JobShopRequest {
const FAMILY: &'static str = "ferrox.jobshop.request";
const VERSION: u16 = 1;
}
fn default_time_limit() -> f64 {
30.0
}
impl JobShopRequest {
pub fn horizon(&self) -> i64 {
self.jobs
.iter()
.flat_map(|j| j.operations.iter())
.map(|o| o.duration.0)
.sum()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ScheduledOp {
pub job_id: JobId,
pub job_name: String,
pub machine_id: MachineId,
pub op_index: usize,
pub start: i64,
pub end: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct JobShopPlan {
pub request_id: String,
pub schedule: Vec<ScheduledOp>,
pub makespan: i64,
pub lower_bound: Option<i64>,
pub solver: String,
pub execution_identity: ExecutionIdentity,
pub status: JobShopSolveStatus,
pub wall_time_seconds: f64,
}
impl FactPayload for JobShopPlan {
const FAMILY: &'static str = "ferrox.jobshop.plan";
const VERSION: u16 = 1;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain_types::{JobId, MachineId, ProcessingTime};
#[test]
fn horizon_zero_when_no_jobs() {
let r = JobShopRequest {
id: "r".into(),
jobs: vec![],
num_machines: 1,
time_limit_seconds: 1.0,
};
assert_eq!(r.horizon(), 0);
}
#[test]
fn horizon_sums_durations() {
let r = JobShopRequest {
id: "r".into(),
jobs: vec![Job {
id: JobId(0),
name: "j".into(),
operations: vec![
Operation {
machine_id: MachineId(0),
duration: ProcessingTime(4),
},
Operation {
machine_id: MachineId(1),
duration: ProcessingTime(6),
},
],
}],
num_machines: 2,
time_limit_seconds: 1.0,
};
assert_eq!(r.horizon(), 10);
}
#[test]
fn request_default_time_limit() {
let json = r#"{"id":"r","jobs":[],"num_machines":1}"#;
let r: JobShopRequest = serde_json::from_str(json).unwrap();
assert!((r.time_limit_seconds - 30.0).abs() < f64::EPSILON);
}
}