1use crate::kernels::geometry::js_hypot;
8
9pub struct Node {
10 pub x: f64,
11 pub y: f64,
12 pub pinned: bool,
13}
14
15pub struct Edge {
21 pub other: i32,
22 pub dx: f64,
23 pub dy: f64,
24 pub weight: f64,
25}
26
27pub struct Graph {
28 pub nodes: Vec<Node>,
29 pub offsets: Vec<u32>,
31 pub edges: Vec<Edge>,
32}
33
34impl Graph {
35 pub fn solve_cycle(&mut self) -> bool {
39 let n = self.nodes.len();
40 if n < 3 || self.edges.len() != 2 * n || self.nodes.iter().filter(|v| v.pinned).count() != 1
41 {
42 return false;
43 }
44 if self.offsets.windows(2).any(|w| w[1] - w[0] != 2) {
45 return false;
46 }
47 let root = self.nodes.iter().position(|v| v.pinned).unwrap();
48 if !self.nodes[root].x.is_finite() || !self.nodes[root].y.is_finite() {
49 return false;
50 }
51 let mut seen = vec![false; n];
52 let mut path = Vec::with_capacity(n);
53 let (mut current, mut previous) = (root, usize::MAX);
54 let (mut dx, mut dy, mut resistance) = (0.0, 0.0, 0.0);
55 for _ in 0..n {
56 if seen[current] {
57 return false;
58 }
59 seen[current] = true;
60 let start = self.offsets[current] as usize;
61 let candidates = &self.edges[start..start + 2];
62 let Some((offset, edge)) = candidates
63 .iter()
64 .enumerate()
65 .find(|(_, e)| e.other as usize != previous)
66 else {
67 return false;
68 };
69 if edge.other < 0
70 || edge.other as usize >= n
71 || edge.other as usize == current
72 || !edge.dx.is_finite()
73 || !edge.dy.is_finite()
74 || !edge.weight.is_finite()
75 || edge.weight <= 0.0
76 {
77 return false;
78 }
79 let next = edge.other as usize;
80 let mirrors = &self.edges[self.offsets[next] as usize..self.offsets[next + 1] as usize];
81 if mirrors
82 .iter()
83 .filter(|e| {
84 e.other == current as i32
85 && e.dx == -edge.dx
86 && e.dy == -edge.dy
87 && e.weight == edge.weight
88 })
89 .count()
90 != 1
91 {
92 return false;
93 }
94 path.push((current, start + offset));
95 dx += edge.dx;
96 dy += edge.dy;
97 resistance += 1.0 / edge.weight;
98 previous = current;
99 current = next;
100 }
101 if current != root || !dx.is_finite() || !dy.is_finite() || !resistance.is_finite() {
102 return false;
103 }
104 let mut positions = Vec::with_capacity(n);
105 let (mut x, mut y) = (self.nodes[root].x, self.nodes[root].y);
106 for &(node, index) in &path {
107 positions.push((node, x, y));
108 let e = &self.edges[index];
109 let (rx, ry) = (-dx / (e.weight * resistance), -dy / (e.weight * resistance));
110 if !rx.is_finite() || !ry.is_finite() || js_hypot(rx, ry) > 6.0 {
111 return false;
112 }
113 x += e.dx + rx;
114 y += e.dy + ry;
115 if !x.is_finite() || !y.is_finite() {
116 return false;
117 }
118 }
119 for (i, x, y) in positions {
120 self.nodes[i].x = x;
121 self.nodes[i].y = y;
122 }
123 true
124 }
125
126 pub fn pass(&mut self, reverse: bool) -> f64 {
130 let n = self.nodes.len();
131 let mut max_change = 0.0f64;
132 for k in 0..n {
133 let i = if reverse { n - 1 - k } else { k };
134 if self.nodes[i].pinned {
135 continue;
136 }
137 let start = self.offsets[i] as usize;
138 let end = self.offsets[i + 1] as usize;
139 if start == end {
140 continue;
141 }
142 let (mut x, mut y, mut total) = (0.0f64, 0.0f64, 0.0f64);
143 {
144 let node = &self.nodes[i];
145 for e in &self.edges[start..end] {
146 if e.other < 0 {
147 continue;
148 }
149 let other = &self.nodes[e.other as usize];
150 let residual = js_hypot(other.x - node.x - e.dx, other.y - node.y - e.dy);
151 let weight = e.weight * (1.0f64).min(6.0 / (1e-6f64).max(residual));
152 x += (other.x - e.dx) * weight;
153 y += (other.y - e.dy) * weight;
154 total += weight;
155 }
156 }
157 if total == 0.0 {
158 continue;
159 }
160 let (px, py) = (x / total, y / total);
161 let node = &mut self.nodes[i];
162 let change = js_hypot(node.x - px, node.y - py);
163 if change > max_change {
164 max_change = change;
165 }
166 node.x = px;
167 node.y = py;
168 }
169 max_change
170 }
171
172 pub fn residual(&self) -> f64 {
175 let mut residual = 0.0f64;
176 for i in 0..self.nodes.len() {
177 let start = self.offsets[i] as usize;
178 let end = self.offsets[i + 1] as usize;
179 let node = &self.nodes[i];
180 for e in &self.edges[start..end] {
181 if e.other < 0 {
182 continue;
183 }
184 let other = &self.nodes[e.other as usize];
185 let r = js_hypot(other.x - node.x - e.dx, other.y - node.y - e.dy);
186 if r > residual {
187 residual = r;
188 }
189 }
190 }
191 residual
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn two_node_chain(dx: f64, dy: f64) -> Graph {
200 Graph {
202 nodes: vec![
203 Node {
204 x: 0.0,
205 y: 0.0,
206 pinned: true,
207 },
208 Node {
209 x: dx + 5.0,
210 y: dy + 5.0,
211 pinned: false,
212 },
213 ],
214 offsets: vec![0, 1, 2],
215 edges: vec![
216 Edge {
217 other: 1,
218 dx,
219 dy,
220 weight: 1.0,
221 },
222 Edge {
223 other: 0,
224 dx: -dx,
225 dy: -dy,
226 weight: 1.0,
227 },
228 ],
229 }
230 }
231
232 fn cycle(n: usize, closure: f64) -> Graph {
233 let nodes = (0..n)
234 .map(|i| Node {
235 x: i as f64 * 10.0,
236 y: 0.0,
237 pinned: i == 0,
238 })
239 .collect();
240 let mut offsets = vec![0];
241 let mut edges = Vec::new();
242 for i in 0..n {
243 edges.push(Edge {
244 other: ((i + 1) % n) as i32,
245 dx: if i + 1 == n {
246 -(n as f64 - 1.0) * 10.0 + closure
247 } else {
248 10.0
249 },
250 dy: 0.0,
251 weight: 1.0,
252 });
253 edges.push(Edge {
254 other: ((i + n - 1) % n) as i32,
255 dx: if i == 0 {
256 (n as f64 - 1.0) * 10.0 - closure
257 } else {
258 -10.0
259 },
260 dy: 0.0,
261 weight: 1.0,
262 });
263 offsets.push(edges.len() as u32);
264 }
265 Graph {
266 nodes,
267 offsets,
268 edges,
269 }
270 }
271
272 #[test]
273 fn cycle_solution_obeys_normal_equations() {
274 let mut graph = cycle(40, 2.0);
275 graph.edges[20].weight = 0.05;
276 graph.edges[23].weight = 0.05;
277 assert!(graph.solve_cycle());
278 for i in 1..graph.nodes.len() {
279 let mut gradient = (0.0, 0.0);
280 for e in &graph.edges[graph.offsets[i] as usize..graph.offsets[i + 1] as usize] {
281 gradient.0 +=
282 e.weight * (graph.nodes[i].x + e.dx - graph.nodes[e.other as usize].x);
283 gradient.1 +=
284 e.weight * (graph.nodes[i].y + e.dy - graph.nodes[e.other as usize].y);
285 }
286 assert!(js_hypot(gradient.0, gradient.1) < 1e-10);
287 }
288 assert_eq!(graph.nodes[0].x, 0.0);
289 }
290
291 #[test]
292 fn ineligible_cycles_are_left_unchanged() {
293 for invalid in 0..6 {
294 let mut graph = cycle(8, if invalid == 0 { 100.0 } else { 2.0 });
295 match invalid {
296 1 => graph.nodes[3].pinned = true,
297 2 => graph.edges[0].weight = 0.0,
298 3 => graph.edges[0].dx = f64::NAN,
299 4 => graph.edges[0].other = -1,
300 5 => graph.edges[0].weight = 2.0, _ => {}
302 }
303 assert!(!graph.solve_cycle());
304 for (i, node) in graph.nodes.iter().enumerate() {
305 assert_eq!(node.x, i as f64 * 10.0);
306 }
307 }
308 }
309
310 #[test]
311 fn relaxes_a_free_node_toward_its_pinned_neighbour() {
312 let mut g = two_node_chain(10.0, -4.0);
313 let change = g.pass(false);
314 assert!(change > 0.0);
315 assert!((g.nodes[1].x - 10.0).abs() < 1e-9);
316 assert!((g.nodes[1].y - -4.0).abs() < 1e-9);
317 assert_eq!(g.residual(), 0.0);
318 }
319
320 #[test]
321 fn missing_other_is_skipped_not_fatal() {
322 let mut g = Graph {
323 nodes: vec![Node {
324 x: 1.0,
325 y: 1.0,
326 pinned: false,
327 }],
328 offsets: vec![0, 1],
329 edges: vec![Edge {
330 other: -1,
331 dx: 0.0,
332 dy: 0.0,
333 weight: 1.0,
334 }],
335 };
336 let change = g.pass(false);
337 assert_eq!(change, 0.0);
338 assert_eq!(g.nodes[0].x, 1.0);
339 }
340}