problemreductions/rules/
highlyconnecteddeletion_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
18use crate::models::graph::HighlyConnectedDeletion;
19use crate::reduction;
20use crate::rules::traits::{ReduceTo, ReductionResult};
21use crate::topology::{Graph, SimpleGraph};
22
23#[derive(Debug, Clone)]
28pub struct ReductionHighlyConnectedDeletionToILP {
29 target: ILP<bool>,
30 edge_variables: Vec<Option<usize>>,
32}
33
34impl ReductionResult for ReductionHighlyConnectedDeletionToILP {
35 type Source = HighlyConnectedDeletion<SimpleGraph>;
36 type Target = ILP<bool>;
37
38 fn target_problem(&self) -> &ILP<bool> {
39 &self.target
40 }
41
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 Ok(self
53 .edge_variables
54 .iter()
55 .map(|variable| variable.is_some_and(|index| target_solution[index] == 0))
56 .collect())
57 }
58}
59
60#[reduction(transform = {
63 exact {
64 num_vars = "num_vertices * (num_vertices + 1) / 2",
65 },
66 upper_bound {
67 num_constraints = "num_vertices^3 + 2 * num_vertices",
68 num_nonzeros = "3 * num_vertices^3 + 2 * num_vertices^2",
69 max_constraint_magnitude_bits = "num_vertices + 2",
70 },
71})]
72impl ReduceTo<ILP<bool>> for HighlyConnectedDeletion<SimpleGraph> {
73 type Result = ReductionHighlyConnectedDeletionToILP;
74
75 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
76 let graph = self.graph();
77 let n = graph.num_vertices();
78 let num_pairs = n.checked_mul(n.saturating_sub(1)).map(|value| value / 2);
79 let num_vars = num_pairs
80 .and_then(|pairs| pairs.checked_add(n))
81 .ok_or_else(|| {
82 crate::rules::ReductionError::integer_overflow::<Self, ILP<bool>>(
83 "counting pair and non-singleton variables",
84 )
85 })?;
86 let flag_offset = num_vars - n;
87 let max_companions =
88 Self::exact_i64(n.saturating_sub(1), "encoding the cluster-size bound")?;
89 let pair = |u: usize, v: usize| {
92 let (u, v) = (u.min(v), u.max(v));
93 u * n - u * (u + 1) / 2 + (v - u - 1)
94 };
95 let mut constraints = Vec::new();
96 for u in 0..n {
97 for v in u + 1..n {
98 for w in v + 1..n {
99 let (a, b, c) = (pair(u, v), pair(u, w), pair(v, w));
100 for (first, second, third) in [(a, b, c), (a, c, b), (b, c, a)] {
101 constraints.push(LinearConstraint::le(
102 vec![(first, 1), (second, 1), (third, -1)],
103 1,
104 ));
105 }
106 }
107 }
108 }
109 for v in 0..n {
110 let mut size_terms = Vec::new();
113 let mut degree_terms = Vec::new();
114 for u in 0..n {
115 if u != v {
116 let variable = pair(u, v);
117 size_terms.push((variable, 1));
118 degree_terms.push((variable, if graph.has_edge(u, v) { 1 } else { -1 }));
120 }
121 }
122 size_terms.push((flag_offset + v, -max_companions));
123 constraints.push(LinearConstraint::le(size_terms, 0));
124 degree_terms.push((flag_offset + v, -2));
127 constraints.push(LinearConstraint::ge(degree_terms, 0));
128 }
129 let edge_variables: Vec<_> = graph
130 .edges()
131 .iter()
132 .map(|&(u, v)| (u != v).then(|| pair(u, v)))
133 .collect();
134 let objective = edge_variables
137 .iter()
138 .flatten()
139 .map(|&variable| (variable, 1))
140 .collect();
141 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize)
142 .map_err(Self::target_construction)?;
143 Ok(ReductionHighlyConnectedDeletionToILP {
144 target,
145 edge_variables,
146 })
147 }
148}
149
150#[cfg(feature = "example-db")]
151pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
152 vec![crate::example_db::specs::RuleExampleSpec {
153 id: "highlyconnecteddeletion_to_ilp",
154 build: || {
155 let source = HighlyConnectedDeletion::new(SimpleGraph::new(
159 4,
160 vec![(0, 1), (0, 2), (1, 2), (2, 3)],
161 ));
162 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
163 },
164 }]
165}
166
167#[cfg(test)]
168#[path = "../unit_tests/rules/highlyconnecteddeletion_ilp.rs"]
169mod tests;