1use crate::models::decision::Decision;
7use crate::registry::{CreateSpec, FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::{Min, One, WeightElement};
11use num_traits::Zero;
12use serde::{Deserialize, Serialize};
13use std::collections::HashSet;
14
15inventory::submit! {
16 ProblemSchemaEntry {
17 name: "MinimumDominatingSet",
18 display_name: "Minimum Dominating Set",
19 aliases: &[],
20 dimensions: &[
21 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
22 VariantDimension::new("weight", "i64", &["i64", "One"]),
23 ],
24 category: crate::registry::ProblemCategory::Graph,
25 module_path: module_path!(),
26 description: "Find minimum weight dominating set in a graph",
27 fields: MinimumDominatingSetCreateSpec::<i64>::FIELDS,
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MinimumDominatingSet<G, W> {
57 graph: G,
59 weights: Vec<W>,
61}
62
63#[derive(Debug, Deserialize, crate::CreateSpec)]
64struct MinimumDominatingSetCreateSpec<W> {
65 graph: SimpleGraph,
67 weights: Vec<W>,
69}
70
71impl<W: Clone + Default> TryFrom<MinimumDominatingSetCreateSpec<W>>
72 for MinimumDominatingSet<SimpleGraph, W>
73{
74 type Error = crate::registry::ConstructionError;
75 fn try_from(spec: MinimumDominatingSetCreateSpec<W>) -> Result<Self, Self::Error> {
76 if spec.weights.len() != spec.graph.num_vertices() {
77 return Err(format!(
78 "weights has {} entries, expected {}",
79 spec.weights.len(),
80 spec.graph.num_vertices()
81 )
82 .into());
83 }
84 Ok(Self::new(spec.graph, spec.weights))
85 }
86}
87
88impl<G: Graph, W: Clone + Default> MinimumDominatingSet<G, W> {
89 pub fn new(graph: G, weights: Vec<W>) -> Self {
91 assert_eq!(
92 weights.len(),
93 graph.num_vertices(),
94 "weights length must match graph num_vertices"
95 );
96 Self { graph, weights }
97 }
98
99 pub fn graph(&self) -> &G {
101 &self.graph
102 }
103
104 pub fn neighbors(&self, v: usize) -> Vec<usize> {
106 self.graph.neighbors(v)
107 }
108
109 pub fn closed_neighborhood(&self, v: usize) -> HashSet<usize> {
111 let mut neighborhood: HashSet<usize> = self.neighbors(v).into_iter().collect();
112 neighborhood.insert(v);
113 neighborhood
114 }
115
116 pub fn weights(&self) -> &[W] {
118 &self.weights
119 }
120
121 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
123 self.is_dominating(config)
124 }
125
126 fn is_dominating(&self, config: &[bool]) -> bool {
128 let n = self.graph.num_vertices();
129 let mut dominated = vec![false; n];
130
131 for (v, &selected) in config.iter().enumerate() {
132 if selected {
133 dominated[v] = true;
135 for neighbor in self.neighbors(v) {
137 if neighbor < n {
138 dominated[neighbor] = true;
139 }
140 }
141 }
142 }
143
144 dominated.iter().all(|&d| d)
145 }
146}
147
148impl<G: Graph, W: WeightElement> MinimumDominatingSet<G, W> {
149 pub fn num_vertices(&self) -> usize {
151 self.graph().num_vertices()
152 }
153
154 pub fn num_edges(&self) -> usize {
156 self.graph().num_edges()
157 }
158}
159
160impl<G, W> Problem for MinimumDominatingSet<G, W>
161where
162 G: Graph + crate::variant::VariantParam,
163 W: WeightElement + crate::variant::VariantParam,
164{
165 const NAME: &'static str = "MinimumDominatingSet";
166 type Solution = Vec<bool>;
167 type Value = Min<W::Sum>;
168
169 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
170
171 fn variant() -> Vec<(&'static str, &'static str)> {
172 crate::variant_params![G, W]
173 }
174
175 fn evaluate(
176 &self,
177 config: &Self::Solution,
178 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
179 if config.len() != self.graph.num_vertices() {
180 return Err(crate::traits::EvaluationError::InvalidConfiguration(
181 "vertex-selection length does not match the graph".into(),
182 ));
183 }
184 Ok({
185 if !self.is_dominating(config) {
186 return Ok(Min(None));
187 }
188 let mut total = W::Sum::zero();
189 for (i, &selected) in config.iter().enumerate() {
190 if selected {
191 total = W::checked_add_to_sum(
192 total,
193 self.weights[i].to_sum(),
194 "summing selected dominating-set weights",
195 )?;
196 }
197 }
198 Min(Some(total))
199 })
200 }
201}
202
203impl<G, W> crate::solvers::BruteForceProblem for MinimumDominatingSet<G, W>
204where
205 G: Graph + crate::variant::VariantParam,
206 W: WeightElement + crate::variant::VariantParam,
207{
208 fn dimensions(&self) -> Vec<usize> {
209 vec![2; self.graph.num_vertices()]
210 }
211}
212
213crate::impl_random_generate!(MinimumDominatingSet<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
214 Ok(MinimumDominatingSet::new(spec.graph()?, vec![1; spec.num_vertices]))
215});
216crate::impl_random_generate!(MinimumDominatingSet<SimpleGraph, One>, crate::random::SimpleGraphRandomSpec, |spec| {
217 Ok(MinimumDominatingSet::new(spec.graph()?, vec![One; spec.num_vertices]))
218});
219
220#[derive(Debug, Deserialize, crate::CreateSpec)]
221struct MinimumDominatingSetOneCreateSpec {
222 graph: SimpleGraph,
224}
225
226impl TryFrom<MinimumDominatingSetOneCreateSpec> for MinimumDominatingSet<SimpleGraph, One> {
227 type Error = crate::registry::ConstructionError;
228 fn try_from(spec: MinimumDominatingSetOneCreateSpec) -> Result<Self, Self::Error> {
229 let weights = vec![One; spec.graph.num_vertices()];
230 Ok(Self::new(spec.graph, weights))
231 }
232}
233
234crate::declare_variants! {
235 default MinimumDominatingSet<SimpleGraph, i64> => "1.4969^num_vertices" create MinimumDominatingSetCreateSpec<i64> random,
236 MinimumDominatingSet<SimpleGraph, One> => "1.4969^num_vertices" create MinimumDominatingSetOneCreateSpec random,
237}
238
239crate::register_brute_force! {
240 MinimumDominatingSet<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
241 MinimumDominatingSet<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
242}
243
244impl<G, W> crate::models::decision::DecisionProblemMeta for MinimumDominatingSet<G, W>
245where
246 G: Graph + crate::variant::VariantParam,
247 W: WeightElement + crate::variant::VariantParam,
248 W::Sum: std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
249{
250 const DECISION_NAME: &'static str = "DecisionMinimumDominatingSet";
251}
252
253impl Decision<MinimumDominatingSet<SimpleGraph, i64>> {
254 pub fn num_vertices(&self) -> usize {
256 self.inner().num_vertices()
257 }
258
259 pub fn num_edges(&self) -> usize {
261 self.inner().num_edges()
262 }
263
264 pub fn k(&self) -> usize {
266 (*self.bound()).try_into().unwrap_or(0)
267 }
268}
269
270impl Decision<MinimumDominatingSet<SimpleGraph, One>> {
271 pub fn num_vertices(&self) -> usize {
273 self.inner().num_vertices()
274 }
275
276 pub fn num_edges(&self) -> usize {
278 self.inner().num_edges()
279 }
280
281 pub fn k(&self) -> usize {
283 (*self.bound()).try_into().unwrap_or(0)
284 }
285}
286
287crate::register_decision_variant!(
288 MinimumDominatingSet<SimpleGraph, i64>,
289 "DecisionMinimumDominatingSet",
290 "1.4969^num_vertices",
291 &[],
292 "Decision version: does a dominating set of cost <= bound exist?",
293 category: crate::registry::ProblemCategory::Graph,
294 dims: [
295 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
296 VariantDimension::new("weight", "i64", &["i64", "One"]),
297 ],
298 fields: [
299 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
300 FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
301 FieldInfo { name: "bound", type_name: "i64", description: "Decision bound (maximum allowed dominating-set cost)" },
302 ],
303 additional: [MinimumDominatingSet<SimpleGraph, One> => "1.4969^num_vertices"],
304 decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
305);
306
307#[cfg(feature = "example-db")]
308pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
309 vec![crate::example_db::specs::ModelExampleSpec {
310 id: "minimum_dominating_set_simplegraph",
311 instance: Box::new(MinimumDominatingSet::new(
312 SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
313 vec![1i64; 5],
314 )),
315 optimal_config: serde_json::json!(vec![false, false, true, true, false]),
316 optimal_value: serde_json::json!(2),
317 }]
318}
319
320#[cfg(feature = "example-db")]
321pub(crate) fn decision_canonical_model_example_specs(
322) -> Vec<crate::example_db::specs::ModelExampleSpec> {
323 vec![
324 crate::example_db::specs::ModelExampleSpec {
325 id: "decision_minimum_dominating_set_simplegraph",
326 instance: Box::new(Decision::new(
327 MinimumDominatingSet::new(
328 SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
329 vec![1i64; 5],
330 ),
331 2,
332 )),
333 optimal_config: serde_json::json!(vec![false, false, true, true, false]),
334 optimal_value: serde_json::json!(true),
335 },
336 crate::example_db::specs::ModelExampleSpec {
337 id: "decision_minimum_dominating_set_six_vertex_graph",
338 instance: Box::new(Decision::new(
339 MinimumDominatingSet::new(
340 SimpleGraph::new(
341 6,
342 vec![(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (3, 5), (4, 5)],
343 ),
344 vec![One; 6],
345 ),
346 2,
347 )),
348 optimal_config: serde_json::json!(vec![true, false, false, true, false, false]),
349 optimal_value: serde_json::json!(true),
350 },
351 ]
352}
353
354#[cfg(feature = "example-db")]
355pub(crate) fn decision_canonical_rule_example_specs(
356) -> Vec<crate::example_db::specs::RuleExampleSpec> {
357 vec![
358 crate::example_db::specs::RuleExampleSpec {
359 id: "decision_minimum_dominating_set_to_minimum_dominating_set",
360 build: || {
361 use crate::example_db::specs::assemble_rule_example;
362 use crate::export::SolutionPair;
363 use crate::rules::{AggregateReductionResult, ReduceToAggregate};
364
365 let source = crate::models::decision::Decision::new(
366 MinimumDominatingSet::new(
367 SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
368 vec![1i64; 5],
369 ),
370 2,
371 );
372 let result = source
373 .reduce_to_aggregate()
374 .expect("reduction should succeed");
375 let target = result.target_problem();
376 let config = vec![false, false, true, true, false];
377 assemble_rule_example(
378 &source,
379 target,
380 vec![SolutionPair {
381 source_config: serde_json::json!(config.clone()),
382 target_config: serde_json::json!(config),
383 }],
384 )
385 },
386 },
387 crate::example_db::specs::RuleExampleSpec {
389 id: "decision_cardinality_dominating_set_to_minimum_dominating_set",
390 build: || {
391 use crate::example_db::specs::assemble_rule_example;
392 use crate::export::SolutionPair;
393 use crate::rules::{AggregateReductionResult, ReduceToAggregate};
394
395 let source = crate::models::decision::Decision::new(
396 MinimumDominatingSet::new(
397 SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
398 vec![One; 5],
399 ),
400 2,
401 );
402 let result = source
403 .reduce_to_aggregate()
404 .expect("reduction should succeed");
405 let target = result.target_problem();
406 let config = vec![false, false, true, true, false];
407 assemble_rule_example(
408 &source,
409 target,
410 vec![SolutionPair {
411 source_config: serde_json::json!(config.clone()),
412 target_config: serde_json::json!(config),
413 }],
414 )
415 },
416 },
417 ]
418}
419
420#[cfg(test)]
425pub(crate) fn is_dominating_set<G: Graph>(graph: &G, selected: &[bool]) -> bool {
426 assert_eq!(
427 selected.len(),
428 graph.num_vertices(),
429 "selected length must match num_vertices"
430 );
431
432 for v in 0..graph.num_vertices() {
434 if selected[v] {
435 continue; }
437 if !graph.neighbors(v).iter().any(|&u| selected[u]) {
439 return false;
440 }
441 }
442
443 true
444}
445
446#[cfg(test)]
447#[path = "../../unit_tests/models/graph/minimum_dominating_set.rs"]
448mod tests;