Skip to main content

problemreductions/rules/
setsplitting_ilp.rs

1//! Reduction from SetSplitting to ILP (Integer Linear Programming).
2//!
3//! Binary variable $x_i \in \{0,1\}$ per universe element: 0 means element $i$
4//! is placed in part $S_1$, 1 means it is placed in part $S_2$.
5//!
6//! For each subset $C = \{i_1, \ldots, i_k\}$ we need:
7//! - At least one element in $S_2$: $\sum_{j \in C} x_j \geq 1$
8//! - At least one element in $S_1$: $\sum_{j \in C} x_j \leq k - 1$
9//!
10//! Objective: feasibility (minimize 0).
11
12use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
13use crate::models::set::SetSplitting;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16
17/// Result of reducing SetSplitting to ILP.
18#[derive(Debug, Clone)]
19pub struct ReductionSetSplittingToILP {
20    target: ILP<bool>,
21}
22
23impl ReductionResult for ReductionSetSplittingToILP {
24    type Source = SetSplitting;
25    type Target = ILP<bool>;
26
27    fn target_problem(&self) -> &ILP<bool> {
28        &self.target
29    }
30
31    fn extract_solution(
32        &self,
33        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
34    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
35        crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
36
37        Ok(target_solution.iter().map(|&value| value == 1).collect())
38    }
39}
40
41#[reduction(
42    transform = exact {
43        num_vars = "universe_size",
44        num_constraints = "2 * num_subsets",
45    },
46    unavailable = {
47        num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
48    }
49)]
50impl ReduceTo<ILP<bool>> for SetSplitting {
51    type Result = ReductionSetSplittingToILP;
52
53    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
54        let num_vars = self.universe_size();
55        let mut constraints = Vec::new();
56
57        for subset in self.subsets() {
58            let terms: Vec<(usize, i64)> = subset.iter().map(|&e| (e, 1)).collect();
59            let k = <Self as ReduceTo<ILP<bool>>>::exact_i64(
60                subset.len() - 1,
61                "encoding the split-set cardinality",
62            )?;
63
64            // At least one element in S2: sum >= 1
65            constraints.push(LinearConstraint::ge(terms.clone(), 1));
66
67            // At least one element in S1: sum <= k - 1
68            constraints.push(LinearConstraint::le(terms, k));
69        }
70
71        let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
72            .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
73        Ok(ReductionSetSplittingToILP { target })
74    }
75}
76
77#[cfg(feature = "example-db")]
78pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
79    vec![crate::example_db::specs::RuleExampleSpec {
80        id: "setsplitting_to_ilp",
81        build: || {
82            let source = SetSplitting::new(
83                6,
84                vec![vec![0, 1, 2], vec![2, 3, 4], vec![0, 4, 5], vec![1, 3, 5]],
85            );
86            crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
87        },
88    }]
89}
90
91#[cfg(test)]
92#[path = "../unit_tests/rules/setsplitting_ilp.rs"]
93mod tests;