Skip to main content

problemreductions/models/misc/
sequencing_to_minimize_tardy_task_weight.rs

1//! Sequencing to Minimize Tardy Task Weight problem implementation.
2//!
3//! A classical NP-hard single-machine scheduling problem (SS8 from
4//! Garey & Johnson, 1979) where tasks with processing times, weights,
5//! and deadlines must be scheduled to minimize the total weight of tardy tasks.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "SequencingToMinimizeTardyTaskWeight",
15        display_name: "Sequencing to Minimize Tardy Task Weight",
16        aliases: &[],
17        dimensions: &[],
18        module_path: module_path!(),
19        description: "Schedule tasks with lengths, weights, and deadlines to minimize total weight of tardy tasks",
20        fields: &[
21            FieldInfo { name: "lengths", type_name: "Vec<u64>", description: "Processing time for each task" },
22            FieldInfo { name: "weights", type_name: "Vec<u64>", description: "Weight w(t) for each task" },
23            FieldInfo { name: "deadlines", type_name: "Vec<u64>", description: "Deadline d(t) for each task" },
24        ],
25    }
26}
27
28/// Sequencing to Minimize Tardy Task Weight problem.
29///
30/// Given tasks with processing times `l(t)`, weights `w(t)`, and deadlines
31/// `d(t)`, find a single-machine schedule that minimizes `sum_{t tardy} w(t)`,
32/// where task `t` is tardy if its completion time `C(t) > d(t)`.
33///
34/// This is the weighted generalization of minimizing the number of tardy tasks
35/// (problem SS8 in Garey & Johnson, 1979, written $1 || sum w_j U_j$).
36///
37/// Configurations are direct permutation encodings with `dims() = [n; n]`:
38/// each position holds the index of the task scheduled at that position.
39/// A configuration is valid iff it is a permutation of `0..n`.
40#[derive(Debug, Clone, Serialize)]
41pub struct SequencingToMinimizeTardyTaskWeight {
42    lengths: Vec<u64>,
43    weights: Vec<u64>,
44    deadlines: Vec<u64>,
45}
46
47#[derive(Deserialize)]
48struct SequencingToMinimizeTardyTaskWeightSerde {
49    lengths: Vec<u64>,
50    weights: Vec<u64>,
51    deadlines: Vec<u64>,
52}
53
54impl SequencingToMinimizeTardyTaskWeight {
55    fn validate(lengths: &[u64], weights: &[u64], deadlines: &[u64]) -> Result<(), String> {
56        if lengths.len() != weights.len() {
57            return Err("lengths length must equal weights length".to_string());
58        }
59        if lengths.len() != deadlines.len() {
60            return Err("lengths length must equal deadlines length".to_string());
61        }
62        if lengths.contains(&0) {
63            return Err("task lengths must be positive".to_string());
64        }
65        if weights.contains(&0) {
66            return Err("task weights must be positive".to_string());
67        }
68        Ok(())
69    }
70
71    /// Create a new sequencing instance.
72    ///
73    /// # Panics
74    ///
75    /// Panics if `lengths`, `weights`, and `deadlines` are not all the same
76    /// length, or if any length or weight is zero.
77    pub fn new(lengths: Vec<u64>, weights: Vec<u64>, deadlines: Vec<u64>) -> Self {
78        Self::validate(&lengths, &weights, &deadlines).unwrap_or_else(|err| panic!("{err}"));
79        Self {
80            lengths,
81            weights,
82            deadlines,
83        }
84    }
85
86    /// Returns the number of tasks.
87    pub fn num_tasks(&self) -> usize {
88        self.lengths.len()
89    }
90
91    /// Returns the processing times.
92    pub fn lengths(&self) -> &[u64] {
93        &self.lengths
94    }
95
96    /// Returns the task weights.
97    pub fn weights(&self) -> &[u64] {
98        &self.weights
99    }
100
101    /// Returns the task deadlines.
102    pub fn deadlines(&self) -> &[u64] {
103        &self.deadlines
104    }
105
106    fn tardy_task_weight(&self, schedule: &[usize]) -> Min<u64> {
107        let mut elapsed: u64 = 0;
108        let mut total: u64 = 0;
109        for &task in schedule {
110            elapsed = elapsed
111                .checked_add(self.lengths[task])
112                .expect("total processing time overflowed u64");
113            if elapsed > self.deadlines[task] {
114                total = total
115                    .checked_add(self.weights[task])
116                    .expect("tardy task weight overflowed u64");
117            }
118        }
119        Min(Some(total))
120    }
121}
122
123impl TryFrom<SequencingToMinimizeTardyTaskWeightSerde> for SequencingToMinimizeTardyTaskWeight {
124    type Error = String;
125
126    fn try_from(value: SequencingToMinimizeTardyTaskWeightSerde) -> Result<Self, Self::Error> {
127        Self::validate(&value.lengths, &value.weights, &value.deadlines)?;
128        Ok(Self {
129            lengths: value.lengths,
130            weights: value.weights,
131            deadlines: value.deadlines,
132        })
133    }
134}
135
136impl<'de> Deserialize<'de> for SequencingToMinimizeTardyTaskWeight {
137    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138    where
139        D: serde::Deserializer<'de>,
140    {
141        let value = SequencingToMinimizeTardyTaskWeightSerde::deserialize(deserializer)?;
142        Self::try_from(value).map_err(serde::de::Error::custom)
143    }
144}
145
146impl Problem for SequencingToMinimizeTardyTaskWeight {
147    const NAME: &'static str = "SequencingToMinimizeTardyTaskWeight";
148    type Value = Min<u64>;
149
150    fn variant() -> Vec<(&'static str, &'static str)> {
151        crate::variant_params![]
152    }
153
154    fn dims(&self) -> Vec<usize> {
155        let n = self.num_tasks();
156        vec![n; n]
157    }
158
159    fn evaluate(&self, config: &[usize]) -> Min<u64> {
160        let n = self.num_tasks();
161        let Some(schedule) = super::decode_permutation(config, n) else {
162            return Min(None);
163        };
164        self.tardy_task_weight(&schedule)
165    }
166}
167
168crate::declare_variants! {
169    default SequencingToMinimizeTardyTaskWeight => "factorial(num_tasks)",
170}
171
172#[cfg(feature = "example-db")]
173pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
174    vec![crate::example_db::specs::ModelExampleSpec {
175        id: "sequencing_to_minimize_tardy_task_weight",
176        // 5 tasks, lengths [3,2,4,1,2], weights [5,3,7,2,4], deadlines [6,4,10,2,8]
177        // Optimal schedule: [t4,t1,t5,t3,t2] = config [3,0,4,2,1]
178        // Start times: t4 starts 0, completes 1 (tardy: C=1 <= d=2, ok)
179        // t1 starts 1, completes 4 (tardy: C=4 <= d=6, ok)
180        // t5 starts 4, completes 6 (tardy: C=6 <= d=8, ok)
181        // t3 starts 6, completes 10 (tardy: C=10 <= d=10, ok)
182        // t2 starts 10, completes 12 (tardy: C=12 > d=4, tardy weight 3)
183        // Total tardy weight = 3
184        instance: Box::new(SequencingToMinimizeTardyTaskWeight::new(
185            vec![3, 2, 4, 1, 2],
186            vec![5, 3, 7, 2, 4],
187            vec![6, 4, 10, 2, 8],
188        )),
189        optimal_config: vec![3, 0, 4, 2, 1],
190        optimal_value: serde_json::json!(3),
191    }]
192}
193
194#[cfg(test)]
195#[path = "../../unit_tests/models/misc/sequencing_to_minimize_tardy_task_weight.rs"]
196mod tests;