1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
12use crate::topology::{Graph, SimpleGraph};
13use crate::traits::Problem;
14use crate::types::{Max, One, WeightElement};
15use crate::variant::{KValue, VariantParam, KN};
16use num_traits::Zero;
17use serde::{Deserialize, Serialize};
18
19inventory::submit! {
20 ProblemSchemaEntry {
21 name: "MaximumCoKPlex",
22 display_name: "Maximum Co-k-Plex",
23 aliases: &[],
24 dimensions: &[
25 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
26 VariantDimension::new("weight", "One", &["One", "i64"]),
27 VariantDimension::new("k", "KN", &["KN"]),
28 ],
29 category: crate::registry::ProblemCategory::Graph,
30 module_path: module_path!(),
31 description: "Find maximum-weight vertex subset whose induced subgraph has maximum degree at most k-1",
32 fields: MaximumCoKPlexCreateSpec::<One>::FIELDS,
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(try_from = "MaximumCoKPlexData<G, W>")]
68#[serde(bound(
69 deserialize = "G: Graph + Deserialize<'de>, W: Clone + Default + Deserialize<'de>, K: KValue"
70))]
71pub struct MaximumCoKPlex<G, W, K: KValue> {
72 graph: G,
74 weights: Vec<W>,
76 bound_k: usize,
83 #[serde(skip)]
84 _phantom: std::marker::PhantomData<K>,
85}
86
87#[derive(Deserialize)]
88struct MaximumCoKPlexData<G, W> {
89 graph: G,
90 weights: Vec<W>,
91 bound_k: usize,
92}
93
94impl<G, W, K> TryFrom<MaximumCoKPlexData<G, W>> for MaximumCoKPlex<G, W, K>
95where
96 G: Graph,
97 W: Clone + Default,
98 K: KValue,
99{
100 type Error = crate::registry::ConstructionError;
101
102 fn try_from(data: MaximumCoKPlexData<G, W>) -> Result<Self, Self::Error> {
103 Self::try_with_k(data.graph, data.weights, data.bound_k)
104 }
105}
106
107#[derive(Debug, Deserialize, crate::CreateSpec)]
108struct MaximumCoKPlexCreateSpec<W> {
109 graph: SimpleGraph,
111 weights: Vec<W>,
113 k: usize,
115}
116
117impl<W: Clone + Default> TryFrom<MaximumCoKPlexCreateSpec<W>>
118 for MaximumCoKPlex<SimpleGraph, W, KN>
119{
120 type Error = crate::registry::ConstructionError;
121
122 fn try_from(spec: MaximumCoKPlexCreateSpec<W>) -> Result<Self, Self::Error> {
123 Self::try_with_k(spec.graph, spec.weights, spec.k)
124 }
125}
126
127impl<G: Graph, W: Clone + Default, K: KValue> MaximumCoKPlex<G, W, K> {
128 pub fn with_k(graph: G, weights: Vec<W>, bound_k: usize) -> Self {
135 Self::try_with_k(graph, weights, bound_k).unwrap_or_else(|error| panic!("{error}"))
136 }
137
138 fn try_with_k(
139 graph: G,
140 weights: Vec<W>,
141 bound_k: usize,
142 ) -> Result<Self, crate::registry::ConstructionError> {
143 if weights.len() != graph.num_vertices() {
144 return Err(crate::registry::ConstructionError::length_mismatch(
145 "weights",
146 weights.len(),
147 graph.num_vertices(),
148 ));
149 }
150 if bound_k == 0 {
151 return Err("co-k-plex parameter k must be at least 1".into());
152 }
153 if let Some(fixed) = K::K {
154 if fixed != bound_k {
155 return Err("fixed K type disagrees with runtime bound_k".into());
156 }
157 }
158 Ok(Self {
159 graph,
160 weights,
161 bound_k,
162 _phantom: std::marker::PhantomData,
163 })
164 }
165
166 pub fn new(graph: G, weights: Vec<W>) -> Self {
172 let k = K::K.expect("KN requires with_k");
173 Self::with_k(graph, weights, k)
174 }
175
176 pub fn graph(&self) -> &G {
178 &self.graph
179 }
180
181 pub fn weights(&self) -> &[W] {
183 &self.weights
184 }
185
186 pub fn bound_k(&self) -> usize {
188 self.bound_k
189 }
190
191 pub fn is_weighted(&self) -> bool
193 where
194 W: WeightElement,
195 {
196 !W::IS_UNIT
197 }
198
199 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
201 is_co_k_plex_config(&self.graph, config, self.bound_k)
202 }
203}
204
205impl<G: Graph, W: WeightElement, K: KValue> MaximumCoKPlex<G, W, K> {
206 pub fn num_vertices(&self) -> usize {
208 self.graph.num_vertices()
209 }
210
211 pub fn num_edges(&self) -> usize {
213 self.graph.num_edges()
214 }
215}
216
217impl<G, W, K> Problem for MaximumCoKPlex<G, W, K>
218where
219 G: Graph + VariantParam,
220 W: WeightElement + VariantParam,
221 K: KValue,
222{
223 const NAME: &'static str = "MaximumCoKPlex";
224 type Solution = Vec<bool>;
225 type Value = Max<W::Sum>;
226
227 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
228
229 fn variant() -> Vec<(&'static str, &'static str)> {
230 crate::variant_params![G, W, K]
231 }
232
233 fn evaluate(
234 &self,
235 config: &Self::Solution,
236 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
237 if config.len() != self.graph.num_vertices() {
238 return Err(crate::traits::EvaluationError::InvalidConfiguration(
239 "vertex-selection length does not match the graph".into(),
240 ));
241 }
242 Ok({
243 if !is_co_k_plex_config(&self.graph, config, self.bound_k) {
244 return Ok(Max(None));
245 }
246 let mut total = W::Sum::zero();
247 for (i, &selected) in config.iter().enumerate() {
248 if selected {
249 total = W::checked_add_to_sum(
250 total,
251 self.weights[i].to_sum(),
252 "summing selected co-k-plex weights",
253 )?;
254 }
255 }
256 Max(Some(total))
257 })
258 }
259}
260
261impl<G, W, K> crate::solvers::BruteForceProblem for MaximumCoKPlex<G, W, K>
262where
263 G: Graph + VariantParam,
264 W: WeightElement + VariantParam,
265 K: KValue,
266{
267 fn dimensions(&self) -> Vec<usize> {
268 vec![2; self.graph.num_vertices()]
269 }
270}
271
272fn is_co_k_plex_config<G: Graph>(graph: &G, config: &[bool], bound_k: usize) -> bool {
275 if bound_k == 0 {
276 return false;
277 }
278 let n = graph.num_vertices();
279 let mut induced_degree = vec![0usize; n];
280 for (u, v) in graph.edges() {
281 let u_selected = config.get(u).copied().unwrap_or(false);
282 let v_selected = config.get(v).copied().unwrap_or(false);
283 if u_selected && v_selected {
284 induced_degree[u] += 1;
285 induced_degree[v] += 1;
286 if induced_degree[u] > bound_k - 1 || induced_degree[v] > bound_k - 1 {
287 return false;
288 }
289 }
290 }
291 true
292}
293
294#[derive(Debug, Deserialize, crate::CreateSpec)]
295struct MaximumCoKPlexOneCreateSpec {
296 graph: SimpleGraph,
298 k: usize,
299}
300
301impl TryFrom<MaximumCoKPlexOneCreateSpec> for MaximumCoKPlex<SimpleGraph, One, KN> {
302 type Error = crate::registry::ConstructionError;
303 fn try_from(spec: MaximumCoKPlexOneCreateSpec) -> Result<Self, Self::Error> {
304 let weights = vec![One; spec.graph.num_vertices()];
305 Self::try_with_k(spec.graph, weights, spec.k)
306 }
307}
308
309crate::declare_variants! {
310 default MaximumCoKPlex<SimpleGraph, One, KN> => "2^num_vertices" create MaximumCoKPlexOneCreateSpec,
311 MaximumCoKPlex<SimpleGraph, i64, KN> => "2^num_vertices" create MaximumCoKPlexCreateSpec<i64>,
312}
313
314crate::register_brute_force! {
315 MaximumCoKPlex<SimpleGraph, One, KN> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
316 MaximumCoKPlex<SimpleGraph, i64, KN> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
317}
318
319#[cfg(feature = "example-db")]
320pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
321 vec![crate::example_db::specs::ModelExampleSpec {
322 id: "maximum_co_k_plex_simplegraph",
323 instance: Box::new(MaximumCoKPlex::<_, i64, KN>::with_k(
324 SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]),
325 vec![5, 1, 4, 1, 3],
326 2,
327 )),
328 optimal_config: serde_json::json!([true, false, true, false, true]),
329 optimal_value: serde_json::json!(12),
330 }]
331}
332
333#[cfg(test)]
334#[path = "../../unit_tests/models/graph/maximum_co_k_plex.rs"]
335mod tests;