problemreductions/models/graph/
maximum_co_k_plex.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry, 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", "i32"]),
27 VariantDimension::new("k", "KN", &["KN"]),
28 ],
29 module_path: module_path!(),
30 description: "Find maximum-weight vertex subset whose induced subgraph has maximum degree at most k-1",
31 fields: &[
32 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
33 FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
34 FieldInfo { name: "bound_k", type_name: "usize", description: "Co-k-plex parameter k >= 1; selected-vertex induced degree must be at most k-1" },
35 ],
36 }
37}
38
39inventory::submit! {
40 ProblemSizeFieldEntry {
41 name: "MaximumCoKPlex",
42 fields: &["num_vertices", "num_edges"],
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(bound(deserialize = "G: serde::Deserialize<'de>, W: serde::Deserialize<'de>"))]
78pub struct MaximumCoKPlex<G, W, K: KValue> {
79 graph: G,
81 weights: Vec<W>,
83 bound_k: usize,
90 #[serde(skip)]
91 _phantom: std::marker::PhantomData<K>,
92}
93
94impl<G: Graph, W: Clone + Default, K: KValue> MaximumCoKPlex<G, W, K> {
95 pub fn with_k(graph: G, weights: Vec<W>, bound_k: usize) -> Self {
102 assert_eq!(
103 weights.len(),
104 graph.num_vertices(),
105 "weights length must match graph num_vertices"
106 );
107 assert!(bound_k >= 1, "co-k-plex parameter k must be at least 1");
108 if let Some(fixed) = K::K {
109 assert_eq!(
110 fixed, bound_k,
111 "fixed K type disagrees with runtime bound_k"
112 );
113 }
114 Self {
115 graph,
116 weights,
117 bound_k,
118 _phantom: std::marker::PhantomData,
119 }
120 }
121
122 pub fn new(graph: G, weights: Vec<W>) -> Self {
128 let k = K::K.expect("KN requires with_k");
129 Self::with_k(graph, weights, k)
130 }
131
132 pub fn graph(&self) -> &G {
134 &self.graph
135 }
136
137 pub fn weights(&self) -> &[W] {
139 &self.weights
140 }
141
142 pub fn bound_k(&self) -> usize {
144 self.bound_k
145 }
146
147 pub fn is_weighted(&self) -> bool
149 where
150 W: WeightElement,
151 {
152 !W::IS_UNIT
153 }
154
155 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
157 is_co_k_plex_config(&self.graph, config, self.bound_k)
158 }
159}
160
161impl<G: Graph, W: WeightElement, K: KValue> MaximumCoKPlex<G, W, K> {
162 pub fn num_vertices(&self) -> usize {
164 self.graph.num_vertices()
165 }
166
167 pub fn num_edges(&self) -> usize {
169 self.graph.num_edges()
170 }
171}
172
173impl<G, W, K> Problem for MaximumCoKPlex<G, W, K>
174where
175 G: Graph + VariantParam,
176 W: WeightElement + VariantParam,
177 K: KValue,
178{
179 const NAME: &'static str = "MaximumCoKPlex";
180 type Value = Max<W::Sum>;
181
182 fn variant() -> Vec<(&'static str, &'static str)> {
183 crate::variant_params![G, W, K]
184 }
185
186 fn dims(&self) -> Vec<usize> {
187 vec![2; self.graph.num_vertices()]
188 }
189
190 fn evaluate(&self, config: &[usize]) -> Max<W::Sum> {
191 if !is_co_k_plex_config(&self.graph, config, self.bound_k) {
192 return Max(None);
193 }
194 let mut total = W::Sum::zero();
195 for (i, &selected) in config.iter().enumerate() {
196 if selected == 1 {
197 total += self.weights[i].to_sum();
198 }
199 }
200 Max(Some(total))
201 }
202}
203
204fn is_co_k_plex_config<G: Graph>(graph: &G, config: &[usize], bound_k: usize) -> bool {
207 if bound_k == 0 {
208 return false;
209 }
210 let n = graph.num_vertices();
211 let mut induced_degree = vec![0usize; n];
212 for (u, v) in graph.edges() {
213 let u_selected = config.get(u).copied().unwrap_or(0) == 1;
214 let v_selected = config.get(v).copied().unwrap_or(0) == 1;
215 if u_selected && v_selected {
216 induced_degree[u] += 1;
217 induced_degree[v] += 1;
218 if induced_degree[u] > bound_k - 1 || induced_degree[v] > bound_k - 1 {
219 return false;
220 }
221 }
222 }
223 true
224}
225
226crate::declare_variants! {
227 default MaximumCoKPlex<SimpleGraph, One, KN> => "2^num_vertices",
228 MaximumCoKPlex<SimpleGraph, i32, KN> => "2^num_vertices",
229}
230
231#[cfg(feature = "example-db")]
232pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
233 vec![crate::example_db::specs::ModelExampleSpec {
234 id: "maximum_co_k_plex_simplegraph_i32",
235 instance: Box::new(MaximumCoKPlex::<_, i32, KN>::with_k(
236 SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]),
237 vec![5, 1, 4, 1, 3],
238 2,
239 )),
240 optimal_config: vec![1, 0, 1, 0, 1],
241 optimal_value: serde_json::json!(12),
242 }]
243}
244
245#[cfg(test)]
246#[path = "../../unit_tests/models/graph/maximum_co_k_plex.rs"]
247mod tests;