1use 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)]
58#[serde(try_from = "MinimumTardinessSequencingData<W>")]
59#[serde(bound(
60 deserialize = "W: Deserialize<'de>, Self: TryFrom<MinimumTardinessSequencingData<W>>, <Self as TryFrom<MinimumTardinessSequencingData<W>>>::Error: std::fmt::Display"
61))]
62pub struct MinimumTardinessSequencing<W> {
63 lengths: Vec<W>,
64 deadlines: Vec<i64>,
65 precedences: Vec<(usize, usize)>,
66}
67
68#[derive(Deserialize)]
69struct MinimumTardinessSequencingData<W> {
70 lengths: Vec<W>,
71 deadlines: Vec<i64>,
72 precedences: Vec<(usize, usize)>,
73}
74
75impl TryFrom<MinimumTardinessSequencingData<One>> for MinimumTardinessSequencing<One> {
76 type Error = crate::registry::ConstructionError;
77
78 fn try_from(data: MinimumTardinessSequencingData<One>) -> Result<Self, Self::Error> {
79 Self::try_new(data.lengths.len(), data.deadlines, data.precedences)
80 }
81}
82
83impl TryFrom<MinimumTardinessSequencingData<i64>> for MinimumTardinessSequencing<i64> {
84 type Error = crate::registry::ConstructionError;
85
86 fn try_from(data: MinimumTardinessSequencingData<i64>) -> Result<Self, Self::Error> {
87 Self::try_with_lengths(data.lengths, data.deadlines, data.precedences)
88 }
89}
90
91#[derive(Debug, Deserialize, crate::CreateSpec)]
92struct MinimumTardinessSequencingOneCreateSpec {
93 deadlines: Vec<i64>,
94 precedences: Option<Vec<(usize, usize)>>,
95}
96impl TryFrom<MinimumTardinessSequencingOneCreateSpec> for MinimumTardinessSequencing<One> {
97 type Error = crate::registry::ConstructionError;
98 fn try_from(spec: MinimumTardinessSequencingOneCreateSpec) -> Result<Self, Self::Error> {
99 let num_tasks = spec.deadlines.len();
100 let precedences = spec.precedences.unwrap_or_default();
101 Self::try_new(num_tasks, spec.deadlines, precedences)
102 }
103}
104
105#[derive(Debug, Deserialize, crate::CreateSpec)]
106struct MinimumTardinessSequencingI64CreateSpec {
107 lengths: Vec<i64>,
108 deadlines: Vec<i64>,
109 precedences: Option<Vec<(usize, usize)>>,
110}
111impl TryFrom<MinimumTardinessSequencingI64CreateSpec> for MinimumTardinessSequencing<i64> {
112 type Error = crate::registry::ConstructionError;
113 fn try_from(spec: MinimumTardinessSequencingI64CreateSpec) -> Result<Self, Self::Error> {
114 Self::try_with_lengths(
115 spec.lengths,
116 spec.deadlines,
117 spec.precedences.unwrap_or_default(),
118 )
119 }
120}
121
122impl MinimumTardinessSequencing<One> {
123 pub fn new(num_tasks: usize, deadlines: Vec<i64>, precedences: Vec<(usize, usize)>) -> Self {
130 Self::try_new(num_tasks, deadlines, precedences).unwrap_or_else(|error| panic!("{error}"))
131 }
132
133 fn try_new(
134 num_tasks: usize,
135 deadlines: Vec<i64>,
136 precedences: Vec<(usize, usize)>,
137 ) -> Result<Self, crate::registry::ConstructionError> {
138 validate_task_data(num_tasks, &deadlines, &precedences)?;
139 Ok(Self {
140 lengths: vec![One; num_tasks],
141 deadlines,
142 precedences,
143 })
144 }
145}
146
147impl MinimumTardinessSequencing<i64> {
148 pub fn with_lengths(
155 lengths: Vec<i64>,
156 deadlines: Vec<i64>,
157 precedences: Vec<(usize, usize)>,
158 ) -> Self {
159 Self::try_with_lengths(lengths, deadlines, precedences)
160 .unwrap_or_else(|error| panic!("{error}"))
161 }
162
163 fn try_with_lengths(
164 lengths: Vec<i64>,
165 deadlines: Vec<i64>,
166 precedences: Vec<(usize, usize)>,
167 ) -> Result<Self, crate::registry::ConstructionError> {
168 validate_task_data(lengths.len(), &deadlines, &precedences)?;
169 if lengths.iter().any(|&length| length <= 0) {
170 return Err("all task lengths must be positive".into());
171 }
172 Ok(Self {
173 lengths,
174 deadlines,
175 precedences,
176 })
177 }
178}
179
180fn validate_task_data(
181 num_tasks: usize,
182 deadlines: &[i64],
183 precedences: &[(usize, usize)],
184) -> Result<(), crate::registry::ConstructionError> {
185 if deadlines.len() != num_tasks {
186 return Err("deadlines length must equal num_tasks".into());
187 }
188 for &(pred, succ) in precedences {
189 if pred >= num_tasks {
190 return Err(
191 format!("predecessor index {pred} out of range (num_tasks = {num_tasks})").into(),
192 );
193 }
194 if succ >= num_tasks {
195 return Err(
196 format!("successor index {succ} out of range (num_tasks = {num_tasks})").into(),
197 );
198 }
199 }
200 Ok(())
201}
202
203impl<W: WeightElement> MinimumTardinessSequencing<W> {
204 pub fn max_processing_time_bits(&self) -> u64 {
206 crate::types::max_numeric_magnitude_bits(self.lengths.iter().map(|length| length.to_sum()))
207 }
208
209 pub fn num_tasks(&self) -> usize {
211 self.deadlines.len()
212 }
213
214 pub fn lengths(&self) -> &[W] {
216 &self.lengths
217 }
218
219 pub fn deadlines(&self) -> &[i64] {
221 &self.deadlines
222 }
223
224 pub fn precedences(&self) -> &[(usize, usize)] {
226 &self.precedences
227 }
228
229 pub fn num_precedences(&self) -> usize {
231 self.precedences.len()
232 }
233
234 fn decode_and_validate(&self, config: &[usize]) -> Option<Vec<usize>> {
237 let n = self.num_tasks();
238 let schedule = super::decode_permutation(config, n)?;
239
240 let mut sigma = vec![0usize; n];
241 for (pos, &task) in schedule.iter().enumerate() {
242 sigma[task] = pos;
243 }
244
245 for &(pred, succ) in &self.precedences {
246 if sigma[pred] >= sigma[succ] {
247 return None;
248 }
249 }
250
251 Some(sigma)
252 }
253}
254
255impl Problem for MinimumTardinessSequencing<One> {
256 const NAME: &'static str = "MinimumTardinessSequencing";
257 type Solution = Vec<usize>;
258 type Value = Min<i64>;
259
260 crate::problem_parameters![
261 ("max_processing_time_bits", max_processing_time_bits),
262 ("num_precedences", num_precedences),
263 ("num_tasks", num_tasks),
264 ];
265
266 fn variant() -> Vec<(&'static str, &'static str)> {
267 crate::variant_params![One]
268 }
269
270 fn evaluate(
271 &self,
272 config: &Self::Solution,
273 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
274 let n = self.num_tasks();
275 if config.len() != n {
276 return Err(crate::traits::EvaluationError::InvalidConfiguration(
277 "schedule length does not match the tasks".into(),
278 ));
279 }
280 if config.iter().any(|&task| task >= n) {
281 return Err(crate::traits::EvaluationError::InvalidConfiguration(
282 "schedule contains an out-of-range task".into(),
283 ));
284 }
285 Ok({
286 let Some(sigma) = self.decode_and_validate(config) else {
287 return Ok(Min(None));
288 };
289
290 let mut tardy_count = 0_i64;
292 for (task, &position) in sigma.iter().enumerate() {
293 let completion = i64::try_from(position)
294 .ok()
295 .and_then(|position| position.checked_add(1))
296 .ok_or_else(|| {
297 crate::traits::EvaluationError::IntegerOverflow(
298 "computing a unit-length task completion time".to_string(),
299 )
300 })?;
301 if completion > self.deadlines[task] {
302 tardy_count = tardy_count.checked_add(1).ok_or_else(|| {
303 crate::traits::EvaluationError::IntegerOverflow(
304 "counting tardy tasks".to_string(),
305 )
306 })?;
307 }
308 }
309
310 Min(Some(tardy_count))
311 })
312 }
313}
314
315impl crate::solvers::BruteForceProblem for MinimumTardinessSequencing<One> {
316 fn dimensions(&self) -> Vec<usize> {
317 super::lehmer_dims(self.num_tasks())
318 }
319}
320
321impl Problem for MinimumTardinessSequencing<i64> {
322 const NAME: &'static str = "MinimumTardinessSequencing";
323 type Solution = Vec<usize>;
324 type Value = Min<i64>;
325
326 crate::problem_parameters![
327 ("max_processing_time_bits", max_processing_time_bits),
328 ("num_precedences", num_precedences),
329 ("num_tasks", num_tasks),
330 ];
331
332 fn variant() -> Vec<(&'static str, &'static str)> {
333 crate::variant_params![i64]
334 }
335
336 fn evaluate(
337 &self,
338 config: &Self::Solution,
339 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
340 let n = self.num_tasks();
341 if config.len() != n {
342 return Err(crate::traits::EvaluationError::InvalidConfiguration(
343 "schedule length does not match the tasks".into(),
344 ));
345 }
346 if config.iter().any(|&task| task >= n) {
347 return Err(crate::traits::EvaluationError::InvalidConfiguration(
348 "schedule contains an out-of-range task".into(),
349 ));
350 }
351 Ok({
352 let Some(sigma) = self.decode_and_validate(config) else {
353 return Ok(Min(None));
354 };
355
356 let mut schedule = vec![0usize; n];
358 for (task, &pos) in sigma.iter().enumerate() {
359 schedule[pos] = task;
360 }
361
362 let mut completion = vec![0_i64; n];
364 let mut cumulative = 0_i64;
365 for &task in &schedule {
366 cumulative = cumulative.checked_add(self.lengths[task]).ok_or_else(|| {
367 crate::traits::EvaluationError::IntegerOverflow(
368 "summing task lengths while computing completion times".to_string(),
369 )
370 })?;
371 completion[task] = cumulative;
372 }
373
374 let mut tardy_count = 0_i64;
375 for (task, &completion_time) in completion.iter().enumerate() {
376 if completion_time > self.deadlines[task] {
377 tardy_count = tardy_count.checked_add(1).ok_or_else(|| {
378 crate::traits::EvaluationError::IntegerOverflow(
379 "counting tardy tasks".to_string(),
380 )
381 })?;
382 }
383 }
384
385 Min(Some(tardy_count))
386 })
387 }
388}
389
390impl crate::solvers::BruteForceProblem for MinimumTardinessSequencing<i64> {
391 fn dimensions(&self) -> Vec<usize> {
392 super::lehmer_dims(self.num_tasks())
393 }
394}
395
396crate::declare_variants! {
397 default MinimumTardinessSequencing<One> => "2^num_tasks" create MinimumTardinessSequencingOneCreateSpec,
398 MinimumTardinessSequencing<i64> => "2^num_tasks" create MinimumTardinessSequencingI64CreateSpec,
399}
400
401crate::register_brute_force! {
402 MinimumTardinessSequencing<One> decode |problem: &MinimumTardinessSequencing<One>, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_tasks()).expect("enumerated Lehmer digits are valid"),
403 MinimumTardinessSequencing<i64> decode |problem: &MinimumTardinessSequencing<i64>, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_tasks()).expect("enumerated Lehmer digits are valid"),
404}
405
406#[cfg(feature = "example-db")]
407pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
408 vec![
409 crate::example_db::specs::ModelExampleSpec {
411 id: "minimum_tardiness_sequencing",
412 instance: Box::new(MinimumTardinessSequencing::<One>::new(
413 4,
414 vec![2, 3, 1, 4],
415 vec![(0, 2)],
416 )),
417 optimal_config: serde_json::json!(vec![0, 1, 2, 3]),
418 optimal_value: serde_json::json!(1),
419 },
420 crate::example_db::specs::ModelExampleSpec {
422 id: "minimum_tardiness_sequencing_weighted",
423 instance: Box::new(MinimumTardinessSequencing::<i64>::with_lengths(
428 vec![3, 2, 2, 1, 2],
429 vec![4, 3, 8, 3, 6],
430 vec![(0, 2), (1, 3)],
431 )),
432 optimal_config: serde_json::json!(vec![0, 4, 2, 1, 3]),
433 optimal_value: serde_json::json!(2),
434 },
435 ]
436}
437
438#[cfg(test)]
439#[path = "../../unit_tests/models/misc/minimum_tardiness_sequencing.rs"]
440mod tests;