Skip to main content

problemreductions/models/misc/
sequencing_with_deadlines_and_set_up_times.rs

1//! Sequencing with Deadlines and Set-Up Times problem implementation.
2//!
3//! A classical NP-hard single-machine scheduling feasibility problem (SS14
4//! from Garey & Johnson, 1979) where tasks use one of several compilers and
5//! a setup time is charged whenever consecutive tasks switch compilers.
6//! The question is whether all deadlines can be met.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Or;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "SequencingWithDeadlinesAndSetUpTimes",
16        display_name: "Sequencing with Deadlines and Set-Up Times",
17        aliases: &[],
18        dimensions: &[],
19        module_path: module_path!(),
20        description: "Determine whether all tasks can be scheduled on a single machine by their deadlines given compiler-switch setup penalties",
21        fields: &[
22            FieldInfo { name: "lengths", type_name: "Vec<u64>", description: "Processing time for each task" },
23            FieldInfo { name: "deadlines", type_name: "Vec<u64>", description: "Deadline d(t) for each task" },
24            FieldInfo { name: "compilers", type_name: "Vec<usize>", description: "Compiler index k(t) for each task" },
25            FieldInfo { name: "setup_times", type_name: "Vec<u64>", description: "Setup time s(c) charged when switching to compiler c" },
26        ],
27    }
28}
29
30/// Sequencing with Deadlines and Set-Up Times problem.
31///
32/// Given tasks with processing times `l(t)`, deadlines `d(t)`, compiler
33/// assignments `k(t)`, and per-compiler setup times `s(c)`, find a
34/// single-machine schedule in which all tasks meet their deadlines, where a
35/// setup penalty `s(k(t'))` is added before any task `t` that uses a
36/// different compiler than the immediately preceding task `t'`.
37///
38/// This is problem SS14 in Garey & Johnson (1979), written
39/// $1 | s_{ij} | \text{feasibility}$.
40///
41/// Configurations are direct permutation encodings with `dims() = [n; n]`:
42/// each position holds the index of the task scheduled at that position.
43/// A configuration is valid iff it is a permutation of `0..n`.
44#[derive(Debug, Clone, Serialize)]
45pub struct SequencingWithDeadlinesAndSetUpTimes {
46    lengths: Vec<u64>,
47    deadlines: Vec<u64>,
48    compilers: Vec<usize>,
49    setup_times: Vec<u64>,
50}
51
52#[derive(Deserialize)]
53struct SequencingWithDeadlinesAndSetUpTimesSerde {
54    lengths: Vec<u64>,
55    deadlines: Vec<u64>,
56    compilers: Vec<usize>,
57    setup_times: Vec<u64>,
58}
59
60impl SequencingWithDeadlinesAndSetUpTimes {
61    fn validate(
62        lengths: &[u64],
63        deadlines: &[u64],
64        compilers: &[usize],
65        setup_times: &[u64],
66    ) -> Result<(), String> {
67        if lengths.len() != deadlines.len() {
68            return Err("lengths length must equal deadlines length".to_string());
69        }
70        if lengths.len() != compilers.len() {
71            return Err("lengths length must equal compilers length".to_string());
72        }
73        if lengths.contains(&0) {
74            return Err("task lengths must be positive".to_string());
75        }
76        let num_compilers = setup_times.len();
77        for &c in compilers {
78            if c >= num_compilers {
79                return Err(format!(
80                    "compiler index {c} is out of range for setup_times of length {num_compilers}"
81                ));
82            }
83        }
84        Ok(())
85    }
86
87    /// Create a new sequencing instance.
88    ///
89    /// # Panics
90    ///
91    /// Panics if the input vectors are inconsistent or contain invalid values.
92    pub fn new(
93        lengths: Vec<u64>,
94        deadlines: Vec<u64>,
95        compilers: Vec<usize>,
96        setup_times: Vec<u64>,
97    ) -> Self {
98        Self::validate(&lengths, &deadlines, &compilers, &setup_times)
99            .unwrap_or_else(|err| panic!("{err}"));
100        Self {
101            lengths,
102            deadlines,
103            compilers,
104            setup_times,
105        }
106    }
107
108    /// Returns the number of tasks.
109    pub fn num_tasks(&self) -> usize {
110        self.lengths.len()
111    }
112
113    /// Returns the number of distinct compilers (= `setup_times.len()`).
114    pub fn num_compilers(&self) -> usize {
115        self.setup_times.len()
116    }
117
118    /// Returns the processing times.
119    pub fn lengths(&self) -> &[u64] {
120        &self.lengths
121    }
122
123    /// Returns the task deadlines.
124    pub fn deadlines(&self) -> &[u64] {
125        &self.deadlines
126    }
127
128    /// Returns the compiler index for each task.
129    pub fn compilers(&self) -> &[usize] {
130        &self.compilers
131    }
132
133    /// Returns the per-compiler setup times.
134    pub fn setup_times(&self) -> &[u64] {
135        &self.setup_times
136    }
137
138    /// Check whether a schedule meets all deadlines.
139    ///
140    /// Returns `true` iff every task in the schedule completes by its deadline.
141    fn all_deadlines_met(&self, schedule: &[usize]) -> bool {
142        let mut elapsed: u64 = 0;
143        let mut prev_compiler: Option<usize> = None;
144        for &task in schedule {
145            // Add setup time if the compiler switches.
146            if let Some(prev) = prev_compiler {
147                if prev != self.compilers[task] {
148                    elapsed = elapsed
149                        .checked_add(self.setup_times[self.compilers[task]])
150                        .expect("elapsed time overflowed u64");
151                }
152            }
153            elapsed = elapsed
154                .checked_add(self.lengths[task])
155                .expect("elapsed time overflowed u64");
156            if elapsed > self.deadlines[task] {
157                return false;
158            }
159            prev_compiler = Some(self.compilers[task]);
160        }
161        true
162    }
163}
164
165impl TryFrom<SequencingWithDeadlinesAndSetUpTimesSerde> for SequencingWithDeadlinesAndSetUpTimes {
166    type Error = String;
167
168    fn try_from(value: SequencingWithDeadlinesAndSetUpTimesSerde) -> Result<Self, Self::Error> {
169        Self::validate(
170            &value.lengths,
171            &value.deadlines,
172            &value.compilers,
173            &value.setup_times,
174        )?;
175        Ok(Self {
176            lengths: value.lengths,
177            deadlines: value.deadlines,
178            compilers: value.compilers,
179            setup_times: value.setup_times,
180        })
181    }
182}
183
184impl<'de> Deserialize<'de> for SequencingWithDeadlinesAndSetUpTimes {
185    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
186    where
187        D: serde::Deserializer<'de>,
188    {
189        let value = SequencingWithDeadlinesAndSetUpTimesSerde::deserialize(deserializer)?;
190        Self::try_from(value).map_err(serde::de::Error::custom)
191    }
192}
193
194impl Problem for SequencingWithDeadlinesAndSetUpTimes {
195    const NAME: &'static str = "SequencingWithDeadlinesAndSetUpTimes";
196    type Value = Or;
197
198    fn variant() -> Vec<(&'static str, &'static str)> {
199        crate::variant_params![]
200    }
201
202    fn dims(&self) -> Vec<usize> {
203        let n = self.num_tasks();
204        vec![n; n]
205    }
206
207    fn evaluate(&self, config: &[usize]) -> Or {
208        let n = self.num_tasks();
209        let Some(schedule) = super::decode_permutation(config, n) else {
210            return Or(false);
211        };
212        Or(self.all_deadlines_met(&schedule))
213    }
214}
215
216crate::declare_variants! {
217    default SequencingWithDeadlinesAndSetUpTimes => "factorial(num_tasks)",
218}
219
220#[cfg(feature = "example-db")]
221pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
222    vec![crate::example_db::specs::ModelExampleSpec {
223        id: "sequencing_with_deadlines_and_set_up_times",
224        // 5 tasks, lengths [2,3,1,2,2], deadlines [4,11,3,16,7], compilers [0,1,0,1,0],
225        // setup_times [1,2].
226        // Optimal config: [2,0,4,1,3] (tasks t3,t1,t5,t2,t4 in 1-indexed)
227        // Position 0: task 2 (compiler 0), no prev  → elapsed = 0+1 = 1  ≤ d[2]=3 ✓
228        // Position 1: task 0 (compiler 0), same     → elapsed = 1+2 = 3  ≤ d[0]=4 ✓
229        // Position 2: task 4 (compiler 0), same     → elapsed = 3+2 = 5  ≤ d[4]=7 ✓
230        // Position 3: task 1 (compiler 1), switch+s[1]=2 → elapsed = 5+2+3 = 10 ≤ d[1]=11 ✓
231        // Position 4: task 3 (compiler 1), same     → elapsed = 10+2 = 12 ≤ d[3]=16 ✓
232        instance: Box::new(SequencingWithDeadlinesAndSetUpTimes::new(
233            vec![2, 3, 1, 2, 2],
234            vec![4, 11, 3, 16, 7],
235            vec![0, 1, 0, 1, 0],
236            vec![1, 2],
237        )),
238        optimal_config: vec![2, 0, 4, 1, 3],
239        optimal_value: serde_json::json!(true),
240    }]
241}
242
243#[cfg(test)]
244#[path = "../../unit_tests/models/misc/sequencing_with_deadlines_and_set_up_times.rs"]
245mod tests;