Skip to main content

problemreductions/rules/
schedulingwithindividualdeadlines_ilp.rs

1//! Reduction from SchedulingWithIndividualDeadlines to `ILP<bool>`.
2//!
3//! Uses a time-indexed binary formulation with per-task deadline windows:
4//! - Variables: Binary x_{j,t} where x_{j,t} = 1 iff task j is scheduled at time slot t,
5//!   for t in 0..max_deadline (slots beyond each task's deadline are zero-fixed).
6//! - Variable index: j * max_deadline + t  for j in 0..num_tasks, t in 0..max_deadline
7//! - Constraints:
8//!   1. One-hot: Σ_{t<d_j} x_{j,t} = 1 for each task j (using only valid slots)
9//!   2. Zero beyond deadline: x_{j,t} = 0 for t >= d_j
10//!   3. Capacity: Σ_j x_{j,t} ≤ m for each time slot t
11//!   4. Precedence: Σ_t t·x_{j,t} ≥ Σ_t t·x_{i,t} + 1 for each (i,j)
12//! - Objective: Minimize 0 (feasibility)
13
14use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
15use crate::models::misc::SchedulingWithIndividualDeadlines;
16use crate::reduction;
17use crate::rules::ilp_helpers::one_hot_decode_rows;
18use crate::rules::traits::{ReduceTo, ReductionResult};
19
20/// Result of reducing SchedulingWithIndividualDeadlines to `ILP<bool>`.
21///
22/// Variable layout: x_{j,t} at index j * max_deadline + t
23/// for j in 0..num_tasks, t in 0..max_deadline.
24#[derive(Debug, Clone)]
25pub struct ReductionSWIDToILP {
26    target: ILP<bool>,
27    num_tasks: usize,
28    max_deadline: usize,
29}
30
31impl ReductionResult for ReductionSWIDToILP {
32    type Source = SchedulingWithIndividualDeadlines;
33    type Target = ILP<bool>;
34
35    fn target_problem(&self) -> &ILP<bool> {
36        &self.target
37    }
38
39    /// Extract schedule from ILP solution.
40    ///
41    /// For each task j, find the time slot t where x_{j,t} = 1.
42    fn extract_solution(
43        &self,
44        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
45    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
46        crate::rules::traits::validate_target_witness(
47            self.target_problem(),
48            target_solution,
49            |value| value.value.is_some(),
50            "target ILP assignment is infeasible",
51        )?;
52
53        one_hot_decode_rows(target_solution, self.num_tasks, self.max_deadline, 0)
54    }
55}
56
57#[crate::aggregate_reduction(ilp_feasibility)]
58impl crate::rules::AggregateReductionResult for ReductionSWIDToILP {}
59
60#[reduction(transform = {
61    exact {
62        num_vars = "num_tasks * max_deadline",
63        num_constraints = "num_tasks + max_deadline + num_precedences + 1",
64    },
65    upper_bound {
66        max_constraint_magnitude_bits = "num_tasks + max_deadline + 1",
67        num_nonzeros = "(num_tasks * max_deadline) * (num_tasks + max_deadline + num_precedences + 1)",
68    },
69})]
70impl ReduceTo<ILP<bool>> for SchedulingWithIndividualDeadlines {
71    type Result = ReductionSWIDToILP;
72
73    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
74        let n = self.num_tasks();
75        let max_d = usize::try_from(self.max_deadline()).map_err(|_| {
76            crate::rules::ReductionError::integer_overflow::<
77                SchedulingWithIndividualDeadlines,
78                ILP<bool>,
79            >("validated deadline must fit usize")
80        })?;
81        let num_vars = n * max_d;
82
83        let var = |j: usize, t: usize| j * max_d + t;
84        let processor_count = Self::exact_i64(
85            self.num_processors().min(n),
86            "encoding the processor capacity",
87        )?;
88
89        let mut constraints = Vec::new();
90
91        // 1. One-hot: for each task j, sum over valid slots 0..d_j equals 1
92        for j in 0..n {
93            let dj = usize::try_from(self.deadlines()[j]).map_err(|_| {
94                crate::rules::ReductionError::integer_overflow::<
95                    SchedulingWithIndividualDeadlines,
96                    ILP<bool>,
97                >("validated deadline must fit usize")
98            })?;
99            let terms: Vec<(usize, i64)> = (0..dj).map(|t| (var(j, t), 1)).collect();
100            constraints.push(LinearConstraint::eq(terms, 1));
101        }
102
103        // Binary variables are nonnegative, so a zero sum fixes every unused slot.
104        let unused_slots = self
105            .deadlines()
106            .iter()
107            .enumerate()
108            .flat_map(|(j, &deadline)| {
109                (usize::try_from(deadline).expect("validated deadline fits usize")..max_d)
110                    .map(move |t| (var(j, t), 1))
111            })
112            .collect();
113        constraints.push(LinearConstraint::eq(unused_slots, 0));
114
115        // 2. Capacity: Σ_j x_{j,t} ≤ m for each time slot t
116        for t in 0..max_d {
117            let terms: Vec<(usize, i64)> = (0..n).map(|j| (var(j, t), 1)).collect();
118            constraints.push(LinearConstraint::le(terms, processor_count));
119        }
120
121        // 3. Precedence: Σ_t t·x_{j,t} - Σ_t t·x_{i,t} ≥ 1 for each (i,j)
122        for &(i, j) in self.precedences() {
123            let di = usize::try_from(self.deadlines()[i]).map_err(|_| {
124                crate::rules::ReductionError::integer_overflow::<
125                    SchedulingWithIndividualDeadlines,
126                    ILP<bool>,
127                >("validated deadline must fit usize")
128            })?;
129            let dj = usize::try_from(self.deadlines()[j]).map_err(|_| {
130                crate::rules::ReductionError::integer_overflow::<
131                    SchedulingWithIndividualDeadlines,
132                    ILP<bool>,
133                >("validated deadline must fit usize")
134            })?;
135            let mut terms: Vec<(usize, i64)> = Vec::new();
136            for t in 0..dj {
137                terms.push((var(j, t), Self::exact_i64(t, "encoding a time slot")?));
138            }
139            for t in 0..di {
140                terms.push((var(i, t), -Self::exact_i64(t, "encoding a time slot")?));
141            }
142            constraints.push(LinearConstraint::ge(terms, 1));
143        }
144
145        Ok(ReductionSWIDToILP {
146            target: ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
147                .map_err(Self::target_construction)?,
148            num_tasks: n,
149            max_deadline: max_d,
150        })
151    }
152}
153
154#[cfg(feature = "example-db")]
155pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
156    vec![crate::example_db::specs::RuleExampleSpec {
157        id: "schedulingwithindividualdeadlines_to_ilp",
158        build: || {
159            // 3 tasks, 2 processors, deadlines [2, 2, 3], precedence (0, 2)
160            let source = SchedulingWithIndividualDeadlines::new(3, 2, vec![2, 2, 3], vec![(0, 2)]);
161            crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
162        },
163    }]
164}
165
166#[cfg(test)]
167#[path = "../unit_tests/rules/schedulingwithindividualdeadlines_ilp.rs"]
168mod tests;