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(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    /// Get a reference to the underlying graph.
144    pub fn graph(&self) -> &SimpleGraph {
145        &self.graph
146    }
147
148    /// Get a reference to the edge weights.
149    pub fn edge_weights(&self) -> &[W] {
150        &self.edge_weights
151    }
152
153    /// Get the required clique size.
154    pub fn k(&self) -> usize {
155        self.k
156    }
157
158    /// Number of vertices in the underlying graph.
159    pub fn num_vertices(&self) -> usize {
160        self.graph.num_vertices()
161    }
162
163    /// Number of edges in the underlying graph.
164    pub fn num_edges(&self) -> usize {
165        self.graph.num_edges()
166    }
167
168    /// Check whether the selected vertices form a clique of size exactly `k`.
169    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            // Sum weights of edges whose both endpoints are selected.
202            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
228/// Check whether `config` selects exactly `k` vertices that form a clique.
229fn 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;