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(crate::registry::ConstructionError::length_mismatch(
124 "edge_weights",
125 edge_weights.len(),
126 graph.num_edges(),
127 ));
128 }
129 for (index, weight) in edge_weights.iter().enumerate() {
130 weight.validate_element(&format!("edge weight at index {index}"))?;
131 }
132 if k > graph.num_vertices() {
133 return Err(ConstructionError::Conversion(format!(
134 "k = {k} must be <= num_vertices = {}",
135 graph.num_vertices()
136 )));
137 }
138 Ok(Self {
139 graph,
140 edge_weights,
141 k,
142 })
143 }
144
145 pub fn graph(&self) -> &SimpleGraph {
147 &self.graph
148 }
149
150 pub fn edge_weights(&self) -> &[W] {
152 &self.edge_weights
153 }
154
155 pub fn k(&self) -> usize {
157 self.k
158 }
159
160 pub fn num_vertices(&self) -> usize {
162 self.graph.num_vertices()
163 }
164
165 pub fn num_edges(&self) -> usize {
167 self.graph.num_edges()
168 }
169
170 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
172 is_k_clique_config(&self.graph, config, self.k)
173 }
174}
175
176impl<W> Problem for MaximumEdgeWeightedKClique<W>
177where
178 W: WeightElement + crate::variant::VariantParam,
179{
180 const NAME: &'static str = "MaximumEdgeWeightedKClique";
181 type Solution = Vec<bool>;
182 type Value = Max<W::Sum>;
183
184 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
185
186 fn variant() -> Vec<(&'static str, &'static str)> {
187 crate::variant_params![W]
188 }
189
190 fn evaluate(
191 &self,
192 config: &Self::Solution,
193 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
194 if config.len() != self.graph.num_vertices() {
195 return Err(crate::traits::EvaluationError::InvalidConfiguration(
196 "vertex-selection length does not match the graph".into(),
197 ));
198 }
199 Ok({
200 if !is_k_clique_config(&self.graph, config, self.k) {
201 return Ok(Max(None));
202 }
203 let mut total = W::Sum::zero();
205 for ((u, v), weight) in self.graph.edges().iter().zip(self.edge_weights.iter()) {
206 if config.get(*u).copied().unwrap_or(false)
207 && config.get(*v).copied().unwrap_or(false)
208 {
209 total = W::checked_add_to_sum(
210 total,
211 weight.to_sum(),
212 "summing selected clique-edge weights",
213 )?;
214 }
215 }
216 Max(Some(total))
217 })
218 }
219}
220
221impl<W> crate::solvers::BruteForceProblem for MaximumEdgeWeightedKClique<W>
222where
223 W: WeightElement + crate::variant::VariantParam,
224{
225 fn dimensions(&self) -> Vec<usize> {
226 vec![2; self.graph.num_vertices()]
227 }
228}
229
230fn is_k_clique_config(graph: &SimpleGraph, config: &[bool], k: usize) -> bool {
232 let n = graph.num_vertices();
233 if config.len() != n {
234 return false;
235 }
236 let selected: Vec<usize> = config
237 .iter()
238 .enumerate()
239 .filter(|(_, &selected)| selected)
240 .map(|(i, _)| i)
241 .collect();
242 if selected.len() != k {
243 return false;
244 }
245 for i in 0..selected.len() {
246 for j in (i + 1)..selected.len() {
247 if !graph.has_edge(selected[i], selected[j]) {
248 return false;
249 }
250 }
251 }
252 true
253}
254
255crate::declare_variants! {
256 default MaximumEdgeWeightedKClique<i64> => "2^num_vertices" create MaximumEdgeWeightedKCliqueCreateSpec<i64>,
257 MaximumEdgeWeightedKClique<f64> => "2^num_vertices" create MaximumEdgeWeightedKCliqueCreateSpec<f64>,
258}
259
260crate::register_brute_force! {
261 MaximumEdgeWeightedKClique<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
262 MaximumEdgeWeightedKClique<f64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
263}
264
265#[cfg(feature = "example-db")]
266pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
267 vec![crate::example_db::specs::ModelExampleSpec {
268 id: "maximum_edge_weighted_k_clique_simplegraph",
269 instance: Box::new(
270 MaximumEdgeWeightedKClique::<i64>::new(
271 SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3)]),
272 vec![5, 4, -1, 1, 0],
273 3,
274 )
275 .unwrap(),
276 ),
277 optimal_config: serde_json::json!(vec![true, true, true, false]),
278 optimal_value: serde_json::json!(8),
279 }]
280}
281
282#[cfg(test)]
283#[path = "../../unit_tests/models/graph/maximum_edge_weighted_k_clique.rs"]
284mod tests;