problemreductions/models/graph/
minimum_geometric_connected_dominating_set.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "MinimumGeometricConnectedDominatingSet",
17 display_name: "Minimum Geometric Connected Dominating Set",
18 aliases: &[],
19 dimensions: &[],
20 module_path: module_path!(),
21 description: "Find minimum connected dominating set in a geometric point set",
22 fields: &[
23 FieldInfo {
24 name: "points",
25 type_name: "Vec<(f64, f64)>",
26 description: "The set of points P in the plane",
27 },
28 FieldInfo {
29 name: "radius",
30 type_name: "f64",
31 description: "The distance threshold B",
32 },
33 ],
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct MinimumGeometricConnectedDominatingSet {
62 points: Vec<(f64, f64)>,
64 radius: f64,
66}
67
68impl MinimumGeometricConnectedDominatingSet {
69 pub fn new(points: Vec<(f64, f64)>, radius: f64) -> Self {
74 assert!(radius > 0.0, "radius must be positive");
75 assert!(!points.is_empty(), "points must be non-empty");
76 Self { points, radius }
77 }
78
79 pub fn try_new(points: Vec<(f64, f64)>, radius: f64) -> Result<Self, String> {
81 if radius <= 0.0 {
82 return Err("radius must be positive".into());
83 }
84 if points.is_empty() {
85 return Err("points must be non-empty".into());
86 }
87 Ok(Self { points, radius })
88 }
89
90 pub fn num_points(&self) -> usize {
92 self.points.len()
93 }
94
95 pub fn radius(&self) -> f64 {
97 self.radius
98 }
99
100 pub fn points(&self) -> &[(f64, f64)] {
102 &self.points
103 }
104
105 fn dist_sq(a: (f64, f64), b: (f64, f64)) -> f64 {
107 let dx = a.0 - b.0;
108 let dy = a.1 - b.1;
109 dx * dx + dy * dy
110 }
111
112 fn within_radius(&self, i: usize, j: usize) -> bool {
114 Self::dist_sq(self.points[i], self.points[j]) <= self.radius * self.radius
115 }
116
117 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
119 let selected: Vec<usize> = config
120 .iter()
121 .enumerate()
122 .filter(|(_, &v)| v == 1)
123 .map(|(i, _)| i)
124 .collect();
125
126 if selected.is_empty() {
127 return false;
128 }
129
130 for (i, &v) in config.iter().enumerate() {
133 if v == 0 && !selected.iter().any(|&s| self.within_radius(i, s)) {
134 return false;
135 }
136 }
137
138 if selected.len() == 1 {
140 return true;
141 }
142 let mut visited = vec![false; selected.len()];
143 let mut queue = VecDeque::new();
144 visited[0] = true;
145 queue.push_back(0);
146 while let Some(u) = queue.pop_front() {
147 for (vi, &vj) in selected.iter().enumerate() {
148 if !visited[vi] && self.within_radius(selected[u], vj) {
149 visited[vi] = true;
150 queue.push_back(vi);
151 }
152 }
153 }
154 visited.iter().all(|&v| v)
155 }
156}
157
158impl Problem for MinimumGeometricConnectedDominatingSet {
159 const NAME: &'static str = "MinimumGeometricConnectedDominatingSet";
160 type Value = Min<usize>;
161
162 fn variant() -> Vec<(&'static str, &'static str)> {
163 crate::variant_params![]
164 }
165
166 fn dims(&self) -> Vec<usize> {
167 vec![2; self.num_points()]
168 }
169
170 fn evaluate(&self, config: &[usize]) -> Min<usize> {
171 if !self.is_valid_solution(config) {
172 return Min(None);
173 }
174 let count = config.iter().filter(|&&v| v == 1).count();
175 Min(Some(count))
176 }
177}
178
179crate::declare_variants! {
180 default MinimumGeometricConnectedDominatingSet => "2^num_points",
181}
182
183#[cfg(feature = "example-db")]
184pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
185 vec![crate::example_db::specs::ModelExampleSpec {
186 id: "minimum_geometric_connected_dominating_set",
187 instance: Box::new(MinimumGeometricConnectedDominatingSet::new(
188 vec![
189 (0.0, 0.0),
190 (3.0, 0.0),
191 (6.0, 0.0),
192 (9.0, 0.0),
193 (0.0, 3.0),
194 (3.0, 3.0),
195 (6.0, 3.0),
196 (9.0, 3.0),
197 ],
198 3.5,
199 )),
200 optimal_config: vec![1, 1, 1, 1, 0, 0, 0, 0],
201 optimal_value: serde_json::json!(4),
202 }]
203}
204
205#[cfg(test)]
206#[path = "../../unit_tests/models/graph/minimum_geometric_connected_dominating_set.rs"]
207mod tests;