problemreductions/topology/
unit_disk_graph.rs1use super::graph::Graph;
7use crate::registry::ConstructionError;
8use crate::types::i64_to_exact_f64;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Serialize)]
35pub struct UnitDiskGraph {
36 positions: Vec<(f64, f64)>,
38 radius: f64,
40 edges: Vec<(usize, usize)>,
42}
43
44impl UnitDiskGraph {
45 pub fn new(positions: Vec<(f64, f64)>, radius: f64) -> Result<Self, ConstructionError> {
52 if !radius.is_finite() {
53 return Err(ConstructionError::NonFiniteFloat(
54 "unit-disk radius must be finite".into(),
55 ));
56 }
57 if radius < 0.0 {
58 return Err(ConstructionError::Conversion(
59 "unit-disk radius must be nonnegative".into(),
60 ));
61 }
62 for (index, &(x, y)) in positions.iter().enumerate() {
63 if !x.is_finite() || !y.is_finite() {
64 return Err(ConstructionError::NonFiniteFloat(format!(
65 "unit-disk position at index {index} must be finite"
66 )));
67 }
68 }
69 let n = positions.len();
70 let mut edges = Vec::new();
71
72 for i in 0..n {
74 for j in (i + 1)..n {
75 if Self::distance(&positions[i], &positions[j])? <= radius {
76 edges.push((i, j));
77 }
78 }
79 }
80
81 Ok(Self {
82 positions,
83 radius,
84 edges,
85 })
86 }
87
88 pub fn unit(positions: Vec<(f64, f64)>) -> Result<Self, ConstructionError> {
90 Self::new(positions, 1.0)
91 }
92
93 fn distance(p1: &(f64, f64), p2: &(f64, f64)) -> Result<f64, ConstructionError> {
95 let dx = p1.0 - p2.0;
96 let dy = p1.1 - p2.1;
97 let distance = (dx * dx + dy * dy).sqrt();
98 distance.is_finite().then_some(distance).ok_or_else(|| {
99 ConstructionError::NonFiniteFloat(
100 "computing a unit-disk distance produced a non-finite value".into(),
101 )
102 })
103 }
104
105 pub fn num_vertices(&self) -> usize {
107 self.positions.len()
108 }
109
110 pub fn num_edges(&self) -> usize {
112 self.edges.len()
113 }
114
115 pub fn radius(&self) -> f64 {
117 self.radius
118 }
119
120 pub fn position(&self, v: usize) -> Option<(f64, f64)> {
122 self.positions.get(v).copied()
123 }
124
125 pub fn positions(&self) -> &[(f64, f64)] {
127 &self.positions
128 }
129
130 pub fn edges(&self) -> &[(usize, usize)] {
132 &self.edges
133 }
134
135 pub fn has_edge(&self, u: usize, v: usize) -> bool {
137 let (u, v) = if u < v { (u, v) } else { (v, u) };
138 self.edges.contains(&(u, v))
139 }
140
141 pub fn vertex_distance(&self, u: usize, v: usize) -> Option<f64> {
143 match (self.positions.get(u), self.positions.get(v)) {
144 (Some(p1), Some(p2)) => Some(
145 Self::distance(p1, p2)
146 .expect("validated unit-disk graph has finite pairwise distances"),
147 ),
148 _ => None,
149 }
150 }
151
152 pub fn neighbors(&self, v: usize) -> Vec<usize> {
154 self.edges
155 .iter()
156 .filter_map(|&(u1, u2)| {
157 if u1 == v {
158 Some(u2)
159 } else if u2 == v {
160 Some(u1)
161 } else {
162 None
163 }
164 })
165 .collect()
166 }
167
168 pub fn degree(&self, v: usize) -> usize {
170 self.neighbors(v).len()
171 }
172
173 pub fn bounding_box(&self) -> Option<((f64, f64), (f64, f64))> {
175 if self.positions.is_empty() {
176 return None;
177 }
178
179 let min_x = self
180 .positions
181 .iter()
182 .map(|p| p.0)
183 .fold(f64::INFINITY, f64::min);
184 let max_x = self
185 .positions
186 .iter()
187 .map(|p| p.0)
188 .fold(f64::NEG_INFINITY, f64::max);
189 let min_y = self
190 .positions
191 .iter()
192 .map(|p| p.1)
193 .fold(f64::INFINITY, f64::min);
194 let max_y = self
195 .positions
196 .iter()
197 .map(|p| p.1)
198 .fold(f64::NEG_INFINITY, f64::max);
199
200 Some(((min_x, min_y), (max_x, max_y)))
201 }
202
203 pub fn grid(
212 rows: usize,
213 cols: usize,
214 spacing: f64,
215 radius: f64,
216 ) -> Result<Self, ConstructionError> {
217 if !spacing.is_finite() {
218 return Err(ConstructionError::NonFiniteFloat(
219 "unit-disk grid spacing must be finite".into(),
220 ));
221 }
222 let capacity = rows.checked_mul(cols).ok_or_else(|| {
223 ConstructionError::IntegerOverflow("unit-disk grid size exceeds usize".into())
224 })?;
225 let mut positions = Vec::with_capacity(capacity);
226 for r in 0..rows {
227 for c in 0..cols {
228 let c = i64::try_from(c).map_err(|_| {
229 ConstructionError::IntegerOverflow(
230 "unit-disk grid column does not fit i64".into(),
231 )
232 })?;
233 let r = i64::try_from(r).map_err(|_| {
234 ConstructionError::IntegerOverflow("unit-disk grid row does not fit i64".into())
235 })?;
236 let x = i64_to_exact_f64(c)? * spacing;
237 let y = i64_to_exact_f64(r)? * spacing;
238 if !x.is_finite() || !y.is_finite() {
239 return Err(ConstructionError::NonFiniteFloat(
240 "computing unit-disk grid coordinates produced a non-finite value".into(),
241 ));
242 }
243 positions.push((x, y));
244 }
245 }
246 Self::new(positions, radius)
247 }
248}
249
250impl<'de> Deserialize<'de> for UnitDiskGraph {
251 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252 where
253 D: serde::Deserializer<'de>,
254 {
255 #[derive(Deserialize)]
256 struct Raw {
257 positions: Vec<(f64, f64)>,
258 radius: f64,
259 edges: Vec<(usize, usize)>,
260 }
261
262 let raw = Raw::deserialize(deserializer)?;
263 let graph = Self::new(raw.positions, raw.radius).map_err(serde::de::Error::custom)?;
264 if graph.edges != raw.edges {
265 return Err(serde::de::Error::custom(
266 "unit-disk edges do not match positions and radius",
267 ));
268 }
269 Ok(graph)
270 }
271}
272
273impl Graph for UnitDiskGraph {
274 const NAME: &'static str = "UnitDiskGraph";
275
276 fn num_vertices(&self) -> usize {
277 self.positions.len()
278 }
279
280 fn num_edges(&self) -> usize {
281 self.edges.len()
282 }
283
284 fn edges(&self) -> Vec<(usize, usize)> {
285 self.edges.clone()
286 }
287
288 fn has_edge(&self, u: usize, v: usize) -> bool {
289 let (u, v) = if u < v { (u, v) } else { (v, u) };
290 self.edges.contains(&(u, v))
291 }
292
293 fn neighbors(&self, v: usize) -> Vec<usize> {
294 self.edges
295 .iter()
296 .filter_map(|&(u1, u2)| {
297 if u1 == v {
298 Some(u2)
299 } else if u2 == v {
300 Some(u1)
301 } else {
302 None
303 }
304 })
305 .collect()
306 }
307}
308
309use crate::impl_variant_param;
310impl_variant_param!(UnitDiskGraph, "graph");
311
312#[cfg(test)]
313#[path = "../unit_tests/topology/unit_disk_graph.rs"]
314mod tests;