problemreductions/models/misc/
precedence_constrained_scheduling.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "PrecedenceConstrainedScheduling",
14 display_name: "Precedence Constrained Scheduling",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Schedule unit-length tasks on m processors by deadline D respecting precedence constraints",
20 fields: PrecedenceConstrainedSchedulingCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PrecedenceConstrainedScheduling {
51 num_tasks: usize,
52 num_processors: usize,
53 deadline: i64,
54 precedences: Vec<(usize, usize)>,
55}
56
57#[derive(Debug, Deserialize, crate::CreateSpec)]
58struct PrecedenceConstrainedSchedulingCreateSpec {
59 num_tasks: usize,
60 num_processors: usize,
61 deadline: i64,
62 precedences: Option<Vec<(usize, usize)>>,
63}
64
65impl TryFrom<PrecedenceConstrainedSchedulingCreateSpec> for PrecedenceConstrainedScheduling {
66 type Error = crate::registry::ConstructionError;
67
68 fn try_from(spec: PrecedenceConstrainedSchedulingCreateSpec) -> Result<Self, Self::Error> {
69 if spec.num_tasks > 0 && spec.num_processors == 0 {
70 return Err("num_processors must be positive when there are tasks"
71 .to_string()
72 .into());
73 }
74 if spec.num_tasks > 0 && spec.deadline == 0 {
75 return Err("deadline must be positive when there are tasks"
76 .to_string()
77 .into());
78 }
79 if spec.deadline < 0 || usize::try_from(spec.deadline).is_err() {
80 return Err("deadline must be nonnegative and fit usize"
81 .to_string()
82 .into());
83 }
84 let precedences = spec.precedences.unwrap_or_default();
85 if let Some(&(pred, succ)) = precedences
86 .iter()
87 .find(|&&(pred, succ)| pred >= spec.num_tasks || succ >= spec.num_tasks)
88 {
89 return Err(format!(
90 "precedence ({pred}, {succ}) is out of range for {} tasks",
91 spec.num_tasks
92 )
93 .into());
94 }
95 Ok(Self::new(
96 spec.num_tasks,
97 spec.num_processors,
98 spec.deadline,
99 precedences,
100 ))
101 }
102}
103
104impl PrecedenceConstrainedScheduling {
105 pub fn new(
112 num_tasks: usize,
113 num_processors: usize,
114 deadline: i64,
115 precedences: Vec<(usize, usize)>,
116 ) -> Self {
117 if num_tasks > 0 {
118 assert!(
119 num_processors > 0,
120 "num_processors must be > 0 when there are tasks"
121 );
122 assert!(deadline > 0, "deadline must be > 0 when there are tasks");
123 }
124 assert!(
125 deadline >= 0 && usize::try_from(deadline).is_ok(),
126 "deadline must be nonnegative and fit usize"
127 );
128 for &(i, j) in &precedences {
129 assert!(
130 i < num_tasks && j < num_tasks,
131 "Precedence ({}, {}) out of bounds for {} tasks",
132 i,
133 j,
134 num_tasks
135 );
136 }
137 Self {
138 num_tasks,
139 num_processors,
140 deadline,
141 precedences,
142 }
143 }
144
145 pub fn num_tasks(&self) -> usize {
147 self.num_tasks
148 }
149
150 pub fn num_processors(&self) -> usize {
152 self.num_processors
153 }
154
155 pub fn deadline(&self) -> i64 {
157 self.deadline
158 }
159
160 pub fn precedences(&self) -> &[(usize, usize)] {
162 &self.precedences
163 }
164
165 pub fn num_precedences(&self) -> usize {
167 self.precedences.len()
168 }
169}
170
171impl Problem for PrecedenceConstrainedScheduling {
172 const NAME: &'static str = "PrecedenceConstrainedScheduling";
173 type Solution = Vec<usize>;
174 type Value = crate::types::Or;
175
176 crate::problem_parameters![
177 ("deadline", deadline),
178 ("num_precedences", num_precedences),
179 ("num_tasks", num_tasks),
180 ];
181
182 fn variant() -> Vec<(&'static str, &'static str)> {
183 crate::variant_params![]
184 }
185
186 fn evaluate(
187 &self,
188 config: &Self::Solution,
189 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
190 Ok({
191 crate::types::Or({
192 if config.len() != self.num_tasks {
193 return Err(crate::traits::EvaluationError::InvalidConfiguration(
194 "schedule length does not match the tasks".into(),
195 ));
196 }
197 let deadline =
198 usize::try_from(self.deadline).expect("validated deadline must fit usize");
199 if config.iter().any(|&v| v >= deadline) {
200 return Err(crate::traits::EvaluationError::InvalidConfiguration(
201 "schedule contains an out-of-range time slot".into(),
202 ));
203 }
204 let mut slot_count = vec![0usize; deadline];
206 for &slot in config {
207 slot_count[slot] += 1;
208 if slot_count[slot] > self.num_processors {
209 return Ok(crate::types::Or(false));
210 }
211 }
212 for &(i, j) in &self.precedences {
214 if config[j] < config[i] + 1 {
215 return Ok(crate::types::Or(false));
216 }
217 }
218 true
219 })
220 })
221 }
222}
223
224impl crate::solvers::BruteForceProblem for PrecedenceConstrainedScheduling {
225 fn dimensions(&self) -> Vec<usize> {
226 vec![
227 usize::try_from(self.deadline).expect("validated deadline must fit usize");
228 self.num_tasks
229 ]
230 }
231}
232
233crate::declare_variants! {
234 default PrecedenceConstrainedScheduling => "2^num_tasks" create PrecedenceConstrainedSchedulingCreateSpec,
235}
236
237crate::register_brute_force! {
238 PrecedenceConstrainedScheduling,
239}
240
241#[cfg(feature = "example-db")]
242pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
243 vec![crate::example_db::specs::ModelExampleSpec {
244 id: "precedence_constrained_scheduling",
245 instance: Box::new(PrecedenceConstrainedScheduling::new(
247 8,
248 3,
249 4,
250 vec![
251 (0, 2),
252 (0, 3),
253 (1, 3),
254 (1, 4),
255 (2, 5),
256 (3, 6),
257 (4, 6),
258 (5, 7),
259 (6, 7),
260 ],
261 )),
262 optimal_config: serde_json::json!(vec![0, 0, 1, 1, 1, 2, 2, 3]),
264 optimal_value: serde_json::json!(true),
265 }]
266}
267
268#[cfg(test)]
269#[path = "../../unit_tests/models/misc/precedence_constrained_scheduling.rs"]
270mod tests;