Skip to main content

problemreductions/rules/
sequencingwithreleasetimesanddeadlines_ilp.rs

1//! Exact release/deadline sequencing using bounded starts and pairwise order.
2//! The binary endpoint uniformly composes the same construction with the
3//! existing bounded-integer encoding; neither construction expands time slots.
4
5use 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        // A zero-duration job at a positive job's start must come first.
33        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                // Relabel identical tasks into index order; their source data are interchangeable.
88                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;