problemreductions/rules/
minimumdominatingset_ilp.rs

1//! Reduction from MinimumDominatingSet to ILP (Integer Linear Programming).
2//!
3//! The Dominating Set problem can be formulated as a binary ILP:
4//! - Variables: One binary variable per vertex (0 = not selected, 1 = selected)
5//! - Constraints: For each vertex v: x_v + sum_{u in N(v)} x_u >= 1
6//!   (v or at least one of its neighbors must be selected)
7//! - Objective: Minimize the sum of weights of selected vertices
8
9use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
10use crate::models::graph::MinimumDominatingSet;
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13use crate::topology::{Graph, SimpleGraph};
14
15/// Result of reducing MinimumDominatingSet to ILP.
16///
17/// This reduction creates a binary ILP where:
18/// - Each vertex corresponds to a binary variable
19/// - For each vertex v, the constraint x_v + sum_{u in N(v)} x_u >= 1 ensures
20///   that v is dominated (either v itself or one of its neighbors is selected)
21/// - The objective minimizes the total weight of selected vertices
22#[derive(Debug, Clone)]
23pub struct ReductionDSToILP {
24    target: ILP<bool>,
25}
26
27impl ReductionResult for ReductionDSToILP {
28    type Source = MinimumDominatingSet<SimpleGraph, i32>;
29    type Target = ILP<bool>;
30
31    fn target_problem(&self) -> &ILP<bool> {
32        &self.target
33    }
34
35    /// Extract solution from ILP back to MinimumDominatingSet.
36    ///
37    /// Since the mapping is 1:1 (each vertex maps to one binary variable),
38    /// the solution extraction is simply copying the configuration.
39    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
40        target_solution.to_vec()
41    }
42}
43
44#[reduction(
45    overhead = {
46        num_vars = "num_vertices",
47        num_constraints = "num_vertices",
48    }
49)]
50impl ReduceTo<ILP<bool>> for MinimumDominatingSet<SimpleGraph, i32> {
51    type Result = ReductionDSToILP;
52
53    fn reduce_to(&self) -> Self::Result {
54        let num_vars = self.graph().num_vertices();
55
56        // Constraints: For each vertex v, x_v + sum_{u in N(v)} x_u >= 1
57        // This ensures that v is dominated (either selected or has a selected neighbor)
58        let constraints: Vec<LinearConstraint> = (0..num_vars)
59            .map(|v| {
60                // Build terms: x_v with coefficient 1, plus each neighbor with coefficient 1
61                let mut terms: Vec<(usize, f64)> = vec![(v, 1.0)];
62                for neighbor in self.neighbors(v) {
63                    terms.push((neighbor, 1.0));
64                }
65                LinearConstraint::ge(terms, 1.0)
66            })
67            .collect();
68
69        // Objective: minimize sum of w_i * x_i (weighted sum of selected vertices)
70        let objective: Vec<(usize, f64)> = self
71            .weights()
72            .iter()
73            .enumerate()
74            .map(|(i, &w)| (i, w as f64))
75            .collect();
76
77        let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
78
79        ReductionDSToILP { target }
80    }
81}
82
83#[cfg(test)]
84#[path = "../unit_tests/rules/minimumdominatingset_ilp.rs"]
85mod tests;