1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::misc::MinimumTardinessSequencing;
9use crate::reduction;
10use crate::rules::ilp_helpers::one_hot_decode;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::types::One;
13
14#[derive(Debug, Clone)]
16pub struct ReductionMTSToILP {
17 target: ILP<bool>,
18 num_tasks: usize,
19}
20
21impl ReductionResult for ReductionMTSToILP {
22 type Source = MinimumTardinessSequencing<One>;
23 type Target = ILP<bool>;
24
25 fn target_problem(&self) -> &ILP<bool> {
26 &self.target
27 }
28
29 fn extract_solution(
30 &self,
31 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
32 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
33 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
34
35 Ok({
36 let n = self.num_tasks;
37
38 one_hot_decode(target_solution, n, n, 0)?
39 })
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct ReductionMTSWeightedToILP {
46 target: ILP<bool>,
47 num_tasks: usize,
48}
49
50impl ReductionResult for ReductionMTSWeightedToILP {
51 type Source = MinimumTardinessSequencing<i64>;
52 type Target = ILP<bool>;
53
54 fn target_problem(&self) -> &ILP<bool> {
55 &self.target
56 }
57
58 fn extract_solution(
59 &self,
60 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
61 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
62 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
63
64 Ok({
65 let n = self.num_tasks;
66
67 one_hot_decode(target_solution, n, n, 0)?
68 })
69 }
70}
71
72fn build_common_constraints(
74 n: usize,
75 positions: &[i64],
76 precedences: &[(usize, usize)],
77 x_var: impl Fn(usize, usize) -> usize,
78) -> Vec<LinearConstraint> {
79 let mut constraints = Vec::new();
80
81 for j in 0..n {
83 let terms: Vec<(usize, i64)> = (0..n).map(|p| (x_var(j, p), 1)).collect();
84 constraints.push(LinearConstraint::eq(terms, 1));
85 }
86
87 for p in 0..n {
89 let terms: Vec<(usize, i64)> = (0..n).map(|j| (x_var(j, p), 1)).collect();
90 constraints.push(LinearConstraint::eq(terms, 1));
91 }
92
93 for &(i, j) in precedences {
95 let mut terms: Vec<(usize, i64)> = Vec::new();
96 for (p, &position) in positions.iter().enumerate() {
97 terms.push((x_var(j, p), position));
98 terms.push((x_var(i, p), -position));
99 }
100 constraints.push(LinearConstraint::ge(terms, 1));
101 }
102
103 constraints
104}
105
106#[reduction(transform = {
108 exact {
109 num_vars = "num_tasks * num_tasks + num_tasks",
110 num_constraints = "2 * num_tasks + num_precedences + num_tasks",
111 },
112 upper_bound {
113 max_constraint_magnitude_bits = "num_tasks + 1",
114 num_nonzeros = "(num_tasks * num_tasks + num_tasks) * (2 * num_tasks + num_precedences + num_tasks)",
115 },
116})]
117impl ReduceTo<ILP<bool>> for MinimumTardinessSequencing<One> {
118 type Result = ReductionMTSToILP;
119
120 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
121 let n = self.num_tasks();
122 let num_x_vars = n * n;
123 let num_vars = num_x_vars + n;
124 let positions = (0..n)
125 .map(|position| Self::exact_i64(position, "representing a task position in ILP rows"))
126 .collect::<Result<Vec<_>, _>>()?;
127 let big_m = Self::exact_i64(n, "representing the number of tasks in ILP rows")?;
128
129 let x_var = |j: usize, p: usize| -> usize { j * n + p };
130 let u_var = |j: usize| -> usize { num_x_vars + j };
131
132 let mut constraints = build_common_constraints(n, &positions, self.precedences(), x_var);
133
134 for j in 0..n {
136 let mut terms: Vec<(usize, i64)> =
137 (0..n).map(|p| (x_var(j, p), positions[p] + 1)).collect();
138 terms.push((u_var(j), -big_m));
139 let deadline = self.deadlines()[j].clamp(0, big_m);
141 constraints.push(LinearConstraint::le(terms, deadline));
142 }
143
144 let objective: Vec<(usize, i64)> = (0..n).map(|j| (u_var(j), 1)).collect();
145
146 Ok(ReductionMTSToILP {
147 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
148 .map_err(Self::target_construction)?,
149 num_tasks: n,
150 })
151 }
152}
153
154#[reduction(transform = {
156 exact {
157 num_vars = "num_tasks * num_tasks + num_tasks",
158 num_constraints = "2 * num_tasks + num_precedences + num_tasks * num_tasks",
159 },
160 upper_bound {
161 max_constraint_magnitude_bits = "max_processing_time_bits + num_tasks + 1",
162 num_nonzeros = "(num_tasks * num_tasks + num_tasks) * (2 * num_tasks + num_precedences + num_tasks * num_tasks)",
163 },
164})]
165impl ReduceTo<ILP<bool>> for MinimumTardinessSequencing<i64> {
166 type Result = ReductionMTSWeightedToILP;
167
168 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
169 let n = self.num_tasks();
170 let num_x_vars = n * n;
171 let num_vars = num_x_vars + n;
172 let total_length = self.lengths().iter().try_fold(0_i64, |total, &length| {
173 total.checked_add(length).ok_or_else(|| {
174 crate::rules::ReductionError::integer_overflow::<
175 MinimumTardinessSequencing<i64>,
176 ILP<bool>,
177 >("summing task lengths")
178 })
179 })?;
180 let big_m = total_length;
181 let positions = (0..n)
182 .map(|position| Self::exact_i64(position, "representing a task position in ILP rows"))
183 .collect::<Result<Vec<_>, _>>()?;
184
185 let x_var = |j: usize, p: usize| -> usize { j * n + p };
186 let u_var = |j: usize| -> usize { num_x_vars + j };
187
188 let mut constraints = build_common_constraints(n, &positions, self.precedences(), x_var);
189
190 let lengths = self.lengths();
192 for j in 0..n {
193 let rhs = self.deadlines()[j]
195 .clamp(0, total_length)
196 .checked_sub(lengths[j])
197 .and_then(|value| value.checked_add(total_length))
198 .ok_or_else(|| {
199 crate::rules::ReductionError::integer_overflow::<
200 MinimumTardinessSequencing<i64>,
201 ILP<bool>,
202 >("computing a tardiness constraint bound")
203 })?;
204 for p in 0..n {
205 let mut terms: Vec<(usize, i64)> = Vec::new();
206 terms.push((x_var(j, p), big_m));
207 for pp in 0..p {
208 for (jj, &len) in lengths.iter().enumerate() {
209 terms.push((x_var(jj, pp), len));
210 }
211 }
212 terms.push((u_var(j), -big_m));
213 constraints.push(LinearConstraint::le(terms, rhs));
214 }
215 }
216
217 let objective: Vec<(usize, i64)> = (0..n).map(|j| (u_var(j), 1)).collect();
218
219 Ok(ReductionMTSWeightedToILP {
220 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
221 .map_err(Self::target_construction)?,
222 num_tasks: n,
223 })
224 }
225}
226
227#[cfg(feature = "example-db")]
228pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
229 vec![
230 crate::example_db::specs::RuleExampleSpec {
231 id: "minimumtardinesssequencing_to_ilp",
232 build: || {
233 let source = MinimumTardinessSequencing::<One>::new(3, vec![2, 3, 1], vec![(0, 2)]);
234 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
235 },
236 },
237 crate::example_db::specs::RuleExampleSpec {
238 id: "minimumtardinesssequencing_weighted_to_ilp",
239 build: || {
240 let source = MinimumTardinessSequencing::<i64>::with_lengths(
241 vec![2, 1, 3],
242 vec![3, 4, 5],
243 vec![(0, 2)],
244 );
245 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
246 },
247 },
248 ]
249}
250
251#[cfg(test)]
252#[path = "../unit_tests/rules/minimumtardinesssequencing_ilp.rs"]
253mod tests;