Skip to main content

problemreductions/models/graph/
maximum_edge_weighted_k_clique.rs

1//! Maximum Edge-Weighted k-Clique problem implementation.
2//!
3//! Given a simple undirected graph G = (V, E), edge weights w: E -> R, and an
4//! integer k with 0 <= k <= |V|, find a subset S ⊆ V with |S| = k such that
5//! every two distinct vertices in S are adjacent in G and the total weight of
6//! the induced clique edges is maximized:
7//!
8//! maximize  Σ_{{u,v} ⊆ S, {u,v} ∈ E} w_{uv}.
9//!
10//! Edge weights may be positive, zero, or negative. Cliques of size 0 and 1
11//! are allowed when `k` takes those values, with objective value 0 because no
12//! pair of selected vertices is induced.
13
14use 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/// The Maximum Edge-Weighted k-Clique problem.
35///
36/// Given a simple undirected graph `G = (V, E)`, edge weights
37/// `w: E -> R`, and an integer `k` with `0 <= k <= |V|`, find a subset
38/// `S ⊆ V` with `|S| = k` such that every two distinct vertices in `S`
39/// are adjacent in `G` and the sum of induced edge weights is maximized.
40///
41/// # Type Parameters
42///
43/// * `W` - Edge weight type (e.g., `i64`, `f64`). The graph is fixed to
44///   [`SimpleGraph`] in the current registered variants.
45///
46/// # Example
47///
48/// ```
49/// use problemreductions::models::graph::MaximumEdgeWeightedKClique;
50/// use problemreductions::topology::SimpleGraph;
51/// use problemreductions::types::Max;
52/// use problemreductions::{BruteForce, Problem};
53///
54/// // Graph from issue #1020: 4 vertices, triangles {0,1,2} and {0,1,3}.
55/// let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3)]);
56/// let weights = vec![5_i64, 4, -1, 1, 0];
57/// let problem = MaximumEdgeWeightedKClique::new(graph, weights, 3).unwrap();
58/// let solution = BruteForce::new().solve(&problem).unwrap().unwrap();
59/// assert_eq!(problem.evaluate(&solution).unwrap(), Max(Some(8)));
60/// ```
61#[derive(Debug, Clone, Serialize)]
62pub struct MaximumEdgeWeightedKClique<W: WeightElement> {
63    /// The underlying graph.
64    graph: SimpleGraph,
65    /// Edge weights, in the graph's edge iteration order.
66    edge_weights: Vec<W>,
67    /// Required clique size.
68    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    /// The underlying graph.
94    graph: SimpleGraph,
95    /// Edge weights; defaults to one per edge.
96    edge_weights: Option<Vec<W>>,
97    /// Required clique size.
98    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    /// Create a new MaximumEdgeWeightedKClique instance.
116    ///
117    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    /// Get a reference to the underlying graph.
146    pub fn graph(&self) -> &SimpleGraph {
147        &self.graph
148    }
149
150    /// Get a reference to the edge weights.
151    pub fn edge_weights(&self) -> &[W] {
152        &self.edge_weights
153    }
154
155    /// Get the required clique size.
156    pub fn k(&self) -> usize {
157        self.k
158    }
159
160    /// Number of vertices in the underlying graph.
161    pub fn num_vertices(&self) -> usize {
162        self.graph.num_vertices()
163    }
164
165    /// Number of edges in the underlying graph.
166    pub fn num_edges(&self) -> usize {
167        self.graph.num_edges()
168    }
169
170    /// Check whether the selected vertices form a clique of size exactly `k`.
171    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            // Sum weights of edges whose both endpoints are selected.
204            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
230/// Check whether `config` selects exactly `k` vertices that form a clique.
231fn 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;