problemreductions/models/misc/
resource_constrained_scheduling.rs1use crate::registry::{ConstructionError, FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "ResourceConstrainedScheduling",
14 display_name: "Resource 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 with resource constraints and a deadline",
20 fields: &[
21 FieldInfo { name: "num_processors", type_name: "usize", description: "Number of identical processors m" },
22 FieldInfo { name: "resource_bounds", type_name: "Vec<i64>", description: "Resource bound B_i for each resource i" },
23 FieldInfo { name: "resource_requirements", type_name: "Vec<Vec<i64>>", description: "R_i(t) for each task t and resource i (n x r matrix)" },
24 FieldInfo { name: "deadline", type_name: "i64", description: "Overall deadline D" },
25 ],
26 }
27}
28
29#[derive(Debug, Clone, Serialize)]
61pub struct ResourceConstrainedScheduling {
62 num_processors: usize,
64 resource_bounds: Vec<i64>,
66 resource_requirements: Vec<Vec<i64>>,
68 deadline: i64,
70}
71
72impl ResourceConstrainedScheduling {
73 pub fn new(
81 num_processors: usize,
82 resource_bounds: Vec<i64>,
83 resource_requirements: Vec<Vec<i64>>,
84 deadline: i64,
85 ) -> Result<Self, ConstructionError> {
86 if deadline <= 0 {
87 return Err(ConstructionError::Conversion(
88 "deadline must be positive".into(),
89 ));
90 }
91 usize::try_from(deadline).map_err(|_| {
92 ConstructionError::IntegerOverflow("deadline does not fit usize".into())
93 })?;
94 if resource_bounds.iter().any(|&bound| bound < 0) {
95 return Err(ConstructionError::Conversion(
96 "resource bounds must be nonnegative".into(),
97 ));
98 }
99 let r = resource_bounds.len();
100 for (t, row) in resource_requirements.iter().enumerate() {
101 if row.len() != r {
102 return Err(ConstructionError::Conversion(format!(
103 "task {t} has {} resource requirements, expected {r}",
104 row.len()
105 )));
106 }
107 if row.iter().any(|&requirement| requirement < 0) {
108 return Err(ConstructionError::Conversion(format!(
109 "task {t} resource requirements must be nonnegative"
110 )));
111 }
112 }
113 Ok(Self {
114 num_processors,
115 resource_bounds,
116 resource_requirements,
117 deadline,
118 })
119 }
120
121 pub fn max_resource_bits(&self) -> u64 {
123 crate::types::max_numeric_magnitude_bits(
124 self.resource_requirements
125 .iter()
126 .flatten()
127 .copied()
128 .chain(self.resource_bounds.iter().copied()),
129 )
130 }
131
132 pub fn num_tasks(&self) -> usize {
134 self.resource_requirements.len()
135 }
136
137 pub fn num_processors(&self) -> usize {
139 self.num_processors
140 }
141
142 pub fn resource_bounds(&self) -> &[i64] {
144 &self.resource_bounds
145 }
146
147 pub fn resource_requirements(&self) -> &[Vec<i64>] {
149 &self.resource_requirements
150 }
151
152 pub fn deadline(&self) -> i64 {
154 self.deadline
155 }
156
157 pub fn num_resources(&self) -> usize {
159 self.resource_bounds.len()
160 }
161}
162
163impl<'de> Deserialize<'de> for ResourceConstrainedScheduling {
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: serde::Deserializer<'de>,
167 {
168 #[derive(Deserialize)]
169 struct Raw {
170 num_processors: usize,
171 resource_bounds: Vec<i64>,
172 resource_requirements: Vec<Vec<i64>>,
173 deadline: i64,
174 }
175
176 let raw = Raw::deserialize(deserializer)?;
177 Self::new(
178 raw.num_processors,
179 raw.resource_bounds,
180 raw.resource_requirements,
181 raw.deadline,
182 )
183 .map_err(serde::de::Error::custom)
184 }
185}
186
187impl Problem for ResourceConstrainedScheduling {
188 const NAME: &'static str = "ResourceConstrainedScheduling";
189 type Solution = Vec<usize>;
190 type Value = crate::types::Or;
191
192 crate::problem_parameters![
193 ("max_resource_bits", max_resource_bits),
194 ("deadline", deadline),
195 ("num_resources", num_resources),
196 ("num_tasks", num_tasks),
197 ];
198
199 fn variant() -> Vec<(&'static str, &'static str)> {
200 crate::variant_params![]
201 }
202
203 fn evaluate(
204 &self,
205 config: &Self::Solution,
206 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
207 Ok({
208 crate::types::Or({
209 let n = self.num_tasks();
210 let d = self.deadline as usize;
211 let r = self.num_resources();
212
213 if config.len() != n {
215 return Err(crate::traits::EvaluationError::InvalidConfiguration(
216 "schedule length does not match the tasks".into(),
217 ));
218 }
219
220 if config.iter().any(|&start| start >= d) {
221 return Err(crate::traits::EvaluationError::InvalidConfiguration(
222 "schedule contains an out-of-range start time".into(),
223 ));
224 }
225
226 let mut tasks: Vec<_> = (0..n).collect();
229 tasks.sort_unstable_by_key(|&task| (config[task], task));
230 for slot_tasks in tasks.chunk_by(|&a, &b| config[a] == config[b]) {
231 let mut resource_usage = vec![0i64; r];
232 for &task in slot_tasks {
233 for (usage, &req) in resource_usage
234 .iter_mut()
235 .zip(self.resource_requirements[task].iter())
236 {
237 *usage = usage.checked_add(req).ok_or_else(|| {
238 crate::traits::EvaluationError::IntegerOverflow(
239 "summing scheduled resource usage".to_string(),
240 )
241 })?;
242 }
243 }
244
245 if slot_tasks.len() > self.num_processors {
247 return Ok(crate::types::Or(false));
248 }
249
250 for (usage, bound) in resource_usage.iter().zip(self.resource_bounds.iter()) {
252 if usage > bound {
253 return Ok(crate::types::Or(false));
254 }
255 }
256 }
257
258 true
259 })
260 })
261 }
262}
263
264impl crate::solvers::BruteForceProblem for ResourceConstrainedScheduling {
265 fn dimensions(&self) -> Vec<usize> {
266 vec![self.deadline as usize; self.num_tasks()]
267 }
268}
269
270crate::declare_variants! {
271 default ResourceConstrainedScheduling => "deadline ^ num_tasks",
272}
273
274crate::register_brute_force! {
275 ResourceConstrainedScheduling,
276}
277
278#[cfg(feature = "example-db")]
279pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
280 vec![crate::example_db::specs::ModelExampleSpec {
281 id: "resource_constrained_scheduling",
282 instance: Box::new(
284 ResourceConstrainedScheduling::new(
285 3,
286 vec![20],
287 vec![vec![6], vec![7], vec![7], vec![6], vec![8], vec![6]],
288 2,
289 )
290 .expect("canonical resource-constrained-scheduling instance must be valid"),
291 ),
292 optimal_config: serde_json::json!(vec![0, 0, 0, 1, 1, 1]),
293 optimal_value: serde_json::json!(true),
294 }]
295}
296
297#[cfg(test)]
298#[path = "../../unit_tests/models/misc/resource_constrained_scheduling.rs"]
299mod tests;