use crate::hnsw::{DistanceMetric, SearchResult};
use ipfrs_core::{Cid, Error, Result};
use ipfrs_network::libp2p::PeerId;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SemanticDHTConfig {
pub embedding_dim: usize,
pub replication_factor: usize,
pub routing_table_size: usize,
pub distance_metric: DistanceMetric,
pub max_hops: usize,
pub query_timeout_ms: u64,
}
impl Default for SemanticDHTConfig {
fn default() -> Self {
Self {
embedding_dim: 768,
replication_factor: 3,
routing_table_size: 20,
distance_metric: DistanceMetric::Cosine,
max_hops: 5,
query_timeout_ms: 5000,
}
}
}
#[derive(Debug, Clone)]
pub struct SemanticPeer {
pub peer_id: PeerId,
pub embedding: Vec<f32>,
pub cluster_id: Option<usize>,
pub last_seen: u64,
pub load: f32,
}
impl SemanticPeer {
pub fn new(peer_id: PeerId, embedding: Vec<f32>) -> Self {
Self {
peer_id,
embedding,
cluster_id: None,
last_seen: current_timestamp(),
load: 0.0,
}
}
pub fn update_last_seen(&mut self) {
self.last_seen = current_timestamp();
}
pub fn update_load(&mut self, load: f32) {
self.load = load.clamp(0.0, 1.0);
}
}
#[derive(Debug)]
pub struct SemanticRoutingTable {
config: SemanticDHTConfig,
peers: Arc<RwLock<HashMap<PeerId, SemanticPeer>>>,
clusters: Arc<RwLock<HashMap<usize, Vec<PeerId>>>>,
local_embedding: Arc<RwLock<Vec<f32>>>,
route_cache: Arc<RwLock<lru::LruCache<u64, Vec<PeerId>>>>,
}
impl SemanticRoutingTable {
pub fn new(config: SemanticDHTConfig) -> Self {
let local_embedding = vec![0.0; config.embedding_dim];
Self {
config,
peers: Arc::new(RwLock::new(HashMap::new())),
clusters: Arc::new(RwLock::new(HashMap::new())),
local_embedding: Arc::new(RwLock::new(local_embedding)),
route_cache: Arc::new(RwLock::new(lru::LruCache::new(
std::num::NonZeroUsize::new(1000).expect("1000 > 0"),
))),
}
}
pub fn update_local_embedding(&self, embedding: Vec<f32>) -> Result<()> {
if embedding.len() != self.config.embedding_dim {
return Err(Error::InvalidInput(format!(
"Expected embedding dimension {}, got {}",
self.config.embedding_dim,
embedding.len()
)));
}
*self.local_embedding.write() = embedding;
Ok(())
}
pub fn add_peer(&self, peer: SemanticPeer) -> Result<()> {
if peer.embedding.len() != self.config.embedding_dim {
return Err(Error::InvalidInput(format!(
"Expected embedding dimension {}, got {}",
self.config.embedding_dim,
peer.embedding.len()
)));
}
self.peers.write().insert(peer.peer_id, peer);
Ok(())
}
pub fn remove_peer(&self, peer_id: &PeerId) {
self.peers.write().remove(peer_id);
}
pub fn find_nearest_peers(&self, embedding: &[f32], k: usize) -> Vec<(PeerId, f32)> {
if let Some(cached_peers) = self.get_cached_route(embedding) {
let peers = self.peers.read();
let result: Vec<(PeerId, f32)> = cached_peers
.iter()
.filter_map(|peer_id| {
peers.get(peer_id).map(|peer| {
let distance = self.compute_distance(embedding, &peer.embedding);
(*peer_id, distance)
})
})
.take(k)
.collect();
if result.len() == k {
return result;
}
}
let peers = self.peers.read();
let mut distances: Vec<(PeerId, f32)> = peers
.values()
.map(|peer| {
let distance = self.compute_distance(embedding, &peer.embedding);
(peer.peer_id, distance)
})
.collect();
distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let result: Vec<(PeerId, f32)> = distances.into_iter().take(k).collect();
let peer_ids: Vec<PeerId> = result.iter().map(|(id, _)| *id).collect();
drop(peers);
self.cache_route(embedding, peer_ids);
result
}
pub fn find_nearest_peers_balanced(&self, embedding: &[f32], k: usize) -> Vec<(PeerId, f32)> {
let peers = self.peers.read();
let mut scored_peers: Vec<(PeerId, f32)> = peers
.values()
.map(|peer| {
let distance = self.compute_distance(embedding, &peer.embedding);
let score = distance * (1.0 + peer.load);
(peer.peer_id, score)
})
.collect();
scored_peers.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
scored_peers.into_iter().take(k).collect()
}
pub fn get_cluster_peers(&self, cluster_id: usize) -> Vec<PeerId> {
self.clusters
.read()
.get(&cluster_id)
.cloned()
.unwrap_or_default()
}
pub fn num_peers(&self) -> usize {
self.peers.read().len()
}
pub fn num_clusters(&self) -> usize {
self.clusters.read().len()
}
fn hash_embedding(embedding: &[f32]) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for &val in embedding.iter().take(8) {
val.to_bits().hash(&mut hasher);
}
hasher.finish()
}
pub fn get_cached_route(&self, embedding: &[f32]) -> Option<Vec<PeerId>> {
let hash = Self::hash_embedding(embedding);
self.route_cache.write().get(&hash).cloned()
}
pub fn cache_route(&self, embedding: &[f32], peers: Vec<PeerId>) {
let hash = Self::hash_embedding(embedding);
self.route_cache.write().put(hash, peers);
}
pub fn clear_route_cache(&self) {
self.route_cache.write().clear();
}
pub fn route_cache_stats(&self) -> (usize, usize) {
let cache = self.route_cache.read();
(cache.len(), cache.cap().get())
}
pub fn update_clusters(&self, num_clusters: usize) -> Result<()> {
let peers = self.peers.read();
if peers.is_empty() {
return Ok(());
}
let embeddings: Vec<Vec<f32>> = peers.values().map(|p| p.embedding.clone()).collect();
let peer_ids: Vec<PeerId> = peers.keys().cloned().collect();
drop(peers);
let assignments = self.kmeans_clustering(&embeddings, num_clusters);
let mut peers_write = self.peers.write();
let mut clusters_write = self.clusters.write();
clusters_write.clear();
for (peer_id, cluster_id) in peer_ids.iter().zip(assignments.iter()) {
if let Some(peer) = peers_write.get_mut(peer_id) {
peer.cluster_id = Some(*cluster_id);
}
clusters_write
.entry(*cluster_id)
.or_default()
.push(*peer_id);
}
Ok(())
}
fn compute_distance(&self, a: &[f32], b: &[f32]) -> f32 {
match self.config.distance_metric {
DistanceMetric::L2 => crate::simd::l2_distance(a, b),
DistanceMetric::Cosine => crate::simd::cosine_distance(a, b),
DistanceMetric::DotProduct => -crate::simd::dot_product(a, b), }
}
fn kmeans_clustering(&self, embeddings: &[Vec<f32>], k: usize) -> Vec<usize> {
if embeddings.is_empty() || k == 0 {
return Vec::new();
}
let k = k.min(embeddings.len());
let dim = embeddings[0].len();
let mut centroids: Vec<Vec<f32>> = (0..k)
.map(|i| embeddings[i % embeddings.len()].clone())
.collect();
let mut assignments = vec![0; embeddings.len()];
let max_iterations = 10;
for _ in 0..max_iterations {
for (i, embedding) in embeddings.iter().enumerate() {
let mut min_dist = f32::MAX;
let mut best_cluster = 0;
for (cluster_id, centroid) in centroids.iter().enumerate() {
let dist = self.compute_distance(embedding, centroid);
if dist < min_dist {
min_dist = dist;
best_cluster = cluster_id;
}
}
assignments[i] = best_cluster;
}
let mut new_centroids = vec![vec![0.0; dim]; k];
let mut counts = vec![0; k];
for (embedding, &cluster_id) in embeddings.iter().zip(assignments.iter()) {
for (j, &val) in embedding.iter().enumerate() {
new_centroids[cluster_id][j] += val;
}
counts[cluster_id] += 1;
}
for (cluster_id, count) in counts.iter().enumerate() {
if *count > 0 {
for val in new_centroids[cluster_id].iter_mut().take(dim) {
*val /= *count as f32;
}
}
}
centroids = new_centroids;
}
assignments
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DHTQuery {
pub embedding: Vec<f32>,
pub k: usize,
pub query_id: String,
pub ttl: usize,
#[serde(skip)]
pub visited: HashSet<PeerId>,
}
#[derive(Debug, Clone)]
pub struct DHTQueryResponse {
pub query_id: String,
pub results: Vec<SearchResult>,
pub peer_id: PeerId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReplicationStrategy {
NearestPeers(usize),
SameCluster,
CrossCluster(usize),
}
#[derive(Debug, Clone)]
pub struct DHTEntry {
pub cid: Cid,
pub embedding: Vec<f32>,
pub primary_peer: PeerId,
pub replicas: Vec<PeerId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SemanticDHTStats {
pub num_peers: usize,
pub num_clusters: usize,
pub num_local_entries: usize,
pub queries_processed: u64,
pub avg_query_latency_ms: f64,
pub multi_hop_queries: u64,
}
fn current_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time is after UNIX epoch")
.as_secs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_routing_table_creation() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
let local_emb = vec![0.5; 768];
assert!(table.update_local_embedding(local_emb).is_ok());
}
#[test]
fn test_add_peer() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
let peer_id = PeerId::random();
let embedding = vec![0.5; 768];
let peer = SemanticPeer::new(peer_id, embedding);
assert!(table.add_peer(peer).is_ok());
}
#[test]
fn test_find_nearest_peers() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
for i in 0..10 {
let peer_id = PeerId::random();
let embedding = vec![i as f32 * 0.1; 768];
let peer = SemanticPeer::new(peer_id, embedding);
table
.add_peer(peer)
.expect("test: add_peer with valid embedding should succeed");
}
let query_embedding = vec![0.5; 768];
let nearest = table.find_nearest_peers(&query_embedding, 3);
assert_eq!(nearest.len(), 3);
}
#[test]
fn test_clustering() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
for i in 0..20 {
let peer_id = PeerId::random();
let mut embedding = vec![0.0; 768];
if i < 10 {
embedding[0] = 1.0;
} else {
embedding[0] = -1.0;
}
let peer = SemanticPeer::new(peer_id, embedding);
table
.add_peer(peer)
.expect("test: add_peer with valid embedding should succeed");
}
assert!(table.update_clusters(2).is_ok());
let cluster0 = table.get_cluster_peers(0);
let cluster1 = table.get_cluster_peers(1);
assert!(!cluster0.is_empty() || !cluster1.is_empty());
}
#[test]
fn test_load_balancing() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
for i in 0..5 {
let peer_id = PeerId::random();
let embedding = vec![0.5; 768];
let mut peer = SemanticPeer::new(peer_id, embedding);
peer.update_load(i as f32 * 0.2); table
.add_peer(peer)
.expect("test: add_peer with valid embedding should succeed");
}
let query_embedding = vec![0.5; 768];
let balanced = table.find_nearest_peers_balanced(&query_embedding, 3);
assert_eq!(balanced.len(), 3);
}
#[test]
fn test_route_caching() {
let config = SemanticDHTConfig::default();
let table = SemanticRoutingTable::new(config);
for i in 0..10 {
let peer_id = PeerId::random();
let embedding = vec![i as f32 * 0.1; 768];
let peer = SemanticPeer::new(peer_id, embedding);
table
.add_peer(peer)
.expect("test: add_peer with valid embedding should succeed");
}
let query_embedding = vec![0.5; 768];
let (cache_size_before, _) = table.route_cache_stats();
assert_eq!(cache_size_before, 0);
let result1 = table.find_nearest_peers(&query_embedding, 3);
assert_eq!(result1.len(), 3);
let (cache_size_after, _) = table.route_cache_stats();
assert_eq!(cache_size_after, 1);
let result2 = table.find_nearest_peers(&query_embedding, 3);
assert_eq!(result2.len(), 3);
let ids1: Vec<_> = result1.iter().map(|(id, _)| id).collect();
let ids2: Vec<_> = result2.iter().map(|(id, _)| id).collect();
assert_eq!(ids1, ids2);
table.clear_route_cache();
let (cache_size_cleared, _) = table.route_cache_stats();
assert_eq!(cache_size_cleared, 0);
}
}