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(
108 transform = exact {
109 num_vars = "num_tasks * num_tasks + num_tasks",
110 num_constraints = "2 * num_tasks + num_precedences + num_tasks",
111 },
112 unavailable = {
113 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
114 }
115)]
116impl ReduceTo<ILP<bool>> for MinimumTardinessSequencing<One> {
117 type Result = ReductionMTSToILP;
118
119 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
120 let n = self.num_tasks();
121 let num_x_vars = n * n;
122 let num_vars = num_x_vars + n;
123 let positions = (0..n)
124 .map(|position| Self::exact_i64(position, "representing a task position in ILP rows"))
125 .collect::<Result<Vec<_>, _>>()?;
126 let big_m = Self::exact_i64(n, "representing the number of tasks in ILP rows")?;
127
128 let x_var = |j: usize, p: usize| -> usize { j * n + p };
129 let u_var = |j: usize| -> usize { num_x_vars + j };
130
131 let mut constraints = build_common_constraints(n, &positions, self.precedences(), x_var);
132
133 for j in 0..n {
135 let mut terms: Vec<(usize, i64)> =
136 (0..n).map(|p| (x_var(j, p), positions[p] + 1)).collect();
137 terms.push((u_var(j), -big_m));
138 let deadline = self.deadlines()[j];
139 constraints.push(LinearConstraint::le(terms, deadline));
140 }
141
142 let objective: Vec<(usize, i64)> = (0..n).map(|j| (u_var(j), 1)).collect();
143
144 Ok(ReductionMTSToILP {
145 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
146 .map_err(Self::target_construction)?,
147 num_tasks: n,
148 })
149 }
150}
151
152#[reduction(
154 transform = exact {
155 num_vars = "num_tasks * num_tasks + num_tasks",
156 num_constraints = "2 * num_tasks + num_precedences + num_tasks * num_tasks",
157 },
158 unavailable = {
159 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
160 }
161)]
162impl ReduceTo<ILP<bool>> for MinimumTardinessSequencing<i64> {
163 type Result = ReductionMTSWeightedToILP;
164
165 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
166 let n = self.num_tasks();
167 let num_x_vars = n * n;
168 let num_vars = num_x_vars + n;
169 let total_length = self.lengths().iter().try_fold(0_i64, |total, &length| {
170 total.checked_add(length).ok_or_else(|| {
171 crate::rules::ReductionError::integer_overflow::<
172 MinimumTardinessSequencing<i64>,
173 ILP<bool>,
174 >("summing task lengths")
175 })
176 })?;
177 let big_m = total_length;
178 let positions = (0..n)
179 .map(|position| Self::exact_i64(position, "representing a task position in ILP rows"))
180 .collect::<Result<Vec<_>, _>>()?;
181
182 let x_var = |j: usize, p: usize| -> usize { j * n + p };
183 let u_var = |j: usize| -> usize { num_x_vars + j };
184
185 let mut constraints = build_common_constraints(n, &positions, self.precedences(), x_var);
186
187 let lengths = self.lengths();
189 for j in 0..n {
190 for p in 0..n {
191 let mut terms: Vec<(usize, i64)> = Vec::new();
192 terms.push((x_var(j, p), big_m));
193 for pp in 0..p {
194 for (jj, &len) in lengths.iter().enumerate() {
195 terms.push((x_var(jj, pp), len));
196 }
197 }
198 terms.push((u_var(j), -big_m));
199 let rhs = self.deadlines()[j]
200 .checked_sub(lengths[j])
201 .and_then(|value| value.checked_add(total_length))
202 .ok_or_else(|| {
203 crate::rules::ReductionError::integer_overflow::<
204 MinimumTardinessSequencing<i64>,
205 ILP<bool>,
206 >("computing a tardiness constraint bound")
207 })?;
208 constraints.push(LinearConstraint::le(terms, rhs));
209 }
210 }
211
212 let objective: Vec<(usize, i64)> = (0..n).map(|j| (u_var(j), 1)).collect();
213
214 Ok(ReductionMTSWeightedToILP {
215 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
216 .map_err(Self::target_construction)?,
217 num_tasks: n,
218 })
219 }
220}
221
222#[cfg(feature = "example-db")]
223pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
224 vec![
225 crate::example_db::specs::RuleExampleSpec {
226 id: "minimumtardinesssequencing_to_ilp",
227 build: || {
228 let source = MinimumTardinessSequencing::<One>::new(3, vec![2, 3, 1], vec![(0, 2)]);
229 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
230 },
231 },
232 crate::example_db::specs::RuleExampleSpec {
233 id: "minimumtardinesssequencing_weighted_to_ilp",
234 build: || {
235 let source = MinimumTardinessSequencing::<i64>::with_lengths(
236 vec![2, 1, 3],
237 vec![3, 4, 5],
238 vec![(0, 2)],
239 );
240 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
241 },
242 },
243 ]
244}
245
246#[cfg(test)]
247#[path = "../unit_tests/rules/minimumtardinesssequencing_ilp.rs"]
248mod tests;