problemreductions/models/misc/
flow_shop_scheduling.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "FlowShopScheduling",
14 display_name: "Flow Shop Scheduling",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Determine if a flow-shop schedule for jobs on m processors meets a deadline",
20 fields: &[
21 FieldInfo { name: "num_processors", type_name: "usize", description: "Number of machines m" },
22 FieldInfo { name: "task_lengths", type_name: "Vec<Vec<i64>>", description: "task_lengths[j][i] = length of job j's task on machine i" },
23 FieldInfo { name: "deadline", type_name: "i64", description: "Global deadline D" },
24 ],
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(try_from = "FlowShopSchedulingData")]
62pub struct FlowShopScheduling {
63 num_processors: usize,
65 task_lengths: Vec<Vec<i64>>,
67 deadline: i64,
69}
70
71#[derive(Deserialize)]
72struct FlowShopSchedulingData {
73 num_processors: usize,
74 task_lengths: Vec<Vec<i64>>,
75 deadline: i64,
76}
77
78impl TryFrom<FlowShopSchedulingData> for FlowShopScheduling {
79 type Error = crate::registry::ConstructionError;
80
81 fn try_from(data: FlowShopSchedulingData) -> Result<Self, Self::Error> {
82 Self::try_new(data.num_processors, data.task_lengths, data.deadline)
83 }
84}
85
86impl FlowShopScheduling {
87 pub fn new(num_processors: usize, task_lengths: Vec<Vec<i64>>, deadline: i64) -> Self {
98 Self::try_new(num_processors, task_lengths, deadline)
99 .unwrap_or_else(|error| panic!("{error}"))
100 }
101
102 fn try_new(
103 num_processors: usize,
104 task_lengths: Vec<Vec<i64>>,
105 deadline: i64,
106 ) -> Result<Self, crate::registry::ConstructionError> {
107 for (j, tasks) in task_lengths.iter().enumerate() {
108 if tasks.len() != num_processors {
109 return Err(format!(
110 "Job {} has {} tasks, expected {}",
111 j,
112 tasks.len(),
113 num_processors
114 )
115 .into());
116 }
117 }
118 if task_lengths.iter().flatten().any(|&length| length < 0) {
119 return Err("task lengths must be nonnegative".into());
120 }
121 if deadline < 0 {
122 return Err("deadline must be nonnegative".into());
123 }
124 Ok(Self {
125 num_processors,
126 task_lengths,
127 deadline,
128 })
129 }
130
131 pub fn num_processors(&self) -> usize {
133 self.num_processors
134 }
135
136 pub fn task_lengths(&self) -> &[Vec<i64>] {
138 &self.task_lengths
139 }
140
141 pub fn deadline(&self) -> i64 {
143 self.deadline
144 }
145
146 pub fn max_time_bits(&self) -> u64 {
148 crate::types::max_numeric_magnitude_bits(
149 self.task_lengths
150 .iter()
151 .flatten()
152 .copied()
153 .chain(std::iter::once(self.deadline)),
154 )
155 }
156
157 pub fn num_jobs(&self) -> usize {
159 self.task_lengths.len()
160 }
161
162 pub fn compute_makespan(
167 &self,
168 job_order: &[usize],
169 ) -> Result<i64, crate::traits::EvaluationError> {
170 let n = job_order.len();
171 let m = self.num_processors;
172 assert_eq!(
173 n,
174 self.task_lengths.len(),
175 "job_order length ({}) does not match num_jobs ({})",
176 n,
177 self.task_lengths.len()
178 );
179 for (k, &job) in job_order.iter().enumerate() {
180 assert!(
181 job < self.task_lengths.len(),
182 "job_order[{}] = {} is out of range (num_jobs = {})",
183 k,
184 job,
185 self.task_lengths.len()
186 );
187 }
188 if n == 0 || m == 0 {
189 return Ok(0);
190 }
191
192 let mut completion = vec![vec![0i64; m]; n];
194
195 for (k, &job) in job_order.iter().enumerate() {
196 for i in 0..m {
197 let prev_machine = if i == 0 { 0 } else { completion[k][i - 1] };
198 let prev_job = if k == 0 { 0 } else { completion[k - 1][i] };
199 let start = prev_machine.max(prev_job);
200 completion[k][i] =
201 start
202 .checked_add(self.task_lengths[job][i])
203 .ok_or_else(|| {
204 crate::traits::EvaluationError::IntegerOverflow(
205 "computing flow-shop completion time".to_string(),
206 )
207 })?;
208 }
209 }
210
211 Ok(completion[n - 1][m - 1])
212 }
213}
214
215impl Problem for FlowShopScheduling {
216 const NAME: &'static str = "FlowShopScheduling";
217 type Solution = Vec<usize>;
218 type Value = crate::types::Or;
219
220 crate::problem_parameters![
221 ("max_time_bits", max_time_bits),
222 ("num_jobs", num_jobs),
223 ("num_processors", num_processors),
224 ];
225
226 fn variant() -> Vec<(&'static str, &'static str)> {
227 crate::variant_params![]
228 }
229
230 fn evaluate(
231 &self,
232 config: &Self::Solution,
233 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
234 let n = self.num_jobs();
235 if config.len() != n {
236 return Err(crate::traits::EvaluationError::InvalidConfiguration(
237 "job ordering length does not match the jobs".into(),
238 ));
239 }
240 if config.iter().any(|&job| job >= n) {
241 return Err(crate::traits::EvaluationError::InvalidConfiguration(
242 "job ordering contains an out-of-range job".into(),
243 ));
244 }
245 Ok({
246 crate::types::Or({
247 let Some(job_order) = super::decode_permutation(config, self.num_jobs()) else {
248 return Ok(crate::types::Or(false));
249 };
250
251 let makespan = self.compute_makespan(&job_order)?;
252 makespan <= self.deadline
253 })
254 })
255 }
256}
257
258impl crate::solvers::BruteForceProblem for FlowShopScheduling {
259 fn dimensions(&self) -> Vec<usize> {
260 super::lehmer_dims(self.num_jobs())
261 }
262}
263
264crate::declare_variants! {
265 default FlowShopScheduling => "factorial(num_jobs)",
266}
267
268crate::register_brute_force! {
269 FlowShopScheduling decode |problem: &FlowShopScheduling, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_jobs()).expect("enumerated Lehmer digits are valid"),
270}
271
272#[cfg(feature = "example-db")]
273pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
274 vec![crate::example_db::specs::ModelExampleSpec {
275 id: "flow_shop_scheduling",
276 instance: Box::new(FlowShopScheduling::new(
277 3,
278 vec![
279 vec![3, 4, 2],
280 vec![2, 3, 5],
281 vec![4, 1, 3],
282 vec![1, 5, 4],
283 vec![3, 2, 3],
284 ],
285 25,
286 )),
287 optimal_config: serde_json::json!(vec![3, 0, 4, 2, 1]),
289 optimal_value: serde_json::json!(true),
290 }]
291}
292
293#[cfg(test)]
294#[path = "../../unit_tests/models/misc/flow_shop_scheduling.rs"]
295mod tests;