1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
9use crate::models::misc::SchedulingToMinimizeWeightedCompletionTime;
10use crate::reduction;
11use crate::rules::ilp_helpers::one_hot_decode_rows;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13
14#[derive(Debug, Clone)]
24pub struct ReductionSMWCTToILP {
25 target: ILP<i64, i64, Bounded>,
26 num_tasks: usize,
27 num_processors: usize,
28}
29
30impl ReductionSMWCTToILP {
31 fn x_var(&self, task: usize, processor: usize) -> usize {
32 task * self.num_processors + processor
33 }
34
35 fn c_var(&self, task: usize) -> usize {
36 self.num_tasks * self.num_processors + task
37 }
38
39 fn y_var(&self, i: usize, j: usize) -> usize {
40 debug_assert!(i < j);
41 let base = self.num_tasks * self.num_processors + self.num_tasks;
42 base + i * (2 * self.num_tasks - i - 1) / 2 + (j - i - 1)
43 }
44}
45
46impl ReductionResult for ReductionSMWCTToILP {
47 type Source = SchedulingToMinimizeWeightedCompletionTime;
48 type Target = ILP<i64, i64, Bounded>;
49
50 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
51 &self.target
52 }
53
54 fn extract_solution(
56 &self,
57 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
58 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
59 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
60
61 one_hot_decode_rows(target_solution, self.num_tasks, self.num_processors, 0)
62 }
63}
64
65#[reduction(transform = {
66 exact {
67 num_vars = "num_tasks * num_processors + num_tasks + num_tasks * (num_tasks - 1) / 2",
68 num_constraints = "num_tasks + num_tasks * num_processors + 2 * num_tasks + 2 * num_tasks * (num_tasks - 1) / 2 * num_processors + num_tasks * (num_tasks - 1) / 2",
69 },
70 upper_bound {
71 max_constraint_magnitude_bits = "max_processing_time_bits + num_tasks + 2",
72 num_nonzeros = "(num_tasks * num_processors + num_tasks + num_tasks * (num_tasks - 1) / 2) * (num_tasks + num_tasks * num_processors + 2 * num_tasks + 2 * num_tasks * (num_tasks - 1) / 2 * num_processors + num_tasks * (num_tasks - 1) / 2)",
73 },
74})]
75impl ReduceTo<ILP<i64, i64, Bounded>> for SchedulingToMinimizeWeightedCompletionTime {
76 type Result = ReductionSMWCTToILP;
77
78 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
79 let n = self.num_tasks();
80 let m = self.num_processors();
81
82 let total_processing_time = self
83 .lengths()
84 .iter()
85 .try_fold(0_i64, |total, &length| total.checked_add(length))
86 .ok_or_else(|| {
87 crate::rules::ReductionError::integer_overflow::<
88 SchedulingToMinimizeWeightedCompletionTime,
89 ILP<i64, i64, Bounded>,
90 >("summing task processing times")
91 })?;
92 let lengths = self.lengths();
93 let weights = self.weights();
94 let big_m = total_processing_time;
95 let two_big_m = big_m.checked_mul(2).ok_or_else(|| {
96 crate::rules::ReductionError::integer_overflow::<
97 SchedulingToMinimizeWeightedCompletionTime,
98 ILP<i64, i64, Bounded>,
99 >("doubling the disjunctive scheduling bound")
100 })?;
101 let three_big_m = big_m.checked_mul(3).ok_or_else(|| {
102 crate::rules::ReductionError::integer_overflow::<
103 SchedulingToMinimizeWeightedCompletionTime,
104 ILP<i64, i64, Bounded>,
105 >("tripling the disjunctive scheduling bound")
106 })?;
107
108 let num_pairs = n * n.saturating_sub(1) / 2;
109 let num_vars = n * m + n + num_pairs;
110
111 let result = ReductionSMWCTToILP {
112 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
113 .map_err(Self::target_construction)?,
114 num_tasks: n,
115 num_processors: m,
116 };
117
118 let mut constraints = Vec::new();
119
120 for t in 0..n {
123 let terms: Vec<(usize, i64)> = (0..m).map(|p| (result.x_var(t, p), 1)).collect();
124 constraints.push(LinearConstraint::eq(terms, 1));
125 }
126
127 for t in 0..n {
129 for p in 0..m {
130 constraints.push(LinearConstraint::le(vec![(result.x_var(t, p), 1)], 1));
131 }
132 }
133
134 for (t, &length) in lengths.iter().enumerate() {
136 constraints.push(LinearConstraint::ge(vec![(result.c_var(t), 1)], length));
137 constraints.push(LinearConstraint::le(vec![(result.c_var(t), 1)], big_m));
138 }
139
140 for i in 0..n {
153 for j in (i + 1)..n {
154 let y = result.y_var(i, j);
155 let ci = result.c_var(i);
156 let cj = result.c_var(j);
157 let li = lengths[i];
158 let lj = lengths[j];
159
160 for p in 0..m {
161 let xip = result.x_var(i, p);
162 let xjp = result.x_var(j, p);
163
164 constraints.push(LinearConstraint::ge(
168 vec![(cj, 1), (ci, -1), (y, -big_m), (xip, -big_m), (xjp, -big_m)],
169 lj - three_big_m,
170 ));
171
172 constraints.push(LinearConstraint::ge(
176 vec![(ci, 1), (cj, -1), (y, big_m), (xip, -big_m), (xjp, -big_m)],
177 li - two_big_m,
178 ));
179 }
180
181 constraints.push(LinearConstraint::le(vec![(y, 1)], 1));
183 }
184 }
185
186 let objective: Vec<(usize, i64)> = weights
188 .iter()
189 .copied()
190 .enumerate()
191 .map(|(task, weight)| (result.c_var(task), weight))
192 .collect();
193
194 let mut variables = vec![IntegerVariable::binary(); num_vars];
195 variables[n * m..n * m + n].fill(
196 IntegerVariable::new(Some(0), Some(total_processing_time))
197 .map_err(Self::target_construction)?,
198 );
199
200 Ok(ReductionSMWCTToILP {
201 target: ILP::with_variables(
202 variables,
203 constraints,
204 objective,
205 ObjectiveSense::Minimize,
206 )
207 .map_err(Self::target_construction)?,
208 num_tasks: n,
209 num_processors: m,
210 })
211 }
212}
213
214#[cfg(feature = "example-db")]
215pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
216 vec![crate::example_db::specs::RuleExampleSpec {
217 id: "schedulingtominimizeweightedcompletiontime_to_ilp",
218 build: || {
219 let source =
221 SchedulingToMinimizeWeightedCompletionTime::new(vec![1, 2, 3], vec![4, 2, 1], 2);
222 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
223 },
224 }]
225}
226
227#[cfg(test)]
228#[path = "../unit_tests/rules/schedulingtominimizeweightedcompletiontime_ilp.rs"]
229mod tests;