1use crate::models::algebraic::{ILPCoefficient, LinearConstraint, ObjectiveSense, ILP};
26use crate::models::graph::MaximumEdgeWeightedKClique;
27use crate::reduction;
28use crate::rules::ilp_helpers::mccormick_product;
29use crate::rules::traits::{ReduceTo, ReductionResult};
30use crate::topology::Graph;
31use crate::variant::VariantParam;
32
33#[derive(Debug, Clone)]
40pub struct ReductionMaximumEdgeWeightedKCliqueToILP<W>
41where
42 W: ILPCoefficient,
43{
44 target: ILP<bool, W>,
45 num_vertices: usize,
46}
47
48impl<W> ReductionResult for ReductionMaximumEdgeWeightedKCliqueToILP<W>
49where
50 W: ILPCoefficient + VariantParam,
51{
52 type Source = MaximumEdgeWeightedKClique<W>;
53 type Target = ILP<bool, W>;
54
55 fn target_problem(&self) -> &ILP<bool, W> {
56 &self.target
57 }
58
59 fn extract_solution(
62 &self,
63 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
64 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
65 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
66
67 Ok(target_solution[..self.num_vertices]
68 .iter()
69 .map(|&value| value == 1)
70 .collect())
71 }
72}
73
74fn build_reduction<W>(
75 src: &MaximumEdgeWeightedKClique<W>,
76) -> Result<ReductionMaximumEdgeWeightedKCliqueToILP<W>, crate::rules::ReductionError>
77where
78 W: ILPCoefficient + VariantParam + From<i8>,
79{
80 let n = src.num_vertices();
81 let edges = src.graph().edges();
82 let m = edges.len();
83 let num_vars = n + m;
84 let k =
85 i64::try_from(src.k()).map_err(|_| {
86 crate::rules::ReductionError::integer_overflow::<
87 MaximumEdgeWeightedKClique<W>,
88 ILP<bool, W>,
89 >("encoding the clique cardinality")
90 })?;
91 let x_idx = |v: usize| -> usize { v };
92 let y_idx = |e: usize| -> usize { n + e };
93
94 let mut constraints: Vec<LinearConstraint<W>> = Vec::new();
95
96 let cardinality_terms = (0..n).map(|v| (x_idx(v), 1_i8.into())).collect();
98 let k = W::from_integer(k).map_err(|error| {
99 crate::rules::ReductionError::invalid_target::<MaximumEdgeWeightedKClique<W>, ILP<bool, W>>(
100 error.to_string(),
101 )
102 })?;
103 constraints.push(LinearConstraint::eq(cardinality_terms, k));
104
105 for u in 0..n {
107 for v in (u + 1)..n {
108 if !src.graph().has_edge(u, v) {
109 constraints.push(LinearConstraint::le(
110 vec![(x_idx(u), 1_i8.into()), (x_idx(v), 1_i8.into())],
111 1_i8.into(),
112 ));
113 }
114 }
115 }
116
117 for (e, &(u, v)) in edges.iter().enumerate() {
119 constraints.extend(mccormick_product(y_idx(e), x_idx(u), x_idx(v)));
120 }
121
122 let objective: Vec<(usize, W)> = src
124 .edge_weights()
125 .iter()
126 .copied()
127 .enumerate()
128 .map(|(e, w)| (y_idx(e), w))
129 .collect();
130
131 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize).map_err(
132 crate::rules::ReductionError::construction::<MaximumEdgeWeightedKClique<W>, ILP<bool, W>>,
133 )?;
134
135 Ok(ReductionMaximumEdgeWeightedKCliqueToILP {
136 target,
137 num_vertices: n,
138 })
139}
140
141#[reduction(
142 transform = exact {
143 num_vars = "num_vertices + num_edges",
144 num_constraints = "1 + num_vertices * (num_vertices - 1) / 2 + 2 * num_edges",
145 },
146 unavailable = {
147 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
148 }
149)]
150impl ReduceTo<ILP<bool>> for MaximumEdgeWeightedKClique<i64> {
151 type Result = ReductionMaximumEdgeWeightedKCliqueToILP<i64>;
152
153 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
154 build_reduction(self)
155 }
156}
157
158#[reduction(
159 transform = exact {
160 num_vars = "num_vertices + num_edges",
161 num_constraints = "1 + num_vertices * (num_vertices - 1) / 2 + 2 * num_edges",
162 },
163 unavailable = {
164 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
165 }
166)]
167impl ReduceTo<ILP<bool, f64>> for MaximumEdgeWeightedKClique<f64> {
168 type Result = ReductionMaximumEdgeWeightedKCliqueToILP<f64>;
169
170 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
171 build_reduction(self)
172 }
173}
174
175#[cfg(feature = "example-db")]
176pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
177 use crate::topology::SimpleGraph;
178 vec![
179 crate::example_db::specs::RuleExampleSpec {
180 id: "exact_maximumedgeweightedkclique_to_ilp",
181 build: || {
182 let source = MaximumEdgeWeightedKClique::<i64>::new(
185 SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3)]),
186 vec![5, 4, -1, 1, 0],
187 3,
188 )
189 .unwrap();
190 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
191 },
192 },
193 crate::example_db::specs::RuleExampleSpec {
194 id: "approximate_maximumedgeweightedkclique_to_ilp",
195 build: || {
196 let source = MaximumEdgeWeightedKClique::<f64>::new(
197 SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3)]),
198 vec![5.0, 4.0, -1.0, 1.0, 0.0],
199 3,
200 )
201 .unwrap();
202 crate::example_db::specs::rule_example_via_float_ilp::<_, bool>(source)
203 },
204 },
205 ]
206}
207
208#[cfg(test)]
209#[path = "../unit_tests/rules/maximumedgeweightedkclique_ilp.rs"]
210mod tests;