problemreductions/models/misc/
scheduling_to_minimize_weighted_completion_time.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
10use crate::traits::Problem;
11use crate::types::Min;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "SchedulingToMinimizeWeightedCompletionTime",
17 display_name: "Scheduling to Minimize Weighted Completion Time",
18 aliases: &[],
19 dimensions: &[],
20 category: crate::registry::ProblemCategory::Misc,
21 module_path: module_path!(),
22 description: "Assign tasks to processors to minimize total weighted completion time (Smith's rule ordering)",
23 fields: SchedulingToMinimizeWeightedCompletionTimeCreateSpec::FIELDS,
24 }
25}
26
27#[derive(Debug, Clone, Serialize)]
58pub struct SchedulingToMinimizeWeightedCompletionTime {
59 lengths: Vec<i64>,
60 weights: Vec<i64>,
61 #[serde(serialize_with = "serialize_num_processors")]
62 num_processors: usize,
63}
64
65#[derive(Debug, Deserialize, crate::CreateSpec)]
66struct SchedulingToMinimizeWeightedCompletionTimeCreateSpec {
67 lengths: Vec<i64>,
69 weights: Option<Vec<i64>>,
71 num_processors: usize,
73}
74impl TryFrom<SchedulingToMinimizeWeightedCompletionTimeCreateSpec>
75 for SchedulingToMinimizeWeightedCompletionTime
76{
77 type Error = crate::registry::ConstructionError;
78 fn try_from(
79 spec: SchedulingToMinimizeWeightedCompletionTimeCreateSpec,
80 ) -> Result<Self, Self::Error> {
81 if spec.num_processors == 0 {
82 return Err("num_processors must be positive".to_string().into());
83 }
84 let count = spec.lengths.len();
85 let weights = spec.weights.unwrap_or_else(|| vec![1; count]);
86 if weights.len() != count {
87 return Err("weights length must equal lengths length"
88 .to_string()
89 .into());
90 }
91 Ok(Self::new(spec.lengths, weights, spec.num_processors))
92 }
93}
94
95fn serialize_num_processors<S: serde::Serializer>(v: &usize, s: S) -> Result<S::Ok, S::Error> {
96 let value = i64::try_from(*v).map_err(serde::ser::Error::custom)?;
97 s.serialize_i64(value)
98}
99
100#[derive(Deserialize)]
101struct SchedulingToMinimizeWeightedCompletionTimeSerde {
102 lengths: Vec<i64>,
103 weights: Vec<i64>,
104 num_processors: usize,
105}
106
107impl SchedulingToMinimizeWeightedCompletionTime {
108 fn validate(
109 lengths: &[i64],
110 weights: &[i64],
111 num_processors: usize,
112 ) -> Result<(), crate::registry::ConstructionError> {
113 if lengths.len() != weights.len() {
114 return Err("lengths and weights must have the same length"
115 .to_string()
116 .into());
117 }
118 if num_processors == 0 {
119 return Err("num_processors must be positive".to_string().into());
120 }
121 if lengths.contains(&0) {
122 return Err("task lengths must be positive".to_string().into());
123 }
124 if weights.contains(&0) {
125 return Err("task weights must be positive".to_string().into());
126 }
127 Ok(())
128 }
129
130 pub fn new(lengths: Vec<i64>, weights: Vec<i64>, num_processors: usize) -> Self {
137 Self::validate(&lengths, &weights, num_processors).unwrap_or_else(|err| panic!("{err}"));
138 Self {
139 lengths,
140 weights,
141 num_processors,
142 }
143 }
144
145 pub fn max_processing_time_bits(&self) -> u64 {
147 crate::types::max_numeric_magnitude_bits(self.lengths.iter().copied())
148 }
149
150 pub fn num_tasks(&self) -> usize {
152 self.lengths.len()
153 }
154
155 pub fn num_processors(&self) -> usize {
157 self.num_processors
158 }
159
160 pub fn lengths(&self) -> &[i64] {
162 &self.lengths
163 }
164
165 pub fn weights(&self) -> &[i64] {
167 &self.weights
168 }
169
170 fn compute_weighted_completion_time(
174 &self,
175 config: &[usize],
176 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
177 let n = self.num_tasks();
178 let m = self.num_processors;
179
180 if config.len() != n {
181 return Ok(Min(None));
182 }
183 if config.iter().any(|&p| p >= m) {
184 return Ok(Min(None));
185 }
186
187 let mut processor_tasks: Vec<Vec<usize>> = vec![vec![]; m];
189 for (task, &processor) in config.iter().enumerate() {
190 processor_tasks[processor].push(task);
191 }
192
193 let mut total_weighted_completion = 0i64;
194
195 for tasks in &mut processor_tasks {
196 for index in 1..tasks.len() {
199 let mut position = index;
200 while position > 0 {
201 let a = tasks[position - 1];
202 let b = tasks[position];
203 let lhs = self.lengths[a]
204 .checked_mul(self.weights[b])
205 .ok_or_else(|| {
206 crate::traits::EvaluationError::IntegerOverflow(
207 "comparing weighted-completion task ratios".into(),
208 )
209 })?;
210 let rhs = self.lengths[b]
211 .checked_mul(self.weights[a])
212 .ok_or_else(|| {
213 crate::traits::EvaluationError::IntegerOverflow(
214 "comparing weighted-completion task ratios".into(),
215 )
216 })?;
217 if lhs < rhs || (lhs == rhs && a < b) {
218 break;
219 }
220 tasks.swap(position - 1, position);
221 position -= 1;
222 }
223 }
224
225 let mut elapsed = 0i64;
226 for &task in tasks.iter() {
227 elapsed = elapsed.checked_add(self.lengths[task]).ok_or_else(|| {
228 crate::traits::EvaluationError::IntegerOverflow(
229 "summing parallel-machine processing times".to_string(),
230 )
231 })?;
232 let contribution = elapsed.checked_mul(self.weights[task]).ok_or_else(|| {
233 crate::traits::EvaluationError::IntegerOverflow(
234 "multiplying task weight by completion time".to_string(),
235 )
236 })?;
237 total_weighted_completion = total_weighted_completion
238 .checked_add(contribution)
239 .ok_or_else(|| {
240 crate::traits::EvaluationError::IntegerOverflow(
241 "summing weighted completion times".to_string(),
242 )
243 })?;
244 }
245 }
246
247 Ok(Min(Some(total_weighted_completion)))
248 }
249}
250
251impl TryFrom<SchedulingToMinimizeWeightedCompletionTimeSerde>
252 for SchedulingToMinimizeWeightedCompletionTime
253{
254 type Error = crate::registry::ConstructionError;
255
256 fn try_from(
257 value: SchedulingToMinimizeWeightedCompletionTimeSerde,
258 ) -> Result<Self, Self::Error> {
259 Self::validate(&value.lengths, &value.weights, value.num_processors)?;
260 Ok(Self {
261 lengths: value.lengths,
262 weights: value.weights,
263 num_processors: value.num_processors,
264 })
265 }
266}
267
268impl<'de> Deserialize<'de> for SchedulingToMinimizeWeightedCompletionTime {
269 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
270 where
271 D: serde::Deserializer<'de>,
272 {
273 let value = SchedulingToMinimizeWeightedCompletionTimeSerde::deserialize(deserializer)?;
274 Self::try_from(value).map_err(serde::de::Error::custom)
275 }
276}
277
278impl Problem for SchedulingToMinimizeWeightedCompletionTime {
279 const NAME: &'static str = "SchedulingToMinimizeWeightedCompletionTime";
280 type Solution = Vec<usize>;
281 type Value = Min<i64>;
282
283 crate::problem_parameters![
284 ("max_processing_time_bits", max_processing_time_bits),
285 ("num_processors", num_processors),
286 ("num_tasks", num_tasks),
287 ];
288
289 fn variant() -> Vec<(&'static str, &'static str)> {
290 crate::variant_params![]
291 }
292
293 fn evaluate(
294 &self,
295 config: &Self::Solution,
296 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
297 if config.len() != self.num_tasks() {
298 return Err(crate::traits::EvaluationError::InvalidConfiguration(
299 "processor assignment length does not match the tasks".into(),
300 ));
301 }
302 if config
303 .iter()
304 .any(|&processor| processor >= self.num_processors)
305 {
306 return Err(crate::traits::EvaluationError::InvalidConfiguration(
307 "assignment contains an out-of-range processor".into(),
308 ));
309 }
310 self.compute_weighted_completion_time(config)
311 }
312}
313
314impl crate::solvers::BruteForceProblem for SchedulingToMinimizeWeightedCompletionTime {
315 fn dimensions(&self) -> Vec<usize> {
316 vec![self.num_processors; self.num_tasks()]
317 }
318}
319
320crate::declare_variants! {
321 default SchedulingToMinimizeWeightedCompletionTime => "num_processors^num_tasks" create SchedulingToMinimizeWeightedCompletionTimeCreateSpec,
322}
323
324crate::register_brute_force! {
325 SchedulingToMinimizeWeightedCompletionTime,
326}
327
328#[cfg(feature = "example-db")]
329pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
330 vec![crate::example_db::specs::ModelExampleSpec {
331 id: "scheduling_to_minimize_weighted_completion_time",
332 instance: Box::new(SchedulingToMinimizeWeightedCompletionTime::new(
333 vec![1, 2, 3, 4, 5],
334 vec![6, 4, 3, 2, 1],
335 2,
336 )),
337 optimal_config: serde_json::json!(vec![0, 1, 0, 1, 0]),
339 optimal_value: serde_json::json!(47),
340 }]
341}
342
343#[cfg(test)]
344#[path = "../../unit_tests/models/misc/scheduling_to_minimize_weighted_completion_time.rs"]
345mod tests;