1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
6use crate::models::misc::SequencingWithReleaseTimesAndDeadlines;
7use crate::reduction;
8use crate::rules::traits::{ReduceTo, ReductionResult};
9
10#[derive(Debug, Clone)]
11pub struct ReductionSWRTDToBoundedILP {
12 target: ILP<i64, i64, Bounded>,
13 lengths: Vec<i64>,
14}
15
16impl ReductionResult for ReductionSWRTDToBoundedILP {
17 type Source = SequencingWithReleaseTimesAndDeadlines;
18 type Target = ILP<i64, i64, Bounded>;
19
20 fn target_problem(&self) -> &Self::Target {
21 &self.target
22 }
23
24 fn extract_solution(&self, values: &Vec<i64>) -> crate::rules::ExtractionResult<Vec<usize>> {
25 crate::rules::traits::validate_target_witness(
26 self.target_problem(),
27 values,
28 |value| value.value.is_some(),
29 "target ILP assignment is infeasible",
30 )?;
31 let mut order: Vec<_> = (0..self.lengths.len()).collect();
32 order.sort_by_key(|&j| (values[j], self.lengths[j], j));
34 Ok(order)
35 }
36}
37
38#[crate::aggregate_reduction(ilp_feasibility)]
39impl crate::rules::AggregateReductionResult for ReductionSWRTDToBoundedILP {}
40
41#[reduction(transform = upper_bound {
42 num_vars = "num_tasks + num_tasks * (num_tasks - 1) / 2",
43 num_constraints = "num_tasks * (num_tasks - 1) + 1",
44 num_nonzeros = "3 * num_tasks * (num_tasks - 1)",
45 max_constraint_magnitude_bits = "time_horizon_bits",
46})]
47impl ReduceTo<ILP<i64, i64, Bounded>> for SequencingWithReleaseTimesAndDeadlines {
48 type Result = ReductionSWRTDToBoundedILP;
49
50 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
51 let construct = <Self as ReduceTo<ILP<i64, i64, Bounded>>>::target_construction;
52 let n = self.num_tasks();
53 let lengths = self.lengths();
54 let releases = self.release_times();
55 let deadlines = self.deadlines();
56 if (0..n).any(|j| releases[j] > deadlines[j] || lengths[j] > deadlines[j] - releases[j]) {
57 return Ok(Self::Result {
58 target: ILP::with_variables(
59 vec![],
60 vec![LinearConstraint::eq(vec![], 1)],
61 vec![],
62 ObjectiveSense::Minimize,
63 )
64 .map_err(construct)?,
65 lengths: lengths.to_vec(),
66 });
67 }
68 let pairs = n
69 .checked_mul(n.saturating_sub(1))
70 .and_then(|count| n.checked_add(count / 2))
71 .ok_or_else(|| {
72 crate::rules::ReductionError::integer_overflow::<Self, ILP<i64, i64, Bounded>>(
73 "counting sequencing variables",
74 )
75 })?;
76 let mut variables = Vec::with_capacity(pairs);
77 for j in 0..n {
78 variables.push(
79 IntegerVariable::new(Some(releases[j]), Some(deadlines[j] - lengths[j]))
80 .map_err(construct)?,
81 );
82 }
83 let mut constraints = Vec::new();
84 for i in 0..n {
85 for j in i + 1..n {
86 let y = variables.len();
87 let identical = (lengths[i], releases[i], deadlines[i])
89 == (lengths[j], releases[j], deadlines[j]);
90 variables.push(
91 IntegerVariable::new(Some(i64::from(identical)), Some(1)).map_err(construct)?,
92 );
93 let forward = (deadlines[i] - releases[j]).max(0);
94 let backward = (deadlines[j] - releases[i]).max(0);
95 constraints.push(LinearConstraint::ge(
96 vec![(j, 1), (i, -1), (y, -forward)],
97 lengths[i] - forward,
98 ));
99 constraints.push(LinearConstraint::ge(
100 vec![(i, 1), (j, -1), (y, backward)],
101 lengths[j],
102 ));
103 }
104 }
105 let target = ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
106 .map_err(construct)?;
107 crate::rules::ilp_helpers::validate_bounded_constraint_arithmetic::<Self>(&target)?;
108 Ok(Self::Result {
109 target,
110 lengths: lengths.to_vec(),
111 })
112 }
113}
114
115#[derive(Debug, Clone)]
116pub struct ReductionSWRTDToILP {
117 bounded: ReductionSWRTDToBoundedILP,
118 binary: crate::rules::ilp_i64_ilp_bool::ReductionIntILPToBinaryILP,
119}
120
121impl ReductionResult for ReductionSWRTDToILP {
122 type Source = SequencingWithReleaseTimesAndDeadlines;
123 type Target = ILP<bool>;
124
125 fn target_problem(&self) -> &Self::Target {
126 self.binary.target_problem()
127 }
128
129 fn extract_solution(&self, values: &Vec<i64>) -> crate::rules::ExtractionResult<Vec<usize>> {
130 self.bounded
131 .extract_solution(&self.binary.extract_solution(values)?)
132 }
133}
134
135#[crate::aggregate_reduction(ilp_feasibility)]
136impl crate::rules::AggregateReductionResult for ReductionSWRTDToILP {}
137
138#[reduction(transform = upper_bound {
139 num_vars = "num_tasks * time_horizon_bits + num_tasks * (num_tasks - 1) / 2",
140 num_constraints = "num_tasks * (num_tasks - 1) + 1",
141 num_nonzeros = "num_tasks * (num_tasks - 1) * (2 * time_horizon_bits + 1)",
142 max_constraint_magnitude_bits = "time_horizon_bits",
143})]
144impl ReduceTo<ILP<bool>> for SequencingWithReleaseTimesAndDeadlines {
145 type Result = ReductionSWRTDToILP;
146
147 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
148 let bounded = ReduceTo::<ILP<i64, i64, Bounded>>::reduce_to(self)?;
149 let binary = ReduceTo::<ILP<bool>>::reduce_to(bounded.target_problem())?;
150 Ok(Self::Result { bounded, binary })
151 }
152}
153
154#[cfg(feature = "example-db")]
155pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
156 use crate::example_db::specs::{
157 rule_example_via_bounded_ilp, rule_example_via_ilp, RuleExampleSpec,
158 };
159 fn source() -> SequencingWithReleaseTimesAndDeadlines {
160 SequencingWithReleaseTimesAndDeadlines::new(vec![1, 2, 1], vec![0, 0, 2], vec![3, 3, 4])
161 }
162 vec![
163 RuleExampleSpec {
164 id: "sequencingwithreleasetimesanddeadlines_to_ilp",
165 build: || rule_example_via_ilp::<_, bool>(source()),
166 },
167 RuleExampleSpec {
168 id: "sequencingwithreleasetimesanddeadlines_to_bounded_ilp",
169 build: || rule_example_via_bounded_ilp(source()),
170 },
171 ]
172}
173
174#[cfg(test)]
175#[path = "../unit_tests/rules/sequencingwithreleasetimesanddeadlines_ilp.rs"]
176mod tests;