Skip to main content

problemreductions/models/graph/
minimum_dominating_set.rs

1//! Dominating Set problem implementation.
2//!
3//! The Dominating Set problem asks for a minimum weight subset of vertices
4//! such that every vertex is either in the set or adjacent to a vertex in the set.
5
6use 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/// The Dominating Set problem.
32///
33/// Given a graph G = (V, E) and weights w_v for each vertex,
34/// find a subset D ⊆ V such that:
35/// - Every vertex is either in D or adjacent to a vertex in D (domination)
36/// - The total weight Σ_{v ∈ D} w_v is minimized
37///
38/// # Example
39///
40/// ```
41/// use problemreductions::models::graph::MinimumDominatingSet;
42/// use problemreductions::topology::SimpleGraph;
43/// use problemreductions::{Problem, BruteForce};
44///
45/// // Star graph: center dominates all
46/// let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]);
47/// let problem = MinimumDominatingSet::new(graph, vec![1; 4]);
48///
49/// let solver = BruteForce::new();
50/// let solutions = solver.find_all_witnesses(&problem).unwrap();
51///
52/// // Minimum dominating set is just the center vertex
53/// assert!(solutions.contains(&vec![true, false, false, false]));
54/// ```
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(try_from = "MinimumDominatingSetData<G, W>")]
57#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: Clone + Default + Deserialize<'de>"))]
58pub struct MinimumDominatingSet<G, W> {
59    /// The underlying graph.
60    graph: G,
61    /// Weights for each vertex.
62    weights: Vec<W>,
63}
64
65#[derive(Deserialize)]
66struct MinimumDominatingSetData<G, W> {
67    graph: G,
68    weights: Vec<W>,
69}
70
71impl<G, W> TryFrom<MinimumDominatingSetData<G, W>> for MinimumDominatingSet<G, W>
72where
73    G: Graph,
74    W: Clone + Default,
75{
76    type Error = crate::registry::ConstructionError;
77
78    fn try_from(data: MinimumDominatingSetData<G, W>) -> Result<Self, Self::Error> {
79        Self::try_new(data.graph, data.weights)
80    }
81}
82
83#[derive(Debug, Deserialize, crate::CreateSpec)]
84struct MinimumDominatingSetCreateSpec<W> {
85    /// The underlying graph G=(V,E).
86    graph: SimpleGraph,
87    /// Vertex weights w: V -> R.
88    weights: Vec<W>,
89}
90
91impl<W: Clone + Default> TryFrom<MinimumDominatingSetCreateSpec<W>>
92    for MinimumDominatingSet<SimpleGraph, W>
93{
94    type Error = crate::registry::ConstructionError;
95    fn try_from(spec: MinimumDominatingSetCreateSpec<W>) -> Result<Self, Self::Error> {
96        Self::try_new(spec.graph, spec.weights)
97    }
98}
99
100impl<G: Graph, W: Clone + Default> MinimumDominatingSet<G, W> {
101    /// Create a Dominating Set problem from a graph with given weights.
102    pub fn new(graph: G, weights: Vec<W>) -> Self {
103        Self::try_new(graph, weights).unwrap_or_else(|error| panic!("{error}"))
104    }
105
106    fn try_new(graph: G, weights: Vec<W>) -> Result<Self, crate::registry::ConstructionError> {
107        if weights.len() != graph.num_vertices() {
108            return Err(crate::registry::ConstructionError::length_mismatch(
109                "weights",
110                weights.len(),
111                graph.num_vertices(),
112            ));
113        }
114        Ok(Self { graph, weights })
115    }
116
117    /// Get a reference to the underlying graph.
118    pub fn graph(&self) -> &G {
119        &self.graph
120    }
121
122    /// Get neighbors of a vertex.
123    pub fn neighbors(&self, v: usize) -> Vec<usize> {
124        self.graph.neighbors(v)
125    }
126
127    /// Get the closed neighborhood `N[v] = {v} ∪ N(v)`.
128    pub fn closed_neighborhood(&self, v: usize) -> HashSet<usize> {
129        let mut neighborhood: HashSet<usize> = self.neighbors(v).into_iter().collect();
130        neighborhood.insert(v);
131        neighborhood
132    }
133
134    /// Get a reference to the weights slice.
135    pub fn weights(&self) -> &[W] {
136        &self.weights
137    }
138
139    /// Check if a configuration is a valid dominating set.
140    pub fn is_valid_solution(&self, config: &[bool]) -> bool {
141        self.is_dominating(config)
142    }
143
144    /// Check if a set of vertices is a dominating set.
145    fn is_dominating(&self, config: &[bool]) -> bool {
146        let n = self.graph.num_vertices();
147        let mut dominated = vec![false; n];
148
149        for (v, &selected) in config.iter().enumerate() {
150            if selected {
151                // v dominates itself
152                dominated[v] = true;
153                // v dominates all its neighbors
154                for neighbor in self.neighbors(v) {
155                    if neighbor < n {
156                        dominated[neighbor] = true;
157                    }
158                }
159            }
160        }
161
162        dominated.iter().all(|&d| d)
163    }
164}
165
166impl<G: Graph, W: WeightElement> MinimumDominatingSet<G, W> {
167    /// Get the number of vertices in the underlying graph.
168    pub fn num_vertices(&self) -> usize {
169        self.graph().num_vertices()
170    }
171
172    /// Get the number of edges in the underlying graph.
173    pub fn num_edges(&self) -> usize {
174        self.graph().num_edges()
175    }
176}
177
178impl<G, W> Problem for MinimumDominatingSet<G, W>
179where
180    G: Graph + crate::variant::VariantParam,
181    W: WeightElement + crate::variant::VariantParam,
182{
183    const NAME: &'static str = "MinimumDominatingSet";
184    type Solution = Vec<bool>;
185    type Value = Min<W::Sum>;
186
187    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
188
189    fn variant() -> Vec<(&'static str, &'static str)> {
190        crate::variant_params![G, W]
191    }
192
193    fn evaluate(
194        &self,
195        config: &Self::Solution,
196    ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
197        if config.len() != self.graph.num_vertices() {
198            return Err(crate::traits::EvaluationError::InvalidConfiguration(
199                "vertex-selection length does not match the graph".into(),
200            ));
201        }
202        Ok({
203            if !self.is_dominating(config) {
204                return Ok(Min(None));
205            }
206            let mut total = W::Sum::zero();
207            for (i, &selected) in config.iter().enumerate() {
208                if selected {
209                    total = W::checked_add_to_sum(
210                        total,
211                        self.weights[i].to_sum(),
212                        "summing selected dominating-set weights",
213                    )?;
214                }
215            }
216            Min(Some(total))
217        })
218    }
219}
220
221impl<G, W> crate::solvers::BruteForceProblem for MinimumDominatingSet<G, W>
222where
223    G: Graph + crate::variant::VariantParam,
224    W: WeightElement + crate::variant::VariantParam,
225{
226    fn dimensions(&self) -> Vec<usize> {
227        vec![2; self.graph.num_vertices()]
228    }
229}
230
231crate::impl_random_generate!(MinimumDominatingSet<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
232    Ok(MinimumDominatingSet::new(spec.graph()?, vec![1; spec.num_vertices]))
233});
234crate::impl_random_generate!(MinimumDominatingSet<SimpleGraph, One>, crate::random::SimpleGraphRandomSpec, |spec| {
235    Ok(MinimumDominatingSet::new(spec.graph()?, vec![One; spec.num_vertices]))
236});
237
238#[derive(Debug, Deserialize, crate::CreateSpec)]
239struct MinimumDominatingSetOneCreateSpec {
240    /// The underlying graph.
241    graph: SimpleGraph,
242}
243
244impl TryFrom<MinimumDominatingSetOneCreateSpec> for MinimumDominatingSet<SimpleGraph, One> {
245    type Error = crate::registry::ConstructionError;
246    fn try_from(spec: MinimumDominatingSetOneCreateSpec) -> Result<Self, Self::Error> {
247        let weights = vec![One; spec.graph.num_vertices()];
248        Self::try_new(spec.graph, weights)
249    }
250}
251
252crate::declare_variants! {
253    default MinimumDominatingSet<SimpleGraph, i64> => "1.4969^num_vertices" create MinimumDominatingSetCreateSpec<i64> random,
254    MinimumDominatingSet<SimpleGraph, One> => "1.4969^num_vertices" create MinimumDominatingSetOneCreateSpec random,
255}
256
257crate::register_brute_force! {
258    MinimumDominatingSet<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
259    MinimumDominatingSet<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
260}
261
262impl<G, W> crate::models::decision::DecisionProblemMeta for MinimumDominatingSet<G, W>
263where
264    G: Graph + crate::variant::VariantParam,
265    W: WeightElement + crate::variant::VariantParam,
266    W::Sum: std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
267{
268    const DECISION_NAME: &'static str = "DecisionMinimumDominatingSet";
269}
270
271impl Decision<MinimumDominatingSet<SimpleGraph, i64>> {
272    /// Number of vertices in the underlying graph.
273    pub fn num_vertices(&self) -> usize {
274        self.inner().num_vertices()
275    }
276
277    /// Number of edges in the underlying graph.
278    pub fn num_edges(&self) -> usize {
279        self.inner().num_edges()
280    }
281
282    /// Decision bound as a nonnegative integer.
283    pub fn k(&self) -> usize {
284        (*self.bound()).try_into().unwrap_or(0)
285    }
286}
287
288impl Decision<MinimumDominatingSet<SimpleGraph, One>> {
289    /// Number of vertices in the underlying graph.
290    pub fn num_vertices(&self) -> usize {
291        self.inner().num_vertices()
292    }
293
294    /// Number of edges in the underlying graph.
295    pub fn num_edges(&self) -> usize {
296        self.inner().num_edges()
297    }
298
299    /// Decision bound as a nonnegative integer.
300    pub fn k(&self) -> usize {
301        (*self.bound()).try_into().unwrap_or(0)
302    }
303}
304
305crate::register_decision_variant!(
306    MinimumDominatingSet<SimpleGraph, i64>,
307    "DecisionMinimumDominatingSet",
308    "1.4969^num_vertices",
309    &[],
310    "Decision version: does a dominating set of cost <= bound exist?",
311    category: crate::registry::ProblemCategory::Graph,
312    dims: [
313        VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
314        VariantDimension::new("weight", "i64", &["i64", "One"]),
315    ],
316    fields: [
317        FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
318        FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
319        FieldInfo { name: "bound", type_name: "i64", description: "Decision bound (maximum allowed dominating-set cost)" },
320    ],
321    additional: [MinimumDominatingSet<SimpleGraph, One> => "1.4969^num_vertices"],
322    decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
323);
324
325#[cfg(feature = "example-db")]
326pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
327    vec![crate::example_db::specs::ModelExampleSpec {
328        id: "minimum_dominating_set_simplegraph",
329        instance: Box::new(MinimumDominatingSet::new(
330            SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
331            vec![1i64; 5],
332        )),
333        optimal_config: serde_json::json!(vec![false, false, true, true, false]),
334        optimal_value: serde_json::json!(2),
335    }]
336}
337
338#[cfg(feature = "example-db")]
339pub(crate) fn decision_canonical_model_example_specs(
340) -> Vec<crate::example_db::specs::ModelExampleSpec> {
341    vec![
342        crate::example_db::specs::ModelExampleSpec {
343            id: "decision_minimum_dominating_set_simplegraph",
344            instance: Box::new(Decision::new(
345                MinimumDominatingSet::new(
346                    SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
347                    vec![1i64; 5],
348                ),
349                2,
350            )),
351            optimal_config: serde_json::json!(vec![false, false, true, true, false]),
352            optimal_value: serde_json::json!(true),
353        },
354        crate::example_db::specs::ModelExampleSpec {
355            id: "decision_minimum_dominating_set_six_vertex_graph",
356            instance: Box::new(Decision::new(
357                MinimumDominatingSet::new(
358                    SimpleGraph::new(
359                        6,
360                        vec![(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (3, 5), (4, 5)],
361                    ),
362                    vec![One; 6],
363                ),
364                2,
365            )),
366            optimal_config: serde_json::json!(vec![true, false, false, true, false, false]),
367            optimal_value: serde_json::json!(true),
368        },
369    ]
370}
371
372#[cfg(feature = "example-db")]
373pub(crate) fn decision_canonical_rule_example_specs(
374) -> Vec<crate::example_db::specs::RuleExampleSpec> {
375    vec![
376        crate::example_db::specs::RuleExampleSpec {
377            id: "decision_minimum_dominating_set_to_minimum_dominating_set",
378            build: || {
379                use crate::example_db::specs::assemble_rule_example;
380                use crate::export::SolutionPair;
381                use crate::rules::{AggregateReductionResult, ReduceToAggregate};
382
383                let source = crate::models::decision::Decision::new(
384                    MinimumDominatingSet::new(
385                        SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
386                        vec![1i64; 5],
387                    ),
388                    2,
389                );
390                let result = source
391                    .reduce_to_aggregate()
392                    .expect("reduction should succeed");
393                let target = result.target_problem();
394                let config = vec![false, false, true, true, false];
395                assemble_rule_example(
396                    &source,
397                    target,
398                    vec![SolutionPair {
399                        source_config: serde_json::json!(config.clone()),
400                        target_config: serde_json::json!(config),
401                    }],
402                )
403            },
404        },
405        // Cardinality variant: Decision<MDS<SG, One>> → MDS<SG, One> (aggregate)
406        crate::example_db::specs::RuleExampleSpec {
407            id: "decision_cardinality_dominating_set_to_minimum_dominating_set",
408            build: || {
409                use crate::example_db::specs::assemble_rule_example;
410                use crate::export::SolutionPair;
411                use crate::rules::{AggregateReductionResult, ReduceToAggregate};
412
413                let source = crate::models::decision::Decision::new(
414                    MinimumDominatingSet::new(
415                        SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
416                        vec![One; 5],
417                    ),
418                    2,
419                );
420                let result = source
421                    .reduce_to_aggregate()
422                    .expect("reduction should succeed");
423                let target = result.target_problem();
424                let config = vec![false, false, true, true, false];
425                assemble_rule_example(
426                    &source,
427                    target,
428                    vec![SolutionPair {
429                        source_config: serde_json::json!(config.clone()),
430                        target_config: serde_json::json!(config),
431                    }],
432                )
433            },
434        },
435    ]
436}
437
438/// Check if a set of vertices is a dominating set.
439///
440/// # Panics
441/// Panics if `selected.len() != graph.num_vertices()`.
442#[cfg(test)]
443pub(crate) fn is_dominating_set<G: Graph>(graph: &G, selected: &[bool]) -> bool {
444    assert_eq!(
445        selected.len(),
446        graph.num_vertices(),
447        "selected length must match num_vertices"
448    );
449
450    // Check each vertex is dominated
451    for v in 0..graph.num_vertices() {
452        if selected[v] {
453            continue; // v dominates itself
454        }
455        // Check if any neighbor of v is selected
456        if !graph.neighbors(v).iter().any(|&u| selected[u]) {
457            return false;
458        }
459    }
460
461    true
462}
463
464#[cfg(test)]
465#[path = "../../unit_tests/models/graph/minimum_dominating_set.rs"]
466mod tests;