problemreductions/models/misc/
minimum_tardiness_sequencing.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
12use crate::traits::Problem;
13use crate::types::{Min, One, WeightElement};
14use serde::{Deserialize, Serialize};
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "MinimumTardinessSequencing",
19 display_name: "Minimum Tardiness Sequencing",
20 aliases: &[],
21 dimensions: &[VariantDimension::new("weight", "One", &["One", "i64"])],
22 category: crate::registry::ProblemCategory::Misc,
23 module_path: module_path!(),
24 description: "Schedule tasks with precedence constraints and deadlines to minimize the number of tardy tasks",
25 fields: MinimumTardinessSequencingI64CreateSpec::FIELDS,
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct MinimumTardinessSequencing<W> {
59 lengths: Vec<W>,
60 deadlines: Vec<i64>,
61 precedences: Vec<(usize, usize)>,
62}
63
64macro_rules! minimum_tardiness_create_spec {
65 ($name:ident, $weight:ty, $construct:expr) => {
66 #[derive(Debug, Deserialize, crate::CreateSpec)]
67 struct $name {
68 lengths: Vec<$weight>,
69 deadlines: Vec<i64>,
70 precedences: Option<Vec<(usize, usize)>>,
71 }
72
73 impl TryFrom<$name> for MinimumTardinessSequencing<$weight> {
74 type Error = crate::registry::ConstructionError;
75
76 fn try_from(spec: $name) -> Result<Self, Self::Error> {
77 if spec.lengths.len() != spec.deadlines.len() {
78 return Err("lengths and deadlines must have the same length"
79 .to_string()
80 .into());
81 }
82 let precedences = spec.precedences.unwrap_or_default();
83 let num_tasks = spec.lengths.len();
84 if let Some(&(pred, succ)) = precedences
85 .iter()
86 .find(|&&(pred, succ)| pred >= num_tasks || succ >= num_tasks)
87 {
88 return Err(format!(
89 "precedence ({pred}, {succ}) is out of range for {num_tasks} tasks"
90 )
91 .into());
92 }
93 $construct(spec.lengths, spec.deadlines, precedences)
94 }
95 }
96 };
97}
98
99#[derive(Debug, Deserialize, crate::CreateSpec)]
100struct MinimumTardinessSequencingOneCreateSpec {
101 deadlines: Vec<i64>,
102 precedences: Option<Vec<(usize, usize)>>,
103}
104impl TryFrom<MinimumTardinessSequencingOneCreateSpec> for MinimumTardinessSequencing<One> {
105 type Error = crate::registry::ConstructionError;
106 fn try_from(spec: MinimumTardinessSequencingOneCreateSpec) -> Result<Self, Self::Error> {
107 let num_tasks = spec.deadlines.len();
108 let precedences = spec.precedences.unwrap_or_default();
109 if precedences
110 .iter()
111 .any(|&(a, b)| a >= num_tasks || b >= num_tasks)
112 {
113 return Err("precedence indices must be within the task count".into());
114 }
115 Ok(Self::new(num_tasks, spec.deadlines, precedences))
116 }
117}
118
119minimum_tardiness_create_spec!(
120 MinimumTardinessSequencingI64CreateSpec,
121 i64,
122 |lengths: Vec<i64>, deadlines, precedences| {
123 if lengths.iter().any(|&length| length <= 0) {
124 return Err("all task lengths must be positive".to_string().into());
125 }
126 Ok(MinimumTardinessSequencing::with_lengths(
127 lengths,
128 deadlines,
129 precedences,
130 ))
131 }
132);
133
134impl MinimumTardinessSequencing<One> {
135 pub fn new(num_tasks: usize, deadlines: Vec<i64>, precedences: Vec<(usize, usize)>) -> Self {
142 assert_eq!(
143 deadlines.len(),
144 num_tasks,
145 "deadlines length must equal num_tasks"
146 );
147 validate_precedences(num_tasks, &precedences);
148 Self {
149 lengths: vec![One; num_tasks],
150 deadlines,
151 precedences,
152 }
153 }
154}
155
156impl MinimumTardinessSequencing<i64> {
157 pub fn with_lengths(
164 lengths: Vec<i64>,
165 deadlines: Vec<i64>,
166 precedences: Vec<(usize, usize)>,
167 ) -> Self {
168 assert_eq!(
169 lengths.len(),
170 deadlines.len(),
171 "lengths and deadlines must have the same length"
172 );
173 assert!(
174 lengths.iter().all(|&l| l > 0),
175 "all task lengths must be positive"
176 );
177 let num_tasks = lengths.len();
178 validate_precedences(num_tasks, &precedences);
179 Self {
180 lengths,
181 deadlines,
182 precedences,
183 }
184 }
185}
186
187fn validate_precedences(num_tasks: usize, precedences: &[(usize, usize)]) {
188 for &(pred, succ) in precedences {
189 assert!(
190 pred < num_tasks,
191 "predecessor index {} out of range (num_tasks = {})",
192 pred,
193 num_tasks
194 );
195 assert!(
196 succ < num_tasks,
197 "successor index {} out of range (num_tasks = {})",
198 succ,
199 num_tasks
200 );
201 }
202}
203
204impl<W: WeightElement> MinimumTardinessSequencing<W> {
205 pub fn num_tasks(&self) -> usize {
207 self.deadlines.len()
208 }
209
210 pub fn lengths(&self) -> &[W] {
212 &self.lengths
213 }
214
215 pub fn deadlines(&self) -> &[i64] {
217 &self.deadlines
218 }
219
220 pub fn precedences(&self) -> &[(usize, usize)] {
222 &self.precedences
223 }
224
225 pub fn num_precedences(&self) -> usize {
227 self.precedences.len()
228 }
229
230 fn decode_and_validate(&self, config: &[usize]) -> Option<Vec<usize>> {
233 let n = self.num_tasks();
234 let schedule = super::decode_permutation(config, n)?;
235
236 let mut sigma = vec![0usize; n];
237 for (pos, &task) in schedule.iter().enumerate() {
238 sigma[task] = pos;
239 }
240
241 for &(pred, succ) in &self.precedences {
242 if sigma[pred] >= sigma[succ] {
243 return None;
244 }
245 }
246
247 Some(sigma)
248 }
249}
250
251impl Problem for MinimumTardinessSequencing<One> {
252 const NAME: &'static str = "MinimumTardinessSequencing";
253 type Solution = Vec<usize>;
254 type Value = Min<i64>;
255
256 crate::problem_parameters![
257 ("num_precedences", num_precedences),
258 ("num_tasks", num_tasks),
259 ];
260
261 fn variant() -> Vec<(&'static str, &'static str)> {
262 crate::variant_params![One]
263 }
264
265 fn evaluate(
266 &self,
267 config: &Self::Solution,
268 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
269 let n = self.num_tasks();
270 if config.len() != n {
271 return Err(crate::traits::EvaluationError::InvalidConfiguration(
272 "schedule length does not match the tasks".into(),
273 ));
274 }
275 if config.iter().any(|&task| task >= n) {
276 return Err(crate::traits::EvaluationError::InvalidConfiguration(
277 "schedule contains an out-of-range task".into(),
278 ));
279 }
280 Ok({
281 let Some(sigma) = self.decode_and_validate(config) else {
282 return Ok(Min(None));
283 };
284
285 let mut tardy_count = 0_i64;
287 for (task, &position) in sigma.iter().enumerate() {
288 let completion = i64::try_from(position)
289 .ok()
290 .and_then(|position| position.checked_add(1))
291 .ok_or_else(|| {
292 crate::traits::EvaluationError::IntegerOverflow(
293 "computing a unit-length task completion time".to_string(),
294 )
295 })?;
296 if completion > self.deadlines[task] {
297 tardy_count = tardy_count.checked_add(1).ok_or_else(|| {
298 crate::traits::EvaluationError::IntegerOverflow(
299 "counting tardy tasks".to_string(),
300 )
301 })?;
302 }
303 }
304
305 Min(Some(tardy_count))
306 })
307 }
308}
309
310impl crate::solvers::BruteForceProblem for MinimumTardinessSequencing<One> {
311 fn dimensions(&self) -> Vec<usize> {
312 super::lehmer_dims(self.num_tasks())
313 }
314}
315
316impl Problem for MinimumTardinessSequencing<i64> {
317 const NAME: &'static str = "MinimumTardinessSequencing";
318 type Solution = Vec<usize>;
319 type Value = Min<i64>;
320
321 crate::problem_parameters![
322 ("num_precedences", num_precedences),
323 ("num_tasks", num_tasks),
324 ];
325
326 fn variant() -> Vec<(&'static str, &'static str)> {
327 crate::variant_params![i64]
328 }
329
330 fn evaluate(
331 &self,
332 config: &Self::Solution,
333 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
334 let n = self.num_tasks();
335 if config.len() != n {
336 return Err(crate::traits::EvaluationError::InvalidConfiguration(
337 "schedule length does not match the tasks".into(),
338 ));
339 }
340 if config.iter().any(|&task| task >= n) {
341 return Err(crate::traits::EvaluationError::InvalidConfiguration(
342 "schedule contains an out-of-range task".into(),
343 ));
344 }
345 Ok({
346 let Some(sigma) = self.decode_and_validate(config) else {
347 return Ok(Min(None));
348 };
349
350 let mut schedule = vec![0usize; n];
352 for (task, &pos) in sigma.iter().enumerate() {
353 schedule[pos] = task;
354 }
355
356 let mut completion = vec![0_i64; n];
358 let mut cumulative = 0_i64;
359 for &task in &schedule {
360 cumulative = cumulative.checked_add(self.lengths[task]).ok_or_else(|| {
361 crate::traits::EvaluationError::IntegerOverflow(
362 "summing task lengths while computing completion times".to_string(),
363 )
364 })?;
365 completion[task] = cumulative;
366 }
367
368 let mut tardy_count = 0_i64;
369 for (task, &completion_time) in completion.iter().enumerate() {
370 if completion_time > self.deadlines[task] {
371 tardy_count = tardy_count.checked_add(1).ok_or_else(|| {
372 crate::traits::EvaluationError::IntegerOverflow(
373 "counting tardy tasks".to_string(),
374 )
375 })?;
376 }
377 }
378
379 Min(Some(tardy_count))
380 })
381 }
382}
383
384impl crate::solvers::BruteForceProblem for MinimumTardinessSequencing<i64> {
385 fn dimensions(&self) -> Vec<usize> {
386 super::lehmer_dims(self.num_tasks())
387 }
388}
389
390crate::declare_variants! {
391 default MinimumTardinessSequencing<One> => "2^num_tasks" create MinimumTardinessSequencingOneCreateSpec,
392 MinimumTardinessSequencing<i64> => "2^num_tasks" create MinimumTardinessSequencingI64CreateSpec,
393}
394
395crate::register_brute_force! {
396 MinimumTardinessSequencing<One> decode |problem: &MinimumTardinessSequencing<One>, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_tasks()).expect("enumerated Lehmer digits are valid"),
397 MinimumTardinessSequencing<i64> decode |problem: &MinimumTardinessSequencing<i64>, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_tasks()).expect("enumerated Lehmer digits are valid"),
398}
399
400#[cfg(feature = "example-db")]
401pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
402 vec![
403 crate::example_db::specs::ModelExampleSpec {
405 id: "minimum_tardiness_sequencing",
406 instance: Box::new(MinimumTardinessSequencing::<One>::new(
407 4,
408 vec![2, 3, 1, 4],
409 vec![(0, 2)],
410 )),
411 optimal_config: serde_json::json!(vec![0, 1, 2, 3]),
412 optimal_value: serde_json::json!(1),
413 },
414 crate::example_db::specs::ModelExampleSpec {
416 id: "minimum_tardiness_sequencing_weighted",
417 instance: Box::new(MinimumTardinessSequencing::<i64>::with_lengths(
422 vec![3, 2, 2, 1, 2],
423 vec![4, 3, 8, 3, 6],
424 vec![(0, 2), (1, 3)],
425 )),
426 optimal_config: serde_json::json!(vec![0, 4, 2, 1, 3]),
427 optimal_value: serde_json::json!(2),
428 },
429 ]
430}
431
432#[cfg(test)]
433#[path = "../../unit_tests/models/misc/minimum_tardiness_sequencing.rs"]
434mod tests;