1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
9use crate::models::misc::FlowShopScheduling;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13#[derive(Debug, Clone)]
22pub struct ReductionFSSToILP {
23 target: ILP<i64, i64, Bounded>,
24 num_jobs: usize,
25 num_machines: usize,
26 num_order_vars: usize,
27}
28
29impl ReductionResult for ReductionFSSToILP {
30 type Source = FlowShopScheduling;
31 type Target = ILP<i64, i64, Bounded>;
32
33 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
34 &self.target
35 }
36
37 fn extract_solution(
41 &self,
42 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
43 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
44 crate::rules::traits::validate_target_witness(
45 self.target_problem(),
46 target_solution,
47 |value| value.value.is_some(),
48 "target ILP assignment is infeasible",
49 )?;
50
51 Ok({
52 let n = self.num_jobs;
53 let m = self.num_machines;
54 let c_offset = self.num_order_vars;
55 let mut jobs: Vec<usize> = (0..n).collect();
56 jobs.sort_by_key(|&j| {
57 let start = c_offset + j * m;
58 (
59 target_solution[start..start + m]
60 .iter()
61 .map(|&time| i128::from(time))
62 .sum::<i128>(),
63 j,
64 )
65 });
66 jobs
67 })
68 }
69}
70
71#[crate::aggregate_reduction(ilp_feasibility)]
72impl crate::rules::AggregateReductionResult for ReductionFSSToILP {}
73
74#[reduction(
75 transform = upper_bound {
76 max_constraint_magnitude_bits = "max_time_bits + 1",
77 num_vars = "num_jobs * (num_jobs - 1) / 2 + num_jobs * num_processors",
78 num_constraints = "num_jobs * (num_jobs - 1) + num_jobs + num_jobs * (num_processors - 1) + num_jobs * (num_jobs - 1) * num_processors + num_jobs",
79 num_nonzeros = "(num_jobs * (num_jobs - 1) / 2 + num_jobs * num_processors) * (num_jobs * (num_jobs - 1) + num_jobs + num_jobs * (num_processors - 1) + num_jobs * (num_jobs - 1) * num_processors + num_jobs)",
80 },
81)]
82impl ReduceTo<ILP<i64, i64, Bounded>> for FlowShopScheduling {
83 type Result = ReductionFSSToILP;
84
85 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
86 let n = self.num_jobs();
87 let m = self.num_processors();
88
89 let num_order_vars = n * n.saturating_sub(1) / 2;
90 let num_completion_vars = n * m;
91 let num_vars = num_order_vars + num_completion_vars;
92
93 let order_var = |i: usize, j: usize| -> usize {
95 debug_assert!(i < j);
96 i * (2 * n - i - 1) / 2 + (j - i - 1)
97 };
98 let c_var = |j: usize, q: usize| -> usize { num_order_vars + j * m + q };
100
101 let p = self.task_lengths();
102 let d = self.deadline();
103 let deadline = d;
104
105 let max_p = p
107 .iter()
108 .flat_map(|row| row.iter())
109 .copied()
110 .max()
111 .unwrap_or(0);
112 let big_m = d.checked_add(max_p).ok_or_else(|| {
113 crate::rules::ReductionError::integer_overflow::<
114 FlowShopScheduling,
115 ILP<i64, i64, Bounded>,
116 >("computing the flow-shop big-M bound")
117 })?;
118 let mut constraints = Vec::new();
119
120 for i in 0..n {
124 for j in (i + 1)..n {
125 constraints.push(LinearConstraint::le(vec![(order_var(i, j), 1)], 1));
126 constraints.push(LinearConstraint::ge(vec![(order_var(i, j), 1)], 0));
127 }
128 }
129
130 for (j, p_j) in p.iter().enumerate() {
132 if let Some(&length) = p_j.first() {
133 constraints.push(LinearConstraint::ge(vec![(c_var(j, 0), 1)], length));
134 }
135 }
136
137 for (j, p_j) in p.iter().enumerate() {
139 for q in 0..(m.saturating_sub(1)) {
140 constraints.push(LinearConstraint::ge(
142 vec![(c_var(j, q + 1), 1), (c_var(j, q), -1)],
143 p_j[q + 1],
144 ));
145 }
146 }
147
148 for i in 0..n {
152 for (j, p_j) in p.iter().enumerate() {
153 if i == j {
154 continue;
155 }
156 for (q, &p_jq) in p_j.iter().enumerate() {
157 if i < j {
158 constraints.push(LinearConstraint::ge(
162 vec![
163 (c_var(j, q), 1),
164 (c_var(i, q), -1),
165 (order_var(i, j), -big_m),
166 ],
167 p_jq - big_m,
168 ));
169 } else {
170 constraints.push(LinearConstraint::ge(
175 vec![
176 (c_var(j, q), 1),
177 (c_var(i, q), -1),
178 (order_var(j, i), big_m),
179 ],
180 p_jq,
181 ));
182 }
183 }
184 }
185 }
186
187 if m > 0 {
189 for j in 0..n {
190 constraints.push(LinearConstraint::le(vec![(c_var(j, m - 1), 1)], deadline));
191 }
192 }
193
194 let mut variables = vec![IntegerVariable::binary(); num_vars];
195 variables[num_order_vars..].fill(
196 IntegerVariable::new(Some(0), Some(deadline)).map_err(Self::target_construction)?,
197 );
198
199 Ok(ReductionFSSToILP {
200 target: ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
201 .map_err(Self::target_construction)?,
202 num_jobs: n,
203 num_machines: m,
204 num_order_vars,
205 })
206 }
207}
208
209#[cfg(feature = "example-db")]
210pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
211 vec![crate::example_db::specs::RuleExampleSpec {
212 id: "flowshopscheduling_to_ilp",
213 build: || {
214 let source = FlowShopScheduling::new(2, vec![vec![2, 3], vec![3, 2], vec![1, 4]], 10);
216 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
217 },
218 }]
219}
220
221#[cfg(test)]
222#[path = "../unit_tests/rules/flowshopscheduling_ilp.rs"]
223mod tests;