Skip to main content

problemreductions/models/misc/
resource_constrained_scheduling.rs

1//! Resource Constrained Scheduling problem implementation.
2//!
3//! A classical NP-complete scheduling problem (Garey & Johnson A5 SS10) where
4//! unit-length tasks must be assigned to identical processors under both a
5//! processor capacity limit and resource usage constraints per time slot.
6
7use crate::registry::{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        module_path: module_path!(),
18        description: "Schedule unit-length tasks on m processors with resource constraints and a deadline",
19        fields: &[
20            FieldInfo { name: "num_processors", type_name: "usize", description: "Number of identical processors m" },
21            FieldInfo { name: "resource_bounds", type_name: "Vec<u64>", description: "Resource bound B_i for each resource i" },
22            FieldInfo { name: "resource_requirements", type_name: "Vec<Vec<u64>>", description: "R_i(t) for each task t and resource i (n x r matrix)" },
23            FieldInfo { name: "deadline", type_name: "u64", description: "Overall deadline D" },
24        ],
25    }
26}
27
28/// The Resource Constrained Scheduling problem.
29///
30/// Given `n` unit-length tasks, `m` identical processors, `r` resources with
31/// bounds `B_i`, resource requirements `R_i(t)` for each task `t` and resource `i`,
32/// and an overall deadline `D`, determine whether there exists a schedule
33/// `σ: T → {0, ..., D-1}` such that:
34/// - At each time slot `u`, at most `m` tasks are scheduled (processor capacity)
35/// - At each time slot `u` and for each resource `i`, the sum of `R_i(t)` over
36///   all tasks `t` scheduled at `u` does not exceed `B_i`
37///
38/// # Representation
39///
40/// Each task has a variable in `{0, ..., D-1}` representing its assigned time slot.
41///
42/// # Example
43///
44/// ```
45/// use problemreductions::models::misc::ResourceConstrainedScheduling;
46/// use problemreductions::{Problem, Solver, BruteForce};
47///
48/// // 6 tasks, 3 processors, 1 resource with bound 20, deadline 2
49/// let problem = ResourceConstrainedScheduling::new(
50///     3,
51///     vec![20],
52///     vec![vec![6], vec![7], vec![7], vec![6], vec![8], vec![6]],
53///     2,
54/// );
55/// let solver = BruteForce::new();
56/// let solution = solver.find_witness(&problem);
57/// assert!(solution.is_some());
58/// ```
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ResourceConstrainedScheduling {
61    /// Number of identical processors.
62    num_processors: usize,
63    /// Resource bounds B_i for each resource.
64    resource_bounds: Vec<u64>,
65    /// Resource requirements R_i(t) for each task t and resource i (n x r matrix).
66    resource_requirements: Vec<Vec<u64>>,
67    /// Overall deadline D.
68    deadline: u64,
69}
70
71impl ResourceConstrainedScheduling {
72    /// Create a new Resource Constrained Scheduling instance.
73    ///
74    /// # Arguments
75    /// * `num_processors` - Number of identical processors `m`
76    /// * `resource_bounds` - Resource bound `B_i` for each resource `i` (length = r)
77    /// * `resource_requirements` - `R_i(t)` for each task `t` and resource `i` (n x r matrix)
78    /// * `deadline` - Overall deadline `D`
79    pub fn new(
80        num_processors: usize,
81        resource_bounds: Vec<u64>,
82        resource_requirements: Vec<Vec<u64>>,
83        deadline: u64,
84    ) -> Self {
85        assert!(deadline > 0, "deadline must be positive");
86        let r = resource_bounds.len();
87        for (t, row) in resource_requirements.iter().enumerate() {
88            assert_eq!(
89                row.len(),
90                r,
91                "task {t} has {} resource requirements, expected {r}",
92                row.len()
93            );
94        }
95        Self {
96            num_processors,
97            resource_bounds,
98            resource_requirements,
99            deadline,
100        }
101    }
102
103    /// Get the number of tasks.
104    pub fn num_tasks(&self) -> usize {
105        self.resource_requirements.len()
106    }
107
108    /// Get the number of processors.
109    pub fn num_processors(&self) -> usize {
110        self.num_processors
111    }
112
113    /// Get the resource bounds.
114    pub fn resource_bounds(&self) -> &[u64] {
115        &self.resource_bounds
116    }
117
118    /// Get the resource requirements matrix.
119    pub fn resource_requirements(&self) -> &[Vec<u64>] {
120        &self.resource_requirements
121    }
122
123    /// Get the deadline.
124    pub fn deadline(&self) -> u64 {
125        self.deadline
126    }
127
128    /// Get the number of resources.
129    pub fn num_resources(&self) -> usize {
130        self.resource_bounds.len()
131    }
132}
133
134impl Problem for ResourceConstrainedScheduling {
135    const NAME: &'static str = "ResourceConstrainedScheduling";
136    type Value = crate::types::Or;
137
138    fn variant() -> Vec<(&'static str, &'static str)> {
139        crate::variant_params![]
140    }
141
142    fn dims(&self) -> Vec<usize> {
143        vec![self.deadline as usize; self.num_tasks()]
144    }
145
146    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
147        crate::types::Or({
148            let n = self.num_tasks();
149            let d = self.deadline as usize;
150            let r = self.num_resources();
151
152            // Check config length
153            if config.len() != n {
154                return crate::types::Or(false);
155            }
156
157            // Check all time slots are in range
158            if config.iter().any(|&slot| slot >= d) {
159                return crate::types::Or(false);
160            }
161
162            // Check processor capacity and resource constraints at each time slot
163            for u in 0..d {
164                // Collect tasks scheduled at time slot u
165                let mut task_count = 0usize;
166                let mut resource_usage = vec![0u64; r];
167
168                for (t, &slot) in config.iter().enumerate() {
169                    if slot == u {
170                        task_count += 1;
171                        // Accumulate resource usage
172                        for (usage, &req) in resource_usage
173                            .iter_mut()
174                            .zip(self.resource_requirements[t].iter())
175                        {
176                            *usage = usage.saturating_add(req);
177                        }
178                    }
179                }
180
181                // Check processor capacity
182                if task_count > self.num_processors {
183                    return crate::types::Or(false);
184                }
185
186                // Check resource bounds
187                for (usage, bound) in resource_usage.iter().zip(self.resource_bounds.iter()) {
188                    if usage > bound {
189                        return crate::types::Or(false);
190                    }
191                }
192            }
193
194            true
195        })
196    }
197}
198
199crate::declare_variants! {
200    default ResourceConstrainedScheduling => "deadline ^ num_tasks",
201}
202
203#[cfg(feature = "example-db")]
204pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
205    vec![crate::example_db::specs::ModelExampleSpec {
206        id: "resource_constrained_scheduling",
207        // 6 tasks, 3 processors, 1 resource B_1=20, deadline 2
208        instance: Box::new(ResourceConstrainedScheduling::new(
209            3,
210            vec![20],
211            vec![vec![6], vec![7], vec![7], vec![6], vec![8], vec![6]],
212            2,
213        )),
214        optimal_config: vec![0, 0, 0, 1, 1, 1],
215        optimal_value: serde_json::json!(true),
216    }]
217}
218
219#[cfg(test)]
220#[path = "../../unit_tests/models/misc/resource_constrained_scheduling.rs"]
221mod tests;