Skip to main content

problemreductions/models/misc/
sequencing_to_minimize_weighted_tardiness.rs

1//! Sequencing to Minimize Weighted Tardiness problem implementation.
2//!
3//! A classical NP-complete single-machine scheduling problem (SS5 from
4//! Garey & Johnson, 1979) asking whether there exists a job order whose
5//! total weighted tardiness is at most a given bound.
6//! Corresponds to scheduling notation `1 || sum w_j T_j`.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "SequencingToMinimizeWeightedTardiness",
15        display_name: "Sequencing to Minimize Weighted Tardiness",
16        aliases: &[],
17        dimensions: &[],
18        module_path: module_path!(),
19        description: "Schedule jobs on one machine so total weighted tardiness is at most K",
20        fields: &[
21            FieldInfo { name: "lengths", type_name: "Vec<u64>", description: "Processing times l_j for each job" },
22            FieldInfo { name: "weights", type_name: "Vec<u64>", description: "Tardiness weights w_j for each job" },
23            FieldInfo { name: "deadlines", type_name: "Vec<u64>", description: "Deadlines d_j for each job" },
24            FieldInfo { name: "bound", type_name: "u64", description: "Upper bound K on total weighted tardiness" },
25        ],
26    }
27}
28
29/// Sequencing to Minimize Weighted Tardiness.
30///
31/// Given jobs with processing times `l_j`, weights `w_j`, deadlines `d_j`,
32/// and a bound `K`, determine whether there exists a permutation schedule on a
33/// single machine whose total weighted tardiness
34/// `sum_j w_j * max(0, C_j - d_j)` is at most `K`, where `C_j` is the
35/// completion time of job `j`.
36///
37/// # Representation
38///
39/// Configurations use Lehmer code to encode permutations of the jobs.
40/// Decoding yields the job order processed by the single machine.
41///
42/// # Example
43///
44/// ```
45/// use problemreductions::models::misc::SequencingToMinimizeWeightedTardiness;
46/// use problemreductions::{BruteForce, Problem, Solver};
47///
48/// let problem = SequencingToMinimizeWeightedTardiness::new(
49///     vec![3, 4, 2, 5, 3],
50///     vec![2, 3, 1, 4, 2],
51///     vec![5, 8, 4, 15, 10],
52///     13,
53/// );
54///
55/// let solver = BruteForce::new();
56/// assert!(solver.find_witness(&problem).is_some());
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SequencingToMinimizeWeightedTardiness {
60    lengths: Vec<u64>,
61    weights: Vec<u64>,
62    deadlines: Vec<u64>,
63    bound: u64,
64}
65
66impl SequencingToMinimizeWeightedTardiness {
67    /// Create a new weighted tardiness scheduling instance.
68    ///
69    /// # Panics
70    ///
71    /// Panics if the input vectors do not have the same length.
72    pub fn new(lengths: Vec<u64>, weights: Vec<u64>, deadlines: Vec<u64>, bound: u64) -> Self {
73        assert_eq!(
74            lengths.len(),
75            weights.len(),
76            "weights length must equal lengths length"
77        );
78        assert_eq!(
79            lengths.len(),
80            deadlines.len(),
81            "deadlines length must equal lengths length"
82        );
83        Self {
84            lengths,
85            weights,
86            deadlines,
87            bound,
88        }
89    }
90
91    /// Returns the job lengths.
92    pub fn lengths(&self) -> &[u64] {
93        &self.lengths
94    }
95
96    /// Returns the tardiness weights.
97    pub fn weights(&self) -> &[u64] {
98        &self.weights
99    }
100
101    /// Returns the deadlines.
102    pub fn deadlines(&self) -> &[u64] {
103        &self.deadlines
104    }
105
106    /// Returns the weighted tardiness bound.
107    pub fn bound(&self) -> u64 {
108        self.bound
109    }
110
111    /// Returns the number of jobs.
112    pub fn num_tasks(&self) -> usize {
113        self.lengths.len()
114    }
115
116    fn decode_schedule(&self, config: &[usize]) -> Option<Vec<usize>> {
117        super::decode_lehmer(config, self.num_tasks())
118    }
119
120    fn schedule_weighted_tardiness(&self, schedule: &[usize]) -> Option<u64> {
121        let mut completion_time = 0u128;
122        let mut total = 0u128;
123        for &job in schedule {
124            completion_time += u128::from(self.lengths[job]);
125            let tardiness = completion_time.saturating_sub(u128::from(self.deadlines[job]));
126            total += tardiness * u128::from(self.weights[job]);
127        }
128        u64::try_from(total).ok()
129    }
130
131    /// Compute the total weighted tardiness of a Lehmer-encoded schedule.
132    ///
133    /// Returns `None` if the configuration is not a valid Lehmer code or if
134    /// the accumulated objective does not fit in `u64`.
135    pub fn total_weighted_tardiness(&self, config: &[usize]) -> Option<u64> {
136        let schedule = self.decode_schedule(config)?;
137        self.schedule_weighted_tardiness(&schedule)
138    }
139}
140
141impl Problem for SequencingToMinimizeWeightedTardiness {
142    const NAME: &'static str = "SequencingToMinimizeWeightedTardiness";
143    type Value = crate::types::Or;
144
145    fn variant() -> Vec<(&'static str, &'static str)> {
146        crate::variant_params![]
147    }
148
149    fn dims(&self) -> Vec<usize> {
150        super::lehmer_dims(self.num_tasks())
151    }
152
153    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
154        crate::types::Or({
155            self.total_weighted_tardiness(config)
156                .is_some_and(|total| total <= self.bound)
157        })
158    }
159}
160
161crate::declare_variants! {
162    default SequencingToMinimizeWeightedTardiness => "factorial(num_tasks)",
163}
164
165#[cfg(feature = "example-db")]
166pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
167    vec![crate::example_db::specs::ModelExampleSpec {
168        id: "sequencing_to_minimize_weighted_tardiness",
169        instance: Box::new(SequencingToMinimizeWeightedTardiness::new(
170            vec![3, 4, 2, 5, 3],
171            vec![2, 3, 1, 4, 2],
172            vec![5, 8, 4, 15, 10],
173            13,
174        )),
175        optimal_config: vec![0, 0, 2, 1, 0],
176        optimal_value: serde_json::json!(true),
177    }]
178}
179
180#[cfg(test)]
181#[path = "../../unit_tests/models/misc/sequencing_to_minimize_weighted_tardiness.rs"]
182mod tests;