1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
8use crate::models::misc::SequencingToMinimizeWeightedTardiness;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11
12#[derive(Debug, Clone)]
21pub struct ReductionSTMWTToILP {
22 target: ILP<i64, i64, Bounded>,
23 num_tasks: usize,
24 num_order_vars: usize,
25}
26
27impl ReductionResult for ReductionSTMWTToILP {
28 type Source = SequencingToMinimizeWeightedTardiness;
29 type Target = ILP<i64, i64, Bounded>;
30
31 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
32 &self.target
33 }
34
35 fn extract_solution(
37 &self,
38 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
39 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
40 crate::rules::traits::validate_target_witness(
41 self.target_problem(),
42 target_solution,
43 |value| value.value.is_some(),
44 "target ILP assignment is infeasible",
45 )?;
46
47 Ok({
48 let n = self.num_tasks;
49 let c_offset = self.num_order_vars;
50 let mut jobs: Vec<usize> = (0..n).collect();
51 jobs.sort_by_key(|&j| (target_solution[c_offset + j], j));
52 jobs
53 })
54 }
55}
56
57#[crate::aggregate_reduction(ilp_feasibility)]
58impl crate::rules::AggregateReductionResult for ReductionSTMWTToILP {}
59
60#[reduction(
61 transform = upper_bound {
62 max_constraint_magnitude_bits = "max_numeric_magnitude_bits + num_tasks",
63 num_vars = "num_tasks^2 + 2 * num_tasks",
64 num_constraints = "2 * num_tasks^2 + 3 * num_tasks + 1",
65 num_nonzeros = "(num_tasks^2 + 2 * num_tasks) * (2 * num_tasks^2 + 3 * num_tasks + 1)",
66 },
67)]
68impl ReduceTo<ILP<i64, i64, Bounded>> for SequencingToMinimizeWeightedTardiness {
69 type Result = ReductionSTMWTToILP;
70
71 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
72 let n = self.num_tasks();
73 let num_order_vars = n * n.saturating_sub(1) / 2;
74 let num_vars = num_order_vars + 2 * n;
75
76 let order_var = |i: usize, j: usize| -> usize {
77 debug_assert!(i < j);
78 i * (2 * n - i - 1) / 2 + (j - i - 1)
79 };
80 let c_var = |j: usize| -> usize { num_order_vars + j };
81 let t_var = |j: usize| -> usize { num_order_vars + n + j };
82
83 let lengths = self.lengths();
84 let deadlines = self.deadlines();
85 let weights = self.weights();
86 let bound = self.bound();
87
88 let horizon = lengths
90 .iter()
91 .try_fold(0_i64, |total, &length| total.checked_add(length))
92 .ok_or_else(|| {
93 crate::rules::ReductionError::integer_overflow::<
94 SequencingToMinimizeWeightedTardiness,
95 ILP<i64, i64, Bounded>,
96 >("summing task processing times")
97 })?;
98 let big_m = horizon;
99
100 let mut constraints = Vec::new();
101
102 for i in 0..n {
104 for j in (i + 1)..n {
105 constraints.push(LinearConstraint::le(vec![(order_var(i, j), 1)], 1));
106 constraints.push(LinearConstraint::ge(vec![(order_var(i, j), 1)], 0));
107 }
108 }
109
110 for (j, &l_j) in lengths.iter().enumerate() {
112 constraints.push(LinearConstraint::ge(vec![(c_var(j), 1)], l_j));
113 }
114
115 for i in 0..n {
117 for (j, &l_j) in lengths.iter().enumerate() {
118 if i == j {
119 continue;
120 }
121 if i < j {
122 constraints.push(LinearConstraint::ge(
126 vec![(c_var(j), 1), (c_var(i), -1), (order_var(i, j), -big_m)],
127 l_j - big_m,
128 ));
129 } else {
130 constraints.push(LinearConstraint::ge(
134 vec![(c_var(j), 1), (c_var(i), -1), (order_var(j, i), big_m)],
135 l_j,
136 ));
137 }
138 }
139 }
140
141 for (j, &d_j) in deadlines.iter().enumerate() {
143 constraints.push(LinearConstraint::ge(
144 vec![(t_var(j), 1), (c_var(j), -1)],
145 -d_j,
146 ));
147 }
148
149 for j in 0..n {
151 constraints.push(LinearConstraint::ge(vec![(t_var(j), 1)], 0));
152 }
153
154 let terms: Vec<(usize, i64)> = (0..n).map(|j| (t_var(j), weights[j])).collect();
156 constraints.push(LinearConstraint::le(terms, bound));
157
158 let mut variables = vec![IntegerVariable::binary(); num_vars];
160 variables[num_order_vars..]
161 .fill(IntegerVariable::new(Some(0), Some(horizon)).map_err(Self::target_construction)?);
162
163 Ok(ReductionSTMWTToILP {
164 target: ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
165 .map_err(Self::target_construction)?,
166 num_tasks: n,
167 num_order_vars,
168 })
169 }
170}
171
172#[cfg(feature = "example-db")]
173pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
174 vec![crate::example_db::specs::RuleExampleSpec {
175 id: "sequencingtominimizeweightedtardiness_to_ilp",
176 build: || {
177 let source = SequencingToMinimizeWeightedTardiness::new(
178 vec![3, 4, 2],
179 vec![2, 3, 1],
180 vec![5, 8, 4],
181 10,
182 );
183 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
184 },
185 }]
186}
187
188#[cfg(test)]
189#[path = "../unit_tests/rules/sequencingtominimizeweightedtardiness_ilp.rs"]
190mod tests;