problemreductions/rules/
precedenceconstrainedscheduling_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
14use crate::models::misc::PrecedenceConstrainedScheduling;
15use crate::reduction;
16use crate::rules::traits::{ReduceTo, ReductionResult};
17
18#[derive(Debug, Clone)]
23pub struct ReductionPCSToILP {
24 target: ILP<bool>,
25 num_tasks: usize,
26 deadline: usize,
27}
28
29impl ReductionResult for ReductionPCSToILP {
30 type Source = PrecedenceConstrainedScheduling;
31 type Target = ILP<bool>;
32
33 fn target_problem(&self) -> &ILP<bool> {
34 &self.target
35 }
36
37 fn extract_solution(
42 &self,
43 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
44 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
45 crate::rules::traits::validate_target_witness(
46 self.target_problem(),
47 target_solution,
48 |value| value.value.is_some(),
49 "target ILP assignment is infeasible",
50 )?;
51
52 crate::rules::ilp_helpers::one_hot_decode_rows(
53 target_solution,
54 self.num_tasks,
55 self.deadline,
56 0,
57 )
58 }
59}
60
61#[crate::aggregate_reduction(ilp_feasibility)]
62impl crate::rules::AggregateReductionResult for ReductionPCSToILP {}
63
64#[reduction(transform = {
65 exact {
66 num_vars = "num_tasks * deadline",
67 num_constraints = "num_tasks + deadline + num_precedences",
68 },
69 upper_bound {
70 max_constraint_magnitude_bits = "num_tasks + deadline + 1",
71 num_nonzeros = "(num_tasks * deadline) * (num_tasks + deadline + num_precedences)",
72 },
73})]
74impl ReduceTo<ILP<bool>> for PrecedenceConstrainedScheduling {
75 type Result = ReductionPCSToILP;
76
77 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
78 let n = self.num_tasks();
79 let d = usize::try_from(self.deadline()).map_err(|_| {
80 crate::rules::ReductionError::integer_overflow::<
81 PrecedenceConstrainedScheduling,
82 ILP<bool>,
83 >("validated deadline must fit usize")
84 })?;
85 let num_vars = n * d;
86
87 let var = |j: usize, t: usize| j * d + t;
89 let processor_count = Self::exact_i64(
91 self.num_processors().min(n),
92 "encoding the processor capacity",
93 )?;
94
95 let mut constraints = Vec::new();
96
97 for j in 0..n {
99 let terms: Vec<(usize, i64)> = (0..d).map(|t| (var(j, t), 1)).collect();
100 constraints.push(LinearConstraint::eq(terms, 1));
101 }
102
103 for t in 0..d {
105 let terms: Vec<(usize, i64)> = (0..n).map(|j| (var(j, t), 1)).collect();
106 constraints.push(LinearConstraint::le(terms, processor_count));
107 }
108
109 for &(i, j) in self.precedences() {
112 let mut terms: Vec<(usize, i64)> = Vec::new();
113 for t in 0..d {
114 let t_i64 = Self::exact_i64(t, "encoding a time slot")?;
115 terms.push((var(j, t), t_i64));
116 terms.push((var(i, t), -t_i64));
117 }
118 constraints.push(LinearConstraint::ge(terms, 1));
119 }
120
121 Ok(ReductionPCSToILP {
122 target: ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
123 .map_err(Self::target_construction)?,
124 num_tasks: n,
125 deadline: d,
126 })
127 }
128}
129
130#[cfg(feature = "example-db")]
131pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
132 vec![crate::example_db::specs::RuleExampleSpec {
133 id: "precedenceconstrainedscheduling_to_ilp",
134 build: || {
135 let source = PrecedenceConstrainedScheduling::new(3, 2, 2, vec![(0, 2)]);
137 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
138 },
139 }]
140}
141
142#[cfg(test)]
143#[path = "../unit_tests/rules/precedenceconstrainedscheduling_ilp.rs"]
144mod tests;