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)]
56pub struct MinimumDominatingSet<G, W> {
57    /// The underlying graph.
58    graph: G,
59    /// Weights for each vertex.
60    weights: Vec<W>,
61}
62
63#[derive(Debug, Deserialize, crate::CreateSpec)]
64struct MinimumDominatingSetCreateSpec<W> {
65    /// The underlying graph G=(V,E).
66    graph: SimpleGraph,
67    /// Vertex weights w: V -> R.
68    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    /// Create a Dominating Set problem from a graph with given weights.
90    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    /// Get a reference to the underlying graph.
100    pub fn graph(&self) -> &G {
101        &self.graph
102    }
103
104    /// Get neighbors of a vertex.
105    pub fn neighbors(&self, v: usize) -> Vec<usize> {
106        self.graph.neighbors(v)
107    }
108
109    /// Get the closed neighborhood `N[v] = {v} ∪ N(v)`.
110    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    /// Get a reference to the weights slice.
117    pub fn weights(&self) -> &[W] {
118        &self.weights
119    }
120
121    /// Check if a configuration is a valid dominating set.
122    pub fn is_valid_solution(&self, config: &[bool]) -> bool {
123        self.is_dominating(config)
124    }
125
126    /// Check if a set of vertices is a dominating set.
127    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                // v dominates itself
134                dominated[v] = true;
135                // v dominates all its neighbors
136                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    /// Get the number of vertices in the underlying graph.
150    pub fn num_vertices(&self) -> usize {
151        self.graph().num_vertices()
152    }
153
154    /// Get the number of edges in the underlying graph.
155    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    /// The underlying graph.
223    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    /// Number of vertices in the underlying graph.
255    pub fn num_vertices(&self) -> usize {
256        self.inner().num_vertices()
257    }
258
259    /// Number of edges in the underlying graph.
260    pub fn num_edges(&self) -> usize {
261        self.inner().num_edges()
262    }
263
264    /// Decision bound as a nonnegative integer.
265    pub fn k(&self) -> usize {
266        (*self.bound()).try_into().unwrap_or(0)
267    }
268}
269
270impl Decision<MinimumDominatingSet<SimpleGraph, One>> {
271    /// Number of vertices in the underlying graph.
272    pub fn num_vertices(&self) -> usize {
273        self.inner().num_vertices()
274    }
275
276    /// Number of edges in the underlying graph.
277    pub fn num_edges(&self) -> usize {
278        self.inner().num_edges()
279    }
280
281    /// Decision bound as a nonnegative integer.
282    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        // Cardinality variant: Decision<MDS<SG, One>> → MDS<SG, One> (aggregate)
388        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/// Check if a set of vertices is a dominating set.
421///
422/// # Panics
423/// Panics if `selected.len() != graph.num_vertices()`.
424#[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    // Check each vertex is dominated
433    for v in 0..graph.num_vertices() {
434        if selected[v] {
435            continue; // v dominates itself
436        }
437        // Check if any neighbor of v is selected
438        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;