problemreductions/models/misc/
multiprocessor_scheduling.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "MultiprocessorScheduling",
14 display_name: "Multiprocessor Scheduling",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Assign tasks to processors so that no processor's load exceeds a deadline",
20 fields: MultiprocessorSchedulingCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct MultiprocessorScheduling {
54 lengths: Vec<i64>,
56 #[serde(deserialize_with = "positive_usize::deserialize")]
58 num_processors: usize,
59 deadline: i64,
61}
62
63#[derive(Debug, Deserialize, crate::CreateSpec)]
64struct MultiprocessorSchedulingCreateSpec {
65 lengths: Vec<i64>,
67 num_processors: usize,
69 deadline: i64,
71}
72impl TryFrom<MultiprocessorSchedulingCreateSpec> for MultiprocessorScheduling {
73 type Error = crate::registry::ConstructionError;
74 fn try_from(spec: MultiprocessorSchedulingCreateSpec) -> Result<Self, Self::Error> {
75 if spec.num_processors == 0 {
76 return Err("num_processors must be positive".to_string().into());
77 }
78 Ok(Self::new(spec.lengths, spec.num_processors, spec.deadline))
79 }
80}
81
82impl MultiprocessorScheduling {
83 pub fn new(lengths: Vec<i64>, num_processors: usize, deadline: i64) -> Self {
88 assert!(num_processors > 0, "num_processors must be positive");
89 assert!(
90 lengths.iter().all(|&length| length >= 0),
91 "task lengths must be nonnegative"
92 );
93 assert!(deadline >= 0, "deadline must be nonnegative");
94 Self {
95 lengths,
96 num_processors,
97 deadline,
98 }
99 }
100
101 pub fn lengths(&self) -> &[i64] {
103 &self.lengths
104 }
105
106 pub fn num_processors(&self) -> usize {
108 self.num_processors
109 }
110
111 pub fn deadline(&self) -> i64 {
113 self.deadline
114 }
115
116 pub fn num_tasks(&self) -> usize {
118 self.lengths.len()
119 }
120
121 pub fn total_length(&self) -> i64 {
123 self.lengths.iter().sum()
124 }
125}
126
127impl Problem for MultiprocessorScheduling {
128 const NAME: &'static str = "MultiprocessorScheduling";
129 type Solution = Vec<usize>;
130 type Value = crate::types::Or;
131
132 crate::problem_parameters![("num_processors", num_processors), ("num_tasks", num_tasks),];
133
134 fn variant() -> Vec<(&'static str, &'static str)> {
135 crate::variant_params![]
136 }
137
138 fn evaluate(
139 &self,
140 config: &Self::Solution,
141 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
142 Ok({
143 crate::types::Or({
144 if config.len() != self.num_tasks() {
145 return Err(crate::traits::EvaluationError::InvalidConfiguration(
146 "processor assignment length does not match the tasks".into(),
147 ));
148 }
149 let m = self.num_processors;
150 if config.iter().any(|&processor| processor >= m) {
151 return Err(crate::traits::EvaluationError::InvalidConfiguration(
152 "assignment contains an out-of-range processor".into(),
153 ));
154 }
155 let mut loads = vec![0i64; m];
156 for (i, &processor) in config.iter().enumerate() {
157 loads[processor] =
158 loads[processor]
159 .checked_add(self.lengths[i])
160 .ok_or_else(|| {
161 crate::traits::EvaluationError::IntegerOverflow(
162 "summing multiprocessor load".into(),
163 )
164 })?;
165 }
166 loads.iter().all(|&load| load <= self.deadline)
167 })
168 })
169 }
170}
171
172impl crate::solvers::BruteForceProblem for MultiprocessorScheduling {
173 fn dimensions(&self) -> Vec<usize> {
174 vec![self.num_processors; self.num_tasks()]
175 }
176}
177
178crate::declare_variants! {
179 default MultiprocessorScheduling => "2^num_tasks" create MultiprocessorSchedulingCreateSpec,
180}
181
182crate::register_brute_force! {
183 MultiprocessorScheduling,
184}
185
186#[cfg(feature = "example-db")]
187pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
188 vec![crate::example_db::specs::ModelExampleSpec {
189 id: "multiprocessor_scheduling",
190 instance: Box::new(MultiprocessorScheduling::new(vec![4, 5, 3, 2, 6], 2, 10)),
191 optimal_config: serde_json::json!(vec![0, 1, 1, 1, 0]),
192 optimal_value: serde_json::json!(true),
193 }]
194}
195
196mod positive_usize {
197 use serde::de::Error;
198 use serde::{Deserialize, Deserializer};
199
200 pub fn deserialize<'de, D>(deserializer: D) -> Result<usize, D::Error>
201 where
202 D: Deserializer<'de>,
203 {
204 let value = usize::deserialize(deserializer)?;
205 if value == 0 {
206 return Err(D::Error::custom("expected positive integer, got 0"));
207 }
208 Ok(value)
209 }
210}
211
212#[cfg(test)]
213#[path = "../../unit_tests/models/misc/multiprocessor_scheduling.rs"]
214mod tests;