problemreductions/models/misc/
preemptive_scheduling.rs1use crate::registry::{ConstructionError, CreateSpec, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "PreemptiveScheduling",
16 display_name: "Preemptive Scheduling",
17 aliases: &[],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Misc,
20 module_path: module_path!(),
21 description: "Minimize makespan for preemptive parallel-processor scheduling with precedence constraints",
22 fields: PreemptiveSchedulingCreateSpec::FIELDS,
23 }
24}
25
26#[derive(Debug, Clone, Serialize)]
62pub struct PreemptiveScheduling {
63 lengths: Vec<i64>,
65 num_processors: usize,
67 precedences: Vec<(usize, usize)>,
69}
70
71#[derive(Debug, Deserialize, crate::CreateSpec)]
72struct PreemptiveSchedulingCreateSpec {
73 lengths: Vec<i64>,
74 num_processors: usize,
75 precedences: Option<Vec<(usize, usize)>>,
76}
77
78impl TryFrom<PreemptiveSchedulingCreateSpec> for PreemptiveScheduling {
79 type Error = ConstructionError;
80
81 fn try_from(spec: PreemptiveSchedulingCreateSpec) -> Result<Self, Self::Error> {
82 let precedences = spec.precedences.unwrap_or_default();
83 Self::new(spec.lengths, spec.num_processors, precedences)
84 }
85}
86
87#[derive(Deserialize)]
88struct PreemptiveSchedulingSerde {
89 lengths: Vec<i64>,
90 num_processors: usize,
91 precedences: Vec<(usize, usize)>,
92}
93
94impl PreemptiveScheduling {
95 fn validate(
96 lengths: &[i64],
97 num_processors: usize,
98 precedences: &[(usize, usize)],
99 ) -> Result<(), ConstructionError> {
100 if lengths.iter().any(|&length| length <= 0) {
101 return Err(ConstructionError::Conversion(
102 "task lengths must be positive".into(),
103 ));
104 }
105 if num_processors == 0 {
106 return Err(ConstructionError::Conversion(
107 "num_processors must be positive".into(),
108 ));
109 }
110 let n = lengths.len();
111 let total_length = lengths
112 .iter()
113 .try_fold(0_i64, |total, &length| total.checked_add(length))
114 .ok_or_else(|| ConstructionError::IntegerOverflow("summing task lengths".into()))?;
115 let horizon = usize::try_from(total_length).map_err(|_| {
116 ConstructionError::IntegerOverflow("task horizon does not fit usize".into())
117 })?;
118 n.checked_mul(horizon).ok_or_else(|| {
119 ConstructionError::IntegerOverflow("configuration size does not fit usize".into())
120 })?;
121 for &(pred, succ) in precedences {
122 if pred >= n || succ >= n {
123 return Err(ConstructionError::Conversion(format!(
124 "precedence index out of range: ({pred}, {succ}) but num_tasks = {n}"
125 )));
126 }
127 }
128 Ok(())
129 }
130
131 pub fn new(
139 lengths: Vec<i64>,
140 num_processors: usize,
141 precedences: Vec<(usize, usize)>,
142 ) -> Result<Self, ConstructionError> {
143 Self::validate(&lengths, num_processors, &precedences)?;
144 Ok(Self {
145 lengths,
146 num_processors,
147 precedences,
148 })
149 }
150
151 pub fn num_tasks(&self) -> usize {
153 self.lengths.len()
154 }
155
156 pub fn num_processors(&self) -> usize {
158 self.num_processors
159 }
160
161 pub fn num_precedences(&self) -> usize {
163 self.precedences.len()
164 }
165
166 pub fn lengths(&self) -> &[i64] {
168 &self.lengths
169 }
170
171 pub fn precedences(&self) -> &[(usize, usize)] {
173 &self.precedences
174 }
175
176 pub fn d_max(&self) -> usize {
178 let total = self
179 .lengths
180 .iter()
181 .try_fold(0_i64, |total, &length| total.checked_add(length))
182 .expect("construction validates the task horizon");
183 usize::try_from(total).expect("validated task horizon fits usize")
184 }
185}
186
187impl TryFrom<PreemptiveSchedulingSerde> for PreemptiveScheduling {
188 type Error = ConstructionError;
189
190 fn try_from(value: PreemptiveSchedulingSerde) -> Result<Self, Self::Error> {
191 Self::new(value.lengths, value.num_processors, value.precedences)
192 }
193}
194
195impl<'de> Deserialize<'de> for PreemptiveScheduling {
196 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197 where
198 D: serde::Deserializer<'de>,
199 {
200 let value = PreemptiveSchedulingSerde::deserialize(deserializer)?;
201 Self::try_from(value).map_err(serde::de::Error::custom)
202 }
203}
204
205impl Problem for PreemptiveScheduling {
206 const NAME: &'static str = "PreemptiveScheduling";
207 type Solution = Vec<Vec<bool>>;
208 type Value = Min<i64>;
209
210 crate::problem_parameters![
211 ("d_max", d_max),
212 ("num_precedences", num_precedences),
213 ("num_processors", num_processors),
214 ("num_tasks", num_tasks),
215 ];
216
217 fn variant() -> Vec<(&'static str, &'static str)> {
218 crate::variant_params![]
219 }
220
221 fn evaluate(
222 &self,
223 solution: &Self::Solution,
224 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
225 let n = self.num_tasks();
226 let d = self.d_max();
227 if solution.len() != n || solution.iter().any(|task| task.len() != d) {
228 return Err(crate::traits::EvaluationError::InvalidConfiguration(
229 "preemptive schedule dimensions do not match the instance".into(),
230 ));
231 }
232 Ok({
233 for (task, &length) in solution.iter().zip(&self.lengths) {
235 let active = task.iter().filter(|&&active| active).count();
236 if i64::try_from(active).expect("active slots fit the validated horizon") != length
237 {
238 return Ok(Min(None));
239 }
240 }
241
242 for u in 0..d {
244 let active_count = solution.iter().filter(|task| task[u]).count();
245 if active_count > self.num_processors {
246 return Ok(Min(None));
247 }
248 }
249
250 for &(pred, succ) in &self.precedences {
253 let last_pred = (0..d).rev().find(|&u| solution[pred][u]);
254 let first_succ = (0..d).find(|&u| solution[succ][u]);
255 if let (Some(lp), Some(fs)) = (last_pred, first_succ) {
256 if lp >= fs {
257 return Ok(Min(None));
258 }
259 }
260 }
261
262 let makespan = solution
264 .iter()
265 .filter_map(|task| (0..d).rev().find(|&u| task[u]))
266 .map(|last| last + 1)
267 .max()
268 .unwrap_or(0);
269
270 Min(Some(
271 i64::try_from(makespan).expect("makespan fits the validated horizon"),
272 ))
273 })
274 }
275}
276
277impl crate::solvers::BruteForceProblem for PreemptiveScheduling {
278 fn dimensions(&self) -> Vec<usize> {
279 let d = self.d_max();
280 vec![2; self.num_tasks() * d]
281 }
282}
283
284crate::declare_variants! {
285 default PreemptiveScheduling => "2^(num_tasks * num_tasks)" create PreemptiveSchedulingCreateSpec,
286}
287
288crate::register_brute_force! {
289 PreemptiveScheduling decode |problem: &PreemptiveScheduling, indices: Vec<usize>| if problem.d_max() == 0 { vec![Vec::new(); problem.num_tasks()] } else { indices.chunks(problem.d_max()).map(crate::config::config_to_bits).collect() },
290}
291
292#[cfg(feature = "example-db")]
293pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
294 let mut config = vec![vec![false; 9]; 5];
309 config[0][0] = true;
310 config[0][1] = true;
311 config[1][0] = true;
312 config[2][2] = true;
313 config[2][3] = true;
314 config[2][4] = true;
315 config[3][2] = true;
316 config[3][3] = true;
317 config[4][1] = true;
318 vec![crate::example_db::specs::ModelExampleSpec {
319 id: "preemptive_scheduling",
320 instance: Box::new(
321 PreemptiveScheduling::new(vec![2, 1, 3, 2, 1], 2, vec![(0, 2), (1, 3)]).unwrap(),
322 ),
323 optimal_config: serde_json::json!(config),
324 optimal_value: serde_json::json!(5),
325 }]
326}
327
328#[cfg(test)]
329#[path = "../../unit_tests/models/misc/preemptive_scheduling.rs"]
330mod tests;