problemreductions/models/misc/
scheduling_to_minimize_weighted_completion_time.rs1use crate::registry::{FieldInfo, 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 module_path: module_path!(),
21 description: "Assign tasks to processors to minimize total weighted completion time (Smith's rule ordering)",
22 fields: &[
23 FieldInfo { name: "lengths", type_name: "Vec<u64>", description: "Processing time l(t) for each task" },
24 FieldInfo { name: "weights", type_name: "Vec<u64>", description: "Weight w(t) for each task" },
25 FieldInfo { name: "num_processors", type_name: "usize", description: "Number of identical processors m" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize)]
61pub struct SchedulingToMinimizeWeightedCompletionTime {
62 lengths: Vec<u64>,
63 weights: Vec<u64>,
64 #[serde(serialize_with = "serialize_num_processors")]
65 num_processors: usize,
66}
67
68fn serialize_num_processors<S: serde::Serializer>(v: &usize, s: S) -> Result<S::Ok, S::Error> {
69 s.serialize_u64(*v as u64)
70}
71
72#[derive(Deserialize)]
73struct SchedulingToMinimizeWeightedCompletionTimeSerde {
74 lengths: Vec<u64>,
75 weights: Vec<u64>,
76 num_processors: usize,
77}
78
79impl SchedulingToMinimizeWeightedCompletionTime {
80 fn validate(lengths: &[u64], weights: &[u64], num_processors: usize) -> Result<(), String> {
81 if lengths.len() != weights.len() {
82 return Err("lengths and weights must have the same length".to_string());
83 }
84 if num_processors == 0 {
85 return Err("num_processors must be positive".to_string());
86 }
87 if lengths.contains(&0) {
88 return Err("task lengths must be positive".to_string());
89 }
90 if weights.contains(&0) {
91 return Err("task weights must be positive".to_string());
92 }
93 Ok(())
94 }
95
96 pub fn new(lengths: Vec<u64>, weights: Vec<u64>, num_processors: usize) -> Self {
103 Self::validate(&lengths, &weights, num_processors).unwrap_or_else(|err| panic!("{err}"));
104 Self {
105 lengths,
106 weights,
107 num_processors,
108 }
109 }
110
111 pub fn num_tasks(&self) -> usize {
113 self.lengths.len()
114 }
115
116 pub fn num_processors(&self) -> usize {
118 self.num_processors
119 }
120
121 pub fn lengths(&self) -> &[u64] {
123 &self.lengths
124 }
125
126 pub fn weights(&self) -> &[u64] {
128 &self.weights
129 }
130
131 fn compute_weighted_completion_time(&self, config: &[usize]) -> Min<u64> {
135 let n = self.num_tasks();
136 let m = self.num_processors;
137
138 if config.len() != n {
139 return Min(None);
140 }
141 if config.iter().any(|&p| p >= m) {
142 return Min(None);
143 }
144
145 let mut processor_tasks: Vec<Vec<usize>> = vec![vec![]; m];
147 for (task, &processor) in config.iter().enumerate() {
148 processor_tasks[processor].push(task);
149 }
150
151 let mut total_weighted_completion = 0u64;
152
153 for tasks in &mut processor_tasks {
154 tasks.sort_by(|&a, &b| {
157 let lhs = self.lengths[a] as u128 * self.weights[b] as u128;
158 let rhs = self.lengths[b] as u128 * self.weights[a] as u128;
159 lhs.cmp(&rhs).then(a.cmp(&b))
160 });
161
162 let mut elapsed = 0u64;
163 for &task in tasks.iter() {
164 elapsed = elapsed
165 .checked_add(self.lengths[task])
166 .expect("processing time overflowed u64");
167 let contribution = elapsed
168 .checked_mul(self.weights[task])
169 .expect("weighted completion time overflowed u64");
170 total_weighted_completion = total_weighted_completion
171 .checked_add(contribution)
172 .expect("total weighted completion time overflowed u64");
173 }
174 }
175
176 Min(Some(total_weighted_completion))
177 }
178}
179
180impl TryFrom<SchedulingToMinimizeWeightedCompletionTimeSerde>
181 for SchedulingToMinimizeWeightedCompletionTime
182{
183 type Error = String;
184
185 fn try_from(
186 value: SchedulingToMinimizeWeightedCompletionTimeSerde,
187 ) -> Result<Self, Self::Error> {
188 Self::validate(&value.lengths, &value.weights, value.num_processors)?;
189 Ok(Self {
190 lengths: value.lengths,
191 weights: value.weights,
192 num_processors: value.num_processors,
193 })
194 }
195}
196
197impl<'de> Deserialize<'de> for SchedulingToMinimizeWeightedCompletionTime {
198 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
199 where
200 D: serde::Deserializer<'de>,
201 {
202 let value = SchedulingToMinimizeWeightedCompletionTimeSerde::deserialize(deserializer)?;
203 Self::try_from(value).map_err(serde::de::Error::custom)
204 }
205}
206
207impl Problem for SchedulingToMinimizeWeightedCompletionTime {
208 const NAME: &'static str = "SchedulingToMinimizeWeightedCompletionTime";
209 type Value = Min<u64>;
210
211 fn variant() -> Vec<(&'static str, &'static str)> {
212 crate::variant_params![]
213 }
214
215 fn dims(&self) -> Vec<usize> {
216 vec![self.num_processors; self.num_tasks()]
217 }
218
219 fn evaluate(&self, config: &[usize]) -> Min<u64> {
220 self.compute_weighted_completion_time(config)
221 }
222}
223
224crate::declare_variants! {
225 default SchedulingToMinimizeWeightedCompletionTime => "num_processors^num_tasks",
226}
227
228#[cfg(feature = "example-db")]
229pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
230 vec![crate::example_db::specs::ModelExampleSpec {
231 id: "scheduling_to_minimize_weighted_completion_time",
232 instance: Box::new(SchedulingToMinimizeWeightedCompletionTime::new(
233 vec![1, 2, 3, 4, 5],
234 vec![6, 4, 3, 2, 1],
235 2,
236 )),
237 optimal_config: vec![0, 1, 0, 1, 0],
239 optimal_value: serde_json::json!(47),
240 }]
241}
242
243#[cfg(test)]
244#[path = "../../unit_tests/models/misc/scheduling_to_minimize_weighted_completion_time.rs"]
245mod tests;