problemreductions/models/misc/
sequencing_to_minimize_weighted_tardiness.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "SequencingToMinimizeWeightedTardiness",
15 display_name: "Sequencing to Minimize Weighted Tardiness",
16 aliases: &[],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Misc,
19 module_path: module_path!(),
20 description: "Schedule jobs on one machine so total weighted tardiness is at most K",
21 fields: SequencingToMinimizeWeightedTardinessCreateSpec::FIELDS,
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct SequencingToMinimizeWeightedTardiness {
56 lengths: Vec<i64>,
57 weights: Vec<i64>,
58 deadlines: Vec<i64>,
59 bound: i64,
60}
61
62#[derive(Debug, Deserialize, crate::CreateSpec)]
63struct SequencingToMinimizeWeightedTardinessCreateSpec {
64 lengths: Vec<i64>,
66 weights: Vec<i64>,
68 deadlines: Vec<i64>,
70 bound: i64,
72}
73impl TryFrom<SequencingToMinimizeWeightedTardinessCreateSpec>
74 for SequencingToMinimizeWeightedTardiness
75{
76 type Error = crate::registry::ConstructionError;
77 fn try_from(
78 spec: SequencingToMinimizeWeightedTardinessCreateSpec,
79 ) -> Result<Self, Self::Error> {
80 if spec.lengths.len() != spec.weights.len() {
81 return Err("weights length must equal lengths length"
82 .to_string()
83 .into());
84 }
85 if spec.lengths.len() != spec.deadlines.len() {
86 return Err("deadlines length must equal lengths length"
87 .to_string()
88 .into());
89 }
90 Ok(Self::new(
91 spec.lengths,
92 spec.weights,
93 spec.deadlines,
94 spec.bound,
95 ))
96 }
97}
98
99impl SequencingToMinimizeWeightedTardiness {
100 pub fn new(lengths: Vec<i64>, weights: Vec<i64>, deadlines: Vec<i64>, bound: i64) -> Self {
106 assert_eq!(
107 lengths.len(),
108 weights.len(),
109 "weights length must equal lengths length"
110 );
111 assert_eq!(
112 lengths.len(),
113 deadlines.len(),
114 "deadlines length must equal lengths length"
115 );
116 assert!(
117 lengths.iter().all(|&length| length >= 0),
118 "task lengths must be nonnegative"
119 );
120 assert!(
121 weights.iter().all(|&weight| weight >= 0),
122 "task weights must be nonnegative"
123 );
124 assert!(
125 deadlines.iter().all(|&deadline| deadline >= 0),
126 "deadlines must be nonnegative"
127 );
128 assert!(bound >= 0, "bound must be nonnegative");
129 Self {
130 lengths,
131 weights,
132 deadlines,
133 bound,
134 }
135 }
136
137 pub fn lengths(&self) -> &[i64] {
139 &self.lengths
140 }
141
142 pub fn weights(&self) -> &[i64] {
144 &self.weights
145 }
146
147 pub fn deadlines(&self) -> &[i64] {
149 &self.deadlines
150 }
151
152 pub fn bound(&self) -> i64 {
154 self.bound
155 }
156
157 pub fn num_tasks(&self) -> usize {
159 self.lengths.len()
160 }
161
162 fn decode_schedule(&self, config: &[usize]) -> Option<Vec<usize>> {
163 super::decode_permutation(config, self.num_tasks())
164 }
165
166 fn schedule_weighted_tardiness(
167 &self,
168 schedule: &[usize],
169 ) -> Result<i64, crate::traits::EvaluationError> {
170 let mut completion_time = 0i64;
171 let mut total = 0i64;
172 for &job in schedule {
173 completion_time = completion_time
174 .checked_add(self.lengths[job])
175 .ok_or_else(|| {
176 crate::traits::EvaluationError::IntegerOverflow(
177 "summing weighted-tardiness completion times".to_string(),
178 )
179 })?;
180 let tardiness = completion_time
181 .checked_sub(self.deadlines[job])
182 .ok_or_else(|| {
183 crate::traits::EvaluationError::IntegerOverflow(
184 "computing job tardiness".to_string(),
185 )
186 })?
187 .max(0);
188 let weighted_tardiness = tardiness.checked_mul(self.weights[job]).ok_or_else(|| {
189 crate::traits::EvaluationError::IntegerOverflow(
190 "multiplying tardiness by job weight".to_string(),
191 )
192 })?;
193 total = total.checked_add(weighted_tardiness).ok_or_else(|| {
194 crate::traits::EvaluationError::IntegerOverflow(
195 "summing weighted job tardiness".to_string(),
196 )
197 })?;
198 }
199 Ok(total)
200 }
201
202 pub fn total_weighted_tardiness(
206 &self,
207 config: &[usize],
208 ) -> Result<Option<i64>, crate::traits::EvaluationError> {
209 let Some(schedule) = self.decode_schedule(config) else {
210 return Ok(None);
211 };
212 Ok(Some(self.schedule_weighted_tardiness(&schedule)?))
213 }
214}
215
216impl Problem for SequencingToMinimizeWeightedTardiness {
217 const NAME: &'static str = "SequencingToMinimizeWeightedTardiness";
218 type Solution = Vec<usize>;
219 type Value = crate::types::Or;
220
221 crate::problem_parameters![("num_tasks", num_tasks),];
222
223 fn variant() -> Vec<(&'static str, &'static str)> {
224 crate::variant_params![]
225 }
226
227 fn evaluate(
228 &self,
229 config: &Self::Solution,
230 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
231 let n = self.num_tasks();
232 if config.len() != n {
233 return Err(crate::traits::EvaluationError::InvalidConfiguration(
234 "schedule length does not match the tasks".into(),
235 ));
236 }
237 if config.iter().any(|&task| task >= n) {
238 return Err(crate::traits::EvaluationError::InvalidConfiguration(
239 "schedule contains an out-of-range task".into(),
240 ));
241 }
242 Ok({
243 crate::types::Or({
244 self.total_weighted_tardiness(config)?
245 .is_some_and(|total| total <= self.bound)
246 })
247 })
248 }
249}
250
251impl crate::solvers::BruteForceProblem for SequencingToMinimizeWeightedTardiness {
252 fn dimensions(&self) -> Vec<usize> {
253 super::lehmer_dims(self.num_tasks())
254 }
255}
256
257crate::declare_variants! {
258 default SequencingToMinimizeWeightedTardiness => "factorial(num_tasks)" create SequencingToMinimizeWeightedTardinessCreateSpec,
259}
260
261crate::register_brute_force! {
262 SequencingToMinimizeWeightedTardiness decode |problem: &SequencingToMinimizeWeightedTardiness, indices: Vec<usize>| super::decode_lehmer(&indices, problem.num_tasks()).expect("enumerated Lehmer digits are valid"),
263}
264
265#[cfg(feature = "example-db")]
266pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
267 vec![crate::example_db::specs::ModelExampleSpec {
268 id: "sequencing_to_minimize_weighted_tardiness",
269 instance: Box::new(SequencingToMinimizeWeightedTardiness::new(
270 vec![3, 4, 2, 5, 3],
271 vec![2, 3, 1, 4, 2],
272 vec![5, 8, 4, 15, 10],
273 13,
274 )),
275 optimal_config: serde_json::json!(vec![0, 1, 4, 3, 2]),
276 optimal_value: serde_json::json!(true),
277 }]
278}
279
280#[cfg(test)]
281#[path = "../../unit_tests/models/misc/sequencing_to_minimize_weighted_tardiness.rs"]
282mod tests;