Skip to main content

problemreductions/rules/
productionplanning_ilp.rs

1//! Inventory conservation with exact production setup indicators.
2use 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;