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 deg_term = 1.0 - (2.0 / (degree as f64 + 2.0));
108 let avg_neighbor_r = if degree > 0 {
109 neighbor_coords.iter().map(|(_, c)| c.r).sum::<f64>() / degree as f64
110 } else {
111 my_coord.r
112 };
113 let target_r = 0.5 * deg_term + 0.5 * avg_neighbor_r;
115 my_coord.r += self.adjustment_rate * (target_r - my_coord.r);
116
117 my_coord.r = my_coord.r.clamp(0.0, 0.999);
119
120 if !neighbor_coords.is_empty() {
122 let avg_theta = neighbor_coords
123 .iter()
124 .map(|(_, coord)| coord.theta)
125 .sum::<f64>()
126 / neighbor_coords.len() as f64;
127
128 let angle_diff = angle_difference(avg_theta, my_coord.theta);
129 my_coord.theta += self.adjustment_rate * angle_diff;
130
131 while my_coord.theta < 0.0 {
133 my_coord.theta += 2.0 * std::f64::consts::PI;
134 }
135 while my_coord.theta >= 2.0 * std::f64::consts::PI {
136 my_coord.theta -= 2.0 * std::f64::consts::PI;
137 }
138 }
139 }
140
141 pub async fn get_coordinate(&self) -> HyperbolicCoordinate {
143 *self.my_coordinate.read().await
144 }
145
146 pub async fn update_neighbor(&self, node_id: NodeId, coord: HyperbolicCoordinate) {
148 let mut neighbors = self.neighbor_coordinates.write().await;
149 neighbors.insert(node_id, coord);
150 }
151
152 pub async fn remove_neighbor(&self, node_id: &NodeId) {
154 let mut neighbors = self.neighbor_coordinates.write().await;
155 neighbors.remove(node_id);
156 }
157
158 pub async fn get_stats(&self) -> RoutingStats {
160 self.routing_stats.read().await.clone()
161 }
162
163 pub async fn get_success_rate(&self) -> f64 {
165 let stats = self.routing_stats.read().await;
166 if stats.attempts == 0 {
167 0.0
168 } else {
169 stats.successes as f64 / stats.attempts as f64
170 }
171 }
172
173 pub async fn record_routing_result(
175 &self,
176 success: bool,
177 hop_count: usize,
178 used_fallback: bool,
179 ) {
180 let mut stats = self.routing_stats.write().await;
181 stats.attempts += 1;
182
183 if success {
184 stats.successes += 1;
185 } else {
186 stats.failures += 1;
187 }
188
189 if used_fallback {
190 stats.fallback_used += 1;
191 }
192
193 let alpha = 0.1;
195 stats.average_hop_count =
196 (1.0 - alpha) * stats.average_hop_count + alpha * hop_count as f64;
197 }
198}
199
200pub fn angle_difference(a: f64, b: f64) -> f64 {
202 let diff = a - b;
203 if diff > std::f64::consts::PI {
204 diff - 2.0 * std::f64::consts::PI
205 } else if diff < -std::f64::consts::PI {
206 diff + 2.0 * std::f64::consts::PI
207 } else {
208 diff
209 }
210}
211
212pub struct HyperbolicRoutingStrategy {
214 space: Arc<HyperbolicSpace>,
216
217 local_id: NodeId,
219
220 max_hops: usize,
222}
223
224impl HyperbolicRoutingStrategy {
225 pub fn new(local_id: NodeId, space: Arc<HyperbolicSpace>) -> Self {
227 Self {
228 space,
229 local_id,
230 max_hops: 10,
231 }
232 }
233
234 async fn find_hyperbolic_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
236 let target_coord = {
238 let neighbors = self.space.neighbor_coordinates.read().await;
239 neighbors.get(target).cloned()
240 };
241
242 let target_coord = match target_coord {
243 Some(coord) => coord,
244 None => {
245 return Err(AdaptiveNetworkError::Routing(
247 "Target coordinate unknown".to_string(),
248 ));
249 }
250 };
251
252 let mut path = Vec::new();
253 let mut _current = self.local_id.clone();
254 let mut visited = std::collections::HashSet::<NodeId>::new();
255 visited.insert(_current.clone());
256
257 for _ in 0..self.max_hops {
259 let next_hop = self.space.greedy_route(&target_coord).await;
261
262 match next_hop {
263 Some(next) => {
264 if next == *target {
265 path.push(next);
267 return Ok(path);
268 }
269
270 if visited.contains(&next) {
271 return Err(AdaptiveNetworkError::Routing(
273 "Routing loop detected".to_string(),
274 ));
275 }
276
277 path.push(next.clone());
278 visited.insert(next.clone());
279 _current = next;
280 }
281 None => {
282 return Err(AdaptiveNetworkError::Routing(
284 "No closer neighbor found".to_string(),
285 ));
286 }
287 }
288 }
289
290 Err(AdaptiveNetworkError::Routing(
292 "Maximum hop count exceeded".to_string(),
293 ))
294 }
295}
296
297#[async_trait]
298impl RoutingStrategy for HyperbolicRoutingStrategy {
299 async fn find_path(&self, target: &NodeId) -> Result<Vec<NodeId>> {
300 let result = self.find_hyperbolic_path(target).await;
302
303 let (success, hop_count, used_fallback) = match &result {
305 Ok(path) => (true, path.len(), false),
306 Err(_) => (false, 0, true), };
308
309 self.space
310 .record_routing_result(success, hop_count, used_fallback)
311 .await;
312
313 result
314 }
315
316 fn route_score(&self, _neighbor: &NodeId, _target: &NodeId) -> f64 {
317 0.5
320 }
321
322 fn update_metrics(&mut self, _path: &[NodeId], _success: bool) {
323 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_hyperbolic_distance() {
333 let origin = HyperbolicCoordinate { r: 0.0, theta: 0.0 };
334 let point = HyperbolicCoordinate {
335 r: 0.5,
336 theta: std::f64::consts::PI,
337 };
338
339 let distance = HyperbolicSpace::distance(&origin, &point);
340 assert!(distance > 0.0);
341
342 let self_distance = HyperbolicSpace::distance(&origin, &origin);
344 assert!((self_distance - 0.0).abs() < 1e-10);
345 }
346
347 #[test]
348 fn test_angle_difference() {
349 assert!((angle_difference(0.0, 0.0) - 0.0).abs() < 1e-10);
350 assert!((angle_difference(std::f64::consts::PI, 0.0) - std::f64::consts::PI).abs() < 1e-10);
351 assert!(
352 (angle_difference(0.0, std::f64::consts::PI) - (-std::f64::consts::PI)).abs() < 1e-10
353 );
354 assert!(
355 (angle_difference(1.9 * std::f64::consts::PI, 0.1 * std::f64::consts::PI)
356 - (-0.2 * std::f64::consts::PI))
357 .abs()
358 < 1e-10
359 );
360 }
361
362 #[tokio::test]
363 async fn test_coordinate_adjustment() {
364 let space = HyperbolicSpace::new();
365 let initial = space.get_coordinate().await;
366
367 use rand::RngCore;
369
370 let mut hash1 = [0u8; 32];
371 rand::thread_rng().fill_bytes(&mut hash1);
372 let mut hash2 = [0u8; 32];
373 rand::thread_rng().fill_bytes(&mut hash2);
374
375 let neighbors = vec![
376 (
377 NodeId::from_bytes(hash1),
378 HyperbolicCoordinate { r: 0.9, theta: 0.0 },
379 ),
380 (
381 NodeId::from_bytes(hash2),
382 HyperbolicCoordinate {
383 r: 0.9,
384 theta: std::f64::consts::PI,
385 },
386 ),
387 ];
388
389 space.adjust_coordinate(&neighbors).await;
390 let adjusted = space.get_coordinate().await;
391
392 assert!(adjusted.r > initial.r);
394 }
395
396 #[tokio::test]
397 async fn test_routing_stats() {
398 let space = HyperbolicSpace::new();
399
400 space.record_routing_result(true, 3, false).await;
402 space.record_routing_result(true, 4, false).await;
403 space.record_routing_result(false, 0, true).await;
404
405 let stats = space.get_stats().await;
406 assert_eq!(stats.attempts, 3);
407 assert_eq!(stats.successes, 2);
408 assert_eq!(stats.failures, 1);
409 assert_eq!(stats.fallback_used, 1);
410
411 let success_rate = space.get_success_rate().await;
412 assert!((success_rate - 0.666).abs() < 0.01);
413 }
414
415 #[tokio::test]
416 async fn test_hyperbolic_routing_strategy() {
417 use rand::RngCore;
418
419 let space = Arc::new(HyperbolicSpace::new());
421
422 let mut hash = [0u8; 32];
423 rand::thread_rng().fill_bytes(&mut hash);
424 let local_id = NodeId::from_bytes(hash);
425
426 let strategy = HyperbolicRoutingStrategy::new(local_id.clone(), space.clone());
427
428 let mut hash1 = [0u8; 32];
430 rand::thread_rng().fill_bytes(&mut hash1);
431 let neighbor1 = NodeId::from_bytes(hash1);
432
433 let mut hash2 = [0u8; 32];
434 rand::thread_rng().fill_bytes(&mut hash2);
435 let neighbor2 = NodeId::from_bytes(hash2);
436
437 let mut hash_target = [0u8; 32];
438 rand::thread_rng().fill_bytes(&mut hash_target);
439 let target = NodeId::from_bytes(hash_target);
440
441 space
443 .update_neighbor(
444 neighbor1.clone(),
445 HyperbolicCoordinate { r: 0.3, theta: 0.0 },
446 )
447 .await;
448 space
449 .update_neighbor(
450 neighbor2.clone(),
451 HyperbolicCoordinate { r: 0.7, theta: 1.0 },
452 )
453 .await;
454 space
455 .update_neighbor(target.clone(), HyperbolicCoordinate { r: 0.8, theta: 1.5 })
456 .await;
457
458 let _result = strategy.find_path(&target).await;
460
461 let stats = space.get_stats().await;
463 assert_eq!(stats.attempts, 1);
464 }
465
466 #[tokio::test]
467 async fn test_greedy_routing() {
468 let space = HyperbolicSpace::new();
469
470 use rand::RngCore;
471
472 let mut neighbors = vec![];
474 for i in 0..5 {
475 let mut hash = [0u8; 32];
476 rand::thread_rng().fill_bytes(&mut hash);
477 let node_id = NodeId::from_bytes(hash);
478
479 let coord = HyperbolicCoordinate {
480 r: 0.1 + (i as f64) * 0.2,
481 theta: (i as f64) * std::f64::consts::PI / 3.0,
482 };
483
484 space.update_neighbor(node_id.clone(), coord).await;
485 neighbors.push((node_id, coord));
486 }
487
488 let target_coord = HyperbolicCoordinate { r: 0.9, theta: 1.0 };
490 let next_hop = space.greedy_route(&target_coord).await;
491
492 assert!(next_hop.is_some());
494
495 if let Some(chosen) = next_hop {
497 let neighbors_map = space.neighbor_coordinates.read().await;
498 let chosen_coord = neighbors_map.get(&chosen).unwrap();
499 let chosen_dist = HyperbolicSpace::distance(chosen_coord, &target_coord);
500
501 for (id, coord) in &neighbors {
503 if id != &chosen {
504 let dist = HyperbolicSpace::distance(coord, &target_coord);
505 assert!(dist >= chosen_dist);
506 }
507 }
508 }
509 }
510}