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)]
61pub struct FlowShopScheduling {
62 num_processors: usize,
64 task_lengths: Vec<Vec<i64>>,
66 deadline: i64,
68}
69
70impl FlowShopScheduling {
71 pub fn new(num_processors: usize, task_lengths: Vec<Vec<i64>>, deadline: i64) -> Self {
82 for (j, tasks) in task_lengths.iter().enumerate() {
83 assert_eq!(
84 tasks.len(),
85 num_processors,
86 "Job {} has {} tasks, expected {}",
87 j,
88 tasks.len(),
89 num_processors
90 );
91 }
92 assert!(
93 task_lengths.iter().flatten().all(|&length| length >= 0),
94 "task lengths must be nonnegative"
95 );
96 assert!(deadline >= 0, "deadline must be nonnegative");
97 Self {
98 num_processors,
99 task_lengths,
100 deadline,
101 }
102 }
103
104 pub fn num_processors(&self) -> usize {
106 self.num_processors
107 }
108
109 pub fn task_lengths(&self) -> &[Vec<i64>] {
111 &self.task_lengths
112 }
113
114 pub fn deadline(&self) -> i64 {
116 self.deadline
117 }
118
119 pub fn num_jobs(&self) -> usize {
121 self.task_lengths.len()
122 }
123
124 pub fn compute_makespan(
129 &self,
130 job_order: &[usize],
131 ) -> Result<i64, crate::traits::EvaluationError> {
132 let n = job_order.len();
133 let m = self.num_processors;
134 assert_eq!(
135 n,
136 self.task_lengths.len(),
137 "job_order length ({}) does not match num_jobs ({})",
138 n,
139 self.task_lengths.len()
140 );
141 for (k, &job) in job_order.iter().enumerate() {
142 assert!(
143 job < self.task_lengths.len(),
144 "job_order[{}] = {} is out of range (num_jobs = {})",
145 k,
146 job,
147 self.task_lengths.len()
148 );
149 }
150 if n == 0 || m == 0 {
151 return Ok(0);
152 }
153
154 let mut completion = vec![vec![0i64; m]; n];
156
157 for (k, &job) in job_order.iter().enumerate() {
158 for i in 0..m {
159 let prev_machine = if i == 0 { 0 } else { completion[k][i - 1] };
160 let prev_job = if k == 0 { 0 } else { completion[k - 1][i] };
161 let start = prev_machine.max(prev_job);
162 completion[k][i] =
163 start
164 .checked_add(self.task_lengths[job][i])
165 .ok_or_else(|| {
166 crate::traits::EvaluationError::IntegerOverflow(
167 "computing flow-shop completion time".to_string(),
168 )
169 })?;
170 }
171 }
172
173 Ok(completion[n - 1][m - 1])
174 }
175}
176
177impl Problem for FlowShopScheduling {
178 const NAME: &'static str = "FlowShopScheduling";
179 type Solution = Vec<usize>;
180 type Value = crate::types::Or;
181
182 crate::problem_parameters![("num_jobs", num_jobs), ("num_processors", num_processors),];
183
184 fn variant() -> Vec<(&'static str, &'static str)> {
185 crate::variant_params![]
186 }
187
188 fn evaluate(
189 &self,
190 config: &Self::Solution,
191 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
192 let n = self.num_jobs();
193 if config.len() != n {
194 return Err(crate::traits::EvaluationError::InvalidConfiguration(
195 "job ordering length does not match the jobs".into(),
196 ));
197 }
198 if config.iter().any(|&job| job >= n) {
199 return Err(crate::traits::EvaluationError::InvalidConfiguration(
200 "job ordering contains an out-of-range job".into(),
201 ));
202 }
203 Ok({
204 crate::types::Or({
205 let Some(job_order) = super::decode_permutation(config, self.num_jobs()) else {
206 return Ok(crate::types::Or(false));
207 };
208
209 let makespan = self.compute_makespan(&job_order)?;
210 makespan <= self.deadline
211 })
212 })
213 }
214}
215
216impl crate::solvers::BruteForceProblem for FlowShopScheduling {
217 fn dimensions(&self) -> Vec<usize> {
218 super::lehmer_dims(self.num_jobs())
219 }
220}
221
222crate::declare_variants! {
223 default FlowShopScheduling => "factorial(num_jobs)",
224}
225
226crate::register_brute_force! {
227 FlowShopScheduling decode |problem: &FlowShopScheduling, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_jobs()).expect("enumerated Lehmer digits are valid"),
228}
229
230#[cfg(feature = "example-db")]
231pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
232 vec![crate::example_db::specs::ModelExampleSpec {
233 id: "flow_shop_scheduling",
234 instance: Box::new(FlowShopScheduling::new(
235 3,
236 vec![
237 vec![3, 4, 2],
238 vec![2, 3, 5],
239 vec![4, 1, 3],
240 vec![1, 5, 4],
241 vec![3, 2, 3],
242 ],
243 25,
244 )),
245 optimal_config: serde_json::json!(vec![3, 0, 4, 2, 1]),
247 optimal_value: serde_json::json!(true),
248 }]
249}
250
251#[cfg(test)]
252#[path = "../../unit_tests/models/misc/flow_shop_scheduling.rs"]
253mod tests;