problemreductions/models/misc/
sequencing_to_minimize_tardy_task_weight.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "SequencingToMinimizeTardyTaskWeight",
15 display_name: "Sequencing to Minimize Tardy Task Weight",
16 aliases: &[],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Misc,
19 module_path: module_path!(),
20 description: "Schedule tasks with lengths, weights, and deadlines to minimize total weight of tardy tasks",
21 fields: SequencingToMinimizeTardyTaskWeightCreateSpec::FIELDS,
22 }
23}
24
25#[derive(Debug, Clone, Serialize)]
38pub struct SequencingToMinimizeTardyTaskWeight {
39 lengths: Vec<i64>,
40 weights: Vec<i64>,
41 deadlines: Vec<i64>,
42}
43
44#[derive(Debug, Deserialize, crate::CreateSpec)]
45struct SequencingToMinimizeTardyTaskWeightCreateSpec {
46 lengths: Vec<i64>,
48 weights: Option<Vec<i64>>,
50 deadlines: Vec<i64>,
52}
53impl TryFrom<SequencingToMinimizeTardyTaskWeightCreateSpec>
54 for SequencingToMinimizeTardyTaskWeight
55{
56 type Error = crate::registry::ConstructionError;
57 fn try_from(spec: SequencingToMinimizeTardyTaskWeightCreateSpec) -> Result<Self, Self::Error> {
58 let count = spec.lengths.len();
59 if spec.deadlines.len() != count {
60 return Err("deadlines length must equal lengths length"
61 .to_string()
62 .into());
63 }
64 let weights = spec.weights.unwrap_or_else(|| vec![1; count]);
65 if weights.len() != count {
66 return Err("weights length must equal lengths length"
67 .to_string()
68 .into());
69 }
70 Ok(Self::new(spec.lengths, weights, spec.deadlines))
71 }
72}
73
74#[derive(Deserialize)]
75struct SequencingToMinimizeTardyTaskWeightSerde {
76 lengths: Vec<i64>,
77 weights: Vec<i64>,
78 deadlines: Vec<i64>,
79}
80
81impl SequencingToMinimizeTardyTaskWeight {
82 fn validate(
83 lengths: &[i64],
84 weights: &[i64],
85 deadlines: &[i64],
86 ) -> Result<(), crate::registry::ConstructionError> {
87 if lengths.len() != weights.len() {
88 return Err("lengths length must equal weights length"
89 .to_string()
90 .into());
91 }
92 if lengths.len() != deadlines.len() {
93 return Err("lengths length must equal deadlines length"
94 .to_string()
95 .into());
96 }
97 if lengths.contains(&0) {
98 return Err("task lengths must be positive".to_string().into());
99 }
100 if weights.contains(&0) {
101 return Err("task weights must be positive".to_string().into());
102 }
103 Ok(())
104 }
105
106 pub fn new(lengths: Vec<i64>, weights: Vec<i64>, deadlines: Vec<i64>) -> Self {
113 Self::validate(&lengths, &weights, &deadlines).unwrap_or_else(|err| panic!("{err}"));
114 Self {
115 lengths,
116 weights,
117 deadlines,
118 }
119 }
120
121 pub fn num_tasks(&self) -> usize {
123 self.lengths.len()
124 }
125
126 pub fn lengths(&self) -> &[i64] {
128 &self.lengths
129 }
130
131 pub fn weights(&self) -> &[i64] {
133 &self.weights
134 }
135
136 pub fn deadlines(&self) -> &[i64] {
138 &self.deadlines
139 }
140
141 fn tardy_task_weight(
142 &self,
143 schedule: &[usize],
144 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
145 let mut elapsed: i64 = 0;
146 let mut total: i64 = 0;
147 for &task in schedule {
148 elapsed = elapsed.checked_add(self.lengths[task]).ok_or_else(|| {
149 crate::traits::EvaluationError::IntegerOverflow(
150 "summing tardiness sequencing processing times".to_string(),
151 )
152 })?;
153 if elapsed > self.deadlines[task] {
154 total = total.checked_add(self.weights[task]).ok_or_else(|| {
155 crate::traits::EvaluationError::IntegerOverflow(
156 "summing tardy task weights".to_string(),
157 )
158 })?;
159 }
160 }
161 Ok(Min(Some(total)))
162 }
163}
164
165impl TryFrom<SequencingToMinimizeTardyTaskWeightSerde> for SequencingToMinimizeTardyTaskWeight {
166 type Error = crate::registry::ConstructionError;
167
168 fn try_from(value: SequencingToMinimizeTardyTaskWeightSerde) -> Result<Self, Self::Error> {
169 Self::validate(&value.lengths, &value.weights, &value.deadlines)?;
170 Ok(Self {
171 lengths: value.lengths,
172 weights: value.weights,
173 deadlines: value.deadlines,
174 })
175 }
176}
177
178impl<'de> Deserialize<'de> for SequencingToMinimizeTardyTaskWeight {
179 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180 where
181 D: serde::Deserializer<'de>,
182 {
183 let value = SequencingToMinimizeTardyTaskWeightSerde::deserialize(deserializer)?;
184 Self::try_from(value).map_err(serde::de::Error::custom)
185 }
186}
187
188impl Problem for SequencingToMinimizeTardyTaskWeight {
189 const NAME: &'static str = "SequencingToMinimizeTardyTaskWeight";
190 type Solution = Vec<usize>;
191 type Value = Min<i64>;
192
193 crate::problem_parameters![("num_tasks", num_tasks),];
194
195 fn variant() -> Vec<(&'static str, &'static str)> {
196 crate::variant_params![]
197 }
198
199 fn evaluate(
200 &self,
201 config: &Self::Solution,
202 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
203 let n = self.num_tasks();
204 if config.len() != n {
205 return Err(crate::traits::EvaluationError::InvalidConfiguration(
206 "schedule length does not match the tasks".into(),
207 ));
208 }
209 if config.iter().any(|&task| task >= n) {
210 return Err(crate::traits::EvaluationError::InvalidConfiguration(
211 "schedule contains an out-of-range task".into(),
212 ));
213 }
214 Ok({
215 let Some(schedule) = super::decode_permutation(config, n) else {
216 return Ok(Min(None));
217 };
218 self.tardy_task_weight(&schedule)?
219 })
220 }
221}
222
223impl crate::solvers::BruteForceProblem for SequencingToMinimizeTardyTaskWeight {
224 fn dimensions(&self) -> Vec<usize> {
225 let n = self.num_tasks();
226 vec![n; n]
227 }
228}
229
230crate::declare_variants! {
231 default SequencingToMinimizeTardyTaskWeight => "factorial(num_tasks)" create SequencingToMinimizeTardyTaskWeightCreateSpec,
232}
233
234crate::register_brute_force! {
235 SequencingToMinimizeTardyTaskWeight,
236}
237
238#[cfg(feature = "example-db")]
239pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
240 vec![crate::example_db::specs::ModelExampleSpec {
241 id: "sequencing_to_minimize_tardy_task_weight",
242 instance: Box::new(SequencingToMinimizeTardyTaskWeight::new(
251 vec![3, 2, 4, 1, 2],
252 vec![5, 3, 7, 2, 4],
253 vec![6, 4, 10, 2, 8],
254 )),
255 optimal_config: serde_json::json!(vec![3, 0, 4, 2, 1]),
256 optimal_value: serde_json::json!(3),
257 }]
258}
259
260#[cfg(test)]
261#[path = "../../unit_tests/models/misc/sequencing_to_minimize_tardy_task_weight.rs"]
262mod tests;