problemreductions/models/graph/
maximum_edge_weighted_k_clique.rs1use crate::registry::{ConstructionError, CreateSpec, ProblemSchemaEntry, VariantDimension};
15use crate::topology::{Graph, SimpleGraph};
16use crate::traits::Problem;
17use crate::types::{Max, WeightElement};
18use num_traits::Zero;
19use serde::{Deserialize, Serialize};
20
21inventory::submit! {
22 ProblemSchemaEntry {
23 name: "MaximumEdgeWeightedKClique",
24 display_name: "Maximum Edge-Weighted k-Clique",
25 aliases: &[],
26 dimensions: &[VariantDimension::new("weight", "i64", &["i64", "f64"])],
27 category: crate::registry::ProblemCategory::Graph,
28 module_path: module_path!(),
29 description: "Select exactly k pairwise-adjacent vertices maximizing the total weight of induced clique edges",
30 fields: MaximumEdgeWeightedKCliqueCreateSpec::<i64>::FIELDS,
31 }
32}
33
34#[derive(Debug, Clone, Serialize)]
62pub struct MaximumEdgeWeightedKClique<W: WeightElement> {
63 graph: SimpleGraph,
65 edge_weights: Vec<W>,
67 k: usize,
69}
70
71#[derive(Deserialize)]
72struct MaximumEdgeWeightedKCliqueData<W> {
73 graph: SimpleGraph,
74 edge_weights: Vec<W>,
75 k: usize,
76}
77
78impl<'de, W> Deserialize<'de> for MaximumEdgeWeightedKClique<W>
79where
80 W: WeightElement + Deserialize<'de>,
81{
82 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
83 where
84 D: serde::Deserializer<'de>,
85 {
86 let data = MaximumEdgeWeightedKCliqueData::deserialize(deserializer)?;
87 Self::new(data.graph, data.edge_weights, data.k).map_err(serde::de::Error::custom)
88 }
89}
90
91#[derive(Debug, Deserialize, crate::CreateSpec)]
92struct MaximumEdgeWeightedKCliqueCreateSpec<W> {
93 graph: SimpleGraph,
95 edge_weights: Option<Vec<W>>,
97 k: usize,
99}
100impl<W> TryFrom<MaximumEdgeWeightedKCliqueCreateSpec<W>> for MaximumEdgeWeightedKClique<W>
101where
102 W: WeightElement,
103{
104 type Error = ConstructionError;
105 fn try_from(spec: MaximumEdgeWeightedKCliqueCreateSpec<W>) -> Result<Self, Self::Error> {
106 let count = spec.graph.num_edges();
107 let edge_weights = spec
108 .edge_weights
109 .unwrap_or_else(|| (0..count).map(|_| W::unit()).collect());
110 Self::new(spec.graph, edge_weights, spec.k)
111 }
112}
113
114impl<W: WeightElement> MaximumEdgeWeightedKClique<W> {
115 pub fn new(
118 graph: SimpleGraph,
119 edge_weights: Vec<W>,
120 k: usize,
121 ) -> Result<Self, ConstructionError> {
122 if edge_weights.len() != graph.num_edges() {
123 return Err(ConstructionError::Conversion(
124 "edge_weights length must match graph num_edges".into(),
125 ));
126 }
127 for (index, weight) in edge_weights.iter().enumerate() {
128 weight.validate_element(&format!("edge weight at index {index}"))?;
129 }
130 if k > graph.num_vertices() {
131 return Err(ConstructionError::Conversion(format!(
132 "k = {k} must be <= num_vertices = {}",
133 graph.num_vertices()
134 )));
135 }
136 Ok(Self {
137 graph,
138 edge_weights,
139 k,
140 })
141 }
142
143 pub fn graph(&self) -> &SimpleGraph {
145 &self.graph
146 }
147
148 pub fn edge_weights(&self) -> &[W] {
150 &self.edge_weights
151 }
152
153 pub fn k(&self) -> usize {
155 self.k
156 }
157
158 pub fn num_vertices(&self) -> usize {
160 self.graph.num_vertices()
161 }
162
163 pub fn num_edges(&self) -> usize {
165 self.graph.num_edges()
166 }
167
168 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
170 is_k_clique_config(&self.graph, config, self.k)
171 }
172}
173
174impl<W> Problem for MaximumEdgeWeightedKClique<W>
175where
176 W: WeightElement + crate::variant::VariantParam,
177{
178 const NAME: &'static str = "MaximumEdgeWeightedKClique";
179 type Solution = Vec<bool>;
180 type Value = Max<W::Sum>;
181
182 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
183
184 fn variant() -> Vec<(&'static str, &'static str)> {
185 crate::variant_params![W]
186 }
187
188 fn evaluate(
189 &self,
190 config: &Self::Solution,
191 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
192 if config.len() != self.graph.num_vertices() {
193 return Err(crate::traits::EvaluationError::InvalidConfiguration(
194 "vertex-selection length does not match the graph".into(),
195 ));
196 }
197 Ok({
198 if !is_k_clique_config(&self.graph, config, self.k) {
199 return Ok(Max(None));
200 }
201 let mut total = W::Sum::zero();
203 for ((u, v), weight) in self.graph.edges().iter().zip(self.edge_weights.iter()) {
204 if config.get(*u).copied().unwrap_or(false)
205 && config.get(*v).copied().unwrap_or(false)
206 {
207 total = W::checked_add_to_sum(
208 total,
209 weight.to_sum(),
210 "summing selected clique-edge weights",
211 )?;
212 }
213 }
214 Max(Some(total))
215 })
216 }
217}
218
219impl<W> crate::solvers::BruteForceProblem for MaximumEdgeWeightedKClique<W>
220where
221 W: WeightElement + crate::variant::VariantParam,
222{
223 fn dimensions(&self) -> Vec<usize> {
224 vec![2; self.graph.num_vertices()]
225 }
226}
227
228fn is_k_clique_config(graph: &SimpleGraph, config: &[bool], k: usize) -> bool {
230 let n = graph.num_vertices();
231 if config.len() != n {
232 return false;
233 }
234 let selected: Vec<usize> = config
235 .iter()
236 .enumerate()
237 .filter(|(_, &selected)| selected)
238 .map(|(i, _)| i)
239 .collect();
240 if selected.len() != k {
241 return false;
242 }
243 for i in 0..selected.len() {
244 for j in (i + 1)..selected.len() {
245 if !graph.has_edge(selected[i], selected[j]) {
246 return false;
247 }
248 }
249 }
250 true
251}
252
253crate::declare_variants! {
254 default MaximumEdgeWeightedKClique<i64> => "2^num_vertices" create MaximumEdgeWeightedKCliqueCreateSpec<i64>,
255 MaximumEdgeWeightedKClique<f64> => "2^num_vertices" create MaximumEdgeWeightedKCliqueCreateSpec<f64>,
256}
257
258crate::register_brute_force! {
259 MaximumEdgeWeightedKClique<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
260 MaximumEdgeWeightedKClique<f64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
261}
262
263#[cfg(feature = "example-db")]
264pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
265 vec![crate::example_db::specs::ModelExampleSpec {
266 id: "maximum_edge_weighted_k_clique_simplegraph",
267 instance: Box::new(
268 MaximumEdgeWeightedKClique::<i64>::new(
269 SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3)]),
270 vec![5, 4, -1, 1, 0],
271 3,
272 )
273 .unwrap(),
274 ),
275 optimal_config: serde_json::json!(vec![true, true, true, false]),
276 optimal_value: serde_json::json!(8),
277 }]
278}
279
280#[cfg(test)]
281#[path = "../../unit_tests/models/graph/maximum_edge_weighted_k_clique.rs"]
282mod tests;