1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
30use crate::models::misc::OpenShopScheduling;
31use crate::reduction;
32use crate::rules::traits::{ReduceTo, ReductionResult};
33
34#[derive(Debug, Clone)]
43pub struct ReductionOSSToILP {
44 target: ILP<i64>,
45 num_jobs: usize,
46 num_machines: usize,
47 num_order_vars: usize,
49}
50
51impl ReductionOSSToILP {
52 fn pair_idx(&self, j: usize, k: usize) -> usize {
53 debug_assert!(j < k);
54 let n = self.num_jobs;
55 j * (2 * n - j - 1) / 2 + (k - j - 1)
56 }
57
58 fn x_var(&self, j: usize, k: usize, i: usize) -> usize {
59 self.pair_idx(j, k) * self.num_machines + i
60 }
61
62 fn s_var(&self, j: usize, i: usize) -> usize {
63 self.num_order_vars + j * self.num_machines + i
64 }
65
66 fn machine_pair_idx(&self, i: usize, ip: usize) -> usize {
67 debug_assert!(i < ip);
68 let m = self.num_machines;
69 i * (2 * m - i - 1) / 2 + (ip - i - 1)
70 }
71
72 fn y_var(&self, j: usize, i: usize, ip: usize) -> usize {
73 let num_machine_pairs = self.num_machines * self.num_machines.saturating_sub(1) / 2;
74 self.num_order_vars
75 + self.num_jobs * self.num_machines
76 + j * num_machine_pairs
77 + self.machine_pair_idx(i, ip)
78 }
79}
80
81impl ReductionResult for ReductionOSSToILP {
82 type Source = OpenShopScheduling;
83 type Target = ILP<i64>;
84
85 fn target_problem(&self) -> &ILP<i64> {
86 &self.target
87 }
88
89 fn extract_solution(
91 &self,
92 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
93 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
94 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
95 let start = self.num_order_vars;
96 let end = start + self.num_jobs * self.num_machines;
97 crate::rules::ilp_helpers::decode_usize_values(&target_solution[start..end])
98 }
99}
100
101#[reduction(
102 transform = exact {
103 num_vars = "num_jobs * (num_jobs - 1) / 2 * num_machines + num_jobs * num_machines + num_jobs * num_machines * (num_machines - 1) / 2 + 1",
104 num_constraints = "num_jobs * (num_jobs - 1) / 2 * num_machines + num_jobs * num_machines + 1 + 2 * num_jobs * (num_jobs - 1) / 2 * num_machines + num_jobs * num_machines * (num_machines - 1) / 2 + 2 * num_jobs * num_machines * (num_machines - 1) / 2 + num_jobs * num_machines",
105 },
106 unavailable = {
107 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
108 }
109)]
110impl ReduceTo<ILP<i64>> for OpenShopScheduling {
111 type Result = ReductionOSSToILP;
112
113 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
114 let n = self.num_jobs();
115 let m = self.num_machines();
116 let p = self.processing_times();
117
118 let num_pairs = n * n.saturating_sub(1) / 2;
119 let num_machine_pairs = m * m.saturating_sub(1) / 2;
120
121 let num_order_vars = num_pairs * m; let num_start_vars = n * m; let num_job_pair_vars = n * num_machine_pairs; let num_vars = num_order_vars + num_start_vars + num_job_pair_vars + 1; let result = ReductionOSSToILP {
128 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
129 .map_err(Self::target_construction)?,
130 num_jobs: n,
131 num_machines: m,
132 num_order_vars,
133 };
134
135 let total_p = p
137 .iter()
138 .flat_map(|row| row.iter())
139 .try_fold(0_i64, |total, &time| total.checked_add(time))
140 .ok_or_else(|| {
141 crate::rules::ReductionError::integer_overflow::<OpenShopScheduling, ILP<i64>>(
142 "summing open-shop processing times",
143 )
144 })?;
145 let big_m = total_p;
146 let processing_times = p;
147
148 let c_var = num_order_vars + num_start_vars + num_job_pair_vars;
149
150 let mut constraints = Vec::new();
151
152 for j in 0..n {
154 for k in (j + 1)..n {
155 for i in 0..m {
156 let x = result.x_var(j, k, i);
157 constraints.push(LinearConstraint::le(vec![(x, 1)], 1));
158 }
159 }
160 }
161
162 for j in 0..n {
165 for i in 0..m {
166 let sji = result.s_var(j, i);
167 constraints.push(LinearConstraint::le(vec![(sji, 1)], big_m));
168 }
169 }
170
171 constraints.push(LinearConstraint::le(vec![(c_var, 1)], big_m));
173
174 for j in 0..n {
190 for k in (j + 1)..n {
191 for (i, (&pji, &pki)) in processing_times[j]
192 .iter()
193 .zip(processing_times[k].iter())
194 .enumerate()
195 {
196 let x = result.x_var(j, k, i);
197 let sj = result.s_var(j, i);
198 let sk = result.s_var(k, i);
199 constraints.push(LinearConstraint::ge(
201 vec![(sk, 1), (sj, -1), (x, -big_m)],
202 pji - big_m,
203 ));
204
205 constraints.push(LinearConstraint::ge(
207 vec![(sj, 1), (sk, -1), (x, big_m)],
208 pki,
209 ));
210 }
211 }
212 }
213
214 for j in 0..n {
216 for i in 0..m {
217 for ip in (i + 1)..m {
218 let y = result.y_var(j, i, ip);
219 constraints.push(LinearConstraint::le(vec![(y, 1)], 1));
220 }
221 }
222 }
223
224 for (j, pj) in processing_times.iter().enumerate() {
231 for i in 0..m {
232 for ip in (i + 1)..m {
233 let y = result.y_var(j, i, ip);
234 let sji = result.s_var(j, i);
235 let sjip = result.s_var(j, ip);
236 let pji = pj[i];
237 let pjip = pj[ip];
238
239 constraints.push(LinearConstraint::ge(
241 vec![(sjip, 1), (sji, -1), (y, -big_m)],
242 pji - big_m,
243 ));
244
245 constraints.push(LinearConstraint::ge(
247 vec![(sji, 1), (sjip, -1), (y, big_m)],
248 pjip,
249 ));
250 }
251 }
252 }
253
254 for (j, pj) in processing_times.iter().enumerate() {
256 for (i, &pji) in pj.iter().enumerate() {
257 let sji = result.s_var(j, i);
258 constraints.push(LinearConstraint::ge(vec![(c_var, 1), (sji, -1)], pji));
259 }
260 }
261
262 let objective = vec![(c_var, 1)];
264
265 Ok(ReductionOSSToILP {
266 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
267 .map_err(Self::target_construction)?,
268 num_jobs: n,
269 num_machines: m,
270 num_order_vars,
271 })
272 }
273}
274
275#[cfg(feature = "example-db")]
276pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
277 vec![crate::example_db::specs::RuleExampleSpec {
278 id: "openshopscheduling_to_ilp",
279 build: || {
280 let source = OpenShopScheduling::new(2, vec![vec![1, 2], vec![2, 1]]);
282 crate::example_db::specs::rule_example_via_ilp::<_, i64>(source)
283 },
284 }]
285}
286
287#[cfg(test)]
288#[path = "../unit_tests/rules/openshopscheduling_ilp.rs"]
289mod tests;