problemreductions/models/graph/
maximum_co_k_plex.rs1use 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(bound(deserialize = "G: serde::Deserialize<'de>, W: serde::Deserialize<'de>"))]
68pub struct MaximumCoKPlex<G, W, K: KValue> {
69 graph: G,
71 weights: Vec<W>,
73 bound_k: usize,
80 #[serde(skip)]
81 _phantom: std::marker::PhantomData<K>,
82}
83
84#[derive(Debug, Deserialize, crate::CreateSpec)]
85struct MaximumCoKPlexCreateSpec<W> {
86 graph: SimpleGraph,
88 weights: Vec<W>,
90 k: usize,
92}
93
94impl<W: Clone + Default> TryFrom<MaximumCoKPlexCreateSpec<W>>
95 for MaximumCoKPlex<SimpleGraph, W, KN>
96{
97 type Error = crate::registry::ConstructionError;
98
99 fn try_from(spec: MaximumCoKPlexCreateSpec<W>) -> Result<Self, Self::Error> {
100 if spec.weights.len() != spec.graph.num_vertices() {
101 return Err(format!(
102 "weights has {} entries, expected {}",
103 spec.weights.len(),
104 spec.graph.num_vertices()
105 )
106 .into());
107 }
108 if spec.k == 0 {
109 return Err("k must be at least 1".to_string().into());
110 }
111 Ok(Self::with_k(spec.graph, spec.weights, spec.k))
112 }
113}
114
115impl<G: Graph, W: Clone + Default, K: KValue> MaximumCoKPlex<G, W, K> {
116 pub fn with_k(graph: G, weights: Vec<W>, bound_k: usize) -> Self {
123 assert_eq!(
124 weights.len(),
125 graph.num_vertices(),
126 "weights length must match graph num_vertices"
127 );
128 assert!(bound_k >= 1, "co-k-plex parameter k must be at least 1");
129 if let Some(fixed) = K::K {
130 assert_eq!(
131 fixed, bound_k,
132 "fixed K type disagrees with runtime bound_k"
133 );
134 }
135 Self {
136 graph,
137 weights,
138 bound_k,
139 _phantom: std::marker::PhantomData,
140 }
141 }
142
143 pub fn new(graph: G, weights: Vec<W>) -> Self {
149 let k = K::K.expect("KN requires with_k");
150 Self::with_k(graph, weights, k)
151 }
152
153 pub fn graph(&self) -> &G {
155 &self.graph
156 }
157
158 pub fn weights(&self) -> &[W] {
160 &self.weights
161 }
162
163 pub fn bound_k(&self) -> usize {
165 self.bound_k
166 }
167
168 pub fn is_weighted(&self) -> bool
170 where
171 W: WeightElement,
172 {
173 !W::IS_UNIT
174 }
175
176 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
178 is_co_k_plex_config(&self.graph, config, self.bound_k)
179 }
180}
181
182impl<G: Graph, W: WeightElement, K: KValue> MaximumCoKPlex<G, W, K> {
183 pub fn num_vertices(&self) -> usize {
185 self.graph.num_vertices()
186 }
187
188 pub fn num_edges(&self) -> usize {
190 self.graph.num_edges()
191 }
192}
193
194impl<G, W, K> Problem for MaximumCoKPlex<G, W, K>
195where
196 G: Graph + VariantParam,
197 W: WeightElement + VariantParam,
198 K: KValue,
199{
200 const NAME: &'static str = "MaximumCoKPlex";
201 type Solution = Vec<bool>;
202 type Value = Max<W::Sum>;
203
204 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
205
206 fn variant() -> Vec<(&'static str, &'static str)> {
207 crate::variant_params![G, W, K]
208 }
209
210 fn evaluate(
211 &self,
212 config: &Self::Solution,
213 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
214 if config.len() != self.graph.num_vertices() {
215 return Err(crate::traits::EvaluationError::InvalidConfiguration(
216 "vertex-selection length does not match the graph".into(),
217 ));
218 }
219 Ok({
220 if !is_co_k_plex_config(&self.graph, config, self.bound_k) {
221 return Ok(Max(None));
222 }
223 let mut total = W::Sum::zero();
224 for (i, &selected) in config.iter().enumerate() {
225 if selected {
226 total = W::checked_add_to_sum(
227 total,
228 self.weights[i].to_sum(),
229 "summing selected co-k-plex weights",
230 )?;
231 }
232 }
233 Max(Some(total))
234 })
235 }
236}
237
238impl<G, W, K> crate::solvers::BruteForceProblem for MaximumCoKPlex<G, W, K>
239where
240 G: Graph + VariantParam,
241 W: WeightElement + VariantParam,
242 K: KValue,
243{
244 fn dimensions(&self) -> Vec<usize> {
245 vec![2; self.graph.num_vertices()]
246 }
247}
248
249fn is_co_k_plex_config<G: Graph>(graph: &G, config: &[bool], bound_k: usize) -> bool {
252 if bound_k == 0 {
253 return false;
254 }
255 let n = graph.num_vertices();
256 let mut induced_degree = vec![0usize; n];
257 for (u, v) in graph.edges() {
258 let u_selected = config.get(u).copied().unwrap_or(false);
259 let v_selected = config.get(v).copied().unwrap_or(false);
260 if u_selected && v_selected {
261 induced_degree[u] += 1;
262 induced_degree[v] += 1;
263 if induced_degree[u] > bound_k - 1 || induced_degree[v] > bound_k - 1 {
264 return false;
265 }
266 }
267 }
268 true
269}
270
271#[derive(Debug, Deserialize, crate::CreateSpec)]
272struct MaximumCoKPlexOneCreateSpec {
273 graph: SimpleGraph,
275 k: usize,
276}
277
278impl TryFrom<MaximumCoKPlexOneCreateSpec> for MaximumCoKPlex<SimpleGraph, One, KN> {
279 type Error = crate::registry::ConstructionError;
280 fn try_from(spec: MaximumCoKPlexOneCreateSpec) -> Result<Self, Self::Error> {
281 let weights = vec![One; spec.graph.num_vertices()];
282 if spec.k == 0 {
283 return Err("k must be at least 1".into());
284 }
285 Ok(Self::with_k(spec.graph, weights, spec.k))
286 }
287}
288
289crate::declare_variants! {
290 default MaximumCoKPlex<SimpleGraph, One, KN> => "2^num_vertices" create MaximumCoKPlexOneCreateSpec,
291 MaximumCoKPlex<SimpleGraph, i64, KN> => "2^num_vertices" create MaximumCoKPlexCreateSpec<i64>,
292}
293
294crate::register_brute_force! {
295 MaximumCoKPlex<SimpleGraph, One, KN> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
296 MaximumCoKPlex<SimpleGraph, i64, KN> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
297}
298
299#[cfg(feature = "example-db")]
300pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
301 vec![crate::example_db::specs::ModelExampleSpec {
302 id: "maximum_co_k_plex_simplegraph",
303 instance: Box::new(MaximumCoKPlex::<_, i64, KN>::with_k(
304 SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]),
305 vec![5, 1, 4, 1, 3],
306 2,
307 )),
308 optimal_config: serde_json::json!([true, false, true, false, true]),
309 optimal_value: serde_json::json!(12),
310 }]
311}
312
313#[cfg(test)]
314#[path = "../../unit_tests/models/graph/maximum_co_k_plex.rs"]
315mod tests;