problemreductions/rules/
productionplanning_ilp.rs1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
3use crate::models::misc::ProductionPlanning;
4use crate::rules::traits::{ReduceTo, ReductionResult};
5
6#[derive(Debug, Clone)]
7pub struct ReductionProductionPlanningToILP {
8 target: ILP<i64, i64, Bounded>,
9 num_periods: usize,
10}
11impl ReductionResult for ReductionProductionPlanningToILP {
12 type Source = ProductionPlanning;
13 type Target = ILP<i64, i64, Bounded>;
14 fn target_problem(&self) -> &Self::Target {
15 &self.target
16 }
17 fn extract_solution(&self, solution: &Vec<i64>) -> crate::rules::ExtractionResult<Vec<usize>> {
18 crate::rules::traits::validate_target_witness(
19 &self.target,
20 solution,
21 |value| value.value.is_some(),
22 "target ILP assignment is infeasible",
23 )?;
24 crate::rules::ilp_helpers::decode_usize_values(&solution[..self.num_periods])
25 }
26}
27#[crate::aggregate_reduction(ilp_feasibility)]
28impl crate::rules::AggregateReductionResult for ReductionProductionPlanningToILP {}
29#[crate::reduction(transform = {
30 exact { num_vars = "3 * num_periods", num_constraints = "3 * num_periods + 1", },
31 upper_bound {
32 num_nonzeros = "10 * num_periods",
33 max_constraint_magnitude_bits = "max_numeric_magnitude_bits + num_periods",
34 },
35})]
36impl ReduceTo<ILP<i64, i64, Bounded>> for ProductionPlanning {
37 type Result = ReductionProductionPlanningToILP;
38 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
39 let overflow = |operation| {
40 crate::rules::ReductionError::integer_overflow::<Self, ILP<i64, i64, Bounded>>(
41 operation,
42 )
43 };
44 let n = self.num_periods();
45 let count = n
46 .checked_mul(3)
47 .ok_or_else(|| overflow("counting planning variables"))?;
48 self.demands()
49 .iter()
50 .try_fold(0_i64, |sum, &d| sum.checked_add(d))
51 .ok_or_else(|| overflow("summing planning demands"))?;
52 let mut variables = Vec::with_capacity(count);
53 for &capacity in self.capacities() {
54 variables.push(
55 IntegerVariable::new(Some(0), Some(capacity)).map_err(Self::target_construction)?,
56 );
57 }
58 let mut total = 0_i64;
59 for &capacity in self.capacities() {
60 total = total
61 .checked_add(capacity)
62 .ok_or_else(|| overflow("summing planning capacities"))?;
63 variables.push(
64 IntegerVariable::new(Some(0), Some(total)).map_err(Self::target_construction)?,
65 );
66 }
67 variables.extend(std::iter::repeat_n(IntegerVariable::binary(), n));
68 let mut constraints = Vec::new();
69 let mut costs = Vec::new();
70 for t in 0..n {
71 let mut flow = vec![(t, 1), (n + t, -1)];
72 if t > 0 {
73 flow.push((n + t - 1, 1));
74 }
75 constraints.push(LinearConstraint::eq(flow, self.demands()[t]));
76 constraints.push(LinearConstraint::le(
77 vec![(t, 1), (2 * n + t, -self.capacities()[t])],
78 0,
79 ));
80 constraints.push(LinearConstraint::ge(vec![(t, 1), (2 * n + t, -1)], 0));
81 costs.extend([
82 (t, self.production_costs()[t]),
83 (n + t, self.inventory_costs()[t]),
84 (2 * n + t, self.setup_costs()[t]),
85 ]);
86 }
87 constraints.push(LinearConstraint::le(costs, self.cost_bound()));
88 let target = ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
89 .map_err(Self::target_construction)?;
90 crate::rules::ilp_helpers::validate_bounded_constraint_arithmetic::<Self>(&target)?;
91 Ok(Self::Result {
92 target,
93 num_periods: n,
94 })
95 }
96}
97#[cfg(feature = "example-db")]
98pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
99 vec![crate::example_db::specs::RuleExampleSpec {
100 id: "productionplanning_to_ilp",
101 build: || {
102 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(ProductionPlanning::new(
103 3,
104 vec![1; 3],
105 vec![2, 0, 1],
106 vec![2; 3],
107 vec![1; 3],
108 vec![1; 3],
109 8,
110 ))
111 },
112 }]
113}
114#[cfg(test)]
115#[path = "../unit_tests/rules/productionplanning_ilp.rs"]
116mod tests;