1use super::*;
19use async_trait::async_trait;
20use std::collections::HashMap;
21use std::sync::Arc;
22use tokio::sync::RwLock;
23
24pub struct HyperbolicSpace {
26 my_coordinate: RwLock<HyperbolicCoordinate>,
28
29 neighbor_coordinates: Arc<RwLock<HashMap<NodeId, HyperbolicCoordinate>>>,
31
32 adjustment_rate: f64,
34
35 routing_stats: Arc<RwLock<RoutingStats>>,
37}
38
39#[derive(Debug, Default, Clone)]
41pub struct RoutingStats {
42 pub attempts: u64,
43 pub successes: u64,
44 pub failures: u64,
45 pub fallback_used: u64,
46 pub average_hop_count: f64,
47}
48
49impl Default for HyperbolicSpace {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl HyperbolicSpace {
56 pub fn new() -> Self {
58 Self {
59 my_coordinate: RwLock::new(HyperbolicCoordinate {
60 r: 0.5,
61 theta: rand::random::<f64>() * 2.0 * std::f64::consts::PI,
62 }),
63 neighbor_coordinates: Arc::new(RwLock::new(HashMap::new())),
64 adjustment_rate: 0.01,
65 routing_stats: Arc::new(RwLock::new(RoutingStats::default())),
66 }
67 }
68
69 pub fn neighbors_arc(&self) -> Arc<RwLock<HashMap<NodeId, HyperbolicCoordinate>>> {
71 Arc::clone(&self.neighbor_coordinates)
72 }
73
74 pub fn distance(a: &HyperbolicCoordinate, b: &HyperbolicCoordinate) -> f64 {
76 let delta = 2.0 * ((a.r - b.r).powi(2) + (a.theta - b.theta).cos().acos().powi(2)).sqrt();
77 let denominator = (1.0 - a.r.powi(2)) * (1.0 - b.r.powi(2));
78
79 (1.0 + delta / denominator).acosh()
80 }
81
82 pub async fn greedy_route(&self, target: &HyperbolicCoordinate) -> Option<NodeId> {
84 let my_coord = self.my_coordinate.read().await;
85 let my_distance = Self::distance(&my_coord, target);
86
87 let neighbors = self.neighbor_coordinates.read().await;
88 neighbors
89 .iter()
90 .filter(|(_, coord)| Self::distance(coord, target) < my_distance)
91 .min_by(|(_, a), (_, b)| {
92 let dist_a = Self::distance(a, target);
93 let dist_b = Self::distance(b, target);
94 dist_a
95 .partial_cmp(&dist_b)
96 .unwrap_or(std::cmp::Ordering::Equal)
97 })
98 .map(|(id, _)| id.clone())
99 }
100
101 pub async fn adjust_coordinate(&self, neighbor_coords: &[(NodeId, HyperbolicCoordinate)]) {
103 let mut my_coord = self.my_coordinate.write().await;
104
105 let degree = neighbor_coords.len();
107 let target_r = 1.0 - (2.0 / (degree as f64 + 2.0));
108 my_coord.r += self.adjustment_rate * (target_r - my_coord.r);
109
110 my_coord.r = my_coord.r.clamp(0.0, 0.999);
112
113 if !neighbor_coords.is_empty() {
115 let avg_theta = neighbor_coords
116 .iter()
117 .map(|(_, coord)| coord.theta)
118 .sum::<f64>()
119 / neighbor_coords.len() as f64;
120
121 let angle_diff = angle_difference(avg_theta, my_coord.theta);
122 my_coord.theta += self.adjustment_rate * angle_diff;
123
124 while my_coord.theta < 0.0 {
126 my_coord.theta += 2.0 * std::f64::consts::PI;
127 }
128 while my_coord.theta >= 2.0 * std::f64::consts::PI {
129 my_coord.theta -= 2.0 * std::f64::consts::PI;
130 }
131 }
132 }
133
134 pub async fn get_coordinate(&self) -> HyperbolicCoordinate {
136 *self.my_coordinate.read().await
137 }
138
139 pub async fn update_neighbor(&self, node_id: NodeId, coord: HyperbolicCoordinate) {
141 let mut neighbors = self.neighbor_coordinates.write().await;
142 neighbors.insert(node_id, coord);
143 }
144
145 pub async fn remove_neighbor(&self, node_id: &NodeId) {
147 let mut neighbors = self.neighbor_coordinates.write().await;
148 neighbors.remove(node_id);
149 }
150
151 pub async fn get_stats(&self) -> RoutingStats {
153 self.routing_stats.read().await.clone()
154 }
155
156 pub async fn get_success_rate(&self) -> f64 {
158 let stats = self.routing_stats.read().await;
159 if stats.attempts == 0 {
160 0.0
161 } else {
162 stats.successes as f64 / stats.attempts as f64
163 }
164 }
165
166 pub async fn record_routing_result(
168 &self,
169 success: bool,
170 hop_count: usize,
171 used_fallback: bool,
172 ) {
173 let mut stats = self.routing_stats.write().await;
174 stats.attempts += 1;
175
176 if success {
177 stats.successes += 1;
178 } else {
179 stats.failures += 1;
180 }
181
182 if used_fallback {
183 stats.fallback_used += 1;
184 }
185
186 let alpha = 0.1;
188 stats.average_hop_count =
189 (1.0 - alpha) * stats.average_hop_count + alpha * hop_count as f64;
190 }
191}
192
193pub fn angle_difference(a: f64, b: f64) -> f64 {
195 let diff = a - b;
196 if diff > std::f64::consts::PI {
197 diff - 2.0 * std::f64::consts::PI
198 } else if diff < -std::f64::consts::PI {
199 diff + 2.0 * std::f64::consts::PI
200 } else {
201 diff
202 }
203}
204
205pub struct HyperbolicRoutingStrategy {
207 space: Arc<HyperbolicSpace>,
209
210 local_id: NodeId,
212
213 max_hops: usize,
215}
216
217impl HyperbolicRoutingStrategy {
218 pub fn new(local_id: NodeId, space: Arc<HyperbolicSpace>) -> Self {
220 Self {
221 space,
222 local_id,
223 max_hops: 10,
224 }
225 }
226
227 async fn find_hyperbolic_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
229 let target_coord = {
231 let neighbors = self.space.neighbor_coordinates.read().await;
232 neighbors.get(target).cloned()
233 };
234
235 let target_coord = match target_coord {
236 Some(coord) => coord,
237 None => {
238 return Err(AdaptiveNetworkError::Routing(
240 "Target coordinate unknown".to_string(),
241 ));
242 }
243 };
244
245 let mut path = Vec::new();
246 let mut _current = self.local_id.clone();
247 let mut visited = std::collections::HashSet::<NodeId>::new();
248 visited.insert(_current.clone());
249
250 for _ in 0..self.max_hops {
252 let next_hop = self.space.greedy_route(&target_coord).await;
254
255 match next_hop {
256 Some(next) => {
257 if next == *target {
258 path.push(next);
260 return Ok(path);
261 }
262
263 if visited.contains(&next) {
264 return Err(AdaptiveNetworkError::Routing(
266 "Routing loop detected".to_string(),
267 ));
268 }
269
270 path.push(next.clone());
271 visited.insert(next.clone());
272 _current = next;
273 }
274 None => {
275 return Err(AdaptiveNetworkError::Routing(
277 "No closer neighbor found".to_string(),
278 ));
279 }
280 }
281 }
282
283 Err(AdaptiveNetworkError::Routing(
285 "Maximum hop count exceeded".to_string(),
286 ))
287 }
288}
289
290#[async_trait]
291impl RoutingStrategy for HyperbolicRoutingStrategy {
292 async fn find_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
293 let result = self.find_hyperbolic_path(target).await;
295
296 let (success, hop_count, used_fallback) = match &result {
298 Ok(path) => (true, path.len(), false),
299 Err(_) => (false, 0, true), };
301
302 self.space
303 .record_routing_result(success, hop_count, used_fallback)
304 .await;
305
306 result
307 }
308
309 fn route_score(&self, _neighbor: &NodeId, _target: &NodeId) -> f64 {
310 0.5
313 }
314
315 fn update_metrics(&mut self, _path: &[NodeId], _success: bool) {
316 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_hyperbolic_distance() {
326 let origin = HyperbolicCoordinate { r: 0.0, theta: 0.0 };
327 let point = HyperbolicCoordinate {
328 r: 0.5,
329 theta: std::f64::consts::PI,
330 };
331
332 let distance = HyperbolicSpace::distance(&origin, &point);
333 assert!(distance > 0.0);
334
335 let self_distance = HyperbolicSpace::distance(&origin, &origin);
337 assert!((self_distance - 0.0).abs() < 1e-10);
338 }
339
340 #[test]
341 fn test_angle_difference() {
342 assert!((angle_difference(0.0, 0.0) - 0.0).abs() < 1e-10);
343 assert!((angle_difference(std::f64::consts::PI, 0.0) - std::f64::consts::PI).abs() < 1e-10);
344 assert!(
345 (angle_difference(0.0, std::f64::consts::PI) - (-std::f64::consts::PI)).abs() < 1e-10
346 );
347 assert!(
348 (angle_difference(1.9 * std::f64::consts::PI, 0.1 * std::f64::consts::PI)
349 - (-0.2 * std::f64::consts::PI))
350 .abs()
351 < 1e-10
352 );
353 }
354
355 #[tokio::test]
356 async fn test_coordinate_adjustment() {
357 let space = HyperbolicSpace::new();
358 let initial = space.get_coordinate().await;
359
360 use rand::RngCore;
362
363 let mut hash1 = [0u8; 32];
364 rand::thread_rng().fill_bytes(&mut hash1);
365 let mut hash2 = [0u8; 32];
366 rand::thread_rng().fill_bytes(&mut hash2);
367
368 let neighbors = vec![
369 (
370 NodeId::from_bytes(hash1),
371 HyperbolicCoordinate { r: 0.9, theta: 0.0 },
372 ),
373 (
374 NodeId::from_bytes(hash2),
375 HyperbolicCoordinate {
376 r: 0.9,
377 theta: std::f64::consts::PI,
378 },
379 ),
380 ];
381
382 space.adjust_coordinate(&neighbors).await;
383 let adjusted = space.get_coordinate().await;
384
385 assert!(adjusted.r > initial.r);
387 }
388
389 #[tokio::test]
390 async fn test_routing_stats() {
391 let space = HyperbolicSpace::new();
392
393 space.record_routing_result(true, 3, false).await;
395 space.record_routing_result(true, 4, false).await;
396 space.record_routing_result(false, 0, true).await;
397
398 let stats = space.get_stats().await;
399 assert_eq!(stats.attempts, 3);
400 assert_eq!(stats.successes, 2);
401 assert_eq!(stats.failures, 1);
402 assert_eq!(stats.fallback_used, 1);
403
404 let success_rate = space.get_success_rate().await;
405 assert!((success_rate - 0.666).abs() < 0.01);
406 }
407
408 #[tokio::test]
409 async fn test_hyperbolic_routing_strategy() {
410 use rand::RngCore;
411
412 let space = Arc::new(HyperbolicSpace::new());
414
415 let mut hash = [0u8; 32];
416 rand::thread_rng().fill_bytes(&mut hash);
417 let local_id = NodeId::from_bytes(hash);
418
419 let strategy = HyperbolicRoutingStrategy::new(local_id.clone(), space.clone());
420
421 let mut hash1 = [0u8; 32];
423 rand::thread_rng().fill_bytes(&mut hash1);
424 let neighbor1 = NodeId::from_bytes(hash1);
425
426 let mut hash2 = [0u8; 32];
427 rand::thread_rng().fill_bytes(&mut hash2);
428 let neighbor2 = NodeId::from_bytes(hash2);
429
430 let mut hash_target = [0u8; 32];
431 rand::thread_rng().fill_bytes(&mut hash_target);
432 let target = NodeId::from_bytes(hash_target);
433
434 space
436 .update_neighbor(
437 neighbor1.clone(),
438 HyperbolicCoordinate { r: 0.3, theta: 0.0 },
439 )
440 .await;
441 space
442 .update_neighbor(
443 neighbor2.clone(),
444 HyperbolicCoordinate { r: 0.7, theta: 1.0 },
445 )
446 .await;
447 space
448 .update_neighbor(target.clone(), HyperbolicCoordinate { r: 0.8, theta: 1.5 })
449 .await;
450
451 let _result = strategy.find_path(&target).await;
453
454 let stats = space.get_stats().await;
456 assert_eq!(stats.attempts, 1);
457 }
458
459 #[tokio::test]
460 async fn test_greedy_routing() {
461 let space = HyperbolicSpace::new();
462
463 use rand::RngCore;
464
465 let mut neighbors = vec![];
467 for i in 0..5 {
468 let mut hash = [0u8; 32];
469 rand::thread_rng().fill_bytes(&mut hash);
470 let node_id = NodeId::from_bytes(hash);
471
472 let coord = HyperbolicCoordinate {
473 r: 0.1 + (i as f64) * 0.2,
474 theta: (i as f64) * std::f64::consts::PI / 3.0,
475 };
476
477 space.update_neighbor(node_id.clone(), coord).await;
478 neighbors.push((node_id, coord));
479 }
480
481 let target_coord = HyperbolicCoordinate { r: 0.9, theta: 1.0 };
483 let next_hop = space.greedy_route(&target_coord).await;
484
485 assert!(next_hop.is_some());
487
488 if let Some(chosen) = next_hop {
490 let neighbors_map = space.neighbor_coordinates.read().await;
491 let chosen_coord = neighbors_map.get(&chosen).unwrap();
492 let chosen_dist = HyperbolicSpace::distance(chosen_coord, &target_coord);
493
494 for (id, coord) in &neighbors {
496 if id != &chosen {
497 let dist = HyperbolicSpace::distance(coord, &target_coord);
498 assert!(dist >= chosen_dist);
499 }
500 }
501 }
502 }
503}