problemreductions/topology/
kings_subgraph.rs1use super::graph::Graph;
7use super::unit_disk_graph::UnitDiskGraph;
8use crate::registry::ConstructionError;
9use crate::types::i64_to_exact_f64;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct KingsSubgraph {
22 positions: Vec<(i64, i64)>,
24}
25
26const KINGS_RADIUS: f64 = 1.5;
28
29impl KingsSubgraph {
30 pub fn new(positions: Vec<(i64, i64)>) -> Self {
32 Self { positions }
33 }
34
35 pub fn positions(&self) -> &[(i64, i64)] {
37 &self.positions
38 }
39
40 pub fn num_positions(&self) -> usize {
42 self.positions.len()
43 }
44
45 fn are_adjacent(p1: (i64, i64), p2: (i64, i64)) -> bool {
46 p1.0.abs_diff(p2.0) <= 1 && p1.1.abs_diff(p2.1) <= 1
47 }
48
49 pub(crate) fn try_to_unit_disk_graph(&self) -> Result<UnitDiskGraph, ConstructionError> {
50 let positions = self
51 .positions
52 .iter()
53 .map(|&(row, column)| {
54 let row = i64_to_exact_f64(row)?;
55 let column = i64_to_exact_f64(column)?;
56 Ok((row, column))
57 })
58 .collect::<Result<Vec<_>, ConstructionError>>()?;
59 let graph = UnitDiskGraph::new(positions, KINGS_RADIUS)?;
60 for first in 0..self.positions.len() {
61 for second in (first + 1)..self.positions.len() {
62 if Self::are_adjacent(self.positions[first], self.positions[second])
63 != graph.has_edge(first, second)
64 {
65 return Err(ConstructionError::Conversion(format!(
66 "king's-subgraph coordinates at indices {first} and {second} cannot be represented in UnitDiskGraph without changing adjacency"
67 )));
68 }
69 }
70 }
71 Ok(graph)
72 }
73}
74
75impl Graph for KingsSubgraph {
76 const NAME: &'static str = "KingsSubgraph";
77
78 fn num_vertices(&self) -> usize {
79 self.positions.len()
80 }
81
82 fn num_edges(&self) -> usize {
83 let n = self.positions.len();
84 let mut count = 0;
85 for i in 0..n {
86 for j in (i + 1)..n {
87 if Self::are_adjacent(self.positions[i], self.positions[j]) {
88 count += 1;
89 }
90 }
91 }
92 count
93 }
94
95 fn edges(&self) -> Vec<(usize, usize)> {
96 let n = self.positions.len();
97 let mut edges = Vec::new();
98 for i in 0..n {
99 for j in (i + 1)..n {
100 if Self::are_adjacent(self.positions[i], self.positions[j]) {
101 edges.push((i, j));
102 }
103 }
104 }
105 edges
106 }
107
108 fn has_edge(&self, u: usize, v: usize) -> bool {
109 if u >= self.positions.len() || v >= self.positions.len() || u == v {
110 return false;
111 }
112 Self::are_adjacent(self.positions[u], self.positions[v])
113 }
114
115 fn neighbors(&self, v: usize) -> Vec<usize> {
116 if v >= self.positions.len() {
117 return Vec::new();
118 }
119 (0..self.positions.len())
120 .filter(|&u| u != v && Self::are_adjacent(self.positions[v], self.positions[u]))
121 .collect()
122 }
123}
124
125impl crate::variant::VariantParam for KingsSubgraph {
126 const CATEGORY: &'static str = "graph";
127 const VALUE: &'static str = "KingsSubgraph";
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::types::MAX_EXACT_F64_INTEGER;
134
135 #[test]
136 fn adjacency_handles_full_i64_coordinate_range() {
137 let graph = KingsSubgraph::new(vec![
138 (i64::MAX, i64::MAX),
139 (i64::MAX - 1, i64::MAX - 1),
140 (i64::MIN, i64::MIN),
141 ]);
142
143 assert!(graph.has_edge(0, 1));
144 assert!(!graph.has_edge(0, 2));
145 }
146
147 #[test]
148 fn integer_adjacency_matches_euclidean_definition() {
149 for row_a in -4..=4 {
150 for column_a in -4..=4 {
151 for row_b in -4..=4 {
152 for column_b in -4..=4 {
153 let dr = (row_a - row_b) as f64;
154 let dc = (column_a - column_b) as f64;
155 let euclidean = dr.hypot(dc) < KINGS_RADIUS;
156 assert_eq!(
157 KingsSubgraph::are_adjacent((row_a, column_a), (row_b, column_b)),
158 euclidean
159 );
160 }
161 }
162 }
163 }
164 }
165
166 #[test]
167 fn unit_disk_conversion_rejects_inexact_coordinates() {
168 let graph = KingsSubgraph::new(vec![(MAX_EXACT_F64_INTEGER + 1, 0)]);
169
170 assert!(matches!(
171 graph.try_to_unit_disk_graph(),
172 Err(ConstructionError::InexactFloatConversion(_))
173 ));
174 }
175}