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 num_tasks(&self) -> usize {
123 self.resource_requirements.len()
124 }
125
126 pub fn num_processors(&self) -> usize {
128 self.num_processors
129 }
130
131 pub fn resource_bounds(&self) -> &[i64] {
133 &self.resource_bounds
134 }
135
136 pub fn resource_requirements(&self) -> &[Vec<i64>] {
138 &self.resource_requirements
139 }
140
141 pub fn deadline(&self) -> i64 {
143 self.deadline
144 }
145
146 pub fn num_resources(&self) -> usize {
148 self.resource_bounds.len()
149 }
150}
151
152impl<'de> Deserialize<'de> for ResourceConstrainedScheduling {
153 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154 where
155 D: serde::Deserializer<'de>,
156 {
157 #[derive(Deserialize)]
158 struct Raw {
159 num_processors: usize,
160 resource_bounds: Vec<i64>,
161 resource_requirements: Vec<Vec<i64>>,
162 deadline: i64,
163 }
164
165 let raw = Raw::deserialize(deserializer)?;
166 Self::new(
167 raw.num_processors,
168 raw.resource_bounds,
169 raw.resource_requirements,
170 raw.deadline,
171 )
172 .map_err(serde::de::Error::custom)
173 }
174}
175
176impl Problem for ResourceConstrainedScheduling {
177 const NAME: &'static str = "ResourceConstrainedScheduling";
178 type Solution = Vec<usize>;
179 type Value = crate::types::Or;
180
181 crate::problem_parameters![
182 ("deadline", deadline),
183 ("num_resources", num_resources),
184 ("num_tasks", num_tasks),
185 ];
186
187 fn variant() -> Vec<(&'static str, &'static str)> {
188 crate::variant_params![]
189 }
190
191 fn evaluate(
192 &self,
193 config: &Self::Solution,
194 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
195 Ok({
196 crate::types::Or({
197 let n = self.num_tasks();
198 let d = self.deadline as usize;
199 let r = self.num_resources();
200
201 if config.len() != n {
203 return Err(crate::traits::EvaluationError::InvalidConfiguration(
204 "schedule length does not match the tasks".into(),
205 ));
206 }
207
208 if config.iter().any(|&start| start >= d) {
209 return Err(crate::traits::EvaluationError::InvalidConfiguration(
210 "schedule contains an out-of-range start time".into(),
211 ));
212 }
213
214 for u in 0..d {
216 let mut task_count = 0usize;
218 let mut resource_usage = vec![0i64; r];
219
220 for (t, &slot) in config.iter().enumerate() {
221 if slot == u {
222 task_count += 1;
223 for (usage, &req) in resource_usage
225 .iter_mut()
226 .zip(self.resource_requirements[t].iter())
227 {
228 *usage = usage.checked_add(req).ok_or_else(|| {
229 crate::traits::EvaluationError::IntegerOverflow(
230 "summing scheduled resource usage".to_string(),
231 )
232 })?;
233 }
234 }
235 }
236
237 if task_count > self.num_processors {
239 return Ok(crate::types::Or(false));
240 }
241
242 for (usage, bound) in resource_usage.iter().zip(self.resource_bounds.iter()) {
244 if usage > bound {
245 return Ok(crate::types::Or(false));
246 }
247 }
248 }
249
250 true
251 })
252 })
253 }
254}
255
256impl crate::solvers::BruteForceProblem for ResourceConstrainedScheduling {
257 fn dimensions(&self) -> Vec<usize> {
258 vec![self.deadline as usize; self.num_tasks()]
259 }
260}
261
262crate::declare_variants! {
263 default ResourceConstrainedScheduling => "deadline ^ num_tasks",
264}
265
266crate::register_brute_force! {
267 ResourceConstrainedScheduling,
268}
269
270#[cfg(feature = "example-db")]
271pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
272 vec![crate::example_db::specs::ModelExampleSpec {
273 id: "resource_constrained_scheduling",
274 instance: Box::new(
276 ResourceConstrainedScheduling::new(
277 3,
278 vec![20],
279 vec![vec![6], vec![7], vec![7], vec![6], vec![8], vec![6]],
280 2,
281 )
282 .expect("canonical resource-constrained-scheduling instance must be valid"),
283 ),
284 optimal_config: serde_json::json!(vec![0, 0, 0, 1, 1, 1]),
285 optimal_value: serde_json::json!(true),
286 }]
287}
288
289#[cfg(test)]
290#[path = "../../unit_tests/models/misc/resource_constrained_scheduling.rs"]
291mod tests;