problemreductions/models/misc/
sequencing_to_minimize_weighted_completion_time.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
14use crate::traits::Problem;
15use crate::types::Min;
16use serde::{Deserialize, Serialize};
17
18inventory::submit! {
19 ProblemSchemaEntry {
20 name: "SequencingToMinimizeWeightedCompletionTime",
21 display_name: "Sequencing to Minimize Weighted Completion Time",
22 aliases: &[],
23 dimensions: &[],
24 module_path: module_path!(),
25 description: "Schedule tasks with lengths, weights, and precedence constraints to minimize total weighted completion time",
26 fields: &[
27 FieldInfo { name: "lengths", type_name: "Vec<u64>", description: "Processing time l(t) for each task" },
28 FieldInfo { name: "weights", type_name: "Vec<u64>", description: "Weight w(t) for each task" },
29 FieldInfo { name: "precedences", type_name: "Vec<(usize, usize)>", description: "Precedence pairs (predecessor, successor)" },
30 ],
31 }
32}
33
34#[derive(Debug, Clone, Serialize)]
43pub struct SequencingToMinimizeWeightedCompletionTime {
44 lengths: Vec<u64>,
45 weights: Vec<u64>,
46 precedences: Vec<(usize, usize)>,
47}
48
49#[derive(Deserialize)]
50struct SequencingToMinimizeWeightedCompletionTimeSerde {
51 lengths: Vec<u64>,
52 weights: Vec<u64>,
53 precedences: Vec<(usize, usize)>,
54}
55
56impl SequencingToMinimizeWeightedCompletionTime {
57 fn validate(
58 lengths: &[u64],
59 weights: &[u64],
60 precedences: &[(usize, usize)],
61 ) -> Result<(), String> {
62 if lengths.len() != weights.len() {
63 return Err("lengths length must equal weights length".to_string());
64 }
65
66 let num_tasks = lengths.len();
67 for &(pred, succ) in precedences {
68 if pred >= num_tasks {
69 return Err(format!(
70 "predecessor index {} out of range (num_tasks = {})",
71 pred, num_tasks
72 ));
73 }
74 if succ >= num_tasks {
75 return Err(format!(
76 "successor index {} out of range (num_tasks = {})",
77 succ, num_tasks
78 ));
79 }
80 }
81
82 Ok(())
83 }
84
85 pub fn new(lengths: Vec<u64>, weights: Vec<u64>, precedences: Vec<(usize, usize)>) -> Self {
92 Self::validate(&lengths, &weights, &precedences).unwrap_or_else(|err| panic!("{err}"));
93
94 Self {
95 lengths,
96 weights,
97 precedences,
98 }
99 }
100
101 pub fn num_tasks(&self) -> usize {
103 self.lengths.len()
104 }
105
106 pub fn lengths(&self) -> &[u64] {
108 &self.lengths
109 }
110
111 pub fn weights(&self) -> &[u64] {
113 &self.weights
114 }
115
116 pub fn precedences(&self) -> &[(usize, usize)] {
118 &self.precedences
119 }
120
121 pub fn num_precedences(&self) -> usize {
123 self.precedences.len()
124 }
125
126 pub fn total_processing_time(&self) -> u64 {
128 self.lengths
129 .iter()
130 .try_fold(0u64, |acc, &length| acc.checked_add(length))
131 .expect("total processing time overflowed u64")
132 }
133
134 fn decode_schedule(&self, config: &[usize]) -> Option<Vec<usize>> {
135 super::decode_lehmer(config, self.num_tasks())
136 }
137
138 fn weighted_completion_time(&self, schedule: &[usize]) -> Min<u64> {
139 let n = self.num_tasks();
140 let mut positions = vec![0usize; n];
141 let mut completion_times = vec![0u64; n];
142 let mut elapsed = 0u64;
143
144 for (position, &task) in schedule.iter().enumerate() {
145 positions[task] = position;
146 elapsed = elapsed
147 .checked_add(self.lengths[task])
148 .expect("total processing time overflowed u64");
149 completion_times[task] = elapsed;
150 }
151
152 for &(pred, succ) in &self.precedences {
153 if positions[pred] >= positions[succ] {
154 return Min(None);
155 }
156 }
157
158 let total = completion_times
159 .iter()
160 .enumerate()
161 .try_fold(0u64, |acc, (task, &completion)| -> Option<u64> {
162 let weighted_completion = completion.checked_mul(self.weights[task])?;
163 acc.checked_add(weighted_completion)
164 })
165 .expect("weighted completion time overflowed u64");
166 Min(Some(total))
167 }
168}
169
170impl TryFrom<SequencingToMinimizeWeightedCompletionTimeSerde>
171 for SequencingToMinimizeWeightedCompletionTime
172{
173 type Error = String;
174
175 fn try_from(
176 value: SequencingToMinimizeWeightedCompletionTimeSerde,
177 ) -> Result<Self, Self::Error> {
178 Self::validate(&value.lengths, &value.weights, &value.precedences)?;
179 Ok(Self {
180 lengths: value.lengths,
181 weights: value.weights,
182 precedences: value.precedences,
183 })
184 }
185}
186
187impl<'de> Deserialize<'de> for SequencingToMinimizeWeightedCompletionTime {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: serde::Deserializer<'de>,
191 {
192 let value = SequencingToMinimizeWeightedCompletionTimeSerde::deserialize(deserializer)?;
193 Self::try_from(value).map_err(serde::de::Error::custom)
194 }
195}
196
197impl Problem for SequencingToMinimizeWeightedCompletionTime {
198 const NAME: &'static str = "SequencingToMinimizeWeightedCompletionTime";
199 type Value = Min<u64>;
200
201 fn variant() -> Vec<(&'static str, &'static str)> {
202 crate::variant_params![]
203 }
204
205 fn dims(&self) -> Vec<usize> {
206 super::lehmer_dims(self.num_tasks())
207 }
208
209 fn evaluate(&self, config: &[usize]) -> Min<u64> {
210 let Some(schedule) = self.decode_schedule(config) else {
211 return Min(None);
212 };
213 self.weighted_completion_time(&schedule)
214 }
215}
216
217crate::declare_variants! {
218 default SequencingToMinimizeWeightedCompletionTime => "factorial(num_tasks)",
219}
220
221#[cfg(feature = "example-db")]
222pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
223 vec![crate::example_db::specs::ModelExampleSpec {
224 id: "sequencing_to_minimize_weighted_completion_time",
225 instance: Box::new(SequencingToMinimizeWeightedCompletionTime::new(
226 vec![2, 1, 3, 1, 2],
227 vec![3, 5, 1, 4, 2],
228 vec![(0, 2), (1, 4)],
229 )),
230 optimal_config: vec![1, 2, 0, 1, 0],
231 optimal_value: serde_json::json!(46),
232 }]
233}
234
235#[cfg(test)]
236#[path = "../../unit_tests/models/misc/sequencing_to_minimize_weighted_completion_time.rs"]
237mod tests;