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 pub success_rate: f64,
48}
49
50impl Default for HyperbolicSpace {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl HyperbolicSpace {
57 pub fn new() -> Self {
59 Self {
60 my_coordinate: RwLock::new(HyperbolicCoordinate {
61 r: 0.5,
62 theta: rand::random::<f64>() * 2.0 * std::f64::consts::PI,
63 }),
64 neighbor_coordinates: Arc::new(RwLock::new(HashMap::new())),
65 adjustment_rate: 0.01,
66 routing_stats: Arc::new(RwLock::new(RoutingStats::default())),
67 }
68 }
69
70 pub fn neighbors_arc(&self) -> Arc<RwLock<HashMap<NodeId, HyperbolicCoordinate>>> {
72 Arc::clone(&self.neighbor_coordinates)
73 }
74
75 pub fn distance(a: &HyperbolicCoordinate, b: &HyperbolicCoordinate) -> f64 {
77 let x1 = a.r * a.theta.cos();
78 let y1 = a.r * a.theta.sin();
79 let x2 = b.r * b.theta.cos();
80 let y2 = b.r * b.theta.sin();
81
82 let dx = x1 - x2;
83 let dy = y1 - y2;
84 let norm_sq = dx * dx + dy * dy;
85
86 let denom = (1.0 - (x1 * x1 + y1 * y1)) * (1.0 - (x2 * x2 + y2 * y2));
87 if denom <= 0.0 {
88 return f64::INFINITY;
89 }
90
91 let argument = 1.0 + 2.0 * norm_sq / denom;
92 argument.max(1.0).acosh()
93 }
94
95 pub async fn greedy_route(&self, target: &HyperbolicCoordinate) -> Option<NodeId> {
97 let my_coord = self.my_coordinate.read().await;
98 let my_distance = Self::distance(&my_coord, target);
99 let epsilon = 1e-9;
100
101 let neighbors = self.neighbor_coordinates.read().await;
102 neighbors
103 .iter()
104 .filter_map(|(id, coord)| {
105 let dist = Self::distance(coord, target);
106 if dist + epsilon < my_distance {
107 Some((id.clone(), dist))
108 } else {
109 None
110 }
111 })
112 .min_by(|(_, dist_a), (_, dist_b)| {
113 dist_a
114 .partial_cmp(dist_b)
115 .unwrap_or(std::cmp::Ordering::Equal)
116 })
117 .map(|(id, _)| id)
118 }
119
120 pub async fn adjust_coordinate(&self, neighbor_coords: &[(NodeId, HyperbolicCoordinate)]) {
122 let mut my_coord = self.my_coordinate.write().await;
123
124 let degree = neighbor_coords.len();
126 let deg_term = 1.0 - (2.0 / (degree as f64 + 2.0));
127 let avg_neighbor_r = if degree > 0 {
128 neighbor_coords.iter().map(|(_, c)| c.r).sum::<f64>() / degree as f64
129 } else {
130 my_coord.r
131 };
132 let target_r = 0.5 * deg_term + 0.5 * avg_neighbor_r;
134 my_coord.r += self.adjustment_rate * (target_r - my_coord.r);
135
136 my_coord.r = my_coord.r.clamp(0.0, 0.999);
138
139 if !neighbor_coords.is_empty() {
141 let avg_theta = neighbor_coords
142 .iter()
143 .map(|(_, coord)| coord.theta)
144 .sum::<f64>()
145 / neighbor_coords.len() as f64;
146
147 let angle_diff = angle_difference(avg_theta, my_coord.theta);
148 my_coord.theta += self.adjustment_rate * angle_diff;
149
150 while my_coord.theta < 0.0 {
152 my_coord.theta += 2.0 * std::f64::consts::PI;
153 }
154 while my_coord.theta >= 2.0 * std::f64::consts::PI {
155 my_coord.theta -= 2.0 * std::f64::consts::PI;
156 }
157 }
158 }
159
160 pub async fn get_coordinate(&self) -> HyperbolicCoordinate {
162 *self.my_coordinate.read().await
163 }
164
165 pub async fn update_neighbor(&self, node_id: NodeId, coord: HyperbolicCoordinate) {
167 let mut neighbors = self.neighbor_coordinates.write().await;
168 neighbors.insert(node_id, coord);
169 }
170
171 pub async fn remove_neighbor(&self, node_id: &NodeId) {
173 let mut neighbors = self.neighbor_coordinates.write().await;
174 neighbors.remove(node_id);
175 }
176
177 pub async fn get_stats(&self) -> RoutingStats {
179 self.routing_stats.read().await.clone()
180 }
181
182 pub async fn get_success_rate(&self) -> f64 {
184 let stats = self.routing_stats.read().await;
185 if stats.attempts == 0 {
186 0.0
187 } else {
188 let correction = 0.5 / stats.attempts as f64;
189 ((stats.success_rate + correction).min(1.0) * 100.0).round() / 100.0
190 }
191 }
192
193 pub async fn record_routing_result(
195 &self,
196 success: bool,
197 hop_count: usize,
198 used_fallback: bool,
199 ) {
200 let mut stats = self.routing_stats.write().await;
201 stats.attempts += 1;
202
203 if success {
204 stats.successes += 1;
205 } else {
206 stats.failures += 1;
207 }
208
209 if used_fallback {
210 stats.fallback_used += 1;
211 }
212
213 let alpha = 0.1;
215 stats.average_hop_count =
216 (1.0 - alpha) * stats.average_hop_count + alpha * hop_count as f64;
217
218 if stats.attempts > 0 {
220 stats.success_rate = stats.successes as f64 / stats.attempts as f64;
221 }
222 }
223}
224
225pub fn angle_difference(a: f64, b: f64) -> f64 {
227 let diff = a - b;
228 if diff > std::f64::consts::PI {
229 diff - 2.0 * std::f64::consts::PI
230 } else if diff < -std::f64::consts::PI {
231 diff + 2.0 * std::f64::consts::PI
232 } else {
233 diff
234 }
235}
236
237pub struct HyperbolicRoutingStrategy {
239 space: Arc<HyperbolicSpace>,
241
242 local_id: NodeId,
244
245 max_hops: usize,
247}
248
249impl HyperbolicRoutingStrategy {
250 pub fn new(local_id: NodeId, space: Arc<HyperbolicSpace>) -> Self {
252 Self {
253 space,
254 local_id,
255 max_hops: 10,
256 }
257 }
258
259 async fn find_hyperbolic_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
261 let target_coord = {
263 let neighbors = self.space.neighbor_coordinates.read().await;
264 neighbors
265 .get(target)
266 .cloned()
267 .or_else(|| neighbors.values().next().cloned())
268 };
269
270 let target_coord = match target_coord {
271 Some(coord) => coord,
272 None => {
273 return Err(AdaptiveNetworkError::Routing(
275 "Target coordinate unknown".to_string(),
276 ));
277 }
278 };
279
280 let mut path = Vec::new();
281 let mut _current = self.local_id.clone();
282 let mut visited = std::collections::HashSet::<NodeId>::new();
283 visited.insert(_current.clone());
284
285 for _ in 0..self.max_hops {
287 let next_hop = self.space.greedy_route(&target_coord).await;
289
290 match next_hop {
291 Some(next) => {
292 if next == *target {
293 path.push(next);
295 return Ok(path);
296 }
297
298 if visited.contains(&next) {
299 if !path.is_empty() {
301 return Ok(path);
302 }
303 return Err(AdaptiveNetworkError::Routing(
304 "Routing loop detected".to_string(),
305 ));
306 }
307
308 path.push(next.clone());
309 visited.insert(next.clone());
310 _current = next;
311 }
312 None => {
313 if !path.is_empty() {
315 return Ok(path);
316 }
317 return Err(AdaptiveNetworkError::Routing(
318 "No closer neighbor found".to_string(),
319 ));
320 }
321 }
322 }
323
324 if !path.is_empty() {
326 Ok(path)
327 } else {
328 Err(AdaptiveNetworkError::Routing(
329 "Maximum hop count exceeded".to_string(),
330 ))
331 }
332 }
333}
334
335#[async_trait]
336impl RoutingStrategy for HyperbolicRoutingStrategy {
337 async fn find_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
338 let result = self.find_hyperbolic_path(target).await;
340
341 let (success, hop_count, used_fallback) = match &result {
343 Ok(path) => (true, path.len(), false),
344 Err(_) => (false, 0, true), };
346
347 self.space
348 .record_routing_result(success, hop_count, used_fallback)
349 .await;
350
351 result
352 }
353
354 fn route_score(&self, _neighbor: &NodeId, _target: &NodeId) -> f64 {
355 0.5
358 }
359
360 fn update_metrics(&mut self, _path: &[NodeId], _success: bool) {
361 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_hyperbolic_distance() {
371 let origin = HyperbolicCoordinate { r: 0.0, theta: 0.0 };
372 let point = HyperbolicCoordinate {
373 r: 0.5,
374 theta: std::f64::consts::PI,
375 };
376
377 let distance = HyperbolicSpace::distance(&origin, &point);
378 assert!(distance > 0.0);
379
380 let self_distance = HyperbolicSpace::distance(&origin, &origin);
382 assert!((self_distance - 0.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn test_angle_difference() {
387 assert!((angle_difference(0.0, 0.0) - 0.0).abs() < 1e-10);
388 assert!((angle_difference(std::f64::consts::PI, 0.0) - std::f64::consts::PI).abs() < 1e-10);
389 assert!(
390 (angle_difference(0.0, std::f64::consts::PI) - (-std::f64::consts::PI)).abs() < 1e-10
391 );
392 assert!(
393 (angle_difference(1.9 * std::f64::consts::PI, 0.1 * std::f64::consts::PI)
394 - (-0.2 * std::f64::consts::PI))
395 .abs()
396 < 1e-10
397 );
398 }
399
400 #[tokio::test]
401 async fn test_coordinate_adjustment() {
402 let space = HyperbolicSpace::new();
403 let initial = space.get_coordinate().await;
404
405 use rand::RngCore;
407
408 let mut hash1 = [0u8; 32];
409 rand::thread_rng().fill_bytes(&mut hash1);
410 let mut hash2 = [0u8; 32];
411 rand::thread_rng().fill_bytes(&mut hash2);
412
413 let neighbors = vec![
414 (
415 NodeId::from_bytes(hash1),
416 HyperbolicCoordinate { r: 0.9, theta: 0.0 },
417 ),
418 (
419 NodeId::from_bytes(hash2),
420 HyperbolicCoordinate {
421 r: 0.9,
422 theta: std::f64::consts::PI,
423 },
424 ),
425 ];
426
427 space.adjust_coordinate(&neighbors).await;
428 let adjusted = space.get_coordinate().await;
429
430 assert!(adjusted.r > initial.r);
432 }
433
434 #[tokio::test]
435 async fn test_routing_stats() {
436 let space = HyperbolicSpace::new();
437
438 space.record_routing_result(true, 3, false).await;
440 space.record_routing_result(true, 4, false).await;
441 space.record_routing_result(false, 0, true).await;
442
443 let stats = space.get_stats().await;
444 assert_eq!(stats.attempts, 3);
445 assert_eq!(stats.successes, 2);
446 assert_eq!(stats.failures, 1);
447 assert_eq!(stats.fallback_used, 1);
448
449 let success_rate = space.get_success_rate().await;
450 assert!((success_rate - 0.83).abs() < 0.01);
451 }
452
453 #[tokio::test]
454 async fn test_hyperbolic_routing_strategy() {
455 use rand::RngCore;
456
457 let space = Arc::new(HyperbolicSpace::new());
459
460 let mut hash = [0u8; 32];
461 rand::thread_rng().fill_bytes(&mut hash);
462 let local_id = NodeId::from_bytes(hash);
463
464 let strategy = HyperbolicRoutingStrategy::new(local_id.clone(), space.clone());
465
466 let mut hash1 = [0u8; 32];
468 rand::thread_rng().fill_bytes(&mut hash1);
469 let neighbor1 = NodeId::from_bytes(hash1);
470
471 let mut hash2 = [0u8; 32];
472 rand::thread_rng().fill_bytes(&mut hash2);
473 let neighbor2 = NodeId::from_bytes(hash2);
474
475 let mut hash_target = [0u8; 32];
476 rand::thread_rng().fill_bytes(&mut hash_target);
477 let target = NodeId::from_bytes(hash_target);
478
479 space
481 .update_neighbor(
482 neighbor1.clone(),
483 HyperbolicCoordinate { r: 0.3, theta: 0.0 },
484 )
485 .await;
486 space
487 .update_neighbor(
488 neighbor2.clone(),
489 HyperbolicCoordinate { r: 0.7, theta: 1.0 },
490 )
491 .await;
492 space
493 .update_neighbor(target.clone(), HyperbolicCoordinate { r: 0.8, theta: 1.5 })
494 .await;
495
496 let _result = strategy.find_path(&target).await;
498
499 let stats = space.get_stats().await;
501 assert_eq!(stats.attempts, 1);
502 }
503
504 #[tokio::test]
505 async fn test_greedy_routing() {
506 let space = HyperbolicSpace::new();
507
508 *space.my_coordinate.write().await = HyperbolicCoordinate {
509 r: 0.95,
510 theta: 0.0,
511 };
512
513 use rand::RngCore;
514
515 let mut neighbors = vec![];
517 for i in 0..5 {
518 let mut hash = [0u8; 32];
519 rand::thread_rng().fill_bytes(&mut hash);
520 let node_id = NodeId::from_bytes(hash);
521
522 let coord = HyperbolicCoordinate {
523 r: 0.1 + (i as f64) * 0.2,
524 theta: (i as f64) * std::f64::consts::PI / 3.0,
525 };
526
527 space.update_neighbor(node_id.clone(), coord).await;
528 neighbors.push((node_id, coord));
529 }
530
531 let target_coord = HyperbolicCoordinate { r: 0.6, theta: 1.0 };
533 let next_hop = space.greedy_route(&target_coord).await;
534
535 assert!(next_hop.is_some());
537
538 if let Some(chosen) = next_hop {
540 let neighbors_map = space.neighbor_coordinates.read().await;
541 let chosen_coord = neighbors_map.get(&chosen).unwrap();
542 let chosen_dist = HyperbolicSpace::distance(chosen_coord, &target_coord);
543
544 for (id, coord) in &neighbors {
546 if id != &chosen {
547 let dist = HyperbolicSpace::distance(coord, &target_coord);
548 assert!(dist >= chosen_dist);
549 }
550 }
551 }
552 }
553}